Сегодня Вы узнаете, как онлайн, с смс и регистрацией задеплоить своё приложение в kubernetes. Поехали!
Совсем мало теории
Итак, что такое kubernetes? Это штука, предназначенная для автоматизации развёртывания, масштабирования и координации контейнеризированных приложений в условиях кластера (спасибо, википедия). Для деплоя приложений в kubernetes можно использовать очень удобный инструмент - helm. Для того чтобы им воспользоваться, вам нужен chart одного или нескольких ваших приложений. chart - описание ресурсов, необходимые для запуска приложения. Выглядит это как куча yaml файлов, в которых описаны объекты kubernetes.
Итого, нам понадобятся:
Один кластер kubernetes. Нету? Что ж, гуглим что-нибудь бесплатное. Находим OpenShift Sandbox. Есть.
Утилиты oc (OpenShift CLI) и helm. Качаем. Есть.
Приложения для деплоя. Тыщу лет назад писал про то как собрать образы для Spring Boot и Vue.js приложений. Вот их и возьмём. Есть.
Чарты для деплоя. Нету. Напишем.
OpenShift Sandbox
Идём на сайт Red Hat за песочницей. На текущий момент Red Hat позволяет получить песочницу на 30 дней. По истечении этого времени песочница будет удалена, но вы сможете её пересоздать снова на 30 дней. Ограничения на количество попыток повторного заведения песочниц, на сколько я знаю, на текущий момент нет. При создании песочницы потребуется ввести свой номер телефона для подтверждения по смс (потребуется каждый раз при пересоздании песочницы).
После создания песочницы нужно будет войти в неё из консоли с помощью утилиты oc. Для этого жмём сюда:
А затем сюда:
Интересующая нас команда:
Через какое-то время токен протухнет и нужно будет перелогинеться.
Helm chart
Готовые чарты можно посмотреть тут. А ниже приведён процесс готовки этих самых чартов.
Создать чарты можно командой:
helm create list-keep-chart
Так же чарты можно создать, например, с помощью IntelliJ IDEA, выбирайте удобный для вас вариант.
Cтруктура чарта, созданного данной командой, представлена ниже.
Созданный чарт будет является вполне рабочим, достаточно подставить свои значения в values.yaml
. Правда, данный чарт предназначен для одного приложения. Если требуется (как в нашем случае) несколько, то нужно изменить структуру чарта. Для каждого приложения требуется свой чарт. Чтобы чарты всех приложений находились в одном чарте - нужно будет положить их как сабчарты. Для этого достаточно просто положить их в папку charts
родительского чарта. В родительском чарте будут только содержаться сабчарты и больше ничего в нём происходить не будет, поэтому нужно удалить из него папку templates
с шаблонами, описывающими объекты kubernetes. Также требуется очистить файл values.yaml
, содержащий значения для шаблонов из templates
.
Чарты для Spring Boot приложения
Создадим сабчарт для Spring Boot приложения. Создание сабчартов ничем не отличается от создания просто чартов (кроме, разве что, того, что они должны быть созданы в директории charts
родительского чарта). Структура данного сабчарта представлена ниже.
Если кому-то интересно, то подробно познакомиться со структурой чартов и конкретно с шаблонами можно в соответствующих разделах документации на сайте helm'а, в статье затрону лишь то, с чем предстоит непосредственно взаимодействовать.
Также следует добавить сабчарт как зависимость в Chart.yaml
родительского чарта, т.е. в list-keep-chart\Chart.yaml
. Это не обязательно, но считается best practice. В конец файла добавим:
dependencies:
- name: list-keep
condition: list-keep.enabled
version: "*"
name
- имя сабчарта. condition
- условие с помощью которого можно включать/выключать сабчарт, т.е. управлять тем нужно ли деплоить это приложение в kubernetes или нет. version
- версия в формате semver. В данном случае в качестве версии указано значение *
для того, чтобы при изменении версии в сабчарте не менять версию в родительском Chart.yaml
.
Итак, что вообще требуется, чтобы задеплоить наш сервис? Как минимум нужно как-то указать наш докер образ. Т.к. это веб сервис, то ещё нужно как-то указать порт контейнера. Собственно, осталось понять как. Для этого проще всего посмотреть, что сгенерилось в values.yaml
сабчарта.
Заходим в list-keep-chart\charts\list-keep\values.yaml
и, в общем-то, сразу же вверху файла видим как следующее:
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
Достаточно переопределить repository
и tag
, для того чтобы задать свой образ.
Теперь поищем как нам указать свой порт. Для этого листаем ниже и находим следующее:
service:
type: ClusterIP
port: 80
Видим здесь port
. Но, увы, это не тот порт, что мы ищем. Этот порт используется в шаблоне service.yaml
, который описывает соответствующий объект kubernetes - Service. Данный объект, по сути, определяет способ предоставления доступа к нашему приложению. В данном конкретном случае он описывает, что наше приложение принимает трафик на порту 80. Также у Service
существует параметр targetPort
, если он не задан, то он равен тому же значению, что и port
. Данный параметр задаёт порт назначения, т.е. это будет порт в вашем контейнере (а точнее в pod).
Что ж, ищем дальше. Открываем deployment.yaml
. Этот шаблон описывает объект kubernetes - Deployment. Этот объект предназначен для деплоя вашего приложения, и в нём описывается, как это нужно делать. Интересующий нас блок:
ports:
- name: http
containerPort: 80
protocol: TCP
Сделаем так, чтобы сюда подставлялось значение порта из values.yaml
. Установим сюда то же значение, что сейчас устанавливается для Service
, т.е. значение порта, на котором приложение принимает трафик, будет совпадать со значением порта из контейнера. Сделать это можно следующим образом:
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
Т.к. у нас Spring Boot приложение, то нужно ещё каким-то образом указать профиль при запуске приложения. Сделать это можно, например, указав переменную окружения SPRING_PROFILES_ACTIVE. Но в сгенерериванном чарте по умолчанию такой возможности нет. Добавить её можно в deployment.yaml
в конфигурацию контейнера - spec.template.spec.containers
. Сделаем так, чтобы можно было задавать переменные окружения через файл valus.yaml
:
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
Здесь, с помощью with меняется текущая область видимости на .Values.env
. А с помощью toYaml копируются значения текущей области видимости .
с отступом, который задаётся с помощью nindent, в 12 пробелов. Т.е., если мы зададим в valus.yaml
блок env
, значения из него будут скопированы в deployment.yaml
в блок spec.template.spec.containers.env
.
Казалось бы всё, но нет. Сейчас приложение не сможет подняться (а точнее оно будет постоянно перезапускаться) из-за livenessProbe и readinessProbe в конфигурации контейнера. Kubernetes используетlivenessProbe
для определения жив или или мёртв контейнер. Если проверка не проходит - значит контейнер мёртв. readinessProbe
используется для определения готовности контейнера принимать сетевой трафик. Если не проходит - значит kubernetes не будет отправлять сетевой трафик в этот контейнер. Сгенерирована была следующая конфигурация:
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
Проблема в том, что в рассматриваемом нами контейнере нет эндпоинта /
, поэтому livenessProbe
и readinessProbe
никогда не пройдут. Что же делать и как дальше жить?! Самим добавлять этот энпоинт?..
На самом деле, добавлять ничего не нужно. Есть уже готовый проект Spring Boot Actuator, который сам добавит нужные эндпоинты для мониторинга работоспособности вашего приложения. Это не единственная его функция, он может ещё много чего. Подробности про этот проект можно почитать в документации.
Для подключения Spring Boot Actuator в проект нужно добавить следующую зависимость:
implementation 'org.springframework.boot:spring-boot-starter-actuator
После этого в приложении, задеплоенном в kubernetes, станут доступны эндпоинты /actuator/health/liveness и /actuator/health/readiness для livenessProbe
и readinessProbe
соответственно. При запуске вне среды kubernetes данные эндпоинты будут недоступны. Будет доступен только глобальный эндпоинт проверки состояния вашего приложения /actuator/health. В приложении, задеплоенном в kubernetes, этот эндпоинт так же будет доступен и будет показывать, по сути, совокупность данных обоих эндпоинтов /actuator/health/liveness
и /actuator/health/readiness
. Итоговая конфигурация для livenessProbe
и readinessProbe
будет выглядеть так:
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
Зададим свои values для данного сабчарта. Сделать это можно как просто поместив их в values.yaml
самого сабчарта, т.е., в данном случае, в list-keep-chart\charts\list-keep\values.yaml
, так и задав их в values.yaml
родительского чарта, т.е. в list-keep-chart\values.yaml
. Во тором варианте values
, на самом деле, будут переопределяться, т.е. можно задать какие-то values
в сабчарте, и переопределить какие-то из них в родительском чарте (но, например, не все). Второй вариант считается best practice, поэтому выберем его. Чтобы указать, что values
относятся к конкретному сабчарту, нужно их указать в разделе с именем данного сабчарта. В нашем случае раздел будет list-keep
.
list-keep:
image:
repository: ghcr.io/vanbv/list-keep
tag: latest
pullPolicy: Always
enabled: true
service:
port: 8082
Как может заметить внимательный читатель, помимо repository
и tag
, о которых я писал выше, я здесь ещё и изменил pullPolicy. В сгенерированном файле оно было IfNotPresent
- стягивает докер-образ, если его нет в локальном registry
. Я заменил его на Always
- стягивает образ всегда из указанного repository
. Сделал это, т.к. для проекта всегда собираю образ latest
, и если не поменять, то стянется один раз и потом будет всегда браться из локального registry
, что не совсем здесь подходит (не делайте так в продакшене!!!).
Что ж, с чартами для Spring Boot приложения мы закончили, давайте двинемся дальше.
Чарты для Vue.js приложения
Сабчарты для Vue.js приложения list-keep-front
создаём аналогично тому, как мы это делали для Spring Boot приложения. В list-keep-chart\Chart.yaml
добавялем:
- name: list-keep-front
condition: list-keep-front.enabled
version: "*"
Здесь нам понадобиться указать докер образ и порт, как это делать мы уже знаем. Ещё нам требуется как-то обращаться к нашему приложению, т.е. нужен его адрес. Обычно для этого используется объект kubernetes - Ingress. Он позволяет сконфигурировать маршруты HTTP и HTTPS из внешней среды для объектов Service. Но в песочнице OpenShift Sandbox он не доступен. Вместо него предлагается использовать Route (наверное, можно этот объект назвать аналогом Ingress в OpenShift). Итак, используя документацию, а также пример, который генерится, если создать Route
руками из веб-админки OpenShift, пример Ingress
, который генерится при создании чартов, и капельку здравого смысла мне удалось написать вот такой list-keep-chart\charts\list-keep-front\templates\route.yaml
:
{{- if .Values.route.enabled -}}
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: {{ include "list-keep-front.fullname" . }}
spec:
host: {{ .Values.route.host }}
port:
targetPort: {{ .Values.service.port }}
to:
kind: Service
name: {{ include "list-keep-front.fullname" . }}
{{- end }}
Давайте подробнее рассмотрим, что тут происходит:
{{- if .Values.route.enabled -}}
- включает/выключает route в зависимости отroute.enabled
вvalues.yaml
(аналогично тому, как сделано вingress.yaml
). Чтобы по умолчаниюroute
был выключен, добавим вvalues.yaml
сабчарта следующее:route: enabled: false
apiVersion: route.openshift.io/v1
- версияAPI Kubernetes
, используемая для создания объекта Route.kind: Route
- тип объекта kubernetes.metadata
- данные, по которым можно идентифицировать объект. В данном случае этоname: {{ include "list-keep-front.fullname" . }}
название объекта (объект назван так же, как и другие объекты kubernetes в сгенерированном сабчарте).spec
- требуемая конфигурация объекта.host
нужно будет указать вvalues.yaml
. В качествеport
будет браться тот, что указан для Service. В блокеto
указывается, что данныйroute
нужно применить к объекту с типом сервис -kind: Service
и имя данного сервиса -name: {{ include "list-keep-front.fullname" . }}
.
Итого в list-keep-chart\values.yaml
получается следующее:
list-keep-front:
image:
repository: ghcr.io/vanbv/list-keep-front
tag: v0.0.11
enabled: true
service:
port: 8080
route:
enabled: true
Внимательный читатель может заметить, что я куда-то потерял route.host
. В принципе его можно указать и здесь, но т.к. OpenShift Sandbox после пересоздания песочницы (напоминаю, это придётся сделать после 30 дневного периода) может сгенерировать новый адрес, то я решил для удобства вынести все такие адреса в переменные sh
скрипта, который покажу в дальнейшем.
И ещё один нюанс. Данное приложение использует nginx. OpenShift Sandbox не позволит запуститься nginx под привилегированным пользователем. Поэтому требуется сконфигурировать nginx таким образом, чтобы он не запускался под привилегированным пользователем. В принципе, можно вообще воспользоваться уж готовым для таких нужд образом nginxinc/nginx-unprivileged. Пример можно посмотреть тут.
Чарты keycloak
Для полноценной работы приведённых выше приложений требуется keycloak. Если коротко, то это Authorization Server, если интересно подробнее, то welcome. Для сторонних приложений обычно уже используются готовые сторонние чарты. Найти их можно на Artifact Hub. Возьмём вот эти чарты от Bitnami. Добавить их в родительские чарты можно, как и сабчарты, написанные нами. В родительский Chart.yaml
добавляем следующее:
- name: keycloak
repository: https://charts.bitnami.com/bitnami
condition: keycloak.enabled
version: 9.8.1
В данных чартах отсутствуют шаблоны для route
, которые нам понадобятся, поэтому напишем их сами. Нам понадобится 2 routes
. Один для http
, второй для https
. В принципе можно было бы обойтись каким-то одним, но с двумя будет проще. route
с http
нам нужен для общения Spring Boot приложения с keycloak, если использовать https
, то потребуется валидный сертификат. Но с route
http
не получится зайти в админку keycloak, по умолчанию она доступна по https
. В принципе, можно зайти в базу keycloak и проапдейтить там данные для того чтобы админка стала доступна по http
, правда, для этого нужно разобраться что где хранится (но технически такое возможно, по крайней мере на момент написания статьи).
Давайте создадим 2 routes
. Ранее мы создавали объекты kubernetes для своих чартов. Как же создать для чужих? Да, в общем-то, так же. Создаём сабчарт с именем стороннего чарта, в нашем случае это keycloak
, и удаляем оттуда всё кроме Chart.yaml
, и values.yaml
, содержимое values.yaml
нужно также удалить. Ну и теперь создаём в папке templates
наши routes
: route-http.yaml
и route-https.yaml
. Содержимое route-http.yaml
будет совпадать с рассмотренным нами ранее содержимым route.yaml
для list-keep-front
, поэтому не будем на нём останавливаться. А вот содержимое route-https.yaml
будет немного отличаться:
{{- if .Values.route.https.enabled -}}
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: {{ include "keycloak.fullname" . }}-https
spec:
host: {{ .Values.route.https.host }}
port:
targetPort: https
tls:
termination: passthrough
to:
kind: Service
name: {{ include "keycloak.fullname" . }}
{{- end }}
Здесь в блоке spec
добавляется блок tls
с termination: passthrough
. Эта конфигурация указывает, что нужно, чтобы зашифрованный tls-трафик отправлялся прямо в наш сервис.
В values.yaml
сабчарта добавим:
route:
http:
enabled: false
https:
enabled: false
Теперь идём в родительский values.yaml
и добавим туда следующее:
keycloak:
enabled: true
podSecurityContext:
enabled: false
containerSecurityContext:
enabled: false
auth:
tls:
enabled: true
autoGenerated: true
postgresql:
primary:
podSecurityContext:
enabled: false
containerSecurityContext:
enabled: false
route:
http:
enabled: true
https:
enabled: true
Итак, здесь зачем-то отключены podSecurityContext и containerSecurityContext. Причина, в общем-то, та же, что описана для nginx. В чартах для podSecurityContext и containerSecurityContext описан запуск контейнеров/подов под привилегированным пользователем, но OpenShift Sandbox не позволит этого сделать, поэтому просто отключим podSecurityContext и containerSecurityContext.
auth.tls.enabled
- включает TLS трафик, напоминаю, требуется для того чтобы попасть в админку. Если его включить, то потребуется соответствующий сертификат, но можно включить автогенерацию сертификата с помощьюauth.tls.autoGenerated
.Для keycloak требуется база данных. По умолчанию с чартами от Bitnami используется PostgreSQL. Чтобы поды/контейнеры не запускались под привилегированным пользователем, необходимо отключить podSecurityContext и containerSecurityContex, аналогично тому, как это было сделано для самого keycloak.
С чартами keycloak в общем-то всё. Теперь нужно добавить адрес keycloak в наши приложения. В Spring Boot приложение можно добавить url напрямую в конфиг приложения, но тогда придётся пересобирать докер-образ при изменении url каждый раз, когда пересоздаётся песочница. Поэтому добавим url через переменные окружения с помощью чартов (аналогично тому, как мы добавили профиль). Конфигурация application-prod.yml
будет выглядеть так:
server:
port: 8082
keycloak:
# Адрес keycloak берётся из соответствующей переменной окружения. Он должен
# совпадать с тем адресом, который будет указан во фронте, т.к. в
# Spring Boot Adapter есть проверка на то что адреса keycloak указанный здесь
# совпадает с адресом из токена
auth-server-url: ${KEYCLOAK_URL}
realm: "list-keep"
resource: "list-keep"
bearer-only: true
security-constraints:
- authRoles:
- uma_authorization
securityCollections:
- patterns:
- /api/*
# Выключаем ssl, чтобы обращаться в keycloak по http
ssl-required: none
Адрес keycloak во Vue.js приложении для production
среды указывается в файле .env.production
. И в данном случае с помощью переменных окружения так просто добавить адрес keycloak не получится. Способ есть, но он показался мне не сильно тривиальным, подробнее о нём можно почитать тут. В принципе, можно просто создать этот файл в репозитории проекта, и каждый раз менять адрес в репозитории, коммитить и собирать новый образ. Но всё-таки можно немного упростить и, например, генерировать данный файл при сборке. В данном случае для сборки используются GitHub Actions, поэтому, для генерации .env.production
можно добавить вот такой шаг:
- name: Create .env.production
run: |
touch .env.production
echo VUE_APP_KEYCLOAK_URL = ${{ secrets.VUE_APP_KEYCLOAK_URL }} >> .env.production
Адрес keycloak лежит в секретах и здесь он просто добавляется в файл. Очевидный минус такого подхода - придётся собирать образ каждый раз, когда пересоздаётся OpenShift Sandbox. Полный файл данного GitHub Action можно посмотреть в соответствующем репозитории.
Деплой
Для установки чартов необходимо выполнить helm install
. Т.к. сама команда получилась достаточно длинной, я для удобства создал скрипт helm-install.sh
, с помощью которого можно её выполнить:
#!/bin/bash
export KEYCLOAK_POSTGRESQL_ADMIN_PASSWORD=postgres
export KEYCLOAK_POSTGRESQL_PASSWORD=postgres
export KEYCLOAK_ADMIN_PASSWORD=user
export CLUSTER_URL=-your-namespace.openshiftapps.com
helm install list-keep ./ --set global.postgresql.auth.postgresPassword=$KEYCLOAK_POSTGRESQL_ADMIN_PASSWORD,global.postgresql.auth.password=$KEYCLOAK_POSTGRESQL_PASSWORD,keycloak.auth.adminPassword=$KEYCLOAK_ADMIN_PASSWORD,list-keep.env[0].name=SPRING_PROFILES_ACTIVE,list-keep.env[0].value=prod,list-keep.env[1].name=KEYCLOAK_URL,list-keep.env[1].value=http://list-keep-keycloak-http$CLUSTER_URL,list-keep-front.route.host=list-keep-front$CLUSTER_URL,keycloak.route.http.host=list-keep-keycloak-http$CLUSTER_URL,keycloak.route.https.host=list-keep-keycloak-https$CLUSTER_URL
Здесь выполняется команда helm install list-keep ./
, и с помощью --set
ей задаются values
, которые в силу соображений безопасности и удобства не были установлены в самих чартах:
global.postgresql.auth.postgresPassword
- пароль пользователя-админа PostgreSQL.global.postgresql.auth.password
- пароль пользователя PostgreSQL БД keycloak.keycloak.auth.adminPassword
- пароль пользователя user от админки keycloak.С помощью
list-keep.env[1].name=KEYCLOAK_URL,list-keep.env[1].value=http://list-keep-keycloak-http$CLUSTER_URL
задаётся переменная окружения с адресом keycloak для Spring Boot приложения. Также пришлось здесь с помощьюlist-keep.env[0].name=SPRING_PROFILES_ACTIVE,list-keep.env[0].value=prod
задать переменную окружения для профиля, т.к. не смотря на то что данныеvalues
присутствуют вvalues.yaml
,install
чартов приводил к ошибке.list-keep-front.route.host=list-keep-front$CLUSTER_URL
- адресroute
для Vue.js приложения.keycloak.route.http.host=list-keep-keycloak-http$CLUSTER_URL
-http
route
для keycloak.keycloak.route.https.host=list-keep-keycloak-https$CLUSTER_URL
-https
route
для keycloak.
После успешного выполнения скрипта, перейдя в раздел Topology
в OpenShift Sandbox, вы увидите следующую картину:
В общем-то всё, осталось сконфигурировать keycloak, и можно будет приступить к эксплуатации данных приложений. Для настройки keycloak можно воспользоваться вот этой статьёй. Кроме того, в репозитории list-keep есть скрипт init.sh, который может помочь упросить данный процесс.
И ещё немного про keycloak
Важно, чтобы в настройках
realm
(в данном случае он называетсяlist-keep
), который используется для Spring Boot и Vue.js приложений, было указаноRequire SSL: none
, иначе, если указать в приложенияхhttp
адрес, то авторизоваться не получится (при использовании init.sh для конфигурацииrealm
используется файлrealm-export.json
, в котором это уже будет указано):
Важно: в настройках
client
(в данном случае он называетсяlist-keep
), который используется для Spring Boot и Vue.js приложений, нужно будет добавить значенияValid Redirect URIs
иWeb Origins
, которые будут соответствовать адресуroute
приложенияlist-keep-front
(при использовании init.sh для конфигурацииrealm
используется файлrealm-export.json
, в котором можно будет добавить соответствующие значения в массивыredirectUris
иwebOrigins
):
Важно: нужно добавить
http
адрес keycloak в Google Cloud Platform:
Проверим что приложения работают корректно. Для этого перейдём в list-keep-front
. Сделать это можно прямо из раздела Topology
:
Нас редиректит на страницу логина keycloak:
Можно, например, зайти через Google. Заходим, видим, что нам требуется подтвердить Email:
Подтверждаем и попадаем на страницу нашего Vue.js приложения list-keep-front
. На ней выводится логин/почта, полученный из Spring Boot приложения list-keep
. Что ж, все задеплоенные нами приложения корректно работают.
Заключение
Вот, в общем-то, и всё. Надеюсь, было интересно, полезно и не слишком нудно, и что на момент, когда вы читаете эту статью сервисы, которые в ней используются, не позаблокировали. В общем, берегите там себя и тех, кто бережёт вас. И удачи во всех ваших начинаниях.