Масштабирование приложения Node.js на MongoDB в Kubernetes с помощью Helm: пользовательские чарты

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 (ElasticsearchFluentd, и Kibana) на Kubernetes. Также читайте Введение в service mesh: основные функции и преимущества.

Больше о Helm можно узнать в мануалах:

Tags: , , ,