Файлы, упомянутые в главе
- .helm/templates/deployment.yaml
- .helm/templates/ingress.yaml
- .helm/templates/service.yaml
- .helm/values.yaml
- .helm/secret-values.yaml
Для того, чтобы приложение заработало в Kubernetes, необходимо описать инфраструктуру приложения как код (IaC). В нашем случае потребуются следующие объекты Kubernetes: Pod, Service и Ingress.
Конфигурацию для Kubernetes нужно шаблонизировать. Один из популярных инструментов для такой шаблонизации — это Helm, его движок встроен в werf. Помимо этого, werf предоставляет возможности работы с секретными значениями, а также дополнительные Go-шаблоны для интеграции собранных образов.
В этой главе мы научимся описывать Helm-шаблоны, используя возможности werf, а также освоим встроенные инструменты отладки.
Составление конфигов инфраструктуры
На сегодняшний день Helm — один из самых удобных (и самых распространённых) способов, которым можно описать свой деплой в Kubernetes. Он позволяет устанавливать готовые чарты с приложениями прямо из репозитория: введя одну команду, можно развернуть в своем кластере готовый Redis, PostgreSQL, RabbitMQ… Кроме того, Helm можно использовать для разработки собственных чартов, применяя удобный синтаксис для шаблонизации выката ваших приложений.
По этим причинам он был встроен в werf для решения соответствующих задач.
Итак, для работы рассматриваемого приложения в среде Kubernetes понадобится:
- описать сущности Deployment (он породит в кластере Pod) и Service;
- направить трафик на приложение, настроив роутинг в кластере с помощью сущности Ingress;
- не забыть создать отдельную сущность Secret, которая позволит Kubernetes скачивать собранные образа из Registry.
Создание Pod’а
Для того, чтобы в кластере появился Pod с нашим приложением, мы создадим объект Deployment. У создаваемого Pod будет один контейнер — basicapp
. Укажем, как этот контейнер будет запускаться.
Здесь и далее будут показаны только фрагменты файлов. Если вам не знаком синтаксис Kubernetes-объектов и вы не можете дополнить приведённые сниппеты самостоятельно — обязательно сверяйтесь с файлами в репозитории.
containers:
- name: basicapp
command: ["java"]
args: ["-jar", "/app/demo.jar", "$JAVA_OPT"]
{{ tuple "basicapp" . | include "werf_container_image" | indent 8 }}
Обратите внимание на вызов werf_container_image
. Данная функция генерирует ключи image
и imagePullPolicy
со значениями, необходимыми для соответствующего контейнера Pod’а, что позволяет гарантировать перевыкат контейнера тогда, когда это нужно.
Для корректной работы приложения ему нужно узнать переменные окружения.
Например, для Java это JAVA_OPT
- различные опции с которыми будет запускаться java. Аналогично в приложение может передаваться, например, пароль к базе данных. Добавим его также (хотя он и не используется в приложении) для иллюстрации механизма.
env:
- name: JAVA_OPT
value: "--debug"
- name: DBPASS
value: "mysuperdbpassword"
<...>
{{ tuple "basicapp" . | include "werf_container_env" | indent 8 }}
Мы задали значение для DBPASS
в явном виде — и это абсолютно не безопасный путь для хранения таких критичных данных. Мы разберём более правильный путь ниже, в главе “Разное поведение в разных окружениях”.
Обратите также внимание на функцию werf_container_env
: с её помощью werf вставляет в описание объекта служебные переменные окружения.
При запуске приложения в Kubernetes логи необходимо отправлять в stdout и stderr — это нужно для простого сбора логов, например, через filebeat
, а также для того, чтобы не разрастались Docker-образы запущенных приложений.
Spring-framework уже автоматически предоставляет логи в stdout. Однако мы можем переопределить уровень логирования в application.properties при необходимости. Подробнее - в документации.
Доступность Pod’а
Для того, чтобы запросы извне попали к нам в приложение, нужно открыть порт у Pod’а, создать объект Service и привязать его к Pod’у, а также создать объект Ingress.
Наше приложение работает на стандартном порту 8080
— откроем порт Pod’у:
ports:
- containerPort: 8080
protocol: TCP
Затем пропишем Service, чтобы к Pod’у могли обращаться другие приложения кластера:
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Chart.Name }}
spec:
selector:
app: {{ .Chart.Name }}
ports:
- name: http
port: 8080
protocol: TCP
Обратите внимание на поле selector
у Service: он должен совпадать с аналогичным полем у Deployment. Ошибки в этой части — самая частая проблема с настройкой маршрута до приложения.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
spec:
selector:
matchLabels:
app: {{ .Chart.Name }}
После этого можно настраивать роутинг на Ingress. Укажем, на какой домен, путь, сервис и порт направлять запросы:
rules:
- host: mydomain.io
http:
paths:
- path: /
backend:
serviceName: {{ .Chart.Name }}
servicePort: 8080
Разное поведение в разных окружениях
Некоторые настройки хочется видеть разными в разных окружениях. К примеру, домен, на котором будет открываться приложение, должен быть либо staging.mydomain.io, либо mydomain.io — в зависимости от того, куда мы задеплоились.
В werf для этого существует три механики:
- Подстановка значений из
values.yaml
по аналогии с Helm. - Проброс значений через аргумент
--set
при работе в CLI-режиме, по аналогии с Helm. - Подстановка секретных значений из
secret-values.yaml
.
Вариант с values.yaml
рассматривался ранее в главе “Создание Pod’а”.
Второй вариант подразумевает задание переменных через CLI. Например, в случае выполнения команды werf deploy --set "global.ci_url=mydomain.io"
в YAML’ах будет доступно значение {{ .Values.global.ci_url }}
.
Этот вариант удобен для проброса, например, имени домена для каждого окружения:
rules:
- host: {{ .Values.global.ci_url }}
Отдельная проблема — хранение и задание секретных переменных, например, учётных данных аутентификации для сторонних сервисов, API-ключей и т.п.
Так как werf рассматривает Git как единственный источник правды, правильно хранить секретные переменные там же. Чтобы делать это корректно, мы храним данные в шифрованном виде. Подстановка значений из этого файла происходит при рендере шаблона, который также запускается при деплое.
Чтобы воспользоваться секретными переменными:
- сгенерируйте ключ (
werf helm secret generate-secret-key
); - определите ключ в переменных окружения для приложения, в текущей сессии консоли (например,
export WERF_SECRET_KEY=634f76ead513e5959d0e03a992372b8e
); - пропишите полученный ключ в
Variables
для вашего репозитория в GitLab (разделSettings
→CI/CD
), название переменной должно бытьWERF_SECRET_KEY
:
После этого можно задать секретную переменную, например DBPASS
. Зайдите в режим редактирования секретных значений:
$ werf helm secret values edit .helm/secret-values.yaml
Откроется консольный текстовый редактор с данными в расшифрованном виде:
app:
password:
_default: my-secret-password
production: my-super-secret-password
После сохранения значения в файле зашифруются и примут примерно такой вид:
app:
password:
_default: 10006755d101c5243fc400ababd7358689a921c19ee7e96a95f0ab82d46e4424573ab50ba666fcf5ce5e5dbd2b696c7706cf
production: 1000bcd51061ebd1b2c2990041d30783be607b3a0aec8890c098f17bc96dc43e93765219651d743c7a37fb7361c10b703c7b
Доступ кластера к Registry
Для того, чтобы кластер имел доступ к собранным образам, необходимо создать ключ доступа к Registry и прописать его в кластер. Этот ключ мы назовём registrysecret
.
Сперва нужно создать API-ключ в GitLab: зайдите в настройки пользователя (Settings
) и в разделе Personal Access Tokens
создайте API-ключ с правами на read_registry
. Корректнее всего создать отдельного служебного пользователя, чтобы не завязываться на персональный аккаунт.
Полученный ключ должен быть прописан в каждом пространстве имён в Kubernetes, куда осуществляется деплой, в виде объекта Secret. Сделать это можно, выполнив на master-узле кластера команду:
kubectl create secret docker-registry registrysecret -n <namespace> --docker-server=<registry_domain> --docker-username=<account_email> --docker-password=<account_password> --docker-email=<account_email>
Здесь:
<namespace>
— название пространства имён в Kubernetes (например,werf-guided-project-production
);<registry_domain>
— домен Registry (например,registry.gitlab.com
);<account_email>
— email вашей учётной записи в GitLab;<account_password>
— созданный API-ключ.
В каждом Deployment также указывается имя секрета:
spec:
imagePullSecrets:
- name: "registrysecret"
Отладка конфигов инфраструктуры и деплой в Kubernetes
После того, как написана основная часть конфигов, хочется проверить корректность конфигов и задеплоить их в Kubernetes. Для того, чтобы отрендерить конфиги инфраструктуры, требуются сведения об окружении, на которое будет произведён деплой, ключ для расшифровки секретных значений и т.п.
Если мы запускаем werf вне Gitlab CI, необходимо сделать несколько операций вручную прежде, чем werf сможет рендерить конфиги и деплоить в Kubernetes. А именно:
- Вручную подключиться к GitLab Registry с помощью
docker login
(если это не было сделано ранее); - Установить переменную окружения
WERF_IMAGES_REPO
с путём до Registry (видаregistry.mydomain.io/myproject
); - Установить переменную окружения
WERF_SECRET_KEY
со значением, сгенерированным ранее в главе “Разное поведение в разных окружениях”; - Установить переменную окружения
WERF_ENV
с названием окружения, в которое будет осуществляться деплой. Вопрос разных окружений будет затронут подробнее в процессе создания CI-процесса, а сейчас — просто установим значениеstaging
. Важно удалить эту переменную в финальном варианте деплоя: иначе деплой всегда будет идти в один и тот же namespace.
Если вы всё правильно сделали, уже должны корректно отрабатывать команды werf helm render
и werf deploy
. Примечание: при локальном запуске эти команды могут жаловаться на нехватку данных, которые в ином случае были бы проброшены из CI. Например, на данные о теге собранного образа. Это нормально.
Запустите деплой и дождитесь успешного завершения:
werf deploy --stages-storage :local
А проверить, что приложение задеплоилось в кластер, можно с помощью kubectl. Должно получиться примерно следующее:
$ kubectl get namespace
NAME STATUS AGE
default Active 161d
werf-guided-project-production Active 4m44s
werf-guided-project-staging Active 3h2m
$ kubectl -n example-1-staging get po
NAME READY STATUS RESTARTS AGE
werf-guided-project-9f6bd769f-rm8nz 1/1 Running 0 6m12s
$ kubectl -n example-1-staging get ingress
NAME HOSTS ADDRESS PORTS AGE
werf-guided-project staging.mydomain.io 80 6m18s
А также вы должны увидеть сервис через браузер.