Масштабирование приложения Node.js на MongoDB в Kubernetes с помощью Helm: подготовка среды и настройка MongoDB
Cloud Server, Java, Linux | Комментировать запись
Kubernetes – это система для запуска современных контейнерных приложений. С его помощью разработчики могут развертывать приложения на кластерах и управлять ими. Его можно использовать для повышения эффективности и надежности при настройке одного экземпляра приложения, но в целом Kubernetes предназначен для запуска нескольких экземпляров на группах компьютеров.
При создании мультисервисных развертываний в Kubernetes многие разработчики предпочитают использовать менеджер пакетов Helm. Helm оптимизирует процесс создания нескольких ресурсов Kubernetes через чарты и шаблоны, которые координируют взаимодействие этих объектов. Он также предлагает готовые чарты для популярных проектов с открытым исходным кодом.
В этом мануале мы развернем приложение Node.js с базой данных MongoDB в кластере Kubernetes с помощью чартов Helm. Мы используем официальный чарт реплик MongoDB для создания объекта StatefulSet, состоящего из трех подов, headless сервиса и трех элементов PersistentVolumeClaims. Также мы создадим чарт для развертывания приложения Node.js с несколькими репликами, используя собственный образ приложения. Настройка, которую мы выполним здесь, будет отражать функциональность кода, описанного в мануале Контейнеризация приложения Node.js для разработки, и станет хорошей отправной точкой для создания гибкого приложения Node.js на MongoDB, которое можно быстро масштабировать.
Требования
Kubernetes 1.10+ с включенным RBAC (управление доступом на основе ролей).
- Инструмент командной строки kubectl, установленный на локальном компьютере или на сервере разработки и настроенный для подключения к кластеру. Подробнее об установке kubectl вы можете узнать в официальной документации.
- Установка Helm на локальном компьютере или на сервере разработки, а также Tiller в вашем кластере. Инструкции вы найдете в разделах 1-2 мануала Установка программного обеспечения в кластер Kubernetes с помощью пакетного менеджера Helm.
- Docker на локальном компьютере или сервере разработки. Если вы работаете в Ubuntu 18.04, выполните шаги 1 и 2 мануала Установка и использование Docker в Ubuntu 18.04; для получения информации об установке на другие операционные системы следуйте официальной документации. Обязательно добавьте пользователя sudo в группу docker, как описано в разделе 2 предложенного мануала.
- Учетная запись Docker Hub. Чтобы узнать, как ее настроить, обратитесь к этой странице Docker Hub.
1: Клонирование и упаковка приложения
Чтобы использовать приложение в Kubernetes, нужно упаковать его, иначе агент kubelet не сможет получить образ. Однако перед упаковкой нужно будет изменить URI соединения MongoDB в коде приложения, чтобы приложение могло подключаться к членам набора реплик, который мы создадим с помощью чарта mongodb-replicaset.
Для начала нужно клонировать репозиторий node-mongo-docker-dev с GitHub. Этот репозиторий содержит код из установки, описанной в серии Контейнеризация приложения Node.js для разработки. Это тестовое приложение Node.js с базой данных MongoDB, на котором вы можете потренироваться и понять, как настраивать среду разработки с помощью Docker Compose.
Клонируйте репозиторий в каталог node_project:
git clone https://github.com/do-community/node-mongo-docker-dev.git node_project
Перейдите в каталог node_project:
cd node_project
Каталог node_project содержит файлы и каталоги приложения, которое работает с пользовательским вводом. Оно было оптимизировано для работы с контейнерами: конфиденциальная информация о конфигурации была удалена из кода приложения и собрана в отдельный файл, откуда она загружается во время выполнения.
Читайте также: Архитектурное проектирование приложений в Kubernetes
Когда мы развернем чарт mongodb-replicaset, он создаст:
- Объект StatefulSet с тремя подами – членами набора реплик MongoDB. Каждый под будет иметь связанный PersistentVolumeClaim и поддерживать фиксированный идентификатор в случае перепланирования.
- Набор реплик MongoDB, состоящий из подов в StatefulSet. В набор войдут один первичный и два вторичных пода. Данные будут реплицироваться с первичного пода на вторичный, что обеспечивает высокую доступность приложения.
Чтобы приложение могло взаимодействовать с репликами базы данных, URI соединения MongoDB в нашем коде должен включать как имена хостов набора реплик, так и имя самого набора. Поэтому эти значения необходимо добавить в URI.
В клонированном репозитории есть файл, в котором указана информация о подключении к базе данных, он называется db.js. Откройте этот файл в nano или другом редакторе:
nano db.js
Читайте также: Переменные, области и поднятие переменных в JavaScript
В настоящее время файл содержит константы, на которые ссылается URI соединения во время выполнения. Значения этих констант вводятся с помощью свойства process.env, которое возвращает объект с информацией о пользовательской среде. Динамическая установка значений позволяет нам отделить код от базовой инфраструктуры, что необходимо в stateless среде.
Константы для URI соединения и сама строка URI в настоящее время выглядят так:
...
const {
MONGO_USERNAME,
MONGO_PASSWORD,
MONGO_HOSTNAME,
MONGO_PORT,
MONGO_DB
} = process.env;
...
const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
...
В соответствии с подходом 12FA мы не должны жестко кодировать имена хостов наших экземпляров реплик или набора реплик в этой строке URI. Вместо этого можно расширить существующую константу MONGO_HOSTNAME и добавить несколько имен хостов – членов нашего набора реплик. Также нужно добавить константу набора реплик в раздел опций строки URI.
Добавьте MONGO_REPLICASET в объект константы URI и в строку соединения:
...
const {
MONGO_USERNAME,
MONGO_PASSWORD,
MONGO_HOSTNAME,
MONGO_PORT,
MONGO_DB,
MONGO_REPLICASET
} = process.env;
...
const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?replicaSet=${MONGO_REPLICASET}&authSource=admin`;
...
Опция replicaSet в разделе опций URI позволяет передавать имя набора реплик, которое (наряду с именами хостов, определенными в константе MONGO_HOSTNAME) позволит нам подключаться к членам набора.
Сохраните и закройте файл, когда вы закончите его редактировать.
Изменив информацию о соединении с БД для работы с наборами реплик, вы можете упаковать свое приложение, собрать образ с помощью команды docker build и отправить его в Docker Hub.
Создайте образ с помощью docker build и флага -t, который позволяет указать запоминающееся имя образа. В этом случае отметьте образ своим именем пользователя в Docker Hub и назовите его node-replicas (или выберите другое имя):
docker build -t your_dockerhub_username/node-replicas .
Точка в команде указывает, что контекстом сборки является текущий каталог.
Процесс займет минуту или две. После завершения проверьте ваши образы:
docker images
Вы увидите следующий вывод:
REPOSITORY TAG IMAGE ID CREATED SIZE
your_dockerhub_username/node-replicas latest 56a69b4bc882 7 seconds ago 90.1MB
node 10-alpine aa57b0242b33 6 days ago 71MB
Затем войдите в вашу учетную запись Docker Hub:
docker login -u your_dockerhub_username
При появлении запроса введите пароль своей учетной записи. В результате такого входа в домашнем каталоге вашего пользователя sudo появится файл ~/.docker/config.json с учетными данными Docker Hub.
Загрузите образ приложения на Docker Hub с помощью команды docker push. Не забудьте заменить your_dockerhub_username своим собственным именем пользователя Docker Hub:
docker push your_dockerhub_username/node-replicas
Теперь у вас есть образ, который вы можете использовать для запуска своего реплицированного приложения с помощью Kubernetes. Следующим шагом будет настройка параметров для работы с чартом MongoDB.
2: Создание секретов для набора реплик MongoDB
Чарт stable/mongodb-replicaset предоставляет различные варианты использования секретов. Мы создадим два секрета для развертывания чарта:
- Секрет для файла набора реплик, который будет работать как общий пароль между членами набора, позволяя им проходить аутентификацию.
- Секрет для администратора MongoDB, который будет работать как пользователь root в базе данных admin. Эта роль позволит вам создавать других пользователей с ограниченными правами при развертывании приложения в рабочей среде.
Имея эти объекты, мы сможем установить предпочтительные значения параметров в отдельном файле значений и создать объект StatefulSet и реплику MongoDB с помощью чарта.
Во-первых, давайте создадим файл для ключей. Мы будем использовать команду openssl с опцией rand, чтобы сгенерировать 756-байтовую случайную строку:
openssl rand -base64 756 > key.txt
Выходные данные, сгенерированные этой командой, будут закодированы в base64, что обеспечит равномерную передачу данных, и будут перенаправлены в файл key.txt (следуя рекомендациям по аутентификации чарта mongodb-replicaset). Длина самого ключа должна составлять от 6 до 1024 символов, включая только символы из набора base64.
Теперь вы можете создать секрет keyfilesecret с помощью kubectl create:
kubectl create secret generic keyfilesecret --from-file=key.txt
Это создаст объект Secret в пространстве имен default, так как мы не создали отдельное пространство имен для этой установки.
Вы увидите следующий вывод, если секрет был создан:
secret/keyfilesecret created
Удалите key.txt:
rm key.txt
Если же вы хотите оставить файл, не забудьте ограничить к нему доступ и добавить его в файл .gitignore, чтобы он не копировался системой контроля версий.
Затем создайте секрет для администратора MongoDB. Сначала нужно преобразовать желаемое имя пользователя и пароль в base64.
Преобразуйте имя пользователя вашей базы данных:
echo -n 'your_database_username' | base64
Запишите значение, которое вы видите в выводе.
Далее конвертируйте пароль:
echo -n 'your_database_password' | base64
Также запишите значение в выводе.
Откройте файл секрета:
nano secret.yaml
Примечание: Объекты Kubernetes обычно определяются с помощью YAML, где для отступа нельзя использовать табы – вместо этого обязательно нужно ставить два пробела. Если вы хотите проверить форматирование файлов YAML, вы можете использовать linter или проверить синтаксис с помощью kubectl create с флагами –dry-run и –validate:
kubectl create -f your_yaml_file.yaml --dry-run --validate=true
В общем, лучше как-то проверить синтаксис перед созданием ресурсов с помощью kubectl.
Добавьте следующий код в файл, чтобы создать секрет, который будет определять пользователя и пароль с помощью только что созданных значений. Обязательно замените наши фиктивные значения:
apiVersion: v1
kind: Secret
metadata:
name: mongo-secret
data:
user: your_encoded_username
password: your_encoded_password
Здесь мы используем имена ключей, которые нужны чарту mongodb-replicaset: user и password. Мы назвали объект mongo-secret, но вы можете назвать его как угодно.
Сохраните и закройте файл.
Создайте объект Secret с помощью следующей команды:
kubectl create -f secret.yaml
Вы увидите следующий вывод:
secret/mongo-secret created
Опять же, вы можете удалить secret.yaml или ограничить к нему доступ и добавить его в.gitignore.
Создав объекты Secret, вы можете добавить параметры для чарта mongodb-replicaset и создать развертывание MongoDB.
3: Настройка чарта MongoDB и создание развертывания
Helm поставляется с поддерживаемым репозиторием stable, который содержит необходимый нам чарт mongodb-replicaset. Чтобы использовать его с только что созданными секретами, мы создадим файл mongodb-values.yaml с параметрами конфигурации, а затем установим чарт с помощью этого файла.
Файл mongodb-values.yaml будет в значительной степени отражать стандартный файл values.yaml в репозитории mongodb-replicaset. Однако мы внесем в наш файл следующие изменения:
- Мы установим значение true для параметра auth, чтобы экземпляры БД запускались с включенной авторизацией. Все клиенты должны будут проходить аутентификацию для доступа к ресурсам и операциям БД.
- Мы добавим информацию о секретах, которые мы создали на предыдущем этапе, чтобы чарт использовал эти значения для создания файла ключей и администратора.
- Мы уменьшим размер томов PersistentVolumes, связанных с каждым подом в StatefulSet, до минимального жизнеспособного блока, 1 ГБ (вы можете изменить его в соответствии с вашими требованиями).
Перед созданием файла mongodb-values.yaml сначала необходимо убедиться, что у вас есть готовый StorageClass, настроенный для оркестровки ресурсов хранения. Каждый из подов базе данных StatefulSet имеет липкий идентификатор и связанный с ним PersistentVolumeClaim, который будет динамически предоставлять PersistentVolume. Если под перенесен, PersistentVolume будет подключен к любой ноде, на который запланирован под (но если под или StatefulSet окончательно удален, каждый связанный с ним том нужно удалять вручную).
Сейчас вам нужно будет создать StorageClass и настроить provisioner по вашему выбору. Подробнее о том, как это сделать, см. официальную документацию.
Когда StorageClass будет настроен, откройте mongodb-values.yaml:
nano mongodb-values.yaml
В этом файле вы установите значения, которые будут:
- Включать авторизацию.
- Ссылаться на секреты keyfilesecret и mongo-secret.
- Указывать 1Gi для PersistentVolumes.
- Устанавливать имя набора реплик db.
- Указывать 3 реплики для набора.
- Закреплять в образе mongo последнюю версию (сейчас это 4.1.9).
Вставьте следующий код в файл:
replicas: 3
port: 27017
replicaSetName: db
podDisruptionBudget: {}
auth:
enabled: true
existingKeySecret: keyfilesecret
existingAdminSecret: mongo-secret
imagePullSecrets: []
installImage:
repository: unguiculus/mongodb-install
tag: 0.7
pullPolicy: Always
copyConfigImage:
repository: busybox
tag: 1.29.3
pullPolicy: Always
image:
repository: mongo
tag: 4.1.9
pullPolicy: Always
extraVars: {}
metrics:
enabled: false
image:
repository: ssalaues/mongodb-exporter
tag: 0.6.1
pullPolicy: IfNotPresent
port: 9216
path: /metrics
socketTimeout: 3s
syncTimeout: 1m
prometheusServiceDiscovery: true
resources: {}
podAnnotations: {}
securityContext:
enabled: true
runAsUser: 999
fsGroup: 999
runAsNonRoot: true
init:
resources: {}
timeout: 900
resources: {}
nodeSelector: {}
affinity: {}
tolerations: []
extraLabels: {}
persistentVolume:
enabled: true
#storageClass: "-"
accessModes:
- ReadWriteOnce
size: 1Gi
annotations: {}
serviceAnnotations: {}
terminationGracePeriodSeconds: 30
tls:
enabled: false
configmap: {}
readinessProbe:
initialDelaySeconds: 5
timeoutSeconds: 1
failureThreshold: 3
periodSeconds: 10
successThreshold: 1
livenessProbe:
initialDelaySeconds: 30
timeoutSeconds: 5
failureThreshold: 3
periodSeconds: 10
successThreshold: 1
Параметр persistentVolume.storageClass закомментирован: удалив комментарий и установив значение “-” отключит динамическую оркестровку. Поскольку здесь мы оставляем это значение неопределенным, диаграмма выберет provisioner по умолчанию.
Также обратите внимание на accessMode, связанный с ключом persistentVolume: ReadWriteOnce означает, что подготовленный том будет доступен для чтения и записи только одной ноде. Просмотрите документацию, чтобы больше узнать о различных режимах доступа. Также смотрите таблицу конфигурации, включенную в репозиторий.
Сохраните и закройте файл.
Перед развертыванием чарта mongodb-replicaset нужно обновить репозиторий stable с помощью команды helm repo update:
helm repo update
Это позволит получить последнюю информацию о графике из стабильного хранилища.
Наконец, установите чарт с помощью следующей команды:
helm install --name mongo -f mongodb-values.yaml stable/mongodb-replicaset
Примечание: Перед установкой чарта можно запустить helm install с параметрами –dry-run и –debug, чтобы проверить сгенерированные манифесты для релиза:
helm install --name your_release_name -f your_values_file.yaml --dry-run --debug your_chart
Обратите внимание, что мы называем релиз mongo. Это имя будет относиться к конкретному развертыванию чарта с указанными параметрами конфигурации. Мы указали на эти опции, добавив флаг -f и наш файл mongodb-values.yaml.
Также обратите внимание, что поскольку мы не включили флаг –namespace в helm install, наши объекты чартов будут созданы в пространстве имен по умолчанию.
Создав релиз, вы увидите вывод о его состоянии, а также информацию о созданных объектах и инструкции по взаимодействию с ними:
NAME: mongo
LAST DEPLOYED: Tue Apr 16 21:51:05 2019
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
mongo-mongodb-replicaset-init 1 1s
mongo-mongodb-replicaset-mongodb 1 1s
mongo-mongodb-replicaset-tests 1 0s
...
Теперь вы можете проверить поды с помощью следующей команды:
kubectl get pods
По мере создания подов вы увидите подобный вывод:
NAME READY STATUS RESTARTS AGE
mongo-mongodb-replicaset-0 1/1 Running 0 67s
mongo-mongodb-replicaset-1 0/1 Init:0/3 0 8s
Выводы READY и STATUS здесь указывают, что модули в StatefulSet готовы не полностью: контейнеры инициализации, связанные с контейнерами пода, все еще работают. Поскольку члены StatefulSet создаются последовательно, каждый под в StatefulSet должен быть запущен и готов, и только тогда будет создан следующий под.
После того, как поды будут созданы и все связанные с ними контейнеры запустятся, вы увидите:
NAME READY STATUS RESTARTS AGE
mongo-mongodb-replicaset-0 1/1 Running 0 2m33s
mongo-mongodb-replicaset-1 1/1 Running 0 94s
mongo-mongodb-replicaset-2 1/1 Running 0 36s
Статус Running указывает, что поды связаны с нодами и что контейнеры, связанные с этими подами, работают. READY указывает, сколько контейнеров запущено в поде. Для получения дополнительной информации, пожалуйста, обратитесь к документации по жизненным циклам пода.
Примечание: Если вы видите неожиданные результаты в столбце STATUS, помните, что вы можете устранить неполадки пода с помощью следующих команд:
kubectl describe pods your_pod
kubectl logs your_pod
Каждый из подов в StatefulSet имеет имя, которое состоит из имени StatefulSet и индекса пода. Поскольку мы создали три реплики, члены StatefulSet имеют индекс 0-2, и у каждого есть стабильная запись DNS, состоящая из таких элементов: $(statefulset-name)-$(ordinal).$(service name).$(namespace).svc.cluster.local.
В данном случае StatefulSet и Headless Service, созданные чартом mongodb-replicaset, имеют одинаковые имена:
kubectl get statefulset
NAME READY AGE
mongo-mongodb-replicaset 3/3 4m2s
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 42m
mongo-mongodb-replicaset ClusterIP None <none> 27017/TCP 4m35s
mongo-mongodb-replicaset-client ClusterIP None <none> 27017/TCP 4m35s
Это означает, что первый член StatefulSet будет иметь следующую запись DNS:
mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local
Поскольку нам нужно, чтобы приложение подключалось к каждому экземпляру MongoDB, важно владеть этой информацией, чтобы напрямую взаимодействовать с подами, а не с сервисами. Когда мы будем создавать пользовательский чарт, мы передадим DNS записи для каждого пода, используя переменные среды. Об этом речь пойдет в следующем мануале.
Tags: Docker, Helm, Kubernetes, MongoDB, Node.js