1 - Основи StatefulSet

Цей підручник надає вступ до управління застосунками за допомогою StatefulSets. Він демонструє, як створювати, видаляти, масштабувати та оновлювати Podʼи StatefulSets.

Перш ніж ви розпочнете

Перш ніж розпочати цей підручник, вам слід ознайомитися з наступними концепціями Kubernetes:

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Вам слід налаштувати kubectl для використання контексту, який використовує простір імен default. Якщо ви використовуєте наявний кластер, переконайтеся, що можна використовувати простір імен цього кластера для практики. Ідеальною буде практика в кластері, де не запущені реальні робочі навантаження.

Також корисно прочитати сторінку з концепціями про StatefulSets.

Цілі

StatefulSets призначені для використання з застосунками, які зберігаються свій стан, та розподіленими системами. Однак, адміністрування стану застосунків та розподілених систем у Kubernetes є широкою та складною темою. Щоб продемонструвати базові можливості StatefulSet і не змішувати першу тему з другою, ви розгорнете простий вебзастосунок за допомогою StatefulSet.

Після цього уроку ви будете знайомі з наступним.

  • Як створити StatefulSet
  • Як StatefulSet керує своїми Podʼами
  • Як видалити StatefulSet
  • Як масштабувати StatefulSet
  • Як оновлювати Podʼи StatefulSet

Створення StatefulSet

Почніть зі створення StatefulSet (і Service, на який він спирається) за допомогою наведеного нижче прикладу. Він схожий на приклад, наведений у концепції StatefulSets. Він створює headless Service, nginx, щоб опублікувати IP-адреси Podʼів у StatefulSet, web.

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.21
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

Вам знадобляться принаймні два термінали. У першому терміналі використовуйте kubectl get для спостереження (watch) за створенням Podʼів StatefulSet.

# використовуйте цей термінал для виконання команд із зазначенням --watch
# завершіть цей watch, коли вам буде запропоновано розпочати новий watch
kubectl get pods --watch -l app=nginx

У другому терміналі використовуйте kubectl apply для створення headless Service та StatefulSet:

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created

Наведена вище команда створює два Podʼи, кожен з яких запускає вебсервер NGINX. Отримайте інформацію про nginx Service…

kubectl get service nginx
NAME      TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx     ClusterIP    None         <none>        80/TCP    12s

…потім отримайте інформацію про web StatefulSet, щоб переконатися, що обидва були створені успішно:

kubectl get statefulset web
NAME   READY   AGE
web    2/2     37s

Упорядковане створення Podʼів

Типово StatefulSet створює свої Podʼи в строгому порядку.

Для StatefulSet з n репліками, коли Podʼи розгортаються, вони створюються послідовно, впорядковані від {0..n-1}. Перегляньте вивід команди kubectl get у першому терміналі. Зрештою вивід буде виглядати як у наведеному нижче прикладі.

# Не починайте новий watch;
# це вже повинно працювати
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         19s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         18s

Зверніть увагу, що Pod web-1 не запускається, поки Pod web-0 не буде Running (див. Фази Pod) та Ready (див. type у Стани Pod).

Пізніше в цьому підручнику ви будете практикувати паралельний запуск.

Podʼи в StatefulSet

Podʼи в StatefulSet мають унікальний порядковий індекс та стабільну мережеву ідентичність.

Перевірка порядкового індексу Podʼів

Отримайте Podʼи StatefulSet:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          1m
web-1     1/1       Running   0          1m

Як зазначено в концепції StatefulSets, Podʼи в StatefulSet мають фіксовану, унікальну ідентичність. Ця ідентичність базується на унікальному порядковому індексі, який призначається кожному Podʼу контролером StatefulSet controller. Імена Podʼів приймають форму <імʼя statefulset>-<порядковий індекс>. Оскільки StatefulSet web має дві репліки, він створює два Podʼи, web-0 та web-1.

Використання стабільних мережевих ідентичностей

Кожен Pod має стабільне імʼя хосту на основі свого порядкового індексу. Використовуйте kubectl exec для виконання команди hostname в кожному Podʼі:

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
web-0
web-1

Використайте kubectl run для запуску контейнера, який надає команду nslookup з пакунка dnsutils. Використовуючи nslookup з іменами хостів Podʼів, ви можете переглянути їх внутрішні адреси DNS:

kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

що запускає нову оболонку. У цій новій оболонці запустіть:

# Виконайте це в оболонці контейнера dns-test
nslookup web-0.nginx

Вивід схожий на:

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.6

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.6

(і тепер вийдіть з оболонки контейнера: exit)

CNAME headless сервісу вказує на SRV записи (один для кожного Podʼа, який виконується та готовий). SRV записи вказують на записи A, які містять IP-адреси Podʼів.

У одному терміналі спостерігайте за Podʼами StatefulSet:

# Розпочніть новий watch
# Завершіть цей watch, коли бачите, що видалення завершено
kubectl get pod --watch -l app=nginx

У другому терміналі використовуйте kubectl delete для видалення всіх Podʼів у StatefulSet:

kubectl delete pod -l app=nginx
Pod "web-0" видалено
Pod "web-1" видалено

Чекайте, поки StatefulSet перезапустить їх, і обидва Podʼи перейдуть до стану Running та Ready:

# Це вже має працювати
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

Використовуйте kubectl exec та kubectl run, щоб переглянути імена хостів Podʼів та внутрішні DNS-записи. Спочатку перегляньте імена хостів Podʼів:

for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
web-0
web-1

потім, запустіть:

kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

що запускає новиe оболонку. У цій новій оболонці запустіть:

# Виконайте це в оболонці контейнера dns-test
nslookup web-0.nginx

Вивід схожий на:

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.7

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.8

(і тепер вийдіть з оболонки контейнера: exit)

Порядкові номери, імена хостів Podʼів, SRV записи та імена записів A не змінилися, але IP-адреси, повʼязані з Podʼами, можуть змінюватися. У кластері, який використовується для цього навчального посібника, вони змінюються. Тому важливо не налаштовувати інші застосунки на підʼєднання до Podʼів у StatefulSet за IP-адресою конкретного Podʼа (можна підключатися до Podʼів, за їх іменем хосту).

Виявлення конкретних Podʼів у StatefulSet

Якщо вам потрібно знайти та підʼєднатись до активних учасників StatefulSet, вам слід запитувати CNAME headless Service (nginx.default.svc.cluster.local). SRV записи, повʼязані з CNAME, будуть містити лише Podʼ у StatefulSet, які виконуються та готові.

Якщо ваш застосунок вже реалізував логіку підʼєднання, яка перевіряє працездатність та готовність, ви можете використовувати SRV записи Podʼів (web-0.nginx.default.svc.cluster.local, web-1.nginx.default.svc.cluster.local), оскільки вони стабільні, і ваш застосунок зможе виявляти адреси Podʼів, коли вони переходять до стану Running та Ready.

Якщо ваш застосунок хоче знайти будь-який здоровий Pod у StatefulSet і, отже, не потрібно відстежувати кожний конкретний Pod, ви також можете підʼєднатись до IP-адреси type: ClusterIP сервісу, Podʼів в цьому StatefulSet. Ви можете використовувати той самий Service, який відстежує StatefulSet (вказаний у serviceName StatefulSet) або окремий Service, який вибирає відповідний набір Podʼів.

Запис у стійке сховище

Отримайте PersistentVolumeClaims для web-0 та web-1:

kubectl get pvc -l app=nginx

Вивід схожий на:

NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           48s
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           48s

Контролер StatefulSet створив два PersistentVolumeClaims, які привʼязані до двох PersistentVolumes.

Оскільки кластер, використаний у цьому посібнику, налаштований на динамічну підготовку PersistentVolumes, PersistentVolumes були створені та привʼязані автоматично.

NGINX вебсервер типово обслуговує індексний файл із /usr/share/nginx/html/index.html. Поле volumeMounts в spec StatefulSet забезпечує, що тека /usr/share/nginx/html є зберігається у PersistentVolume.

Запишіть імена хостів Podʼів у їх файли index.html та перевірте, що вебсервери NGINX обслуговують імена хостів:

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

У одному терміналі спостерігайте за Podʼами StatefulSet:

# Завершіть цей watch, коли ви дійдете до кінця розділу.
# На початку "Scaling a StatefulSet" ви розпочнете новий watch.
kubectl get pod --watch -l app=nginx

У другому терміналі видаліть всі Podʼи StatefulSet:

kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

Дослідіть вивід команди kubectl get у першому терміналі та зачекайте, поки всі Podʼи перейдуть до стану Running та Ready.

# Це вже має працювати
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

Перевірте, чи продовжують вебсервери обслуговувати свої імена хостів:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

Навіть якщо web-0 та web-1 переплановані, вони продовжують обслуговувати свої імена хостів, оскільки PersistentVolumes, повʼязані з їхніми PersistentVolumeClaims, знову монтується до їхніх volumeMounts. Незалежно від того, на якому вузлі заплановані web-0 та web-1, їхні PersistentVolumes будуть підключені до відповідних точок монтування.

Масштабування StatefulSet

Масштабування StatefulSet передбачає збільшення або зменшення кількості реплік (горизонтальне масштабування). Це досягається шляхом оновлення поля replicas. Ви можете використовувати або kubectl scale, або kubectl patch, щоб масштабувати StatefulSet.

Збільшення масштабу

Збільшення масштабу означає додавання додаткових реплік. Забезпечивши те, що ваш застосунок може розподіляти роботу між StatefulSet, новий більший набір Podʼів може виконувати більше цієї роботи.

У одному вікні термінала спостерігайте за Podʼами у StatefulSet:

# Якщо у вас вже запущений watch, ви можете продовжити його використовувати.
# В іншому випадку запустіть один.
# Закінчіть цей watch, коли для StatefulSet буде 5 справних Podʼів
kubectl get pods --watch -l app=nginx

У іншому вікні термінала використовуйте kubectl scale, щоб змінити кількість реплік на 5:

kubectl scale sts web --replicas=5
statefulset.apps/web scaled

Дослідіть вивід команди kubectl get у першому терміналі та зачекайте, доки три додаткові Podʼи перейдуть у стан Running та Ready.

# Це вже має працювати
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2h
web-1     1/1       Running   0          2h
NAME      READY     STATUS    RESTARTS   AGE
web-2     0/1       Pending   0          0s
web-2     0/1       Pending   0         0s
web-2     0/1       ContainerCreating   0         0s
web-2     1/1       Running   0         19s
web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         0s
web-3     0/1       ContainerCreating   0         0s
web-3     1/1       Running   0         18s
web-4     0/1       Pending   0         0s
web-4     0/1       Pending   0         0s
web-4     0/1       ContainerCreating   0         0s
web-4     1/1       Running   0         19s

Контролер StatefulSet змінив кількість реплік. Як і при створенні StatefulSet, контролер StatefulSet створював кожен Pod послідовно з урахуванням його порядкового індексу, і чекав, доки попередній Pod перейде у стан Running та Ready перед запуском наступного Podʼа.

Зменшення масштабу

Зменшення масштабу означає зменшення кількості реплік. Наприклад, ви можете це зробити через те, що рівень трафіку до служби зменшився, і на поточному масштабі є ресурси, що простоюють.

У одному вікні термінала спостерігайте за Podʼами у StatefulSet:

# Закінчіть цей watch, коли лишиться лише 3 Podʼи для StatefulSet
kubectl get pod --watch -l app=nginx

У іншому вікні термінала використовуйте kubectl patch, щоб зменшити кількість реплік StatefulSet до трьох:

kubectl patch sts web -p '{"spec":{"replicas":3}}'
statefulset.apps/web patched

Зачекайте, доки web-4 і web-3 перейдуть у стан Terminating.

# Це вже має працювати
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          3h
web-1     1/1       Running             0          3h
web-2     1/1       Running             0          55s
web-3     1/1       Running             0          36s
web-4     0/1       ContainerCreating   0          18s
NAME      READY     STATUS    RESTARTS   AGE
web-4     1/1       Running   0          19s
web-4     1/1       Terminating   0         24s
web-4     1/1       Terminating   0         24s
web-3     1/1       Terminating   0         42s
web-3     1/1       Terminating   0         42s

Послідовне завершення Podʼів

Панель управління видаляє кожний Pod по одному, у зворотньому порядку щодо його порядкового індексу, і чекає, доки кожен Pod повністю не зупиниться перед видаленням наступного.

Отримайте PersistentVolumeClaims StatefulSet:

kubectl get pvc -l app=nginx
NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-2   Bound     pvc-e1125b27-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-3   Bound     pvc-e1176df6-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-4   Bound     pvc-e11bb5f8-b508-11e6-932f-42010a800002   1Gi        RWO           13h

Ще існують пʼять PersistentVolumeClaims та пʼять PersistentVolumes. Під час дослідження стабільного сховища Podʼа, ви бачили, що PersistentVolumes, змонтовані у Podʼи StatefulSet, не видаляються, коли Podʼи StatefulSet видаляються. Це також вірно, коли видалення Podʼів викликано зменшенням масштабу StatefulSet.

Оновлення StatefulSets

Контролер StatefulSet підтримує автоматизоване оновлення. Стратегія, яка використовується, визначається полем spec.updateStrategy обʼєкта API StatefulSet. Ця функція може бути використана для оновлення образів контейнерів, запитів ресурсів та/або лімітів, міток та анотацій Podʼів у StatefulSet.

Існують дві стратегії оновлення: RollingUpdate (типово) та OnDelete.

RollingUpdate

Стратегія оновлення RollingUpdate оновлює всі Podʼи в StatefulSet у зворотньому порядку щодо їх порядкового індексу, з дотриманням гарантій StatefulSet.

Ви можете розділити оновлення StatefulSet, який використовує стратегію RollingUpdate, на партиції, вказавши .spec.updateStrategy.rollingUpdate.partition. Ви будете практикувати це пізніше у цьому посібнику.

Спочатку спробуйте просте поетапне оновлення.

В одному вікні термінала відредагуйте StatefulSet web, щоб знову змінити образ контейнера:

kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.24"}]'
statefulset.apps/web patched

В іншому терміналі спостерігайте за Podʼами у StatefulSet:

# Закінчіть це спостереження, коли відбудеться розгортання
#
# Якщо ви не впевнені, залиште його ще на одну хвилину
kubectl get pod -l app=nginx --watch

Вивід буде подібний до:

NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          7m
web-1     1/1       Running   0          7m
web-2     1/1       Running   0          8m
web-2     1/1       Terminating   0         8m
web-2     1/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Pending   0         0s
web-2     0/1       Pending   0         0s
web-2     0/1       ContainerCreating   0         0s
web-2     1/1       Running   0         19s
web-1     1/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         6s
web-0     1/1       Terminating   0         7m
web-0     1/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Pending   0         0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         10s

Podʼи в StatefulSet оновлюються у зворотньому порядку щодо їх порядкового індексу. Контролер StatefulSet завершує кожен Pod і чекає, доки він не перейде в стан Running та Ready, перед тим як оновити наступний Pod. Зверніть увагу, що, навіть якщо контролер StatefulSet не продовжить оновлювати наступний Pod, поки його порядковий наступник не буде Running та Ready, він відновить будь-який Pod, який зазнав помилки під час оновлення до попередньої версії.

Podʼи, які вже отримали оновлення, будуть відновлені до оновленої версії, а Podʼи, які ще не отримали оновлення, будуть відновлені до попередньої версії. Таким чином, контролер намагається продовжувати забезпечувати працездатність застосунку та зберігати оновлення в однорідному стані при наявності тимчасових збоїв.

Отримайте Podʼи, щоб переглянути їх образи контейнерів:

for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24

Всі Podʼи в StatefulSet зараз використовують попередній образ контейнера.

Підготовка оновлення

Ви можете розбити оновлення StatefulSet, який використовує стратегію RollingUpdate, на розділи, вказавши .spec.updateStrategy.rollingUpdate.partition.

Для отримання додаткового контексту ви можете прочитати Поточні оновлення частинами на сторінці концепції StatefulSet.

Ви можете підготувати оновлення StatefulSet, використовуючи поле partition всередині .spec.updateStrategy.rollingUpdate. Для цього оновлення ви залишите наявні Podʼи в StatefulSet без змін, поки змінюєте шаблон Podʼа для StatefulSet. Потім ви (або, це поза цим навчальним посібником, якась зовнішня автоматизація) можете запустити це підготовлене оновлення.

Спочатку виправте StatefulSet web, щоб додати розділ до поля updateStrategy:

# Значення "partition" визначає, до яких порядкових номерів застосовується зміна
# Переконайтеся, що використовуєте число, більше, ніж останній порядковий номер для
# StatefulSet
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
statefulset.apps/web patched

Знову виправте StatefulSet, щоб змінити образ контейнера, який використовує цей StatefulSet:

kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.21"}]'
statefulset.apps/web patched

Видаліть Pod у StatefulSet:

kubectl delete pod web-2
pod "web-2" deleted

Зачекайте, поки замінений Pod web-2 буде запущений і готовий:

# Закінчіть перегляд, коли побачите, що web-2 справний
kubectl get pod -l app=nginx --watch
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running   0         18s

Отримайте образ контейнера Podʼа:

kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.24

Зверніть увагу, що, навіть якщо стратегія оновлення — RollingUpdate, StatefulSet відновив Pod з початковим образом контейнера. Це тому, що порядковий номер Podʼа менше, ніж partition, вказаний у updateStrategy.

Канаркове оновлення

Тепер ви спробуєте канаркове оновлення цієї підготовленої зміни.

Ви можете виконати канаркове оновлення (для тестування зміненого шаблону) шляхом зменшення partition, який ви вказали вище.

Виправте StatefulSet, щоб зменшити розділ:

# Значення "partition" повинно відповідати найвищому існуючому порядковому номеру для
# StatefulSet
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
statefulset.apps/web patched

Панель управління спровокує заміну для web-2 (реалізовану через належне видалення, а потім створення нового Podʼа, як тільки видалення завершиться). Зачекайте, поки новий Pod web-2 буде запущений і готовий.

# Це вже має бути запущено
kubectl get pod -l app=nginx --watch
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running   0         18s

Отримайте інформацію про контейнер Podʼа:

kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.21

Коли ви змінили partition, контролер StatefulSet автоматично оновив Pod web-2, оскільки порядковий номер Podʼа був більшим або рівним partition.

Видаліть Pod web-1:

kubectl delete pod web-1
pod "web-1" deleted

Зачекайте, поки Pod web-1 буде запущений і готовий.

# Це вже має бути запущено
kubectl get pod -l app=nginx --watch

Вивід подібний до:

NAME      READY     STATUS        RESTARTS   AGE
web-0     1/1       Running       0          6m
web-1     0/1       Terminating   0          6m
web-2     1/1       Running       0          2m
web-1     0/1       Terminating   0         6m
web-1     0/1       Terminating   0         6m
web-1     0/1       Terminating   0         6m
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         18s

Отримайте образ контейнера Podʼа web-1:

kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.24

web-1 був відновлений до своєї початкової конфігурації, тому що порядковий номер Podʼа був меншим за partition. Коли вказано partition, всі Podʼи з порядковим номером, який більше або дорівнює partition, будуть оновлені, коли буде оновлено .spec.template StatefulSet. Якщо Pod з порядковим номером, який менший за partition, буде видалений або інакше припинений, він буде відновлено до початкової конфігурації.

Поступові оновлення

Ви можете виконати поступове оновлення (наприклад, лінійне, геометричне або експоненціальне оновлення) за допомогою розділеного оновлення з аналогічним методом до того, як ви впровадили канаркове оновлення. Щоб виконати поступове оновлення, встановіть partition на порядковий номер, на якому ви хочете, щоб контролер призупинив оновлення.

Розділ в цей момент встановлено на 2. Встановіть розділ на 0:

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched

Зачекайте, поки всі Podʼи в StatefulSet стануть запущеними і готовими.

# Це вже має бути запущено
kubectl get pod -l app=nginx --watch

Вивід подібний до:

NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          3m
web-1     0/1       ContainerCreating   0          11s
web-2     1/1       Running             0          2m
web-1     1/1       Running   0         18s
web-0     1/1       Terminating   0         3m
web-0     1/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Pending   0         0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         3s

Отримайте деталі образу контейнера для Podʼів у StatefulSet:

for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21

Переміщуючи partition на 0, ви дозволили StatefulSet продовжити процес оновлення.

OnDelete

Ви обираєте цю стратегію оновлення для StatefulSet, встановивши .spec.template.updateStrategy.type на OnDelete.

Виправте StatefulSet web, щоб використовувати стратегію оновлення OnDelete:

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"OnDelete"}}}'
statefulset.apps/web patched

Коли ви обираєте цю стратегію оновлення, контролер StatefulSet не автоматично оновлює Podʼи, коли вноситься зміна до поля .spec.template StatefulSet. Вам потрібно керувати оновленням самостійно — або вручну, або за допомогою окремої автоматизації.

Видалення StatefulSet

StatefulSet підтримує як не каскадне, так і каскадне видалення. При не каскадному видаленні Podʼи StatefulSet не видаляються при видаленні самого StatefulSet. При каскадному видаленні видаляються як StatefulSet, так і його Podʼи.

Прочитайте Використання каскадного видалення у кластері, щоб дізнатися про каскадне видалення загалом.

Некаскадне видалення

В одному вікні термінала спостерігайте за Podʼами у StatefulSet.

# Завершіть цей перегляд, коли не буде Podʼів для StatefulSet
kubectl get pods --watch -l app=nginx

Використовуйте kubectl delete, щоб видалити StatefulSet. Переконайтеся, що ви передали параметр --cascade=orphan до команди. Цей параметр повідомляє Kubernetes видаляти лише StatefulSet, і не видаляти жодного з його Podʼів.

kubectl delete statefulset web --cascade=orphan
statefulset.apps "web" deleted

Отримайте Podʼи, щоб перевірити їх статус:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          6m
web-1     1/1       Running   0          7m
web-2     1/1       Running   0          5m

Навіть якщо web був видалений, всі Podʼи все ще запущені і готові. Видаліть web-0:

kubectl delete pod web-0
pod "web-0" deleted

Отримайте Podʼи StatefulSet:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-1     1/1       Running   0          10m
web-2     1/1       Running   0          7m

Оскільки StatefulSet web було видалено, web-0 не було перезапущено.

В одному терміналі спостерігайте за Podʼами StatefulSet.

# Залиште цей перегляд запущеним до наступного разу, коли ви почнете перегляд
kubectl get pods --watch -l app=nginx

У другому терміналі створіть знову StatefulSet. Зверніть увагу, якщо ви не видалили nginx Service (що ви не повинні були робити), ви побачите помилку, яка вказує, що Service вже існує.

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
statefulset.apps/web created
service/nginx unchanged

Ігноруйте помилку. Вона лише вказує на те, що була спроба створити headless Service nginx, незважаючи на те, що цей Service вже існує.

Дослідіть вихідні дані команди kubectl get, яка працює у першому терміналі.

# Це вже має бути запущено
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-1     1/1       Running   0          16m
web-2     1/1       Running   0          2m
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         18s
web-2     1/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m

Коли StatefulSet web був відновлений, він спочатку перезапустив web-0. Оскільки web-1 вже був запущений і готовий, коли web-0 перейшов до стану Running і Ready, він став резервним для цього Podʼа. Оскільки ви відновили StatefulSet з replicas рівним 2, як тільки web-0 був відновлений, і як тільки web-1 вже був визначений як Running і Ready, web-2 був закінчений.

Тепер ще раз розгляньте вміст файлу index.html, який обслуговують вебсервери Podʼів:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

Навіть якщо ви видалили як StatefulSet, так і Pod web-0, він все ще обслуговує імʼя хоста, яке спочатку було введено в його файл index.html. Це тому, що StatefulSet ніколи не видаляє PersistentVolumes, повʼязані з Podʼом. Коли ви відновили StatefulSet і перезапустили web-0, його оригінальний PersistentVolume знову змонтувався.

Каскадне видалення

В одному вікні термінала спостерігайте за Podʼами у StatefulSet.

# Залиште це запущеним до наступного розділу сторінки
kubectl get pods --watch -l app=nginx

В іншому терміналі знову видаліть StatefulSet. Цього разу не використовуйте параметр --cascade=orphan.

kubectl delete statefulset web
statefulset.apps "web" deleted

Дослідіть вихідні дані команди kubectl get, яка працює у першому терміналі, і зачекайте, поки всі Podʼи перейдуть у стан Terminating.

# Це вже має бути запущено
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          11m
web-1     1/1       Running   0          27m
NAME      READY     STATUS        RESTARTS   AGE
web-0     1/1       Terminating   0          12m
web-1     1/1       Terminating   0         29m
web-0     0/1       Terminating   0         12m
web-0     0/1       Terminating   0         12m
web-0     0/1       Terminating   0         12m
web-1     0/1       Terminating   0         29m
web-1     0/1       Terminating   0         29m
web-1     0/1       Terminating   0         29m

Як ви бачили в розділі Зменшення мастабу, Podʼи видаляються по одному, з урахуванням зворотного порядку їх порядкових індексів. Перед видаленням Podʼа контролер StatefulSet чекає, поки Pod-наступник буде повністю видалено.

kubectl delete service nginx
service "nginx" deleted

Знову створіть StatefulSet і headless Service:

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created

Коли всі Podʼи StatefulSet перейдуть у стан Running і Ready, отримайте вміст їх файлів index.html:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

Навіть якщо ви повністю видалили StatefulSet і всі його Podʼи, Podʼи перестворюються з монтуванням їх PersistentVolumes, і web-0 й web-1 продовжують обслуговувати свої імена хостів.

Нарешті, видаліть Service nginx

kubectl delete service nginx
service "nginx" deleted

…і StatefulSet web:

kubectl delete statefulset web
statefulset "web" deleted

Політика управління Podʼами

Для деяких розподілених систем порядок, гарантований StatefulSet, є непотрібним або небажаним. Ці системи потребують лише унікальності та ідентичності.

Ви можете вказати політику управління Podʼами, щоб уникнути цього строгого порядку; або OrderedReady (типово), або Parallel.

Політика управління Podʼами OrderedReady

Управління Podʼами OrderedReady є стандартним значенням для StatefulSet. Воно каже контролеру StatefulSet дотримуватися гарантій порядку, показаних вище.

Використовуйте цей параметр, коли ваш застосунок вимагає або очікує, що зміни, такі як запуск нової версії вашого застосунку, відбуваються у строгому порядку за порядковим номером (номером Pod), який надає StatefulSet. Іншими словами, якщо у вас є Podʼи app-0, app-1 та app-2, Kubernetes оновить app-0 першим і перевірить його. Якщо перевірка пройшла успішно, Kubernetes оновить app-1, а потім app-2.

Якщо ви додали ще два Podʼи, Kubernetes налаштує app-3 та почекає, доки він не стане справним, перед тим як розгорнути app-4.

Оскільки це стандартне налаштування, ви вже практикували його використання.

Політика управління Podʼами Parallel

Альтернатива, управління Podʼами Parallel, каже контролеру StatefulSet запускати або видаляти всі Podʼи паралельно, і не чекати, поки Podʼи не стануть Running і Ready або повністю видаленими перед запуском або видаленням іншого Podʼа.

Опція управління Podʼами Parallel впливає лише на поведінку при масштабуванні. Оновлення не піддаються впливу; Kubernetes все ще впроваджує зміни по черзі. Для цього навчального посібника застосунок дуже простий: вебсервер, який повідомляє вам його імʼя хосту (тому що це StatefulSet, імʼя хосту для кожного Podʼа є різним і передбачуваним).

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  podManagementPolicy: "Parallel"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.24
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

Цей маніфест ідентичний тому, який ви завантажили вище, за винятком того, що .spec.podManagementPolicy StatefulSet web встановлено на Parallel.

В одному терміналі спостерігайте за Podʼами в StatefulSet.

# Залиште це запущеним до кінця розділу
kubectl get pod -l app=nginx --watch

В іншому терміналі змініть конфігурацію StatefulSet на управління Podʼами Parallel:

kubectl apply -f https://k8s.io/examples/application/web/web-parallel.yaml
service/nginx updated
statefulset.apps/web updated

Залиште відкритим термінал, де ви запустили спостереження. У іншому вікні терміналу масштабуйте StatefulSet:

kubectl scale statefulset/web --replicas=5
statefulset.apps/web scaled

Дослідіть вихідні дані терміналу, де запущена команда kubectl get. Вони можуть виглядати приблизно так

web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         7s
web-3     0/1       ContainerCreating   0         7s
web-2     0/1       Pending   0         0s
web-4     0/1       Pending   0         0s
web-2     1/1       Running   0         8s
web-4     0/1       ContainerCreating   0         4s
web-3     1/1       Running   0         26s
web-4     1/1       Running   0         2s

StatefulSet запустив три нові Podʼи, і він не чекав, поки перший стане Running і Ready перед запуском другого і третього Podʼів.

Цей підхід є корисним, якщо ваше робоче навантаження зберігає стан або Podʼи мають можливість ідентифікувати один одного за передбачуваною назвою, особливо якщо вам іноді потрібно швидко надати більше потужності. Якщо цей простий вебсервіс для навчального посібника раптово отримав додаткові 1,000,000 запитів на хвилину, то ви хотіли б запустити додаткові Podʼи — але ви також не хотіли б чекати, поки кожен новий Pod буде запущений. Запуск додаткових Podʼів паралельно скорочує час між запитом на додаткову потужність і її доступністю для використання.

Очищення

У вас маєть бути два термінали, готові для запуску команд kubectl для проведення очищення.

kubectl delete sts web
# sts - скорочення для statefulset

Ви можете відстежувати виконання команди kubectl get, щоб переглянути видалення цих ресурсів.

# завершіть перегляд, коли побачите необхідне
kubectl get pod -l app=nginx --watch
web-3     1/1       Terminating   0         9m
web-2     1/1       Terminating   0         9m
web-3     1/1       Terminating   0         9m
web-2     1/1       Terminating   0         9m
web-1     1/1       Terminating   0         44m
web-0     1/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-3     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-1     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-2     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-1     0/1       Terminating   0         44m
web-1     0/1       Terminating   0         44m
web-1     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-3     0/1       Terminating   0         9m
web-3     0/1       Terminating   0         9m
web-3     0/1       Terminating   0         9m

Під час видалення StatefulSet видаляються всі Podʼи одночасно; не чекаючи завершення роботи наступного Podʼа у черзі.

Закрийте термінал, де виконується команда kubectl get, та видаліть Service nginx:

kubectl delete svc nginx

Видаліть носій постійного зберігання для PersistentVolumes, що використовувалися в цьому посібнику.

kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-2bf00408-d366-4a12-bad0-1869c65d0bee   1Gi        RWO            standard       25m
www-web-1   Bound    pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4   1Gi        RWO            standard       24m
www-web-2   Bound    pvc-cba6cfa6-3a47-486b-a138-db5930207eaf   1Gi        RWO            standard       15m
www-web-3   Bound    pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752   1Gi        RWO            standard       15m
www-web-4   Bound    pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e   1Gi        RWO            standard       14m
kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752   1Gi        RWO            Delete           Bound    default/www-web-3   standard                15m
pvc-2bf00408-d366-4a12-bad0-1869c65d0bee   1Gi        RWO            Delete           Bound    default/www-web-0   standard                25m
pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e   1Gi        RWO            Delete           Bound    default/www-web-4   standard                14m
pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4   1Gi        RWO            Delete           Bound    default/www-web-1   standard                24m
pvc-cba6cfa6-3a47-486b-a138-db5930207eaf   1Gi        RWO            Delete           Bound    default/www-web-2   standard                15m
kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4
persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
persistentvolumeclaim "www-web-3" deleted
persistentvolumeclaim "www-web-4" deleted
kubectl get pvc
No resources found in default namespace.

2 - Приклад: Розгортання WordPress та MySQL з постійними томами

У цьому посібнику ви дізнаєтеся, як розгорнути сайт WordPress та базу даних MySQL за допомогою Minikube. Обидва застосунки використовують PersistentVolumes та PersistentVolumeClaims для зберігання даних.

Постійні томи (PersistentVolume (PV)) — це частина системи зберігання в кластері, яку адміністратор вручну надав або яку Kubernetes автоматично надав за допомогою StorageClass. Запити на постійні томи (PersistentVolumeClaim (PVC)) — це запит на зберігання, який користувач може отримати через PV. PersistentVolumes та PersistentVolumeClaims незалежні від життєвого циклу Podʼів і зберігають дані під час перезапуску, перепланування та навіть видалення Podʼів.

Цілі

  • Створення PersistentVolumeClaims and PersistentVolumes
  • Створення kustomization.yaml з
    • генератором Secret
    • конфігураціями ресурсів MySQL
    • конфігураціями ресурсів WordPress
  • Застосування теки kustomization за допомогою kubectl apply -k ./
  • Очищення

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Для перевірки версії введіть kubectl version.

Наведений на цій сторінці приклад працює з kubectl версії 1.27 і вище.

Завантажте наступні конфігураційні файли:

  1. mysql-deployment.yaml

  2. wordpress-deployment.yaml

Створення запитів на постійні томи та постійних томів

Для зберігання даних MySQL та Wordpress кожному потрібен постійний том (PersistentVolume). Їх запити на постійні томи (PersistentVolumeClaim) будуть створені на етапі розгортання.

У багатьох середовищах кластера встановлено типовий StorageClass. Якщо StorageClass не вказано у запиті на постійний том (PersistentVolumeClaim), то використовується типовий StorageClass кластера.

Коли створюється запит на постійний том (PersistentVolumeClaim), постійний том (PersistentVolume) динамічно надається на основі конфігурації StorageClass.

Створення файлу kustomization.yaml

Додавання генератора Secret

Secret — це обʼєкт, який зберігає чутливі дані, такі як паролі або ключі. Починаючи з версії 1.14, kubectl підтримує керування обʼєктами Kubernetes за допомогою файлу kustomization. Ви можете створити Secret за допомогою генераторів у файлі kustomization.yaml.

Додайте генератор Secret у файл kustomization.yaml за допомогою наступної команди. Вам потрібно буде замінити YOUR_PASSWORD на пароль, який ви хочете використовувати.

cat <<EOF >./kustomization.yaml
secretGenerator:
- name: mysql-pass
  literals:
  - password=YOUR_PASSWORD
EOF

Додавання конфігурації ресурсів для MySQL та WordPress

Наступний маніфест описує Deployment одного екземпляра MySQL. Контейнер MySQL монтує PersistentVolume у /var/lib/mysql. Змінна оточення MYSQL_ROOT_PASSWORD встановлює пароль бази даних з Secret.

apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
      - image: mysql:8.0
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        - name: MYSQL_DATABASE
          value: wordpress
        - name: MYSQL_USER
          value: wordpress
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

Наступний маніфест описує Deployment одного екземпляра WordPress. Контейнер WordPress монтує PersistentVolume у /var/www/html для файлів даних вебсайту. Змінна оточення WORDPRESS_DB_HOST встановлює імʼя служби MySQL, визначеної вище, і WordPress буде звертатися до бази даних через службу. Змінна оточення WORDPRESS_DB_PASSWORD встановлює пароль бази даних згенерованого kustomize Secret.

apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - image: wordpress:6.2.1-apache
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: wordpress-mysql
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        - name: WORDPRESS_DB_USER
          value: wordpress
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: wordpress-persistent-storage
        persistentVolumeClaim:
          claimName: wp-pv-claim
  1. Завантажте файл конфігурації розгортання MySQL.

    curl -LO https://k8s.io/examples/application/wordpress/mysql-deployment.yaml
    
  2. Завантажте файл конфігурації розгортання WordPress.

    curl -LO https://k8s.io/examples/application/wordpress/wordpress-deployment.yaml
    
  3. Додайте їх до файлу kustomization.yaml.

    cat <<EOF >>./kustomization.yaml
    resources:
      - mysql-deployment.yaml
      - wordpress-deployment.yaml
    EOF
    

Застосування та перевірка

Файл kustomization.yaml містить всі ресурси для розгортання сайту WordPress та бази даних MySQL. Ви можете застосувати цю теку командою

kubectl apply -k ./

Тепер перевірте, що всі обʼєкти існують.

  1. Перевірте, що Secret існує, виконавши наступну команду:

    kubectl get secrets
    

    Відповідь має бути подібною до цієї:

    NAME                    TYPE                                  DATA   AGE
    mysql-pass-c57bb4t7mf   Opaque                                1      9s
    
  2. Перевірте, що PersistentVolume був динамічно наданий.

    kubectl get pvc
    

    Відповідь має бути подібною до цієї:

    NAME             STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS       AGE
    mysql-pv-claim   Bound     pvc-8cbd7b2e-4044-11e9-b2bb-42010a800002   20Gi       RWO            standard           77s
    wp-pv-claim      Bound     pvc-8cd0df54-4044-11e9-b2bb-42010a800002   20Gi       RWO            standard           77s
    
  3. Перевірте, що Pod працює, виконавши наступну команду:

    kubectl get pods
    

    Відповідь має бути подібною до цієї:

    NAME                               READY     STATUS    RESTARTS   AGE
    wordpress-mysql-1894417608-x5dzt   1/1       Running   0          40s
    
  4. Перевірте, що Service працює, виконавши наступну команду:

    kubectl get services wordpress
    

    Відповідь має бути подібною до цієї:

    NAME        TYPE            CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
    wordpress   LoadBalancer    10.0.0.89    <pending>     80:32406/TCP   4m
    
  5. Виконайте наступну команду, щоб отримати IP-адресу для Service WordPress:

    minikube service wordpress --url
    

    Відповідь має бути подібною до цієї:

    http://1.2.3.4:32406
    
  6. Скопіюйте IP-адресу та завантажте сторінку у своєму оглядачі, щоб переглянути ваш сайт.

    Ви повинні побачити сторінку налаштування WordPress, схожу на знімок екрана нижче.

    wordpress-init

Очищення

  1. Виконайте наступну команду, щоб видалити ваш Secret, Deployments, Services та PersistentVolumeClaims:

    kubectl delete -k ./
    

Що далі

3 - Приклад: Розгортання Cassandra з використанням StatefulSet

Цей підручник покаже вам, як запустити Apache Cassandra у Kubernetes. Cassandra, база даних, потребує постійного сховища для забезпечення стійкості даних (стан застосунку). У цьому прикладі використовується власний постачальник насіння Cassandra, що дозволяє базі даних виявляти нові екземпляри Cassandra, коли вони приєднуються до кластера Cassandra.

StatefulSet полегшує розгортання стійких застосунків у вашому кластері Kubernetes. Для отримання додаткової інформації про використані у цьому підручнику функції дивіться StatefulSet.

Цілі

  • Створення та перевірка Cassandra headless Service.
  • Використання StatefulSet для створення кільця Cassandra.
  • Перевірка StatefulSet.
  • Зміна StatefulSet.
  • Видалення StatefulSet та його Podʼів.

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Для роботи з цим посібником ви повинні вже мати базові знання про Podʼи, Сервіси, та StatefulSets.

Додаткові інструкції щодо налаштування Minikube

Створення headless Service для Cassandra

У Kubernetes Service описує набір Podʼів, які виконують одну й ту ж задачу.

Наступний Service використовується для DNS-пошуку між Podʼами Cassandra та клієнтами у вашому кластері:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: cassandra
  name: cassandra
spec:
  clusterIP: None
  ports:
  - port: 9042
  selector:
    app: cassandra

Створіть Service для відстеження всіх членів StatefulSet Cassandra з файлу cassandra-service.yaml:

kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-service.yaml

Перевірка (необов’язково)

Отримайте інформацію про Cassandra Service.

kubectl get svc cassandra

Відповідь буде

NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
cassandra   ClusterIP   None         <none>        9042/TCP   45s

Якщо ви не бачите Service з назвою cassandra, це означає, що його створення не вдалося. Прочитайте Налагодження Service, щоб отримати довідку з усунення загальних проблем.

Використання StatefulSet для створення кільця Cassandra

Маніфест StatefulSet, наведений нижче, створює кільце Cassandra, що складається з трьох Podʼів.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cassandra
  labels:
    app: cassandra
spec:
  serviceName: cassandra
  replicas: 3
  selector:
    matchLabels:
      app: cassandra
  template:
    metadata:
      labels:
        app: cassandra
    spec:
      terminationGracePeriodSeconds: 500
      containers:
      - name: cassandra
        image: gcr.io/google-samples/cassandra:v13
        imagePullPolicy: Always
        ports:
        - containerPort: 7000
          name: intra-node
        - containerPort: 7001
          name: tls-intra-node
        - containerPort: 7199
          name: jmx
        - containerPort: 9042
          name: cql
        resources:
          limits:
            cpu: "500m"
            memory: 1Gi
          requests:
            cpu: "500m"
            memory: 1Gi
        securityContext:
          capabilities:
            add:
              - IPC_LOCK
        lifecycle:
          preStop:
            exec:
              command: 
              - /bin/sh
              - -c
              - nodetool drain
        env:
          - name: MAX_HEAP_SIZE
            value: 512M
          - name: HEAP_NEWSIZE
            value: 100M
          - name: CASSANDRA_SEEDS
            value: "cassandra-0.cassandra.default.svc.cluster.local"
          - name: CASSANDRA_CLUSTER_NAME
            value: "K8Demo"
          - name: CASSANDRA_DC
            value: "DC1-K8Demo"
          - name: CASSANDRA_RACK
            value: "Rack1-K8Demo"
          - name: POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        readinessProbe:
          exec:
            command:
            - /bin/bash
            - -c
            - /ready-probe.sh
          initialDelaySeconds: 15
          timeoutSeconds: 5
        # Ці точки монтування томів є постійними. Вони подібни до вбудованих заявок,
        # але не зовсіс, тому що імена повинні точно збігатись з одним з томів
        # томів Podʼів stateful.
        volumeMounts:
        - name: cassandra-data
          mountPath: /cassandra_data
  # Це перетворюється у заявки на томи контролером
  # та монтується в шлях, зазначений вище.
  # не використовуйте це в операційній діяльності, допоки тип ssd є GCEPersistentDisk чи інший ssd pd
  volumeClaimTemplates:
  - metadata:
      name: cassandra-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: fast
      resources:
        requests:
          storage: 1Gi
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: fast
provisioner: k8s.io/minikube-hostpath
parameters:
  type: pd-ssd

Створіть StatefulSet Cassandra з файлу cassandra-statefulset.yaml:

# Використовуйте це, якщо ви можете застосувати cassandra-statefulset.yaml без змін
kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml

Якщо вам потрібно змінити cassandra-statefulset.yaml для вашого кластера, завантажте https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml та застосуйте цей маніфест, з теки, в яку ви зберегли змінену версію:

# Використовуйте це, якщо ви змінили cassandra-statefulset.yaml локально
kubectl apply -f cassandra-statefulset.yaml

Перевірка StatefulSet Cassandra

  1. Отримайте StatefulSet Cassandra:

    kubectl get statefulset cassandra
    

    Відповідь буде схожа на:

    NAME        DESIRED   CURRENT   AGE
    cassandra   3         0         13s
    

    Ресурс StatefulSet розгортає Podʼи послідовно.

  2. Отримайте Podʼи, щоб побачити статус створення в зазначеному порядку:

    kubectl get pods -l="app=cassandra"
    

    Відповідь буде схожа на:

    NAME          READY     STATUS              RESTARTS   AGE
    cassandra-0   1/1       Running             0          1m
    cassandra-1   0/1       ContainerCreating   0          8s
    

    На створення всіх трьох Podʼів може піти декілька хвилин. Після їх розгортання ця ж команда повертає вихідні дані, схожі на:

    NAME          READY     STATUS    RESTARTS   AGE
    cassandra-0   1/1       Running   0          10m
    cassandra-1   1/1       Running   0          9m
    cassandra-2   1/1       Running   0          8m
    
  3. Виконайте інструмент nodetool Cassandra всередині першого Podʼа, щоб переглянути стан кільця.

    kubectl exec -it cassandra-0 -- nodetool status
    

    Відповідь буде схожою на:

    Datacenter: DC1-K8Demo
    ======================
    Status=Up/Down
    |/ State=Normal/Leaving/Joining/Moving
    --  Address     Load       Tokens       Owns (effective)  Host ID                               Rack
    UN  172.17.0.5  83.57 KiB  32           74.0%             e2dd09e6-d9d3-477e-96c5-45094c08db0f  Rack1-K8Demo
    UN  172.17.0.4  101.04 KiB  32           58.8%             f89d6835-3a42-4419-92b3-0e62cae1479c  Rack1-K8Demo
    UN  172.17.0.6  84.74 KiB  32           67.1%             a6a1e8c2-3dc5-4417-b1a0-26507af2aaad  Rack1-K8Demo
    

Зміна StatefulSet Cassandra

Використовуйте kubectl edit, щоб змінити розмір StatefulSet Cassandra.

  1. Виконайте наступну команду:

    kubectl edit statefulset cassandra
    

    Ця команда відкриває редактор у вашому терміналі. Рядок, який вам потрібно змінити, — це поле replicas. Наведений нижче приклад — це уривок з файлу StatefulSet:

    # Будь ласка, відредагуйте об’єкт нижче. Рядки, що починаються з '#', будуть проігноровані,
    # і пустий файл призведе до відмови редагування. Якщо під час збереження файлу виникне помилка,
    # цей файл буде знову відкритий із відповідними несправностями.
    #
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      creationTimestamp: 2016-08-13T18:40:58Z
    
    
      generation: 1
      labels:
      app: cassandra
      name: cassandra
      namespace: default
      resourceVersion: "323"
      uid: 7a219483-6185-11e6-a910-42010a8a0fc0
    spec:
      replicas: 3
    
  2. Змініть кількість реплік на 4 і збережіть маніфест.

    Тепер StatefulSet масштабується для роботи з 4 Podʼами.

  3. Отримайте StatefulSet Cassandra, щоб перевірити зміни:

    kubectl get statefulset cassandra
    

    Відповідь буде схожа на:

    NAME        DESIRED   CURRENT   AGE
    cassandra   4         4         36m
    

Очищення

Видалення або зменшення масштабу StatefulSet не призводить до видалення томів, повʼязаних із StatefulSet. Це налаштування захищає вас, оскільки ваші дані цінніші, ніж автоматичне очищення всіх повʼязаних ресурсів StatefulSet.

  1. Виконайте наступні команди (послідовно обʼєднані в одну команду) для видалення всього в StatefulSet Cassandra:

    grace=$(kubectl get pod cassandra-0 -o=jsonpath='{.spec.terminationGracePeriodSeconds}') \
      && kubectl delete statefulset -l app=cassandra \
      && echo "Sleeping ${grace} seconds" 1>&2 \
      && sleep $grace \
      && kubectl delete persistentvolumeclaim -l app=cassandra
    
  2. Виконайте наступну команду для видалення Service, який ви налаштували для Cassandra:

    kubectl delete service -l app=cassandra
    

Змінні середовища контейнера Cassandra

Podʼи в цьому посібнику використовують образ gcr.io/google-samples/cassandra:v13 з реєстру контейнерів Google. Докер-образ вище базується на debian-base і містить OpenJDK 8.

Цей образ включає стандартну установку Cassandra з репозиторію Apache Debian. За допомогою змінних середовища ви можете змінити значення, які вставляються в cassandra.yaml.

Змінна середовищаСтандартне значенняанням
CASSANDRA_CLUSTER_NAME'Test Cluster'
CASSANDRA_NUM_TOKENS32
CASSANDRA_RPC_ADDRESS0.0.0.0

Що далі

4 - Запуск ZooKeeper, розподіленого системного координатора

Цей посібник демонструє як запускати Apache Zookeeper в Kubernetes, використовуючи StatefulSets, PodDisruptionBudgets, та PodAntiAffinity.

Перш ніж ви розпочнете

Перед тим як розпочати, переконайтеся, що ви маєте уявлення про:

Вам необхідно мати кластер із щонайменше чотирма вузлами, і кожен вузол повинен мати щонайменше 2 ЦП та 4 ГБ памʼяті. У цьому посібнику ви будете закривати (cordon) та очищувати для обслуговування (drain) вузли кластера. Це означає, що кластер припинить роботу та виселить всі Podʼи зі своїх вузлів, і вузли тимчасово стануть не придатними до розміщення Podʼів. Вам слід використовувати окремий кластер для цього посібника, або ви повинні забезпечити, що порушення, яке ви викличете, не нашкодить іншим мешканцям кластера.

У цьому посібнику передбачається, що ви налаштували свій кластер для динамічного надання постійних томів (PersistentVolumes). Якщо ваш кластер не налаштований на це, вам доведеться вручну створити три томи обсягом 20 ГБ перед початком виконання кроків цього посібника.

Цілі

Після проходження цього посібника ви будете знати, як:

  • Розгортати ансамбль Apache Zookeeper використовуючи StatefulSet.
  • Як надійно налаштовувати ансамбль.
  • Як поширювати розгортання серверів Zookeeper в ансамблі.
  • Як використовувати PodDisruptionBudgets для забезпечення високої доступності послуг під час запланованого обслуговування.

ZooKeeper

Apache ZooKeeper — це розподілена, відкрита координаційна служба для розподілених застосунків. ZooKeeper дозволяє читати, записувати та спостерігати за оновленнями даних. Дані організовані у вигляді ієрархії файлової системи та реплікуються на всі сервери ZooKeeper в ансамблі (набір серверів ZooKeeper). Всі операції з даними є атомарними та послідовно консистентними. ZooKeeper забезпечує це, використовуючи протокол консенсусу Zab для реплікації машини стану на всіх серверах в ансамблі.

Ансамбль використовує протокол Zab для вибору лідера, ансамбль не може записувати дані, поки цей вибір лідера не завершиться. Після його завершення ансамбль використовує Zab, щоб забезпечити реплікацію всіх записів до кворуму, перш ніж він підтверджує їх та робить їх видимими для клієнтів. Без зваженого кворуму, кворум — буде більшістю складових ансамблю, які містять поточного лідера. Наприклад, якщо в ансамблі є три сервери, компонент, який містить лідера і ще один сервер, становитимуть кворум. Якщо ансамбль не може досягти кворуму, він не може записувати дані.

Сервери ZooKeeper зберігають свою повну машину стану в памʼяті та записують кожну зміну до довгострокового WAL (Write Ahead Log) на носії інформації. Коли сервер аварійно завершує роботу, він може відновити свій попередній стан, відтворюючи WAL. Щоб запобігти безмежному зростанню WAL, сервери ZooKeeper періодично роблять знімки їхнього стану в памʼяті на носій інформації. Ці знімки можуть бути завантажені безпосередньо в памʼять, а всі записи WAL, які передували знімку, можуть бути видалені.

Створення ансамблю ZooKeeper

Маніфест нижче містить:

apiVersion: v1
kind: Service
metadata:
  name: zk-hs
  labels:
    app: zk
spec:
  ports:
  - port: 2888
    name: server
  - port: 3888
    name: leader-election
  clusterIP: None
  selector:
    app: zk
---
apiVersion: v1
kind: Service
metadata:
  name: zk-cs
  labels:
    app: zk
spec:
  ports:
  - port: 2181
    name: client
  selector:
    app: zk
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  selector:
    matchLabels:
      app: zk
  maxUnavailable: 1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zk
spec:
  selector:
    matchLabels:
      app: zk
  serviceName: zk-hs
  replicas: 3
  updateStrategy:
    type: RollingUpdate
  podManagementPolicy: OrderedReady
  template:
    metadata:
      labels:
        app: zk
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"
      containers:
      - name: kubernetes-zookeeper
        imagePullPolicy: Always
        image: "registry.k8s.io/kubernetes-zookeeper:1.0-3.4.10"
        resources:
          requests:
            memory: "1Gi"
            cpu: "0.5"
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 2888
          name: server
        - containerPort: 3888
          name: leader-election
        command:
        - sh
        - -c
        - "start-zookeeper \
          --servers=3 \
          --data_dir=/var/lib/zookeeper/data \
          --data_log_dir=/var/lib/zookeeper/data/log \
          --conf_dir=/opt/zookeeper/conf \
          --client_port=2181 \
          --election_port=3888 \
          --server_port=2888 \
          --tick_time=2000 \
          --init_limit=10 \
          --sync_limit=5 \
          --heap=512M \
          --max_client_cnxns=60 \
          --snap_retain_count=3 \
          --purge_interval=12 \
          --max_session_timeout=40000 \
          --min_session_timeout=4000 \
          --log_level=INFO"
        readinessProbe:
          exec:
            command:
            - sh
            - -c
            - "zookeeper-ready 2181"
          initialDelaySeconds: 10
          timeoutSeconds: 5
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - "zookeeper-ready 2181"
          initialDelaySeconds: 10
          timeoutSeconds: 5
        volumeMounts:
        - name: datadir
          mountPath: /var/lib/zookeeper
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi

Відкрийте термінал і скористайтеся командою kubectl apply, щоб створити маніфест.

kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml

Це створить zk-hs Headless Service, zk-cs Service, zk-pdb PodDisruptionBudget та zk StatefulSet.

service/zk-hs створено
service/zk-cs створено
poddisruptionbudget.policy/zk-pdb створено
statefulset.apps/zk створено

Скористайтеся kubectl get, щоб спостерігати за створенням StatefulSet контролером Podʼів StatefulSet.

kubectl get pods -w -l app=zk

Коли Pod zk-2 в стані Running та Ready, скористайтеся CTRL-C, щоб припинити виконання kubectl.

NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Pending   0          0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         19s
zk-0      1/1       Running   0         40s
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s
zk-1      0/1       ContainerCreating   0         0s
zk-1      0/1       Running   0         18s
zk-1      1/1       Running   0         40s
zk-2      0/1       Pending   0         0s
zk-2      0/1       Pending   0         0s
zk-2      0/1       ContainerCreating   0         0s
zk-2      0/1       Running   0         19s
zk-2      1/1       Running   0         40s

Контролер StatefulSet створює три Podʼи, і кожен Pod містить контейнер з сервером ZooKeeper.

Забезпечення виборів лідера

Оскільки в анонімній мережі немає алгоритму завершення для вибору лідера, Zab вимагає явної конфігурації членства для виконання виборів лідера. Кожен сервер в ансамблі повинен мати унікальний ідентифікатор, всі сервери повинні знати глобальний набір ідентифікаторів, і кожен ідентифікатор повинен бути повʼязаний з мережевою адресою.

Використовуйте kubectl exec, щоб отримати імена хостів Podʼів у StatefulSet zk.

for i in 0 1 2; do kubectl exec zk-$i -- hostname; done

Контролер StatefulSet надає кожному Podʼу унікальне імʼя хосту на основі його порядкового індексу. Імена хостів мають форму <імʼя statefulset>-<порядковий індекс>. Оскільки поле replicas StatefulSet zk встановлено на 3, контролер набору створює три Podʼа з іменами хостів zk-0, zk-1 і zk-2.

zk-0
zk-1
zk-2

Сервери в ансамблі ZooKeeper використовують натуральні числа як унікальні ідентифікатори, і зберігають ідентифікатор кожного сервера у файлі, який називається myid, в теці даних сервера.

Щоб переглянути вміст файлу myid для кожного сервера, скористайтеся такою командою.

for i in 0 1 2; do echo "myid zk-$i";kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done

Оскільки ідентифікатори є натуральними числами, а порядкові індекси є не відʼємними цілими числами, можна згенерувати ідентифікатор, додавши 1 до порядкового номера.

myid zk-0
1
myid zk-1
2
myid zk-2
3

Щоб отримати повне доменне імʼя (FQDN) кожного Podʼа у StatefulSet zk, використовуйте таку команду.

for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done

Service zk-hs створює домен для всіх Podʼів, zk-hs.default.svc.cluster.local.

zk-0.zk-hs.default.svc.cluster.local
zk-1.zk-hs.default.svc.cluster.local
zk-2.zk-hs.default.svc.cluster.local

Записи A в DNS Kubernetes перетворюють FQDN в IP-адреси Podʼів. Якщо Kubernetes переплановує Podʼи, він оновлює записи A із новими IP-адресами Podʼів, але імена записів A не змінюються.

ZooKeeper зберігає свою конфігурацію застосунку в файлі з іменем zoo.cfg. Використовуйте kubectl exec, щоб переглянути вміст файлу zoo.cfg у Поді zk-0.

kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg

У властивостях server.1, server.2 та server.3 внизу файлу, 1, 2 та 3 відповідають ідентифікаторам у файлах myid серверів ZooKeeper. Вони встановлені на FQDN для Podʼів у StatefulSet zk.

clientPort=2181
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/log
tickTime=2000
initLimit=10
syncLimit=2000
maxClientCnxns=60
minSessionTimeout= 4000
maxSessionTimeout= 40000
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888

Досягнення консенсусу

Протоколи консенсусу вимагають, щоб ідентифікатори кожного учасника були унікальними. Два учасники у протоколі Zab не повинні претендувати на той самий унікальний ідентифікатор. Це необхідно для того, щоб процеси в системі могли погодитися щодо того, які процеси затвердили які дані. Якщо запускаються два Podʼа з тим самим порядковим номером, два сервери ZooKeeper ідентифікуватимуть себе як один і той самий сервер.

kubectl get pods -w -l app=zk
NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Pending   0          0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         19s
zk-0      1/1       Running   0         40s
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s
zk-1      0/1       ContainerCreating   0         0s
zk-1      0/1       Running   0         18s
zk-1      1/1       Running   0         40s
zk-2      0/1       Pending   0         0s
zk-2      0/1       Pending   0         0s
zk-2      0/1       ContainerCreating   0         0s
zk-2      0/1       Running   0         19s
zk-2      1/1       Running   0         40s

Записи A для кожного Pod вводяться, коли Pod стає готовим. Тому, FQDN серверів ZooKeeper посилається на єдину точку доступу, і ця точка доступу буде унікальним сервером ZooKeeper, який претендує на ідентифікацію, налаштовану в його файлі myid.

zk-0.zk-hs.default.svc.cluster.local
zk-1.zk-hs.default.svc.cluster.local
zk-2.zk-hs.default.svc.cluster.local

Це забезпечує, що властивості servers у файлах zoo.cfg ZooKeepers становлять собою правильно налаштований ансамбль.

server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888

Коли сервери використовують протокол Zab, щоб спробувати затвердити значення, вони або досягатимуть консенсусу і затверджуватимуть значення (якщо вибори лідера пройшли успішно і принаймні два Podʼа працюють та готові), або вони не зможуть цього зробити (якщо будь-яка з умов не виконується). Ні один стан не призведе до того, що один сервер підтверджує запис від імені іншого.

Перевірка адекватності ансамблю

Найбільш базове тестування на адекватність — це запис даних на один сервер ZooKeeper і читання даних з іншого.

Наведена нижче команда виконує скрипт zkCli.sh, щоб записати world в шлях /hello у Pod zk-0 в ансамблі.

kubectl exec zk-0 -- zkCli.sh create /hello world
WATCHER::

WatchedEvent state:SyncConnected type:None path:null
Created /hello

Щоб отримати дані з Podʼа zk-1, використовуйте таку команду.

kubectl exec zk-1 -- zkCli.sh get /hello

Дані, які ви створили на zk-0, доступні на всіх серверах в ансамблі.

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x100000002
ctime = Thu Dec 08 15:13:30 UTC 2016
mZxid = 0x100000002
mtime = Thu Dec 08 15:13:30 UTC 2016
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

Забезпечення стійкості зберігання

Як зазначено в розділі Основи ZooKeeper, ZooKeeper фіксує всі записи в стійкому журналі (WAL) та періодично записує знімки стану памʼяті на носії. Використання WAL для забезпечення стійкості є поширеною технікою для застосунків, які використовують протоколи консенсусу для досягнення реплікованої машини станів.

Використовуйте команду kubectl delete, щоб видалити обʼєкт StatefulSet zk.

kubectl delete statefulset zk
statefulset.apps "zk" deleted

Спостерігайте за завершенням роботи Podʼів у StatefulSet.

kubectl get pods -w -l app=zk

Коли zk-0 повністю завершить роботу, використовуйте CTRL-C, щоб завершити виконання kubectl.

zk-2      1/1       Terminating   0         9m
zk-0      1/1       Terminating   0         11m
zk-1      1/1       Terminating   0         10m
zk-2      0/1       Terminating   0         9m
zk-2      0/1       Terminating   0         9m
zk-2      0/1       Terminating   0         9m
zk-1      0/1       Terminating   0         10m
zk-1      0/1       Terminating   0         10m
zk-1      0/1       Terminating   0         10m
zk-0      0/1       Terminating   0         11m
zk-0      0/1       Terminating   0         11m
zk-0      0/1       Terminating   0         11m

Повторно застосуйте маніфест у файлі zookeeper.yaml.

kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml

Це створює обʼєкт StatefulSet zk, але інші обʼєкти API у маніфесті не модифікуються, оскільки вони вже існують.

Спостерігайте, як контролер StatefulSet перестворює Podʼи StatefulSet.

kubectl get pods -w -l app=zk

Коли Pod zk-2 відзначається як Running і Ready, використовуйте CTRL-C, щоб завершити виконання kubectl.

NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Pending   0          0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         19s
zk-0      1/1       Running   0         40s
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s
zk-1      0/1       ContainerCreating   0         0s
zk-1      0/1       Running   0         18s
zk-1      1/1       Running   0         40s
zk-2      0/1       Pending   0         0s
zk-2      0/1       Pending   0         0s
zk-2      0/1       ContainerCreating   0         0s
zk-2      0/1       Running   0         19s
zk-2      1/1       Running   0         40s

Використайте наведену нижче команду, щоб отримати значення, яке ви ввели під час тестування на адекватність, з Podʼа zk-2.

kubectl exec zk-2 zkCli.sh get /hello

Навіть якщо ви зупинили та знову створили всі Podʼи в StatefulSet zk, ансамбль все ще обслуговує початкове значення.

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x100000002
ctime = Thu Dec 08 15:13:30 UTC 2016
mZxid = 0x100000002
mtime = Thu Dec 08 15:13:30 UTC 2016
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

Секція volumeClaimTemplates поля spec обʼєкта zk StatefulSet вказує на PersistentVolume, який створюється для кожного Podʼа.

volumeClaimTemplates:
  - metadata:
      name: datadir
      annotations:
        volume.alpha.kubernetes.io/storage-class: anything
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 20Gi

Контролер StatefulSet генерує PersistentVolumeClaim для кожного Podʼа у StatefulSet.

Використайте наступну команду, щоб отримати PersistentVolumeClaims StatefulSet.

kubectl get pvc -l app=zk

Коли StatefulSet повторно створює Podʼи, він повторно монтує PersistentVolumes Podʼів.

NAME           STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
datadir-zk-0   Bound     pvc-bed742cd-bcb1-11e6-994f-42010a800002   20Gi       RWO           1h
datadir-zk-1   Bound     pvc-bedd27d2-bcb1-11e6-994f-42010a800002   20Gi       RWO           1h
datadir-zk-2   Bound     pvc-bee0817e-bcb1-11e6-994f-42010a800002   20Gi       RWO           1h

Розділ volumeMounts контейнера template StatefulSet монтує PersistentVolumes в теки даних серверів ZooKeeper.

volumeMounts:
- name: datadir
  mountPath: /var/lib/zookeeper

Коли Pod у zk StatefulSet (пере)планується, у нього завжди монтується той самий PersistentVolume у теку даних сервера ZooKeeper. Навіть коли Podʼи переплануються, всі записи, зроблені у логах WAL серверів ZooKeeper, та всі їх знімки залишаються стійкими.

Забезпечення однорідної конфігурації

Як вказано в розділах Забезпечення виборів лідера та Досягнення консенсусу, сервери в ансамблі ZooKeeper потребують однорідної конфігурації для вибору лідера та формування кворуму. Також потрібна однорідна конфігурація протоколу Zab для коректної роботи протоколу мережею. У нашому прикладі ми досягаємо однорідної конфігурації, вбудувавши конфігурацію безпосередньо у маніфест.

Отримайте zk StatefulSet.

kubectl get sts zk -o yaml

command:
      - sh
      - -c
      - "start-zookeeper \
        --servers=3 \
        --data_dir=/var/lib/zookeeper/data \
        --data_log_dir=/var/lib/zookeeper/data/log \
        --conf_dir=/opt/zookeeper/conf \
        --client_port=2181 \
        --election_port=3888 \
        --server_port=2888 \
        --tick_time=2000 \
        --init_limit=10 \
        --sync_limit=5 \
        --heap=512M \
        --max_client_cnxns=60 \
        --snap_retain_count=3 \
        --purge_interval=12 \
        --max_session_timeout=40000 \
        --min_session_timeout=4000 \
        --log_level=INFO"

Команда, яка використовується для запуску серверів ZooKeeper, передає конфігурацію як параметр командного рядка. Ви також можете використовувати змінні середовища для передачі конфігурації в ансамбль.

Налаштування системи логування

Один з файлів, що генерується сценарієм zkGenConfig.sh, керує логуванням ZooKeeper. ZooKeeper використовує Log4j, і типово він використовує інструмент постійного додавання, який покладається на час та розмір для конфігурації логування.

Використайте команду нижче, щоб отримати конфігурацію логування з одного з контейнерів у StatefulSet zk.

kubectl exec zk-0 cat /usr/etc/zookeeper/log4j.properties

Конфігурація логування нижче призведе до того, що процес ZooKeeper буде записувати всі свої логи у вихідний файловий потік стандартного виводу.

zookeeper.root.logger=CONSOLE
zookeeper.console.threshold=INFO
log4j.rootLogger=${zookeeper.root.logger}
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n

Це найпростіший спосіб безпечного логування всередині контейнера. Тому що програми записують логи у стандартний вивід, Kubernetes буде відповідальним за ротацію логів для вас. Kubernetes також реалізує розумну політику зберігання, яка гарантує, що логи застосунків, записані у потоки стандартного виводу та помилок, не виснажують локальні носії інформації.

Використайте kubectl logs, щоб отримати останні 20 рядків логів з одного з контейнерів.

kubectl logs zk-0 --tail 20

Ви можете переглядати логи застосунків, записані у стандартний вивід або стандартну помилку, використовуючи kubectl logs та з Kubernetes Dashboard.

2016-12-06 19:34:16,236 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52740
2016-12-06 19:34:16,237 [myid:1] - INFO  [Thread-1136:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52740 (no session established for client)
2016-12-06 19:34:26,155 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52749
2016-12-06 19:34:26,155 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52749
2016-12-06 19:34:26,156 [myid:1] - INFO  [Thread-1137:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52749 (no session established for client)
2016-12-06 19:34:26,222 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52750
2016-12-06 19:34:26,222 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52750
2016-12-06 19:34:26,226 [myid:1] - INFO  [Thread-1138:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52750 (no session established for client)
2016-12-06 19:34:36,151 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52760
2016-12-06 19:34:36,152 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52760
2016-12-06 19:34:36,152 [myid:1] - INFO  [Thread-1139:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52760 (no session established for client)
2016-12-06 19:34:36,230 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52761
2016-12-06 19:34:36,231 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52761
2016-12-06 19:34:36,231 [myid:1] - INFO  [Thread-1140:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52761 (no session established for client)
2016-12-06 19:34:46,149 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52767
2016-12-06 19:34:46,149 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52767
2016-12-06 19:34:46,149 [myid:1] - INFO  [Thread-1141:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52767 (no session established for client)
2016-12-06 19:34:46,230 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52768
2016-12-06 19:34:46,230 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52768
2016-12-06 19:34:46,230 [myid:1] - INFO  [Thread-1142:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52768 (no session established for client)

Kubernetes інтегрується з багатьма рішеннями для логування. Ви можете вибрати рішення для логування, яке найкраще підходить для вашого кластера та застосунків. Для логування та агрегації на рівні кластера розгляньте розгортання sidecar контейнера, для ротації та надсилання ваших логів.

Налаштування не привілейованого користувача

Найкращі практики дозволити застосунку працювати як привілейований користувач всередині контейнера — це предмет для обговорення. Якщо ваша організація вимагає, щоб застосунки працювали як непривілейований користувач, ви можете використовувати SecurityContext для контролю над користувачем, під яким запускається точка входу.

У template Podʼів StatefulSet zk є SecurityContext.

securityContext:
  runAsUser: 1000
  fsGroup: 1000

У контейнерах Podʼів, UID 1000 відповідає користувачеві zookeeper, а GID 1000 відповідає групі zookeeper.

Отримайте інформацію про процес ZooKeeper з Поду zk-0.

kubectl exec zk-0 -- ps -elf

Оскільки поле runAsUser обʼєкта securityContext встановлене на 1000, замість того, щоб запускатися як root, процес ZooKeeper запускається як користувач zookeeper.

F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S zookeep+     1     0  0  80   0 -  1127 -      20:46 ?        00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground
0 S zookeep+    27     1  0  80   0 - 1155556 -    20:46 ?        00:00:19 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg

Типово, коли PersistentVolumes Поду монтується в теку даних сервера ZooKeeper, він доступний тільки для користувача root. Ця конфігурація заважає процесу ZooKeeper записувати свій лог та зберігати свої знімки.

Використовуйте команду нижче, щоб отримати права доступу до файлу теки даних ZooKeeper на Поді zk-0.

kubectl exec -ti zk-0 -- ls -ld /var/lib/zookeeper/data

Оскільки поле fsGroup обʼєкта securityContext встановлене на 1000, власність PersistentVolumes Podʼів встановлюється на групу zookeeper, і процес ZooKeeper може читати та записувати свої дані.

drwxr-sr-x 3 zookeeper zookeeper 4096 Dec  5 20:45 /var/lib/zookeeper/data

Управління процесом ZooKeeper

В документації ZooKeeper зазначено, що "Вам захочеться мати наглядовий процес, який керує кожним з ваших процесів сервера ZooKeeper (JVM)." Використання watchdog (наглядового процесу) для перезапуску процесів, що зазнали збою, у розподіленій системі є загальним шаблоном. При розгортанні застосунку в Kubernetes, замість використання зовнішньої утиліти як наглядового процесу, ви повинні використовувати Kubernetes як watchdog для вашого застосунку.

Оновлення ансамблю

StatefulSet zk налаштований на використання стратегії оновлення RollingUpdate.

Ви можете використовувати kubectl patch, щоб оновити кількість cpus, виділених серверам.

kubectl patch sts zk --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"0.3"}]'
statefulset.apps/zk відредаговано

Використовуйте kubectl rollout status, щоб спостерігати за статусом оновлення.

kubectl rollout status sts/zk
waiting for statefulset rolling update to complete 0 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
waiting for statefulset rolling update to complete 1 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
waiting for statefulset rolling update to complete 2 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
statefulset rolling update complete 3 pods at revision zk-5db4499664...

Це припиняє роботу Podʼів, по одному, у зворотному порядку, і перестворює їх з новою конфігурацією. Це забезпечує підтримку кворуму під час постійного оновлення.

Використовуйте команду kubectl rollout history, щоб переглянути історію або попередні конфігурації.

kubectl rollout history sts/zk

Вивід схожий на такий:

statefulsets "zk"
REVISION
1
2

Використовуйте команду kubectl rollout undo, щоб скасувати модифікацію.

kubectl rollout undo sts/zk

Вивід схожий на такий:

statefulset.apps/zk повернувся до попереднього стану

Обробка відмови процесу

Політики перезапуску контролюють, як Kubernetes обробляє відмови процесів для вхідної точки контейнера в Pod. Для Podʼів у StatefulSet єдине припустиме значення RestartPolicy — це Always, і це є стандартним значенням. Для StatefulSet застосунків ви ніколи не повинні змінювати стандартні значення.

Використовуйте наступну команду, щоб переглянути дерево процесів сервера ZooKeeper, який працює в поді zk-0.

kubectl exec zk-0 -- ps -ef

Команда, яка використовується як точка входу контейнера, має PID 1, а процес ZooKeeper, нащадок точки входу, має PID 27.

UID        PID  PPID  C STIME TTY          TIME CMD
zookeep+     1     0  0 15:03 ?        00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground
zookeep+    27     1  0 15:03 ?        00:00:03 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg

У іншому терміналі спостерігайте за Podʼами у StatefulSet zk за допомогою наступної команди.

kubectl get pod -w -l app=zk

У іншому терміналі завершіть процес ZooKeeper у поді zk-0 за допомогою наступної команди.

kubectl exec zk-0 -- pkill java

Завершення процесу ZooKeeper призвело до завершення його батьківського процесу. Оскільки RestartPolicy контейнера — завжди, він перезапустив батьківський процес.

NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   0          21m
zk-1      1/1       Running   0          20m
zk-2      1/1       Running   0          19m
NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Error     0          29m
zk-0      0/1       Running   1         29m
zk-0      1/1       Running   1         29m

Якщо ваш застосунок використовує сценарій (наприклад, zkServer.sh) для запуску процесу, який реалізує бізнес-логіку застосунку, сценарій повинен завершуватися разом з дочірнім процесом. Це забезпечує перезапуск контейнера застосунку, коли процес, що реалізує бізнес-логіку застосунку, зазнає збою.

Тестування на доступність

Налаштування вашого застосунку на перезапуск збійних процесів це ще не все, щоб забезпечити справність розподіленої системи. Існують сценарії, коли процеси системи можуть бути живими, але недоступними або інакше несправними. Вам слід використовувати проби на доступність, щоб повідомити Kubernetes про те, що процеси вашого застосунку є несправними, і його слід перезапустити.

Шаблон Pod для StatefulSet zk визначає пробу на доступність.

  livenessProbe:
    exec:
      command:
      - sh
      - -c
      - "zookeeper-ready 2181"
    initialDelaySeconds: 15
    timeoutSeconds: 5

Проба викликає скрипт bash, який використовує чотирилітерне слово ruok ZooKeeper для перевірки стану справності сервера.

OK=$(echo ruok | nc 127.0.0.1 $1)
if [ "$OK" == "imok" ]; then
    exit 0
else
    exit 1
fi

У одному вікні термінала використовуйте наступну команду, щоб спостерігати за подами у StatefulSet zk.

kubectl get pod -w -l app=zk

У іншому вікні, використовуйте наступну команду, щоб видалити скрипт zookeeper-ready з файлової системи Podʼа zk-0.

kubectl exec zk-0 -- rm /opt/zookeeper/bin/zookeeper-ready

Коли проба на доступність для процесу ZooKeeper невдала, Kubernetes автоматично перезапустить процес за вас, забезпечуючи тим самим перезапуск несправних процесів у наборі.

kubectl get pod -w -l app=zk
NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   0          1h
zk-1      1/1       Running   0          1h
zk-2      1/1       Running   0          1h
NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Running   0          1h
zk-0      0/1       Running   1         1h
zk-0      1/1       Running   1         1h

Тестування готовності

Готовність не те саме, що і доступність. Якщо процес живий, він запланований і справний. Якщо процес готовий, він може обробляти вхідні дані. Доступність є необхідною, але не достатньою, умовою для готовності. Є випадки, особливо під час ініціалізації та завершення, коли процес може бути доступним, але не готовим.

Якщо ви вказуєте пробу на готовність, Kubernetes буде переконуватися, що процеси вашого застосунку не отримуватимуть мережевого трафіку до тих пір, поки їхні перевірки готовності не пройдуть.

Для сервера ZooKeeper, доступність означає готовність. Тому проба готовності з маніфесту zookeeper.yaml ідентична пробі на доступність.

  readinessProbe:
    exec:
      command:
      - sh
      - -c
      - "zookeeper-ready 2181"
    initialDelaySeconds: 15
    timeoutSeconds: 5

Навіть якщо проби на доступність та готовність ідентичні, важливо вказати обидві. Це забезпечує, що мережевий трафік отримуватимуть лише справні сервери в ансамблі ZooKeeper.

Толерантність до відмов вузлів

Для успішного збереження змін даних ZooKeeper потрібен кворум серверів. Для ансамблю з трьох серверів, два сервери повинні бути справними, щоб записи вдалися. У системах на основі кворуму учасники розгорнені в областях відмов для забезпечення доступності. Щоб уникнути перебоїв через втрату окремої машини, найкращі практики виключають спільне розташування кількох екземплярів застосунку на одній машині.

Станадартно Kubernetes може розміщувати Podʼи в StatefulSet на тому ж вузлі. Для створеного вами ансамблю з трьох серверів, якщо два сервери знаходяться на тому ж вузлі, і цей вузол виходить з ладу, клієнти вашої служби ZooKeeper зазнають перебою, поки хоча б один з Podʼів не буде перепланований.

Ви завжди повинні надавати додаткові можливості для перепланування процесів критичних систем у разі відмови вузла. Якщо ви це зробите, перерва буде тривати лише до тих пір, поки планувальник Kubernetes перепланує один з серверів ZooKeeper. Однак, якщо ви хочете, щоб ваша служба терпіла відмови вузлів без перебою роботи, вам слід встановити podAntiAffinity.

Використовуйте наведену нижче команду, щоб отримати вузли для Podʼів у StatefulSet zk.

for i in 0 1 2; do kubectl get pod zk-$i --template={{.spec.nodeName}}; echo ""; done

Всі Podʼи у StatefulSet zk розгорнуті на різних вузлах.

kubernetes-node-cxpk
kubernetes-node-a5aq
kubernetes-node-2g2d

Це через те, що у Podʼах у StatefulSet zk вказано PodAntiAffinity.

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
            - key: "app"
              operator: In
              values:
                - zk
        topologyKey: "kubernetes.io/hostname"

Поле requiredDuringSchedulingIgnoredDuringExecution говорить планувальнику Kubernetes, що він ніколи не повинен розміщувати два Podʼи з міткою app як zk у домені, визначеному за допомогою topologyKey. Ключ topologyKey kubernetes.io/hostname вказує, що домен — це окремий вузол. Використовуючи різні правила, мітки та селектори, ви можете розширити цей метод, щоб розподілити свій ансамбль на фізичні, мережеві та домени відмов.

Виживання під час обслуговування

У цьому розділі ви здійсните блокування та виведення вузлів для обслуговування. Якщо ви використовуєте цей посібник на спільному кластері, переконайтеся, що це не позначиться негативно на інших мешканцях.

Попередній розділ показав, як розподілити ваші Podʼи по вузлах, щоб вижити в разі непередбачуваних відмов вузлів, але вам також потрібно планувати тимчасові відмови вузлів, які виникають через заплановане обслуговування.

Використайте цю команду, щоб отримати вузли у вашому кластері.

kubectl get nodes

У цьому посібнику передбачається наявність кластера з щонайменше чотирма вузлами. Якщо в кластері є більше чотирьох вузлів, використовуйте kubectl cordon, щоб заборонити доступ до всіх вузлів, окрім чотирьох. Обмеження до чотирьох вузлів гарантуватиме, що Kubernetes врахує обмеження подібності та PodDisruptionBudget при плануванні Podʼів zookeeper у наступній симуляції обслуговування.

kubectl cordon <імʼя-вузла>

Використайте цю команду, щоб отримати zk-pdb PodDisruptionBudget.

kubectl get pdb zk-pdb

Поле max-unavailable показує Kubernetes, що в будь-який момент може бути недоступний найбільше один Pod з StatefulSet zk.

NAME      MIN-AVAILABLE   MAX-UNAVAILABLE   ALLOWED-DISRUPTIONS   AGE
zk-pdb    N/A             1                 1

У одному терміналі використовуйте цю команду, щоб переглядати Podʼи у StatefulSet zk.

kubectl get pods -w -l app=zk

У іншому терміналі використовуйте цю команду, щоб отримати вузли, на яких наразі заплановані Podʼи.

for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done

Вихідна інформація подібна до наступної:

kubernetes-node-pb41
kubernetes-node-ixsl
kubernetes-node-i4c4

Використовуйте kubectl drain, щоб заблокувати та вивести з використання вузол, на якому запланований Pod zk-0.

kubectl drain $(kubectl get pod zk-0 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data

Вихідна інформація подібна до наступної:

node "kubernetes-node-pb41" cordoned

WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-pb41, kube-proxy-kubernetes-node-pb41; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-o5elz
pod "zk-0" deleted
node "kubernetes-node-pb41" drained

Оскільки у вашому кластері є чотири вузла, команда kubectl drain успішно виконується, і zk-0 перепланований на інший вузол.

NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   2          1h
zk-1      1/1       Running   0          1h
zk-2      1/1       Running   0          1h
NAME      READY     STATUS        RESTARTS   AGE
zk-0      1/1       Terminating   2          2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Pending   0         0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         51s
zk-0      1/1       Running   0         1m

Продовжуйте слідкувати за Podʼами StatefulSet у першому терміналі і виведіть вузол, на якому запланований zk-1.

kubectl drain $(kubectl get pod zk-1 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data

Вихідна інформація подібна до наступної:

"kubernetes-node-ixsl" cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-ixsl, kube-proxy-kubernetes-node-ixsl; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-voc74
pod "zk-1" deleted
node "kubernetes-node-ixsl" drained

Pod zk-1 не може бути перепланований, оскільки StatefulSet zk містить правило PodAntiAffinity, яке запобігає спільному розташуванню Podʼів, і так як доступні тільки два вузла, Pod залишиться в стані очікування.

kubectl get pods -w -l app=zk

Вихідна інформація подібна до наступної:

NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   2          1h
zk-1      1/1       Running   0          1h
zk-2      1/1       Running   0          1h
NAME      READY     STATUS        RESTARTS   AGE
zk-0      1/1       Terminating   2          2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Pending   0         0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         51s
zk-0      1/1       Running   0         1m
zk-1      1/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s

Продовжуйте слідкувати за Podʼами StatefulSet, і виведіть з використання вузол, на якому запланований zk-2.

kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data

Вихідна інформація подібна до наступної:

node "kubernetes-node-i4c4" cordoned

WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
WARNING: Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog; Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4
There are pending pods when an error occurred: Cannot evict pod as it would violate the pod's disruption budget.
pod/zk-2

Використовуйте CTRL-C для припинення роботи kubectl.

Ви не можете вивести з роботи третій вузол, оскільки zk-2 буде порушувати zk-budget. Однак, вузол залишатиметься заблокованим (cordoned).

Використовуйте zkCli.sh для отримання значень введених впродовж перевірки адекватності zk-0.

kubectl exec zk-0 zkCli.sh get /hello

Service все ще доступний, оскільки його PodDisruptionBudget не порушено.

WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x200000002
ctime = Wed Dec 07 00:08:59 UTC 2016
mZxid = 0x200000002
mtime = Wed Dec 07 00:08:59 UTC 2016
pZxid = 0x200000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

Використовуйте kubectl uncordon, щоб розблокувати вузол.

kubectl uncordon kubernetes-node-pb41

Вивід буде подібний до наступного:

node "kubernetes-node-pb41" uncordoned

zk-1 переплановано на цей вузол. Зачекайте доки zk-1 не буде Running та Ready.

kubectl get pods -w -l app=zk

Вивід буде подібний до наступного:

NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   2          1h
zk-1      1/1       Running   0          1h
zk-2      1/1       Running   0          1h
NAME      READY     STATUS        RESTARTS   AGE
zk-0      1/1       Terminating   2          2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Pending   0         0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         51s
zk-0      1/1       Running   0         1m
zk-1      1/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         12m
zk-1      0/1       ContainerCreating   0         12m
zk-1      0/1       Running   0         13m
zk-1      1/1       Running   0         13m

Спробуйте вивести вузол з обслуговування на якмоу запланований zk-2.

kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data

Вивід буде подібний до наступного:

node "kubernetes-node-i4c4" already cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
pod "heapster-v1.2.0-2604621511-wht1r" deleted
pod "zk-2" deleted
node "kubernetes-node-i4c4" drained

На цей раз kubectl drain успішно виконується.

Розблокуйте другий вузол, щоб дозволити перепланування zk-2.

kubectl uncordon kubernetes-node-ixsl

Вивід буде подібний до наступного:

node "kubernetes-node-ixsl" uncordoned

Ви можете використовувати kubectl drain разом з PodDisruptionBudgets, щоб забезпечити доступність ваших служб під час обслуговування. Якщо drain використовується для блокування вузлів та видалення Podʼів до виключення вузла з експлуатації для обслуговування, служби, які виражають бюджет відмов, мають поважати цей бюджет . Ви завжди повинні виділяти додаткову потужність для критичних служб, щоб їх Podʼи могли негайно бути переплановані.

Очищення

  • Використайте kubectl uncordon, щоб розблокувати всі вузли у вашому кластері.
  • Ви повинні видалити носії постійного носія для PersistentVolumes, використаних у цьому посібнику. Дотримуйтеся необхідних кроків, залежно від вашого середовища, конфігурації зберігання та методу надання послуг, щоб переконатися, що всі збережені дані вилучено.