Масштабирование приложения Node.js на MongoDB в Kubernetes с помощью Helm: пользовательские чарты
Cloud Server, Java, Linux | Комментировать запись
Kubernetes – это система для запуска современных контейнерных приложений. С его помощью разработчики могут развертывать приложения на кластерах и управлять ими. Его можно использовать для повышения эффективности и надежности при настройке одного экземпляра приложения, но в целом Kubernetes предназначен для запуска нескольких экземпляров на группах компьютеров.
При создании мультисервисных развертываний в Kubernetes многие разработчики предпочитают использовать менеджер пакетов Helm. Helm оптимизирует процесс создания нескольких ресурсов Kubernetes через чарты и шаблоны, которые координируют взаимодействие этих объектов. Он также предлагает готовые чарты для популярных проектов с открытым исходным кодом.
Ранее мы использовали официальный чарт реплик MongoDB для создания объекта StatefulSet, состоящего из трех подов, headless сервиса и трех элементов PersistentVolumeClaims. В этом мануале мы создадим чарт для развертывания приложения Node.js с несколькими репликами, используя собственный образ приложения. Настройка, которую мы выполним здесь, будет отражать функциональность кода, описанного в мануале Контейнеризация приложения Node.js для разработки.
Примечание: Этот мануал является продолжением Масштабирование приложения Node.js на MongoDB в Kubernetes с помощью Helm: подготовка среды и настройка MongoDB. В первой части вы найдете все предварительные требования и начальную настройку.
1: Создание и настройка пользовательского чарта приложения
Сейчас мы создадим пользовательский чарт для приложения Node и изменим файлы по умолчанию в стандартном каталоге чартов, чтобы приложение могло работать с подготовленным набором реплик. Мы также создадим файлы для определения объектов ConfigMap и Secret для нашего приложения.
Сначала создайте новый каталог nodeapp с помощью следующей команды:
helm create nodeapp
Это создаст каталог nodeapp в каталоге ~/node_project со следующими ресурсами:
- Файл Chart.yaml с основной информацией о чарте.
- Файл values.yaml, который позволяет устанавливать конкретные значения параметров, как вы делали это при развертывании MongoDB.
- Файл .helmignore с шаблонами файлов и каталогов, которые будут игнорироваться при упаковке чартов.
- Каталог templates/ с файлами шаблонов, которые будут генерировать манифесты Kubernetes.
- Каталог templates/tests/ для тестовых файлов.
- Каталог charts/ для любых чартов, от которых зависит этот чарт.
Первый файл из этих файлов по умолчанию, который мы изменим, это values.yaml. Откройте этот файл:
nano nodeapp/values.yaml
Значения, которые нужно здесь установить:
- Количество реплик.
- Образ приложения, который нужно использовать. В данном случае это образ node-replicas, который мы создали в предыдущем мануале.
- ServiceType. В этом случае мы укажем LoadBalancer для создания точки доступа к приложению в целях тестирования.
- targetPort укажет порт пода, где будет отображаться приложение.
Мы не будем вводить переменные среды в этот файл. Вместо этого мы создадим шаблоны для объектов ConfigMap и Secret и добавим эти значения в манифест развертывания приложения, расположенный по адресу ~/node_project/nodeapp/templates/deployment.yaml.
Установите следующие значения в файле values.yaml:
# Default values for nodeapp.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 3
image:
repository: your_dockerhub_username/node-replicas
tag: latest
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
service:
type: LoadBalancer
port: 80
targetPort: 8080
...
Сохраните и закройте файл.
Затем откройте файл secret.yaml в каталоге nodeapp/templates:
nano nodeapp/templates/secret.yaml
В этом файле укажите значения для ваших констант приложения MONGO_USERNAME и MONGO_PASSWORD. Это те константы, к которым приложение будет искать доступ во время выполнения, как указано в db.js, файле подключения к вашей БД. При добавлении значений для этих констант не забывайте, что они должны быть в кодировке base64 (их мы получили в разделе 2 предыдущего мануала, при создании объекта mongo-secrettobject). Если вам нужно восстановить эти значения, вы можете вернуться и снова запустить соответствующие команды.
Добавьте следующий код в файл:
apiVersion: v1
kind: Secret
metadata:
name: {{ .Release.Name }}-auth
data:
MONGO_USERNAME: your_encoded_username
MONGO_PASSWORD: your_encoded_password
Имя этого объекта Secret будет зависеть от имени вашего релиза Helm, которое вы укажете при развертывании диаграммы приложения.
Сохраните и закройте файл.
Затем откройте следующий файл, чтобы создать ConfigMap для вашего приложения:
nano nodeapp/templates/configmap.yaml
В этом файле мы определим остальные переменные, которые нужны приложению: MONGO_HOSTNAME, MONGO_PORT, MONGO_DB и MONGO_REPLICASET. Наша переменная MONGO_HOSTNAME будет включать запись DNS для каждого экземпляра в наборе реплик, поскольку это требуется для URI подключения MongoDB.
Согласно документации Kubernetes, когда приложение проверяет работоспособность и готовность, при подключении к подам должны использоваться записи SRV. Как говорилось ранее, SRV записи подов следуют этому шаблону: $(statefulset-name)-$(ordinal).$(service name).$(namespace).svc.cluster.local.
Поскольку StatefulSet MongoDB проверяет живучесть и готовность, мы должны использовать эти стабильные идентификаторы при определении значений переменной MONGO_HOSTNAME.
Добавьте следующий код в файл, чтобы определить переменные MONGO_HOSTNAME, MONGO_PORT, MONGO_DB и MONGO_REPLICASET. Вы можете использовать другое имя для своей базы данных MONGO_DB, но значения MONGO_HOSTNAME и MONGO_REPLICASET должны быть записаны так, как здесь:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
data:
MONGO_HOSTNAME: "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local,mongo-mongodb-replicaset-1.mongo-mongodb-replicaset.default.svc.cluster.local,mongo-mongodb-replicaset-2.mongo-mongodb-replicaset.default.svc.cluster.local"
MONGO_PORT: "27017"
MONGO_DB: "sharkinfo"
MONGO_REPLICASET: "db"
Поскольку мы уже создали объект StatefulSet и набор реплик, перечисленные здесь имена хостов должны быть в вашем файле точно так же, как в этом примере. Если вы уничтожите эти объекты и переименуете ваш релиз MongoDB, вам потребуется пересмотреть значения, включенные в этот ConfigMap. То же самое относится и к MONGO_REPLICASET, так как мы указали имя набора реплик в релизе MongoDB.
Также обратите внимание, что перечисленные здесь значения приведены в кавычках – это требование к переменным среды в Helm.
Сохраните и закройте файл.
Определив значения параметров чарта и создав манифесты Secret и ConfigMap, вы можете отредактировать шаблон Deployment.
2: Интеграция переменных среды в развертывание Helm
Имея файлы для Secret и ConfigMap, мы должны убедиться, что Deployment приложения может использовать эти значения. Мы также настроим показатели живучести и готовности, которые уже определены в манифесте Deployment.
Откройте шаблон развертывания приложения:
nano nodeapp/templates/deployment.yaml
Это файл YAML, но шаблоны Helm для создания манифестов используют синтаксис, который отличается от стандартных файлов YAML Kubernetes. Больше информации о шаблонах можно найти в документации Helm.
Сначала добавьте ключ env в спецификации контейнера приложения под ключом imagePullPolicy и перед ports:
apiVersion: apps/v1
kind: Deployment
metadata:
...
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
ports:
Затем добавьте следующие ключи в список переменных env:
apiVersion: apps/v1
kind: Deployment
metadata:
...
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: MONGO_USERNAME
valueFrom:
secretKeyRef:
key: MONGO_USERNAME
name: {{ .Release.Name }}-auth
- name: MONGO_PASSWORD
valueFrom:
secretKeyRef:
key: MONGO_PASSWORD
name: {{ .Release.Name }}-auth
- name: MONGO_HOSTNAME
valueFrom:
configMapKeyRef:
key: MONGO_HOSTNAME
name: {{ .Release.Name }}-config
- name: MONGO_PORT
valueFrom:
configMapKeyRef:
key: MONGO_PORT
name: {{ .Release.Name }}-config
- name: MONGO_DB
valueFrom:
configMapKeyRef:
key: MONGO_DB
name: {{ .Release.Name }}-config
- name: MONGO_REPLICASET
valueFrom:
configMapKeyRef:
key: MONGO_REPLICASET
name: {{ .Release.Name }}-config
Каждая переменная содержит ссылку на свое значение, определяемое либо ключом secretKeyRef (в случае значений Secret), либо ключом configMapKeyRef (для значений ConfigMap). Эти ключи указывают на файлы Secret и ConfigMap, которые мы создали ранее.
Затем, под ключом ports, измените определение containerPort, чтобы указать в контейнере порт, по которому будет отображаться приложение:
apiVersion: apps/v1
kind: Deployment
metadata:
...
spec:
containers:
...
env:
...
ports:
- name: http
containerPort: 8080
protocol: TCP
...
Затем давайте отредактируем проверки живучести и готовности, которые включены в этот манифест по умолчанию. Эти проверки гарантируют, что приложение работает и готово обслуживать трафик:
- Проверка готовности оценивает, готов ли под обслуживать трафик, прекращая все запросы к поду до тех пор, пока проверки не пройдут успешно.
- Проверка живучести проверяет базовое поведение приложения, чтобы определить, работает ли приложение в контейнере и функционирует ли оно должным образом. В случае сбоя этой проверки Kubernetes перезапустит контейнер.
В нашем случае мы будем использовать запрос httpGet, предоставленный Helm по умолчанию, и проверим, принимает ли наше приложение запросы на конечную точку /sharks. Сервис kubelet выполнит проверку, отправив запрос GET на сервер Nod, работающий в контейнере пода и прослушивающий порт 8080. Если код состояния в ответе находится в диапазоне от 200 до 400, то kubelet сделает вывод, что контейнер исправен. Если же код в ответе между 400 или 500, kubelet либо остановит трафик к контейнеру после проверки готовности, либо перезапустит контейнер после проверки живучести.
Добавьте следующие строки path для проверок живучести и готовности:
apiVersion: apps/v1
kind: Deployment
metadata:
...
spec:
containers:
...
env:
...
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /sharks
port: http
readinessProbe:
httpGet:
path: /sharks
port: http
Сохраните и закройте файл.
Теперь вы готовы создать релиз приложения с помощью Helm. Запустите следующую команду helm install, которая включает название релиза и расположение каталога чарта:
helm install --name nodejs ./nodeapp
Помните, что сначала вы можете запустить helm install с параметрами –dry-run и –debug, чтобы проверить сгенерированные манифесты релиза.
Опять же, поскольку мы не включаем флаг –namespace в helm install, объекты чартов будут созданы в пространстве имен default.
Вы увидите следующий вывод, когда релиз будет создан:
NAME: nodejs
LAST DEPLOYED: Wed Apr 17 18:10:29 2019
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
nodejs-config 4 1s
==> v1/Deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nodejs-nodeapp 0/3 3 0 1s
...
Выходные данные будут указывать состояние релиза вместе с информацией о созданных объектах и о том, как вы можете взаимодействовать с ними.
Проверьте статус ваших подов:
kubectl get pods
NAME READY STATUS RESTARTS AGE
mongo-mongodb-replicaset-0 1/1 Running 0 57m
mongo-mongodb-replicaset-1 1/1 Running 0 56m
mongo-mongodb-replicaset-2 1/1 Running 0 55m
nodejs-nodeapp-577df49dcc-b5fq5 1/1 Running 0 117s
nodejs-nodeapp-577df49dcc-bkk66 1/1 Running 0 117s
nodejs-nodeapp-577df49dcc-lpmt2 1/1 Running 0 117s
После того, как ваши поды будут запущены и начнут работу, проверьте сервисы:
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 96m
mongo-mongodb-replicaset ClusterIP None <none> 27017/TCP 58m
mongo-mongodb-replicaset-client ClusterIP None <none> 27017/TCP 58m
nodejs-nodeapp LoadBalancer 10.245.33.46 your_lb_ip 80:31518/TCP 3m22s
EXTERNAL_IP, связанный с сервисом nodejs-nodeapp, – это IP-адрес, по которому можно получить доступ к приложению вне кластера. Если в столбце EXTERNAL_IP вы видите состояние <pending>, это означает, что балансировщик нагрузки все еще создается.
Когда вы увидите IP-адрес в этом столбце, перейдите к нему в браузере:
http://your_lb_ip
Вы должны увидеть целевую страницу приложения.
Теперь, когда приложение запущено, давайте добавим некоторые тестовые данные, чтобы убедиться, что репликация между членами набора работает правильно.
3: Тестирование репликации MongoDB
Когда приложение работает и доступно через внешний IP-адрес, мы можем добавить тестовые данные и проверить их репликацию между членами набора MongoDB.
Сначала убедитесь, что вы находитесь на целевой странице приложения в браузере.
Нажмите на кнопку Get Shark Info. Вы увидите страницу с формой ввода, где вы можете ввести название и общее описание характера акулы:
Enter Your Shark
Shark Name
Shark Character
Добавьте в форму тестовые данные. Для примера мы добавим Megalodon Shark в первое поле, а Ancient – во второе.
Нажмите на кнопку Submit. Вы увидите вашу информацию на странице приложения.
Теперь вернитесь к форме информации, нажав на Sharks в верхней панели навигации.
Введите новую информацию, например, Whale Shark и Large.
Как только вы нажмете Submit, вы увидите, что новая информация была добавлена в коллекцию в вашей базе данных.
Теперь давайте проверим, реплицируются ли введенные нами данные между первичными и вторичными членами набора реплик.
Запросите список ваших подов:
kubectl get pods
NAME READY STATUS RESTARTS AGE
mongo-mongodb-replicaset-0 1/1 Running 0 74m
mongo-mongodb-replicaset-1 1/1 Running 0 73m
mongo-mongodb-replicaset-2 1/1 Running 0 72m
nodejs-nodeapp-577df49dcc-b5fq5 1/1 Running 0 5m4s
nodejs-nodeapp-577df49dcc-bkk66 1/1 Running 0 5m4s
nodejs-nodeapp-577df49dcc-lpmt2 1/1 Running 0 5m4s
Чтобы получить доступ к оболочке mongo подов, вы можете использовать команду kubectl exec и указать имя пользователя, которое вы использовали для создания секрета mongo. Откройте оболочку mongo на первом поде в StatefulSet с помощью следующей команды:
kubectl exec -it mongo-mongodb-replicaset-0 -- mongo -u your_database_username -p --authenticationDatabase admin
По запросу введите пароль, связанный с этим пользователем:
MongoDB shell version v4.1.9
Enter password:
Вы попадете в оболочку администратора:
MongoDB server version: 4.1.9
Welcome to the MongoDB shell.
...
db:PRIMARY>
Вы можете вручную проверить, какой член набора реплик является ведущим с помощью метода rs.isMaster (хотя сама командная строка содержит эту информацию):
rs.isMaster()
Вы увидите подобный вывод с указанием имени хоста первичного сервера:
db:PRIMARY> rs.isMaster()
{
"hosts" : [
"mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
"mongo-mongodb-replicaset-1.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
"mongo-mongodb-replicaset-2.mongo-mongodb-replicaset.default.svc.cluster.local:27017"
],
...
"primary" : "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
...
Затем перейдите в базу данных sharkinfo:
use sharkinfo
switched to db sharkinfo
Просмотрите коллекции в базе данных:
show collections
sharks
Выведите документы коллекции:
db.sharks.find()
Вы увидите следующий вывод:
{ "_id" : ObjectId("5cb7702c9111a5451c6dc8bb"), "name" : "Megalodon Shark", "character" : "Ancient", "__v" : 0 }
{ "_id" : ObjectId("5cb77054fcdbf563f3b47365"), "name" : "Whale Shark", "character" : "Large", "__v" : 0 }
Закройте оболочку MongoDB:
exit
Теперь, когда мы проверили данные на первичном сервере, давайте узнаем, реплицируются ли они на вторичный сервер. Запустите kubectl exec в mongo-mongodb-replicaset-1:
kubectl exec -it mongo-mongodb-replicaset-1 -- mongo -u your_database_username -p --authenticationDatabase admin
Оказавшись в административной оболочке, нужно использовать метод db.setSlaveOk(), чтобы разрешить операции чтения из вторичного экземпляра:
db.setSlaveOk(1)
Перейдите в базу данных sharkinfo:
use sharkinfo
switched to db sharkinfo
Разрешите операцию чтения документов в коллекции sharks:
db.setSlaveOk(1)
Выведите документы коллекции:
db.sharks.find()
Теперь вы должны увидеть ту же информацию, что и при запуске этого метода на первичном экземпляре:
db:SECONDARY> db.sharks.find()
{ "_id" : ObjectId("5cb7702c9111a5451c6dc8bb"), "name" : "Megalodon Shark", "character" : "Ancient", "__v" : 0 }
{ "_id" : ObjectId("5cb77054fcdbf563f3b47365"), "name" : "Whale Shark", "character" : "Large", "__v" : 0 }
Этот вывод подтверждает, что данные приложения реплицируются между членами вашего набора реплик.
Заключение
Вы развернули реплицированное высокодоступное приложение в кластере Kubernetes, используя чарты Helm. Это простое приложение и описанный здесь рабочий процесс могут стать хорошей отправной точкой для создания пользовательских чартов Helm.
По мере продвижения проекта к производству рассмотрите внедрение централизованного логирования и мониторинга. Вы можете посмотреть, как настроить стек EFK (Elasticsearch, Fluentd, и Kibana) на Kubernetes. Также читайте Введение в service mesh: основные функции и преимущества.
Больше о Helm можно узнать в мануалах:
- Основы работы с Helm, пакетным менеджером Kubernetes
- Установка программного обеспечения в кластер Kubernetes с помощью пакетного менеджера Helm
- Документация Helm.