Часть 3. Давайте добавим данные!

Это последняя часть серии Как построить конвейер CI/CD для приложений с отслеживанием состояния Kubernetes. На данный момент мы развернули и настроили:

  • настроить
  • Оператор сообщества MongoDB
  • Ондат

В этой статье мы собираемся развернуть приложение вместе с базой данных MongoDB как Kubernetes StatefulSet, используя Operator и Skaffold, чтобы создать конвейер непрерывной разработки для нашего локального кластера k3s. Наконец, мы протестируем конвейер и обновим наше замечательное приложение Marvel! Давайте начнем!

Примечание. если вы хотите продолжить, вы найдете ресурсы, которые мы использовали на Github:

Создание настраиваемого ресурса MongoDB и определение требований к службам данных

Как обычно, пользовательский ресурс передается в Kubernetes в виде файла YAML. Репозиторий MongoDB Community Operator дает несколько примеров здесь. Вам нужно будет настроить определенные параметры в соответствии с вашим вариантом использования.

В корне репозитория application manifests, который вы ранее клонировали, вы найдете пользовательский ресурс MongoDB, который мы использовали. Это результат, вычисленный Kustomize после запуска kustomize build overlay/dev. Имя файла mongodb-config.yaml, подробное описание которого приведено ниже:

apiVersion: mongodbcommunity.mongodb.com/v1
kind: MongoDBCommunity
metadata:
  name: mongodb
spec:
  members: 3
  version: 5.0.5
  security:
    authentication:
      modes:
      - SCRAM
  statefulSet:
    spec:
      selector: {}
      serviceName: mongodb
      volumeClaimTemplates:
      - metadata:
          name: data-volume
        spec:
          accessModes:
          - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
          storageClassName: ondat-replicated
      - metadata:
          name: logs-volume
        spec:
          accessModes:
          - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
          storageClassName: ondat-replicated
  type: ReplicaSet
  users:
  - db: admin
    name: admin
    passwordSecretRef:
      name: admin-password-df8t2cdf9f
    roles:
    - db: admin
      name: clusterAdmin
    - db: admin
      name: userAdminAnyDatabase
    - db: admin
      name: dbAdminAnyDatabase
    - db: admin
      name: readWriteAnyDatabase
    scramCredentialsSecretName: admin

Мы выделили основные функции жирным шрифтом. Пользовательский ресурс определяет количество начальных узлов MongoDB в кластере, версию MongoDB, настраивает MongoDB ReplicaSet, Kubernetes StorageClass и указывает имя пользователя/пароль администратора базы данных и роли. Он инкапсулирует информацию, необходимую для создания базы данных, и логически представляет собой абстракцию желаемой конфигурации базы данных. Эти параметры динамически вычисляются Kustomize в соответствии с целевой средой, описанной в наложении.

StorageClass определяет, какой CSI MongoDB StatefulSetиспользует для управления своими данными. Крайне важно выбрать подходящего поставщика на каждом этапе жизненного цикла приложения, от разработки до производства. Предпосылка Shift Left уже обсуждалась в предыдущем блоге, поэтому я не буду углубляться. Идея состоит в том, что разработчикам нужны согласованные инструменты от разработки до производства, от их ноутбука до производственного кластера из 100 узлов. Они должны иметь возможность тестировать свой код на каждом этапе разработки с одинаковыми возможностями. Ondat обеспечивает эту согласованность для постоянных томов Kubernetes и дополнительных корпоративных функций, которые имеют решающее значение при выполнении рабочих нагрузок с отслеживанием состояния в производственной среде в масштабе. Он включает в себя синхронную репликацию, оптимизацию производительности, надежное шифрование при передаче и хранении, а также подход Kube-Native к управлению этими функциями.

ondat-replicated StorageClass определяется в манифестах, созданных Kustomize. Конфигурация StorageClass, принятая Kubernetes API после ее вычисления Kustomize, выглядит следующим образом:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ondat-replicated
parameters:
  csi.storage.k8s.io/fstype: xfs
  csi.storage.k8s.io/secret-name: storageos-api
  csi.storage.k8s.io/secret-namespace: storageos
  storageos.com/encryption: "true"
  storageos.com/replicas: "1"
provisioner: csi.storageos.com
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate

Опять же, мы выделили основные функции жирным шрифтом. Мы изменили файловую систему по умолчанию, используемую для постоянных томов, на XFS в соответствии с рекомендациями MongoDB для лучшего масштабирования и производительности. Мы также включили шифрование и установили количество реплик тома равным 1. Это означает, что каждый постоянный том, предоставленный Ondat CSI и настроенный этим StorageClass, будет отформатирован с помощью XFS, зашифрован в состоянии покоя с помощью шифра AES-256 (в пути). шифрование включено по умолчанию и не может быть изменено), и иметь 1 реплику, доступную в сетке данных Ondat.

Развернуть базу данных

Саму базу данных не нужно развертывать вручную. Skaffold будет обрабатывать рабочий процесс развертывания приложений, который включает в себя кластер MongoDB. Единственным предварительным условием является работающий оператор MongoDB и доступный в Kubernetes тип пользовательского ресурса. Убедитесь, что оператор запущен, используя следующую команду:

$ kubectl get pods -n mongo-operator
NAME                                           READY   STATUS  ...
mongodb-kubernetes-operator-6d46dd4b74-ldfcc   1/1     Running ...

Также убедитесь, что был определен тип пользовательского ресурса:

$ kubectl  api-resources | grep mongo
mongodbcommunity  mdbc  mongodbcommunity.mongodb.com  ...

Работа с базой данных с помощью Pymongo

Прежде чем погрузиться в настройку конвейера с помощью Skaffold, давайте посмотрим, как закодировать наш Kubernetes Job и наш FE для доступа к базе данных MongoDB.

Pymongo предоставляет привязки Python для взаимодействия с MongoDB. На момент написания последняя версия — 4.0.1, а документация доступна по адресу https://pymongo.readthedocs.io/en/stable/index.html. Его довольно просто использовать, и вот пример кода, показывающий, как KubernetesJobподключается к базе данных, создает коллекцию MongoDB и добавляет в нее документ JSON.

Поскольку мы используем набор реплик Mongo, драйвер ожидает исходный список. Он попытается найти всех членов набора в этом начальном списке, но операция не будет заблокирована. Оно молча возвращается. Поэтому, если вы хотите отловить какую-либо ошибку подключения, вы должны добавить в свой код следующее (из документации):

В коде функции add_mongo_document мы указываем начальный список с тремя элементами, который передается приложению через переменные среды. Эти переменные включены в Kubernetes ConfigMap, который автоматически генерируется Kustomize configMapGenerator, о котором мы упоминали ранее. Оба манифеста Kubernetes Job и FE Deployment содержат ссылки на этот ConfigMap . Опять же, вы можете проверить это, запустив kustomize build overlay/dev. Интересная часть заключается в следующем:

spec:
  containers:
  - envFrom:
    - configMapRef:
        name: mongo-config-b29f887ch6

Вы можете заметить, что Kustomize фактически сгенерировал уникальное имя со случайным строковым суффиксом, на который ссылаются как Job, так и Deploymentmanifests. Переменные, определенные в этих манифестах, указаны как литералы в файле kustomization.yaml в разделе configMagGenerator:

configMapGenerator:
- name: mongo-config
  literals:
  - MONGO_SEED0=mongodb-0.mongodb.default.svc.cluster.local
  - MONGO_SEED1=mongodb-1.mongodb.default.svc.cluster.local
  - MONGO_SEED2=mongodb-2.mongodb.default.svc.cluster.local
  - OFFSET=600
  - MONGO_USERNAME=admin

Точно так же пароль, требуемый функцией, передается через Secret , где значение пароля связано с ключом с именем password.

secretGenerator:
- name: admin-password
  literals:
  - password=mongo

Далее в строке 10 db.marvel неявно определяет новую коллекцию MongoDB (эквивалент таблицы реляционной базы данных NoSQL).

В строке 11 db.characters.insert_one(document) создает новый документ в коллекции. Каждый ответ JSON от конечной точки API Marvel анализируется, чтобы соответствовать следующей структуре:

{
  "id": 10093467,
  "name": "Iron Fist (Danny Rand)",
  "thumbnail": "http://i.annihil.us/u/prod/marvel...",
  "extension": "jpg",
  "comics":
    {
      "available": "98",
      "collectionURI": "http://gateway.marvel.com/v1/public/cha...",
      "items": 
      [{
          "resourceURI": "http://gateway.marvel.com/v1/public/...",
          "name": "A+X (2012) #5"
       },
       {  "resourceURI": "http://gateway.marvel.com/v1/public/...",
          "name": "Absolute Carnage: Lethal Protectors (2019) #2"
       }
       ...
      "returned": 20
    }
}

Установите и настройте Skaffold

Установить Skaffold довольно просто, достаточно выполнить шаги, указанные здесь. Следующим шагом будет настройка Skaffold, запустив skaffold init. Он создает файл с именем skaffold.yaml, который предоставляет стандартный шаблон для работы. Команда задает несколько вопросов, но вы можете выбрать любой из ответов, цель состоит в том, чтобы просто начать с файла YAML, который не является пустым… Я имею в виду, кому нравится начинать с пустой конфигурации YAML :-)?

Здесь мы указываем наши параметры сборки и развертывания, и окончательная конфигурация выглядит следующим образом (файл конфигурации skaffold.yaml находится в корне репозитория приложения):

apiVersion: skaffold/v2beta26
kind: Config
metadata:
  name: demo-marvel-app
build:
  artifacts:
  - image: vfiftyfive/flask_marvel
    custom:
      buildCommand: sh build.sh
  local:
    push: true
deploy:
  kustomize:
    paths: 
    - <path_to_dev_overlay>

Пользовательская команда сборки — это файл build.sh, о котором я упоминал ранее, а путь Kustomize — это путь к локальному каталогу оверлея dev. Мы также публикуем образ в Docker Hub, для чего Skaffold должен знать ваши учетные данные Docker Hub. Самый простой способ интеграции с Docker Hub — войти в Docker с помощью docker login, а затем настроить Skaffold для использования вашего реестра, запустив skaffold dev --default-repo=<your_registry>, где <your_registry> Docker Hub — это ваше имя пользователя.

После того, как вы настроили переменную среды KUBECONFIG для подключения к кластеру разработки Kubernetes, осталось только запустить skaffold dev :

$ export KUBECONFIG=<path_to_kubeconfig_file>
$ skaffold dev
Listing files to watch...
 - vfiftyfive/flask_marvel
Generating tags...
 - vfiftyfive/flask_marvel -> vfiftyfive/flask_marvel:6f061f0
Checking cache...
 - vfiftyfive/flask_marvel: Not found. Building
Starting build...
Building [vfiftyfive/flask_marvel]...
+ docker buildx build --builder skaffold-builder --tag vfiftyfive/flask_marvel:6f061f0 --platform linux/amd64,linux/arm64 --push /Users/nvermande/Documents/Dev/Ondat/FlaskMarvelApp
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 299B 0.0s done
#1 DONE 0.1s
#2 [internal] load .dockerignore
#2 transferring context: 2B 0.0s done
#2 DONE 0.0s
#3 [linux/arm64 internal] load metadata for docker.io/library/python:3.9
#3 ...
...
Tags used in deployment:
 - vfiftyfive/flask_marvel -> vfiftyfive/flask_marvel:6f061f0@sha256:629734c5e62206752f051e9f47fdc3bc6d1f61e399b9a89920c8d7d9f87ee0f8
Starting deploy...
 - storageclass.storage.k8s.io/ondat created
 - service/marvel-frontend created
 - service/mongodb created
 - deployment.apps/marvel-frontend created
 - statefulset.apps/mongodb created
 - job.batch/add-data-to-mongodb created
Waiting for deployments to stabilize...
 - deployment/marvel-frontend: creating container flask-marvel
    - pod/marvel-frontend-5bdd684d78-zpbvm: creating container flask-marvel
    - pod/marvel-frontend-5bdd684d78-2mf4m: creating container flask-marvel
 - statefulset/mongodb: creating container mongodb
    - pod/mongodb-0: creating container mongodb
 - deployment/marvel-frontend is ready. [1/2 deployment(s) still pending]
...
Generating tags...
 - vfiftyfive/flask_marvel -> vfiftyfive/flask_marvel:6f061f0
Checking cache...
 - vfiftyfive/flask_marvel: Found Remotely
Tags used in deployment:
 - vfiftyfive/flask_marvel -> vfiftyfive/flask_marvel:6f061f0@sha256:629734c5e62206752f051e9f47fdc3bc6d1f61e399b9a89920c8d7d9f87ee0f8
Starting deploy...
Waiting for deployments to stabilize...
 - statefulset/mongodb is ready. [1/2 deployment(s) still pending]
 - deployment/marvel-frontend is ready.
Deployments stabilized in 1.49 second
Watching for changes...

Как видно из приведенного выше вывода, Skaffold создает образ Docker и развертывает манифесты Kubernetes с помощью Kustomize, настраивая новый образ FE с определенным тегом образа и дайджестом в результате сборки образа.

У Skaffold есть еще одна интересная функция — возможность выводить журналы мониторов Podsit в режиме реального времени. Так, например, если вы создадите артефакты foo и bar, Skaffold отобразит выходные данные foo и bar Pods при их обновлении. Это доступно, когда Skaffold работает в режиме демона с опцией dev выше. Это означает, что при разработке приложения вам не нужно использовать kubectl для сбора журналов контейнеров из нескольких мест. Skaffold централизует их, а затем отправляет на стандартный вывод демона! Довольно удобно, должен признать.

Если мы посмотрим, что сейчас работает в кластере Kubernetes, то увидим следующее (поле age обрезано):

$ kubectl get pods
NAME                               READY   STATUS      RESTARTS   
add-data-to-mongodb-9brkw          0/1     Completed   0          
marvel-frontend-5bdd684d78-2mf4m   1/1     Running     0          
marvel-frontend-5bdd684d78-zpbvm   1/1     Running     0          
mongodb-0                          1/1     Running     0          
mongodb-1                          1/1     Running     0          
mongodb-2                          1/1     Running     0

Skaffold развернул все компоненты, необходимые для работы нашего приложения. Прежде чем пытаться изменить исходный код, давайте проверим, правильно ли работает приложение. Для этого мы просто используем kubectl port-forward и проверяем результат локально в предпочитаемом нами браузере:

$ kubectl port-forward svc/marvel-frontend 8080
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

Если мы перейдем к http://localhost:8080, мы увидим, что приложение работает:

Допустим, теперь мы хотим изменить некоторый текст в этом приложении. Мы хотим заменить «Комиксы» на «Комикс(ы)». Для этого достаточно отредактировать HTML-код в репозитории приложения под app > templates > pages.html

Замените все вхождения «Comics» на «Comic(s)». Вот пример возникновения:

Затем сохраните файл, и вы должны увидеть следующий вывод Skaffold:

Generating tags...
 - vfiftyfive/flask_marvel -> vfiftyfive/flask_marvel:186a97d
Checking cache...
 - vfiftyfive/flask_marvel: Found. Tagging
Tags used in deployment:
 - vfiftyfive/flask_marvel -> vfiftyfive/flask_marvel:186a97d@sha256:33034d0241d6fbd586f550766ae22ed8633f099b53cca9a4544510c856f77811
 - vfiftyfive/marvel_init_db -> vfiftyfive/marvel_init_db:186a97d@sha256:ca57d37157384fb83616708b69ee12e60b8023fa05cef2325b9537b13bd934ce
Starting deploy...
 - deployment.apps/marvel-frontend configured
Waiting for deployments to stabilize...
 - dev:deployment/marvel-frontend is ready.
Deployments stabilized in 4.263 seconds
Watching for changes...

В Kubernetes вы должны увидеть создание новых интерфейсных контейнеров и уничтожение старых:

$ kubectl get pods -w
marvel-frontend-7d876b7bff-57vxb  1/1     Running      0 10s
marvel-frontend-7d876b7bff-wr9s9  1/1     Running      0 9s
marvel-frontend-7d876b7bff-6hnx5  1/1     Running      0 7s
marvel-frontend-65d655d644-fksdl  0/1     Terminating  0 7m23s
marvel-frontend-65d655d644-fksdl  0/1     Terminating  0 7m26s
marvel-frontend-65d655d644-fksdl  0/1     Terminating  0 7m26s

Вы можете перейти по тому же URL-адресу и увидеть обновленное приложение:

Заключение

Мы надеемся, что это глубокое погружение в жизненный цикл разработки приложений с отслеживанием состояния в Kubernetes выявило их проблемы.

Во-первых, им требуется уровень абстракции для управления их конфигурацией и развертыванием, что может быть достигнуто за счет использования определенных операторов. Тем не менее, в их подходе к базам данных отсутствует стандарт, что может привести к путанице и техническим проблемам, когда требуется тонкая настройка.

Во-вторых, Kubernetes предоставляет возможность инкапсулировать требования к инфраструктуре в виде YAML. Поэтому их можно легко внедрить в конвейеры CI/CD, но для отображения различных компонентов требуется много клея. Skaffold — это ценный инструмент, который интегрирует Docker и пользовательские сценарии на этапе сборки и Kustomize на этапе развертывания. В режиме разработки Skaffold обновит соответствующие компоненты, как только вы сохраните изменения локально в своей среде разработки.

Наконец, Kubernetes по умолчанию не предоставляет премиум-функции хранения. Но репликация, шифрование, тонкое выделение ресурсов и оптимизированная производительность для постоянных томов являются ключевыми требованиями при запуске приложений с отслеживанием состояния в облачной ОС де-факто. Ondat предоставляет плоскость данных для включения этих возможностей при полной интеграции с плоскостью управления Kubernetes. Независимо от расположения кластера Kubernetes (например, в вашем локальном центре обработки данных, в общедоступном облаке или на вашем ноутбуке) Ondat предоставляет распределенное программно-определяемое хранилище. Затем вы можете воспользоваться преимуществами критически важных служб данных, чтобы обеспечить масштабируемость приложений с отслеживанием состояния, отказоустойчивость и согласованность производительности.

Вещи, которые я узнал на этом пути

  • Если у вас есть Kubernetes jobs в Skaffold, вам нужно пройти --force при использовании skaffold dev . Это связано с тем, что когда в Kubernetes существует job, его раздел template, содержащий свойство image, становится неизменяемым. Параметр force удаляет его, а не обновляет, так же, как вы сделали бы с kubectl replace --force -f my-job.yaml.
  • Оператор Kubernetes сообщества MongoDB по умолчанию включает аутентификацию и создает пользователя. Имя пользователя настраивается, но помните, что им управляет Оператор. Любая ручная модификация этого пользователя с помощью императивных команд mongo будет отменена. Это связано с тем, что Оператор полагается на цикл согласования, который обеспечивает приоритет конфигурации настраиваемого ресурса. Так, например, если вам нужно добавить роли этому пользователю, сделайте это из раздела Custom Resource, отвечающего за настройку пользователя:
users:
    - name: admin
      db: admin
      passwordSecretRef:
        name: admin-password
      roles:
        - name: clusterAdmin
          db: admin
        - name: userAdminAnyDatabase
          db: admin
        - name: dbAdminAnyDatabase
          db: admin
        - name: readWriteAnyDatabase
          db: admin
      scramCredentialsSecretName: my-scram

Мы добавили пару новых ролей, выделенных жирным шрифтом, поэтому у нашего администратора есть соответствующие привилегии для всех баз данных.

  • Оператор также ожидает ключ с именем «пароль» от пользователя Secret. Значение пароля представляет собой пароль пользователя.
  • В Kustomize каждый узел YAML должен иметь поле имени в манифестах, расположенных в базовом каталоге. Если вы опустите имя в базовой папке и добавите исправление в файл настройки, исправление не будет применено.
  • При использовании Kustomize для замены определенных полей в пользовательских ресурсах выходным значением из генератора Kustomize в файле Kustomization требуется раздел конфигурации для определения дополнительных сопоставлений. Например, если вам нужно указать имя Secret, сгенерированного Kustomize secretgenerator, в качестве значения для поля MongoDBCommunity Пользовательский ресурс passwordSecretRef, вам нужно сообщить Kustomize, что каждый раз, когда он сталкивается с passwordSecretRef, он должен заменить его значение на имя сгенерированного KubernetesSecret.

Бонус

На прошлой неделе я имел честь показать технологию, использованную в этой серии блогов, во время прямой трансляции CNCF, а также добавить полный конвейер CI/CD для Kubernetes с Tekton и политику как код с Kyverno. Просто перейдите по ссылке ниже: