Как поднять приложение с базой данных MongoDB в Kubernetes

Read on English

Пару дней назад в CodeX проводился митап по основам Kubernetes. За полтора часа сложно освоить данную технологию. Решено было взять боевую задачу и получить опыт самостоятельно.

Описание задачи

В CodeX есть проект Codex.Bot – это наш умный помощник, который сообщает в Telegram чате о новых коммитах в репозитории, напоминает о незакрытых задачах, скидывает статистику посещений сайтов и уведомления о новых ошибках.

Проект уже 5 лет работает без существенных изменений кода. Но недавно наша команда наконец завернула его в Docker образ.

Теперь наша задача запустить его в Kubernetes кластере.

Поднимать будем ядро нашего бота. Оно использует следующие сервисы:

Устанавливаем софт

Загрузим следующее ПО:

По умолчанию в данной статье представлены команды и инструкции для MacOS.

Minikube

В первую очередь нам понадобится сам Kubernetes. Для первого опыта достаточно установить локальный minikube.

curl -LO <https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64> sudo install minikube-darwin-amd64 /usr/local/bin/minikube minikube start

Команды для установки на Linux:

  1. curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
  2. sudo install minikube-linux-amd64 /usr/local/bin/minikube

В консоли вы увидите что-то вроде:

😄 minikube v1.25.1 на Darwin 12.0.1 ✨ Automatically selected the docker driver. Other choices: hyperkit, virtualbox, ssh 👍 Запускается control plane узел minikube в кластере minikube 🚜 Скачивается базовый образ ... 🔥 Creating docker container (CPUs=2, Memory=2200MB) ... 🐳 Подготавливается Kubernetes v1.23.1 на Docker 20.10.12 ... ▪ kubelet.housekeeping-interval=5m ▪ Generating certificates and keys ... ▪ Booting up control plane ...

Kubectl

Далее нам понадобится kubectl, чтобы выполнять команды в Kubernetes:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl" chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl sudo chown root: /usr/local/bin/kubectl

Команды для установки на Linux и других ОС:

  1. curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
  2. sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

Выполним команду kubectl get nodes и проверим, что он подхватил запущенную в minikube ноду.

NAME STATUS ROLES AGE VERSION minikube Ready control-plane,master 106m v1.23.1

Lens

Нам часто нужно будет заглядывать во внутренности кластера. Чтобы не мучиться с различными kubernetes-dashboard, скачаем гораздо более удобный инструмент: Lens. Он позволит нам:

Интерфейс Lens

После установки достаточно будет запустить программу и найти там minikube кластер. Для удобства его можно запинить в левом меню.

Helm

В Kubernetes есть свой пакетный менеджер Helm, который можно использовать для загрузки готовых зависимостей (RabbitMQ, MongoDB) и для упаковки своего приложения.

Для установки на ОС отличной от MacOS выберите соответствующий релиз (например, замените darwin на linux).

  1. Скачаем готовый релиз https://get.helm.sh/helm-v3.8.0-darwin-amd64.tar.gz
  2. Распаковываем бинарный файл tar -zxvf helm-v3.8.0-darwin-amd64.tar.gz
  3. Устанавливаем его mv linux-amd64/helm /usr/local/bin/helm
  4. Проверяем версию helm version
version.BuildInfo{Version:"v3.8.0", GitCommit:"d14138609b01886f544b2025f5000351c9eb092e", GitTreeState:"clean", GoVersion:"go1.17.5"}

Скачиваем зависимости

Сначала создадим Helm Chart (пакет нашего приложения) с помощью команды:

helm create k8s-codex-bot

В текущей директории появится папка k8s-codex-bot, заполненная шаблонными файлами, которые мы будем редактировать.

Первым шагом удалим тесты, которые обязательно сломаются, когда мы изменим приложения с дефолтного Nginx на своё.

rm -rf k8s-codex-bot/templates/tests/

Теперь загуглим готовые зависимости (Helm Chars) для MongoDB и RabbitMQ на сайте с зависимостями: https://artifacthub.io/

Там можно узнать текущую актуальную версию и общие настройки, которые можно редактировать

Откроем файл k8s-codex-bot/Chart.yml и внесем в него в конец (после appVersion**: **"1.16.0") информацию о зависимостях:

dependencies: - name: mongodb version: 11.0.x repository: https://charts.bitnami.com/bitnami - name: rabbitmq version: 8.27.x repository: https://charts.bitnami.com/bitnami

Зайдите в директорию проекта k8s-codex-bot и выполните команду helm dependency update:

> helm dependency update Update Complete. ⎈Happy Helming!⎈ Saving 2 charts Downloading mongodb from repo <https://charts.bitnami.com/bitnami> Downloading rabbitmq from repo <https://charts.bitnami.com/bitnami> Deleting outdated charts

Helm обновит реестр зависимостей и скачает файлы с чартами в виде tar.gz архивов в папку charts.

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

mongodb: auth: enabled: false rabbitmq: auth: username: "user" password: "user" erlangCookie: "hg8+qOWd2/fv0f/fP4Jeghec3YGNLJqNOdW4MRx8"
Названия секций (mongodb, rabbitmq) должны соответствовать значениям поля name из секции dependencies.

Пробный запуск Helm Chart

В Kubernetes все ресурсы удобно логически разделять на разные кучи (namespaces), чтобы фильтровать их при выводе. Будем работать в рамках namespace’а под названием codex-bot. Создадим его вручную:

kubectl create ns codex-bot

Теперь достаточно выполнить команду для запуска чарта с именем k8s-codex-bot в пространстве имён codex-bot.

helm install k8s-codex-bot ./ -n codex-bot

Изучаем базовый образ

В Lens вы уже сможете увидеть что-то вроде:

Список запущенных контейнеров (Pods)

Нажмите на контейнер со странным именем, в правом окне найдите кнопку проброса порта (Forward) и нажмите Start.

Проброс порта контейнера на локальный компьютер

У вас в браузере откроется страница веб-сервера Nginx. Это потому, что Nginx идет по умолчанию как демонстрационный образ при создании Helm чарта.

Подключаемся к MongoDB

Чтобы подключиться к порту любого контейнера, достаточно прописать команду:

kubectl port-forward service/k8s-codex-bot-mongodb 28015:27017 -n codex-bot

В данном случае мы проксировали порт 27017 контейнера k8s-codex-bot-mongodb на локальный порт 28015 (подставьте любой случайный незанятый порт). К нему теперь можно подключиться с локального компьютера через MongoDB Compass:

mongodb://localhost:28015/

Подключаемся к RabbitMQ

У RabbitMQ есть своя веб админка. Найдите порт 15672 в Lens и проксируйте его себе в браузер.

Проброс портов RabbitMQ

В появившемся окне введите логин user и пароль user для доступа к панели администрирования RabbitMQ.

Окно RabbitMQ management

Редактируем шаблон приложения

В данной секции опишем:

Основные настройки приложения

Укажем свой Docker image, тег и Docker registry для приложения в файле values.yml.

image: repository: ghcr.io/codex-team/codex-bot-core pullPolicy: Always tag: "stage"

pullPolicy: Always нам понадобится, чтобы Helm после каждого запуска проверял и подхватывал свежий образ приложения.

Заполним желаемое название приложения в fullnameOverride: "core-bot"

Выберем порт, на котором будет слушать приложение:

service: type: ClusterIP port: 1337

В файле deployment.yml указаны секции livenessProbe и readinessProbe. Они необходимы для проверки успешности работы контейнера и его перезапуска. Сейчас CodeX.Bot их не поддерживает, поэтому удалим их пока, чтобы не мешали и не перезапускали лишний раз работающий контейнер.

Передача переменных окружения

Любое приложение нужно конфигурировать. CodeX Bot конфигурируется через передачу переменных окружения (env). Хранить мы их будем в сущности ConfigMap кубернетеса.

Откроем файл deployment.yml, содержащий шаблон приложения и внесем изменения в секцию spec.template.spec.containers:

ports: - name: http containerPort: 1337 protocol: TCP envFrom: - configMapRef: name: env-configmap

Здесь мы отредактировали порт, на котором слушает приложение, а также добавили указание брать переменные окружения из ConfigMap с именем env-configmap.

Создадим файл env-configmap.yml в папке templates и заполним его нужными значениями:

apiVersion: v1 kind: ConfigMap metadata: name: env-configmap data: SERVER_HOST: "0.0.0.0" SERVER_PORT: "1337" RABBITMQ_URL: "amqp://user:user@k8s-codex-bot-rabbitmq/" DATABASE_NAME: default DATABASE_HOST: k8s-codex-bot-mongodb DATABASE_PORT: "27017" TELEGRAM_CALLBACK_ROUTE: /telegram/callback TELEGRAM_BOT_NAME: "<...>" TELEGRAM_API_TOKEN: "<...>" TELEGRAM_API_URL: "https://api.telegram.org/bot" SLACK_BOT_NAME: "" SLACK_CLIENT_ID: "" SLACK_CLIENT_SECRET: ""

Здесь мы пока не будем париться с шифрованием секретов, а напишем все пароли и чувствительные данные в открытом виде. Данный Helm чарт будет располагаться в закрытом репозитории.

Накатываем изменения

Для того, чтобы обновить запущенные образы достаточно выполнить команду:

helm upgrade k8s-codex-bot ./ -n codex-bot

Helm сам разберется, какие файлы изменились, какие контейнеры нужно остановить, а какие перезапустить.

Если вы теперь выберете контейнер с именем core-bot-..., то сможете увидеть переменные окружения:

Переменные окружения

В Lens также удобно смотреть логи любого контейнера.

Кнопка открытия окна с логами контейнера

Учимся дебажить

Очень часто возникает ситуация, что контейнер падает без каких-либо логов об ошибках. Если вы не можете разобраться в чем дело, очень удобно зайти по-старинке в bash и запустить все руками, посмотрев истинную причину падения.

Для этого добавим в deployment.yaml в секцию spec.template.spec.containers следующие два значения:

command: ["/bin/sh"] args: ["-c", "while true; do echo hello; sleep 10;done"]

Теперь контейнер стартует успешно, и вы сможете просто подключиться к нему, нажав кнопку Pod shell.

Кнопка открытия shell в контейнере

Не забудьте убрать command и args перед тем, как продолжить.

Telegram Webhook с помощью Ngrok

CodeX Bot использует вебхук для получения сообщений из Telegram. Чтобы протестировать данную связку на локальном компьютере, предлагаем использовать Ngrok.

Первым делом нужно зарегистрироваться на сайте и получить Authtoken в личном кабинете.

Ngrok – это обычное приложение, завернутое в Docker. Создадим файл ngrok-deployment.yml в папке templates.

apiVersion: apps/v1 kind: Deployment metadata: name: ngrok spec: selector: matchLabels: app: ngrok replicas: 1 template: metadata: labels: app: ngrok spec: containers: - name: ngrok image: wernight/ngrok command: ["ngrok"] args: ["http", "core-bot:1337", "--authtoken", "<...>"] ports: - containerPort: 4040

Тут важно указать свой authtoken в качестве аргумента. Пробрасывать пользователя мы будем на приложение core-bot. Это имя соответствует полю fullnameOverride из values.yml.

Применим изменения при помощи команды helm upgrade k8s-codex-bot ./ -n codex-bot

В Lens можно увидеть новый контейнер с именем ngrok. Пробросим его порт 4040 и скопируем публичный URL для вебхука.

Осталось передать этот параметр приложению.

Для этого добавим в values.yml ключ и значение:

url: "https://052e-92-100-146-207.ngrok.io/"

А в env-configmap.yml в секцию data:

URL: {{ .Values.url }}

Теперь приложение будет получать вебхук через переменную окружения прямиком из файла values.yml.

Вручную удалим основное приложение core-bot, нажав в Lens кнопку Delete.

Бот сам перезапустится и теперь с ним можно будет пообщаться в Telegram чате.

Выводы

Мы взяли небольшой Docker образ приложения, которое требует MongoDB и RabbitMQ для своей работы и заставили это приложение работать в Kubernetes.

Попутно мы изучили огромный стек технологий и теперь примерно понимаем как это все работает:

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

Теперь самое время открыть документацию, изучить терминологию и познавать теорию Kubernetes.