Запуск кластерів etcd як Kubernetes StatefulSet

Запуск etcd як Kubernetes StatefulSet

Нижче показано, як виконати статичний процес завантаження як Kubernetes StatefulSet.

Приклад маніфесту

Цей маніфест містить сервіс та statefulset для розгортання статичного кластера etcd у Kubernetes.

Якщо ви скопіюєте вміст маніфесту у файл з назвою etcd.yaml, його можна застосувати до кластера за допомогою цієї команди.

$ kubectl apply --filename etcd.yaml

Після застосування, зачекайте, поки podʼи стануть готовими.

$ kubectl get pods
NAME     READY   STATUS    RESTARTS   AGE
etcd-0   1/1     Running   0          24m
etcd-1   1/1     Running   0          24m
etcd-2   1/1     Running   0          24m

Контейнер, використаний у прикладі, включає etcdctl і може бути викликаний безпосередньо всередині podʼів.

$ kubectl exec -it etcd-0 -- etcdctl member list -wtable
+------------------+---------+--------+-------------------------+-------------------------+------------+
|        ID        | STATUS  |  NAME  |       PEER ADDRS        |      CLIENT ADDRS       | IS LEARNER |
+------------------+---------+--------+-------------------------+-------------------------+------------+
| 4f98c3545405a0b0 | started | etcd-2 | http://etcd-2.etcd:2380 | http://etcd-2.etcd:2379 |      false |
| a394e0ee91773643 | started | etcd-0 | http://etcd-0.etcd:2380 | http://etcd-0.etcd:2379 |      false |
| d10297b8d2f01265 | started | etcd-1 | http://etcd-1.etcd:2380 | http://etcd-1.etcd:2379 |      false |
+------------------+---------+--------+-------------------------+-------------------------+------------+

Щоб розгорнути з самопідписним сертифікатом, зверніться до коментованих заголовків конфігурації, що починаються з ## TLS, щоб знайти значення, які можна розкоментувати. Додаткові інструкції щодо створення сертифіката за допомогою cert-manager наведені в розділі нижче.

# file: etcd.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: etcd
  namespace: default
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: etcd
  ##
  ## В ідеалі ми мали б використовувати SRV-записи для виявлення однорангових систем для ініціалізації.
  ## На жаль, виявлення не працюватиме без логіки очікування, поки вони
  ## заповнять контейнер. Цю проблему відносно легко вирішити шляхом
  ## внесенням змін, щоб запобігти запуску процесу etcd до того, як записи
  ## не буде заповнено. У документації до statefulsets коротко описано, як це зробити.
  ##   https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#stable-network-id
  publishNotReadyAddresses: true
  ##
  ## Схема іменування портів клієнта і сервера відповідає схемі, яку використовує etcd
  ## використовує при виявленні за допомогою SRV-записів.
  ports:
  - name: etcd-client
    port: 2379
  - name: etcd-server
    port: 2380
  - name: etcd-metrics
    port: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  namespace: default
  name: etcd
spec:
  ##
  ## Імʼя сервісу встановлюється для того, щоб використовувати сервіс headless.
  ## https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
  serviceName: etcd
  ##
  ## Якщо ви збільшуєте кількість реплік існуючого кластера, вам слід
  ## також оновити прапорець --initial-cluster-state, як зазначено нижче у
  ## конфігурації контейнера.
  replicas: 3
  ##
  ## Для ініціалізації, podʼів etcd повинні бути доступні один одному до того, як
  ## як вони будуть "ready" для трафіку. Політика "Parallel" робить це можливим.
  podManagementPolicy: Parallel
  ##
  ## Для забезпечення доступності кластера etcd, використовується стратегія
  ## rolling update. Для доступності необхідно, щоб принаймні 51% вузлів etcd
  ## онлайн у будь-який момент часу.
  updateStrategy:
    type: RollingUpdate
  ##
  ## Це запит міток поверх podʼів, які мають відповідати кількості реплік.
  ## Він має відповідати міткам шаблону pod. Для отримання додаткової інформації зверніться до
  ## наступної документації:
  ##   https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
  selector:
    matchLabels:
      app: etcd
  ##
  ## Шаблон для конфігурації pod.
  template:
    metadata:
      ##
      ## Маркування тут привʼязано до “matchLabels" цього StatefulSet та
      ## конфігурації "affinity"  podʼа, що буде створено.
      ##
      ## Ця схема міток у прикладі підходить для одного кластера etcd на
      ## простір імен, але якщо вам потрібно декілька кластерів для одного простору імен,
      ## вам потрібно буде оновити схему міток, щоб вона була унікальною для кожного кластера etcd.
      labels:
        app: etcd
      annotations:
        ##
        ## На нього посилаються у конфігурації контейнера etcd як на частину
        ## імені DNS. Воно має відповідати імені служби, створеної для кластера etcd
        ## Вирішено розмістити його в анотації, а не у параметрах env
        ## пояснюється тим, що на одному кластері etcd має бути лише 1 служба.
        serviceName: etcd
    spec:
      ##
      ## Налаштування спорідненості вузлів необхідне для того, щоб etcd-сервери
      ## не опинилися на одному апаратному забезпеченні.
      ##
      ## Докладнішу інформацію про це наведено у документації з планування:
      ##   https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
      affinity:
        ## podAntiAffinity - це набір правил для планування, які описують
        ## коли НЕ розміщувати на вузлі pod з цього StatefulSet.
        podAntiAffinity:
          ##
          ## При підготовці до розміщення podʼа на вузлі, планувальник перевірить
          ## на наявність інших podʼів, що відповідають правилам, описаним у labelSelector
          ## відокремлених обраним ключем топології.
          requiredDuringSchedulingIgnoredDuringExecution:
          ## Цей селектор міток шукає app=etcd
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - etcd
            ## Цей ключ топології позначає спільну мітку, яка використовується на вузлах у
            ## кластері. Конфігурація podAntiAffinity передбачає, що
            ## якщо інший pod має мітку app=etcd на вузлі, то
            ## планувальник не повинен розміщувати на цьому вузлі інший pod.
            ##   https://kubernetes.io/docs/reference/labels-annotations-taints/#kubernetesiohostname
            topologyKey: "kubernetes.io/hostname"
      ##
      ## Контейнери в podʼі
      containers:
      ## У цьому прикладі є тільки цей контейнер etcd.
      - name: etcd
        image: quay.io/coreos/etcd:v3.5.15
        imagePullPolicy: IfNotPresent
        ports:
        - name: etcd-client
          containerPort: 2379
        - name: etcd-server
          containerPort: 2380
        - name: etcd-metrics
          containerPort: 8080
        ##
        ## Ці проби не зможуть пройти через TLS для самопідписних сертифікатів, тому etcd
        ## налаштовано на передачу метрик через порт 8080 і далі.
        ##
        ## Як зазначено на сторінці «Моніторинг etcd», /readyz та /livez було
        ## додано у версії 3.5.12. До цього моніторинг вимагав додаткового інструментарію
        ## всередині контейнера, щоб змусити ці проби працювати.
        ##
        ## Значення у цій перевірці готовності слід додатково перевірити,
        ## це лише приклад конфігурації.
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 30
        ## Значення у цій конфігурації слід додатково перевірити, це
        ## це лише приклад конфігурації.
        livenessProbe:
          httpGet:
            path: /livez
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        env:
        ##
        ## Змінні оточення, визначені тут, можуть бути використані іншими частинами
        ## конфігурації контейнера. Вони інтерпретуються Kubernetes, а не
        ## у середовищі контейнера.
        ##
        ## Ці змінні env передають інформацію про контейнер.
        - name: K8S_NAMESPACE
          valueFrom:
            fieldRef:
             fieldPath: metadata.namespace
        - name: HOSTNAME
          valueFrom:
            fieldRef:
             fieldPath: metadata.name
        - name: SERVICE_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['serviceName']
        ##
        ## Налаштування etcdctl всередині контейнера для підключення до вузла etcd
        ## у контейнері зменшує плутанину під час налагодження.
        - name: ETCDCTL_ENDPOINTS
          value: $(HOSTNAME).$(SERVICE_NAME):2379
        ##
        ## Конфігурація TLS-клієнта для etcdctl у контейнері.
        ## Шляхи до цих файлів є частиною монтування тому "etcd-client-certs".
        # - name: ETCDCTL_KEY
        #   value: /etc/etcd/certs/client/tls.key
        # - name: ETCDCTL_CERT
        #   value: /etc/etcd/certs/client/tls.crt
        # - name: ETCDCTL_CACERT
        #   value: /etc/etcd/certs/client/ca.crt
        ##
        ## Використовуйте це значення URI_SCHEME для не-TLS кластерів.
        - name: URI_SCHEME
          value: "http"
        ## TLS: Використовуйте цей URI_SCHEME для кластерів TLS.
        # - name: URI_SCHEME
        # value: "https"
        ##
        ## Якщо ви використовуєте інший контейнер, виконуваний файл може знаходитися у
        ## іншому місці. У цьому прикладі використано повний шлях, щоб усунути
        ## двозначності для вас, читача.
        ## Часто замість "/usr/local/bin/etcd" можна просто використати "etcd", і це
        ## працюватиме, оскільки у $PATH є тека, яка містить "etcd".
        command:
        - /usr/local/bin/etcd
        ##
        ## Аргументи, що використовуються з командою etcd всередині контейнера.
        args:
        ##
        ## Вкажіть імʼя сервера etcd.
        - --name=$(HOSTNAME)
        ##
        ## Налаштуйте etcd на використання постійного сховища, налаштованого нижче.
        - --data-dir=/data
        ##
        ## У цьому прикладі ми консолідуємо WAL для спільного використання простору з
        ## текою даних. Це не є ідеальним варіантом у промислових середовищах, і
        ## слід розміщувати у власному томі.
        - --wal-dir=/data/wal
        ##
        ## Конфігурації URL-адрес тут параметризовано, і вам не потрібно
        ## нічого з ними робити.
        - --listen-peer-urls=$(URI_SCHEME)://0.0.0.0:2380
        - --listen-client-urls=$(URI_SCHEME)://0.0.0.0:2379
        - --advertise-client-urls=$(URI_SCHEME)://$(HOSTNAME).$(SERVICE_NAME):2379
        ##
        ## Для початкового завантаження кластера має бути встановлено значення "new". Для масштабування
        ## кластера, цей параметр слід змінити на "existing", коли кількість реплік
        ## збільшується. Якщо параметр встановлено неправильно, etcd спробує
        ## запуск, але беспечно зазнає невдачі.
        - --initial-cluster-state=new
        ##
        ## Токен, який використовується для ініціалізації кластера. Для цього рекомендується
        ## використовувати унікальний токен для кожного кластера. У цьому прикладі задано параметр
        ## на унікальність для простору імен, але якщо ви розгортаєте декілька кластерів etcd
        ## у тому самому просторі імен, вам слід зробити щось додаткове, щоб
        ## щоб забезпечити унікальність серед кластерів.
        - --initial-cluster-token=etcd-$(K8S_NAMESPACE)
        ##
        ## Початковий прапорець кластера потрібно оновити відповідно до кількості
        ## налаштованих реплік. У поєднанні вони трохи важко читаються.
        ## Ось як виглядає один параметризований одноранговий кластер:
        ##   etcd-0=$(URI_SCHEME)://etcd-0.$(SERVICE_NAME):2380
        - --initial-cluster=etcd-0=$(URI_SCHEME)://etcd-0.$(SERVICE_NAME):2380,etcd-1=$(URI_SCHEME)://etcd-1.$(SERVICE_NAME):2380,etcd-2=$(URI_SCHEME)://etcd-2.$(SERVICE_NAME):2380
        ##
        ## Прапорець peer urls має бути без змін.
        - --initial-advertise-peer-urls=$(URI_SCHEME)://$(HOSTNAME).$(SERVICE_NAME):2380
        ##
        ## Це дозволяє уникнути збою проби, якщо ви вирішите налаштувати TLS.
        - --listen-metrics-urls=http://0.0.0.0:8080
        ##
        ## Ось деякі конфігурації, які ви можете спробувати увімкнути, але
        ## варто вивчити докладніше, щоб визначити, які налаштування найкраще підходять саме вам.
        # - --auto-compaction-mode=periodic
        # - --auto-compaction-retention=10m
        ##
        ## Конфігурація TLS-клієнта для etcd, повторне використання змінних etcdctl env.
        # - --client-cert-auth
        # - --trusted-ca-file=$(ETCDCTL_CACERT)
        # - --cert-file=$(ETCDCTL_CERT)
        # - --key-file=$(ETCDCTL_KEY)
        ##
        ## Налаштування сервера TLS для etcdctl у контейнері.
        ## Шляхи до цих файлів є частиною монтування тому "etcd-server-certs".
        # - --peer-client-cert-auth
        # - --peer-trusted-ca-file=/etc/etcd/certs/server/ca.crt
        # - --peer-cert-file=/etc/etcd/certs/server/tls.crt
        # - --peer-key-file=/etc/etcd/certs/server/tls.key
        ##
        ## Це конфігурація монтування.
        volumeMounts:
        - name: etcd-data
          mountPath: /data
        ##
        ## Конфігурація TLS-клієнта для etcdctl
        # - name: etcd-client-tls
        #   mountPath: "/etc/etcd/certs/client"
        #   readOnly: true
        ##
        ## Конфігурація сервера TLS
        # - name: etcd-server-tls
        #   mountPath: "/etc/etcd/certs/server"
        #   readOnly: true
      volumes:
      ##
      ## Конфігурація TLS-клієнта
      # - name: etcd-client-tls
      #   secret:
      #     secretName: etcd-client-tls
      #     optional: false
      ##
      ## Конфігурація сервера TLS
      # - name: etcd-server-tls
      #   secret:
      #     secretName: etcd-server-tls
      #     optional: false
  ##
  ## Цей StatefulSet використовує поле volumeClaimTemplate для створення PVC у
  ## кластері для кожної репліки. Ці PVC не можуть бути легко змінені пізніше.
  volumeClaimTemplates:
  - metadata:
      name: etcd-data
    spec:
      accessModes: ["ReadWriteOnce"]
      ##
      ## У деяких кластерах потрібно явно задавати клас сховища.
      ## У цьому прикладі буде використано стандартний клас сховища.
      # storageClassName: ""
      resources:
        requests:
          storage: 1Gi

Генерація сертифікатів

У цьому розділі ми використовуємо Helm для встановлення оператора з назвою cert-manager.

З встановленим cert-manager у кластері можна генерувати самопідписні сертифікати в кластері. Ці згенеровані сертифікати розміщуються всередині обʼєкта секрету, який можна прикріпити як файли в контейнерах.

Це команда helm для встановлення cert-manager.

$ helm upgrade --install --create-namespace --namespace cert-manager cert-manager cert-manager --repo https://charts.jetstack.io --set crds.enabled=true

Це приклад конфігурації ClusterIssuer для генерації самопідписних сертифікатів.

# file: issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned
spec:
  selfSigned: {}

Цей маніфест створює обʼєкти сертифікатів для клієнтських і серверних сертифікатів, посилаючись на ClusterIssuer “selfsigned”. dnsNames повинні бути вичерпним списком дійсних імен хостів для сертифікатів, які створює cert-manager.

# file: certificates.yaml
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: etcd-server
  namespace: default
spec:
  secretName: etcd-server-tls
  issuerRef:
    name: selfsigned
    kind: ClusterIssuer
  commonName: etcd
  dnsNames:
  - etcd
  - etcd.default
  - etcd.default.svc.cluster.local
  - etcd-0
  - etcd-0.etcd
  - etcd-0.etcd.default
  - etcd-0.etcd.default.svc
  - etcd-0.etcd.default.svc.cluster.local
  - etcd-1
  - etcd-1.etcd
  - etcd-1.etcd.default
  - etcd-1.etcd.default.svc
  - etcd-1.etcd.default.svc.cluster.local
  - etcd-2
  - etcd-2.etcd
  - etcd-2.etcd.default
  - etcd-2.etcd.default.svc
  - etcd-2.etcd.default.svc.cluster.local
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: etcd-client
  namespace: default
spec:
  secretName: etcd-client-tls
  issuerRef:
    name: selfsigned
    kind: ClusterIssuer
  commonName: etcd
  dnsNames:
  - etcd
  - etcd.default
  - etcd.default.svc.cluster.local
  - etcd-0
  - etcd-0.etcd
  - etcd-0.etcd.default
  - etcd-0.etcd.default.svc
  - etcd-0.etcd.default.svc.cluster.local
  - etcd-1
  - etcd-1.etcd
  - etcd-1.etcd.default
  - etcd-1.etcd.default.svc
  - etcd-1.etcd.default.svc.cluster.local
  - etcd-2
  - etcd-2.etcd
  - etcd-2.etcd.default
  - etcd-2.etcd.default.svc
  - etcd-2.etcd.default.svc.cluster.local