Часть 3. Давайте добавим данные!
Это последняя часть серии Как построить конвейер CI/CD для приложений с отслеживанием состояния Kubernetes. На данный момент мы развернули и настроили:
- настроить
- Оператор сообщества MongoDB
- Ондат
В этой статье мы собираемся развернуть приложение вместе с базой данных MongoDB как Kubernetes StatefulSet
, используя Operator и Skaffold, чтобы создать конвейер непрерывной разработки для нашего локального кластера k3s. Наконец, мы протестируем конвейер и обновим наше замечательное приложение Marvel! Давайте начнем!
Примечание. если вы хотите продолжить, вы найдете ресурсы, которые мы использовали на Github:
- Манифесты оператора MongoDB: https://github.com/vfiftyfive/mongodb-community-operator-manifests
- Код приложения, конфигурация конвейера и упаковки: https://github.com/vfiftyfive/FlaskMarvelApp
- Манифесты приложения: https://github.com/vfiftyfive/CFD12-Demo-Manifests. Используйте ветку
dev
.
Создание настраиваемого ресурса 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
, так и Deployment
manifests. Переменные, определенные в этих манифестах, указаны как литералы в файле 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 есть еще одна интересная функция — возможность выводить журналы мониторов Pods
it в режиме реального времени. Так, например, если вы создадите артефакты 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
, сгенерированного Kustomizesecretgenerator
, в качестве значения для поляMongoDBCommunity
Пользовательский ресурсpasswordSecretRef
, вам нужно сообщить Kustomize, что каждый раз, когда он сталкивается сpasswordSecretRef
, он должен заменить его значение на имя сгенерированного KubernetesSecret
.
Бонус
На прошлой неделе я имел честь показать технологию, использованную в этой серии блогов, во время прямой трансляции CNCF, а также добавить полный конвейер CI/CD для Kubernetes с Tekton и политику как код с Kyverno. Просто перейдите по ссылке ниже: