Це багатосторінковий друкований вигляд цього розділу. Натисність щоб друкувати.

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

Запуск застосунків

Запуск застосунків без збереження стану і зі збереженням стану та керування ними.

1 - Запуск одноекземплярного застосунку зі збереженням стану

На цій сторінці показано, як запустити одноекземплярний застосунок зі збереженням стану в Kubernetes, використовуючи PersistentVolume та Deployment. Застосунок — MySQL.

Цілі

  • Створити PersistentVolume, що посилається на диск у вашому середовищі.
  • Створити Deployment MySQL.
  • Відкрити доступ до MySQL іншим Podʼам у кластері за відомим DNS-імʼям.

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

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

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

  • Вам потрібно мати або динамічний provisioner PersistentVolume з типовим StorageClass, або самостійно статично надати PersistentVolumes, щоб задовольнити PersistentVolumeClaims, які використовуються тут.

Розгортання MySQL

Ви можете запустити застосунок зі збереженням стану, створивши Deployment Kubernetes і підʼєднавши його до наявного PersistentVolume за допомогою PersistentVolumeClaim. Наприклад, цей файл YAML описує Deployment, який запускає MySQL і посилається на PersistentVolumeClaim. Файл визначає монтування тому для /var/lib/mysql, а потім створює PersistentVolumeClaim, що шукає том на 20G. Цю заявку задовольняє будь-який наявний том, що відповідає вимогам, або динамічний provisioner.

Примітка: пароль визначено у файлі конфігурації yaml, і це небезпечно. Безпечне рішення дивіться в розділі Kubernetes Secrets.

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
  clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:9
        name: mysql
        env:
          # Use secret in real usage
        - name: MYSQL_ROOT_PASSWORD
          value: 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
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  1. Розгорніть PV та PVC з файлу YAML:

    kubectl apply -f https://k8s.io/examples/application/mysql/mysql-pv.yaml
    
  2. Розгорніть вміст файлу YAML:

    kubectl apply -f https://k8s.io/examples/application/mysql/mysql-deployment.yaml
    
  3. Виведіть інформацію про Deployment:

    kubectl describe deployment mysql
    

    Вивід подібний до такого:

    Name:                 mysql
    Namespace:            default
    CreationTimestamp:    Tue, 01 Nov 2016 11:18:45 -0700
    Labels:               app=mysql
    Annotations:          deployment.kubernetes.io/revision=1
    Selector:             app=mysql
    Replicas:             1 desired | 1 updated | 1 total | 0 available | 1 unavailable
    StrategyType:         Recreate
    MinReadySeconds:      0
    Pod Template:
      Labels:       app=mysql
      Containers:
        mysql:
        Image:      mysql:9
        Port:       3306/TCP
        Environment:
          MYSQL_ROOT_PASSWORD:      password
        Mounts:
          /var/lib/mysql from mysql-persistent-storage (rw)
      Volumes:
        mysql-persistent-storage:
        Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
        ClaimName:  mysql-pv-claim
        ReadOnly:   false
    Conditions:
      Type          Status  Reason
      ----          ------  ------
      Available     False   MinimumReplicasUnavailable
      Progressing   True    ReplicaSetUpdated
    OldReplicaSets:       <none>
    NewReplicaSet:        mysql-63082529 (1/1 replicas created)
    Events:
      FirstSeen    LastSeen    Count    From                SubobjectPath    Type        Reason            Message
      ---------    --------    -----    ----                -------------    --------    ------            -------
      33s          33s         1        {deployment-controller }             Normal      ScalingReplicaSet Scaled up replica set mysql-63082529 to 1
    
  4. Перегляньте Podʼи, створені Deployment:

    kubectl get pods -l app=mysql
    

    Вивід подібний до такого:

    NAME                   READY     STATUS    RESTARTS   AGE
    mysql-63082529-2z3ki   1/1       Running   0          3m
    
  5. Перевірте PersistentVolumeClaim:

    kubectl describe pvc mysql-pv-claim
    

    Вивід подібний до такого:

    Name:         mysql-pv-claim
    Namespace:    default
    StorageClass:
    Status:       Bound
    Volume:       mysql-pv-volume
    Labels:       <none>
    Annotations:    pv.kubernetes.io/bind-completed=yes
                    pv.kubernetes.io/bound-by-controller=yes
    Capacity:     20Gi
    Access Modes: RWO
    Events:       <none>
    

Доступ до екземпляра MySQL

Наведений вище файл YAML створює сервіс, який дає іншим Podʼам у кластері змогу отримувати доступ до бази даних. Параметр Service clusterIP: None дозволяє DNS-імені Service розвʼязуватися безпосередньо в IP-адресу Podʼа. Це оптимально, коли за Service стоїть лише один Pod і ви не маєте наміру збільшувати кількість Podʼів.

Запустіть клієнта MySQL, щоб підʼєднатися до сервера:

kubectl run -it --rm --image=mysql:9 --restart=Never mysql-client -- mysql -h mysql -ppassword

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

Waiting for pod default/mysql-client-274442439-zyp6i to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.

mysql>

Оновлення

Образ або будь-яку іншу частину Deployment можна оновити як зазвичай за допомогою команди kubectl apply. Ось деякі запобіжні заходи, специфічні для застосунків зі збереженням стану:

  • Не масштабуйте застосунок. Це налаштування призначене лише для одноекземплярних застосунків. Базовий PersistentVolume можна змонтувати лише в один Pod. Про кластеризовані застосунки зі збереженням стану дивіться документацію StatefulSet.
  • Використовуйте strategy: type: Recreate у файлі конфігурації YAML для Deployment. Це вказує Kubernetes не використовувати поетапні оновлення. Поетапні оновлення не працюватимуть, оскільки одночасно не може працювати більше одного Podʼа. Стратегія Recreate зупинить перший Pod перед створенням нового з оновленою конфігурацією.

Видалення Deployment

Видаліть розгорнуті обʼєкти за назвою:

kubectl delete deployment,svc mysql
kubectl delete pvc mysql-pv-claim
kubectl delete pv mysql-pv-volume

Якщо ви надали PersistentVolume вручну, вам також потрібно вручну видалити його й звільнити базовий ресурс. Якщо ви використовували динамічний provisioner, він автоматично видаляє PersistentVolume, коли бачить, що ви видалили PersistentVolumeClaim. Деякі динамічні provisioners (наприклад, для EBS і PD) також звільняють базовий ресурс під час видалення PersistentVolume.

Що далі

2 - Запуск реплікованого застосунку зі збереженням стану

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

Примітка:

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

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

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

  • Вам потрібно мати або динамічний provisioner PersistentVolume з типовим StorageClass, або самостійно статично надати PersistentVolumes, щоб задовольнити PersistentVolumeClaims, які використовуються тут.

  • Цей підручник передбачає, що ви знаєте про Постійні томи та StatefulSets, а також інші основні поняття, такі як Pod, Service та ConfigMap.
  • Трохи знань MySQL корисні, але цей підручник має на меті представити загальні патерни, які можуть бути корисними для інших систем.
  • Ви використовуєте простір імен default або інший простір імен, в якому відсутні обʼєкти, що конфліктують.
  • У вас має бути AMD64-сумісний процесор.

Цілі

  • Розгорнути репліковану топологію MySQL за допомогою StatefulSet.
  • Надіслати клієнтський трафік MySQL.
  • Спостерігати стійкість до перерв у роботі.
  • Масштабувати StatefulSet вгору та вниз.

Розгортання MySQL

Приклад розгортання складається з ConfigMap, двох Service та StatefulSet.

Створення ConfigMap

Створіть ConfigMap із наступного файлу конфігурації YAML:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
data:
  primary.cnf: |
    # Apply this config only on the primary.
    [mysqld]
    log-bin
  replica.cnf: |
    # Apply this config only on replicas.
    [mysqld]
    super-read-only

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

Цей ConfigMap надає перевизначення для my.cnf, що дозволяє вам незалежно керувати конфігурацією на головному сервері MySQL та його репліках. У цьому випадку ви хочете, щоб головний сервер міг надавати логи реплікації реплікам, а репліки відхиляли будь-які записи, які надходять не через реплікацію.

Немає нічого особливого у самому ConfigMap, що зумовлює застосування різних частин до різних Podʼів. Кожен Pod вирішує, яку частину дивитися при ініціалізації, на основі інформації, що надає контролер StatefulSet.

Створення Service

Створіть Service із наступного файлу конфігурації YAML:

# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the primary: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
    readonly: "true"
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml

Headless Service надає місце для DNS-записів, які контролери StatefulSet створюють для кожного Podʼа, що є частиною набору. Оскільки headless Service називається mysql, Podʼи доступні через розвʼязання імені <pod-name>.mysql з будь-якого іншого Podʼа в тому самому кластері Kubernetes і просторі імен.

Клієнтський Service, з назвою mysql-read, є звичайним Service з власним кластерним IP, який розподіляє підключення між всіма Podʼами MySQL, які повідомляють про готовність. Набір потенційних точок доступу включає головний сервер MySQL та всі репліки.

Зверніть увагу, що лише запити на читання можуть використовувати клієнтський Service з балансуванням навантаження. Оскільки існує лише один головний сервер MySQL, клієнти повинні підключатися безпосередньо до головного Podʼа MySQL (через його DNS-запис у headless Service) для виконання записів.

Створення StatefulSet

Наостанок, створіть StatefulSet із наступного файлу конфігурації YAML:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
      app.kubernetes.io/name: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
        app.kubernetes.io/name: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Generate mysql server-id from pod ordinal index.
          [[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          # Add an offset to avoid reserved server-id=0 value.
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # Copy appropriate conf.d files from config-map to emptyDir.
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/primary.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/replica.cnf /mnt/conf.d/
          fi
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      - name: clone-mysql
        image: gcr.io/google-samples/xtrabackup:1.0
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Skip the clone if data already exists.
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Skip the clone on primary (ordinal index 0).
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          # Clone data from previous peer.
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # Prepare the backup.
          xtrabackup --prepare --target-dir=/var/lib/mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            # Перевірити, що ми можемо виконувати запити через TCP (skip-networking вимкнено).
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: gcr.io/google-samples/xtrabackup:1.0
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql

          # Determine binlog position of cloned data, if any.
          if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
            # XtraBackup already generated a partial "CHANGE MASTER TO" query
            # because we're cloning from an existing replica. (Need to remove the tailing semicolon!)
            cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f xtrabackup_slave_info xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # We're cloning directly from primary. Parse binlog position.
            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm -f xtrabackup_binlog_info xtrabackup_slave_info
            echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
                  MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
          fi

          # Check if we need to complete a clone by starting replication.
          if [[ -f change_master_to.sql.in ]]; then
            echo "Waiting for mysqld to be ready (accepting connections)"
            until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done

            echo "Initializing replication from clone position"
            mysql -h 127.0.0.1 \
                  -e "$(<change_master_to.sql.in), \
                          MASTER_HOST='mysql-0.mysql', \
                          MASTER_USER='root', \
                          MASTER_PASSWORD='', \
                          MASTER_CONNECT_RETRY=10; \
                        START SLAVE;" || exit 1
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
          fi

          # Start a server to send backups when requested by peers.
          exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
            "xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml

Ви можете спостерігати за процесом запуску, виконавши:

kubectl get pods -l app=mysql --watch

Через деякий час ви повинні побачити, що всі 3 Podʼи стануть Running:

NAME      READY     STATUS    RESTARTS   AGE
mysql-0   2/2       Running   0          2m
mysql-1   2/2       Running   0          1m
mysql-2   2/2       Running   0          1m

Натисніть Ctrl+C, щоб скасувати перегляд.

Примітка:

Якщо ви не бачите жодних змін, переконайтеся, що у вас увімкнений динамічний provisioner PersistentVolume, як зазначено у передумовах.

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

Розуміння ініціалізації Podʼа зі збереженням стану

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

Крім того, контролер призначає кожному Podʼу унікальне, стабільне імʼя у формі <statefulset-name>-<ordinal-index>, в результаті отримуємо Podʼи з іменами mysql-0, mysql-1 та mysql-2.

Шаблон Podʼа у вищенаведеному маніфесті StatefulSet використовує ці властивості, щоб здійснити впорядкований запуск реплікації MySQL.

Створення конфігурації

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

Перший контейнер ініціалізації, init-mysql, генерує спеціальні конфігураційні файли MySQL на основі порядкового індексу.

Скрипт визначає свій власний порядковий індекс, витягуючи його з кінця імені Podʼа, яке повертається командою hostname. Потім він зберігає порядковий індекс (з числовим зміщенням для уникнення зарезервованих значень) у файлі з назвою server-id.cnf в теці conf.d MySQL. Це перетворює унікальний, стабільний ідентифікатор, наданий StatefulSet, у домен ідентифікаторів серверів MySQL, які вимагають таких же властивостей.

Скрипт у контейнері init-mysql також застосовує або primary.cnf, або replica.cnf з ConfigMap, копіюючи вміст у conf.d. Оскільки у топології цього прикладу є лише один головний сервер MySQL та будь-яка кількість реплік, скрипт призначає порядковий індекс 0 головному серверу, а всі інші — репліками. Разом з гарантією контролера StatefulSet щодо порядку розгортання, це забезпечує готовність головного сервера MySQL перед створенням реплік, щоб вони могли почати реплікацію.

Клонування наявних даних

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

Другий контейнер ініціалізації, з назвою clone-mysql, виконує операцію клонування на реплікаційному Podʼі першого разу, коли він запускається на порожньому PersistentVolume. Це означає, що він копіює всі наявні дані з іншого працюючого Podʼа, таким чином, його локальний стан є достатньо консистентним для початку реплікації з головного сервера.

MySQL сам по собі не надає механізму для цього, тому приклад використовує популярний відкритий інструмент — Percona XtraBackup. Під час клонування сервер-джерело MySQL може мати знижену продуктивність. Щоб мінімізувати вплив на головний сервер MySQL, скрипт вказує кожному Podʼу отримувати дані з Podʼа, чий порядковий індекс на одиницю менший. Це працює через те, що контролер StatefulSet завжди гарантує, що Pod N є готовим перед запуском Podʼа N+1.

Початок реплікації

Після успішного завершення контейнерів ініціалізації запускаються звичайні контейнери. Podʼи MySQL складаються з контейнера mysql, в якому працює фактичний сервер mysqld, та контейнера xtrabackup, який діє як sidecar.

Sidecar xtrabackup переглядає клоновані файли даних і визначає, чи необхідно ініціалізувати реплікацію MySQL на репліці. У цьому випадку він чекає, доки mysqld буде готовий, а потім виконує команди CHANGE MASTER TO та START SLAVE з параметрами реплікації, отриманими з клонованих файлів XtraBackup.

Після того як репліка починає реплікацію, вона запамʼятовує свій головний сервер MySQL і автоматично підʼєднується, якщо сервер перезапускається або зʼєднання втрачається. Також, оскільки репліки шукають головний сервер за його стабільним DNS-імʼям (mysql-0.mysql), вони автоматично знаходять головний сервер навіть якщо він отримує новий IP Podʼа через перепланування.

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

Надсилання клієнтського трафіку

Ви можете надіслати тестові запити до головного сервера MySQL (хост mysql-0.mysql) запустивши тимчасовий контейнер з образом mysql:5.7 і використовуючи бінарний файл клієнта mysql.

kubectl run mysql-client --image=mysql:5.7 -i --rm --restart=Never --\
  mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello');
EOF

Використовуйте хост mysql-read, щоб надіслати тестові запити до будь-якого сервера, який повідомляє про готовність:

kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
  mysql -h mysql-read -e "SELECT * FROM test.messages"

Ви повинні отримати вивід схожий на цей:

Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello   |
+---------+
pod "mysql-client" deleted

Щоб продемонструвати, що Service mysql-read розподіляє підключення між серверами, ви можете запустити SELECT @@server_id у циклі:

kubectl run mysql-client-loop --image=mysql:5.7 -i -t --rm --restart=Never --\
  bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"

Ви повинні бачити, що @@server_id змінюється випадковим чином, оскільки може бути вибрана інша точка доступу при кожній спробі підключення:

+-------------+---------------------+
| @@server_id | NOW()               |
+-------------+---------------------+
|         100 | 2006-01-02 15:04:05 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW()               |
+-------------+---------------------+
|         102 | 2006-01-02 15:04:06 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW()               |
+-------------+---------------------+
|         101 | 2006-01-02 15:04:07 |
+-------------+---------------------+

Ви можете натиснути Ctrl+C, коли захочете зупинити цикл, але цікаво тримати його запущеним в іншому вікні, щоб бачити ефект від наступних кроків.

Симуляція відмови Podʼа та вузла

Щоб продемонструвати підвищену доступність читання з пулу реплік замість одного сервера, залиште цикл SELECT @@server_id, запущений вище, активним, тоді як ви змушуєте Pod вийти зі стану Ready.

Зламайте пробу готовності

Проба готовності для контейнера mysql виконує команду mysql -h 127.0.0.1 -e 'SELECT 1', щоб переконатися, що сервер запущений і може виконувати запити.

Один зі способів змусити цю пробу готовності не спрацювати — зламати цю команду:

kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off

Ця дія втручається у файлову систему контейнера Podʼа mysql-2 і перейменовує команду mysql, щоб проба готовності не могла її знайти. Через кілька секунд Pod повинен повідомити про один зі своїх контейнерів як неготовий, що можна перевірити за допомогою:

kubectl get pod mysql-2

Перевірте значення 1/2 у стовпці READY:

NAME      READY     STATUS    RESTARTS   AGE
mysql-2   1/2       Running   0          3m

На цьому етапі ви повинні бачити продовження роботи вашого циклу SELECT @@server_id, хоча він більше не повідомляє про 102. Нагадаємо, що скрипт init-mysql визначив server-id як 100 + $ordinal, тож ідентифікатор сервера 102 відповідає Podʼу mysql-2.

Тепер відновіть Pod і він повинен знову зʼявитися у виводі циклу через кілька секунд:

kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql

Видалення Podʼів

Контролер StatefulSet також створює Podʼи знову, якщо вони видалені, подібно до того, як це робить ReplicaSet для Podʼів без збереження стану.

kubectl delete pod mysql-2

Контролер StatefulSet помічає, що Podʼа mysql-2 більше не існує, і створює новий з тією ж назвою та повʼязаний з тим самим PersistentVolumeClaim. Ви повинні побачити, що ідентифікатор сервера 102 зникне з виводу циклу протягом певного часу і потім повернеться самостійно.

Виконайте drain вузла

Якщо у вашому кластері Kubernetes є кілька вузлів, ви можете симулювати відмову вузла (наприклад, під час оновлення вузлів) за допомогою команди drain.

Спочатку визначте, на якому вузлі знаходиться один із Podʼів MySQL:

kubectl get pod mysql-2 -o wide

Назва вузла повинна зʼявитися у останньому стовпчику:

NAME      READY     STATUS    RESTARTS   AGE       IP            NODE
mysql-2   2/2       Running   0          15m       10.244.5.27   kubernetes-node-9l2t

Потім виконайте drain вузла такою командою: вона виконує cordon вузла, щоб нові Podʼи не могли туди плануватися, а потім виселяє всі наявні Podʼи. Замість <node-name> підставте назву вузла, яку ви знайшли на попередньому кроці.

Увага:

Drain вузла може вплинути на інші робочі навантаження та застосунки, що працюють на тому ж вузлі. Виконуйте наступний крок тільки на тестовому кластері.
# Дивіться вище поради щодо впливу на інші робочі навантаження
kubectl drain <node-name> --force --delete-emptydir-data --ignore-daemonsets

Тепер ви можете спостерігати, як Pod переплановується на іншому вузлі:

kubectl get pod mysql-2 -o wide --watch

Це має виглядати приблизно так:

NAME      READY   STATUS          RESTARTS   AGE       IP            NODE
mysql-2   2/2     Terminating     0          15m       10.244.1.56   kubernetes-node-9l2t
[...]
mysql-2   0/2     Pending         0          0s        <none>        kubernetes-node-fjlm
mysql-2   0/2     Init:0/2        0          0s        <none>        kubernetes-node-fjlm
mysql-2   0/2     Init:1/2        0          20s       10.244.5.32   kubernetes-node-fjlm
mysql-2   0/2     PodInitializing 0          21s       10.244.5.32   kubernetes-node-fjlm
mysql-2   1/2     Running         0          22s       10.244.5.32   kubernetes-node-fjlm
mysql-2   2/2     Running         0          30s       10.244.5.32   kubernetes-node-fjlm

І знову, ви повинні побачити, що ідентифікатор сервера 102 зник з виводу циклу SELECT @@server_id протягом певного часу, а потім повернувся.

Тепер виконайте uncordon вузла, щоб повернути його до звичайного стану:

kubectl uncordon <node-name>

Масштабування кількості реплік

Коли ви використовуєте реплікацію MySQL, ви можете збільшувати пропускну здатність для запитів на читання, додаючи репліки. Для StatefulSet це можна зробити однією командою:

kubectl scale statefulset mysql  --replicas=5

Спостерігайте, як нові Podʼи запускаються, виконавши:

kubectl get pods -l app=mysql --watch

Коли вони будуть готові, ви побачите, що ідентифікатори серверів 103 та 104 починають зʼявлятися у виводі циклу SELECT @@server_id.

Ви також можете перевірити, що ці нові сервери мають дані, які ви додали ще до того, як вони зʼявилися:

kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
  mysql -h mysql-3.mysql -e "SELECT * FROM test.messages"
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello   |
+---------+
pod "mysql-client" deleted

Зменшення кількості реплік так само безпроблемне:

kubectl scale statefulset mysql --replicas=3

Примітка:

Хоча збільшення створює нові PersistentVolumeClaims автоматично, зменшення автоматично не видаляє ці PVC.

Це дає вам можливість залишити ці ініціалізовані PVC для швидшого збільшення, або вилучити дані перед їх видаленням.

Ви можете перевірити це, виконавши:

kubectl get pvc -l app=mysql

Це покаже, що всі 5 PVC все ще існують, попри те, що StatefulSet був зменшений до 3:

NAME           STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
data-mysql-0   Bound     pvc-8acbf5dc-b103-11e6-93fa-42010a800002   10Gi       RWO           20m
data-mysql-1   Bound     pvc-8ad39820-b103-11e6-93fa-42010a800002   10Gi       RWO           20m
data-mysql-2   Bound     pvc-8ad69a6d-b103-11e6-93fa-42010a800002   10Gi       RWO           20m
data-mysql-3   Bound     pvc-50043c45-b1c5-11e6-93fa-42010a800002   10Gi       RWO           2m
data-mysql-4   Bound     pvc-500a9957-b1c5-11e6-93fa-42010a800002   10Gi       RWO           2m

Якщо ви не збираєтеся повторно використовувати додаткові PVC, ви можете їх видалити:

kubectl delete pvc data-mysql-3
kubectl delete pvc data-mysql-4

Очищення

  1. Припиніть цикл SELECT @@server_id, натиснувши Ctrl+C в його терміналі або виконавши наступне з іншого терміналу:

    kubectl delete pod mysql-client-loop --now
    
  2. Видаліть StatefulSet. Це також розпочне завершення Podʼів.

    kubectl delete statefulset mysql
    
  3. Перевірте, що Podʼи зникають. Завершення їхньої роботи може зайняти деякий час.

    kubectl get pods -l app=mysql
    

    Ви будете знати, що Podʼи завершилися, коли вищезазначена команда поверне:

    No resources found.
    
  4. Видаліть ConfigMap, Services та PersistentVolumeClaims.

    kubectl delete configmap,service,pvc -l app=mysql
    
  5. Якщо ви вручну надавали PersistentVolumes, вам також потрібно вручну видалити їх, а також звільнити відповідні ресурси. Якщо ви використовували динамічний provisioner, він автоматично видаляє PersistentVolumes, коли бачить, що ви видалили PersistentVolumeClaims. Деякі динамічні provisioners (такі як ті, що стосуються EBS та PD) також звільняють відповідні ресурси при видаленні PersistentVolumes.

Що далі

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

Це завдання показує, як масштабувати StatefulSet. Масштабування StatefulSet означає збільшення або зменшення кількості реплік.

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

  • StatefulSets доступні лише у Kubernetes версії 1.5 або новішій. Щоб перевірити версію Kubernetes, виконайте kubectl version.

  • Не всі застосунки зі збереженням стану добре масштабуються. Якщо ви не впевнені, чи варто масштабувати ваші StatefulSets, докладніше дивіться в концепції StatefulSet або в навчальному матеріалі про StatefulSet.

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

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

Використання kubectl для масштабування StatefulSets

Спочатку знайдіть StatefulSet, який ви хочете масштабувати.

kubectl get statefulsets <stateful-set-name>

Змініть кількість реплік вашого StatefulSet:

kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>

Оновлення ваших StatefulSets на місці

Також можна виконати оновлення на місці ваших StatefulSets.

Якщо ваш StatefulSet спочатку створено за допомогою kubectl apply, оновіть .spec.replicas у маніфестах StatefulSet, а потім виконайте kubectl apply:

kubectl apply -f <stateful-set-file-updated>

Інакше відредагуйте це поле за допомогою kubectl edit:

kubectl edit statefulsets <stateful-set-name>

Або використовуйте kubectl patch:

kubectl patch statefulsets <stateful-set-name> -p '{"spec":{"replicas":<new-replicas>}}'

Усунення несправностей

Зменшення масштабу не працює належним чином

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

Якщо spec.replicas > 1, Kubernetes не може визначити причину несправності Podʼа. Це може бути наслідком постійного або тимчасового збою. Тимчасовий збій може бути спричинений перезапуском, потрібним для оновлення або обслуговування.

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

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

Що далі

4 - Видалення StatefulSet

Це завдання показує, як видалити StatefulSet.

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

  • Це завдання передбачає, що у вас у кластері працює застосунок, представлений StatefulSet.

Видалення StatefulSet

Ви можете видалити StatefulSet так само, як й інші ресурси в Kubernetes: скористайтеся командою kubectl delete і вкажіть StatefulSet за файлом або за назвою.

kubectl delete -f <file.yaml>
kubectl delete statefulsets <statefulset-name>

Після видалення самого StatefulSet може знадобитися окремо видалити повʼязаний headless Service.

kubectl delete service <service-name>

Під час видалення StatefulSet через kubectl StatefulSet масштабується до 0. Усі Podʼи, що є частиною цього робочого навантаження, також видаляються. Якщо ви хочете видалити лише StatefulSet, а не Podʼи, використовуйте --cascade=orphan. Наприклад:

kubectl delete -f <file.yaml> --cascade=orphan

Якщо передати --cascade=orphan до kubectl delete, Podʼи, якими керує StatefulSet, залишаться навіть після видалення самого обʼєкта StatefulSet. Якщо Podʼи мають мітку app.kubernetes.io/name=MyApp, їх можна видалити так:

kubectl delete pods -l app.kubernetes.io/name=MyApp

Постійні томи

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

Примітка:

Будьте обережні під час видалення PVC, оскільки це може призвести до втрати даних.

Повне видалення StatefulSet

Щоб видалити все у StatefulSet, зокрема повʼязані Podʼи, можна виконати серію команд, подібних до наведених нижче:

grace=$(kubectl get pods <stateful-set-pod> --template '{{.spec.terminationGracePeriodSeconds}}')
kubectl delete statefulset -l app.kubernetes.io/name=MyApp
sleep $grace
kubectl delete pvc -l app.kubernetes.io/name=MyApp

У наведеному вище прикладі Podʼи мають мітку app.kubernetes.io/name=MyApp; за потреби підставте власну мітку.

Примусове видалення Podʼів StatefulSet

Якщо деякі Podʼи у вашому StatefulSet тривалий час залишаються у стані 'Terminating' або 'Unknown', може знадобитися втрутитися вручну, щоб примусово видалити Podʼи з API-сервера. Це потенційно небезпечне завдання. Докладніше дивіться в розділі Примусове видалення Podʼів StatefulSet.

Що далі

Дізнайтеся більше про примусове видалення Podʼів StatefulSet.