Нас все чаще спрашивают про разработку микросервисов в Kubernetes. Разработчики, особенно интерпретируемых языков, хотят быстро поправить код в любимой IDE и без ожидания сборки/деплоя увидеть результат — по простому нажатию на F5. И когда речь шла про монолитное приложение, достаточно было локально поднять базу данных и веб-сервер (в Docker, VirtualBox…), после чего — сразу же наслаждаться разработкой. С распиливанием монолитов на микросервисы и приходом Kubernetes, с появлением зависимостей друг от друга, всё стало немного сложнее. Чем больше этих микросервисов, тем больше проблем. Чтобы вновь насладиться разработкой, нужно поднять уже не один и не два Docker-контейнера, а иногда — даже не один десяток… В общем, на всё это может уходить достаточно много времени, поскольку требуется ещё и поддерживать в актуальном состоянии.
В разное время мы пробовали разные решения проблемы. И начну я с накопленных workarounds или попросту «костылей».
1. Костыли
Большинство IDE имеет возможность править код прямо на сервере с помощью FTP/SFTP. Такой путь весьма очевиден и мы сразу решили им воспользоваться. Суть его сводится к следующему:
- В pod’е у окружений для разработки (dev/review) запускается дополнительный контейнер с доступом по SSH и пробросом публичного SSH-ключа того разработчика, что будет коммитить/деплоить приложение.
- На init-стадии (в рамках контейнера
prepare-app
) переносим код вemptyDir
, чтобы иметь доступ к коду из контейнеров с приложением и SSH-сервера.
Для лучшего понимания технической реализации такой схемы приведу фрагменты задействованных YAML-конфигураций в Kubernetes.
Конфигурации
1.1. values.yaml
ssh_pub_key:
vasya.pupkin: <ssh public key in base64>
Здесь
vasya.pupkin
— это значение переменной ${GITLAB_USER_LOGIN}
.1.2. deployment.yaml
...
{{ if eq .Values.global.debug "yes" }}
volumes:
- name: ssh-pub-key
secret:
defaultMode: 0600
secretName: {{ .Chart.Name }}-ssh-pub-key
- name: app-data
emptyDir: {}
initContainers:
- name: prepare-app
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
volumeMounts:
- name: app-data
mountPath: /app-data
command: ["bash", "-c", "cp -ar /app/* /app-data/" ]
{{ end }}
containers:
{{ if eq .Values.global.debug "yes" }}
- name: ssh
image: corbinu/ssh-server
volumeMounts:
- name: ssh-pub-key
readOnly: true
mountPath: /root/.ssh/authorized_keys
subPath: authorized_keys
- name: app-data
mountPath: /app
ports:
- name: ssh
containerPort: 22
protocol: TCP
{{ end }}
- name: backend
volumeMounts:
{{ if eq .Values.global.debug "yes" }}
- name: app-data
mountPath: /app
{{ end }}
command: ["/usr/sbin/php-fpm7.2", "--fpm-config", "/etc/php/7.2/php-fpm.conf", "-F"]
...
1.3. secret.yaml
{{ if eq .Values.global.debug "yes" }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .Chart.Name }}-ssh-pub-key
type: Opaque
data:
authorized_keys: "{{ first (pluck .Values.global.username .Values.ssh_pub_key) }}"
{{ end }}
Финальный штрих
После этого останется только передать нужные переменные gitlab-ci.yml:
dev:
stage: deploy
script:
- type multiwerf && source <(multiwerf use 1.0 beta)
- type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
- werf deploy
--namespace ${CI_PROJECT_NAME}-stage
--set "global.env=stage"
--set "global.git_rev=${CI_COMMIT_SHA}"
--set "global.debug=yes"
--set "global.username=${GITLAB_USER_LOGIN}"
tags:
- build
Вуаля: разработчик, запустивший деплой, может подключиться по имени сервиса (как безопасно выдавать доступы к кластеру, мы уже рассказывали) со своего десктопа по SFTP и править код без ожидания его доставки в кластер.
Это вполне рабочее решение, однако с точки зрения реализации имеет очевидные минусы:
- необходимость в доработке Helm-чарта, что в дальнейшем затрудняет его чтение;
- использовать может только тот, кто задеплоил сервис;
- нужно не забыть потом синхронизировать его с локальной директорией с кодом и коммитнуть в Git.
2. Telepresence
Проект Telepresence известен достаточно давно, однако всерьёз попробовать его на деле у нас, что называется, «не доходили руки». Однако спрос сделал своё дело и теперь мы рады поделиться опытом, который может оказаться полезным читателям нашего блога — тем более, что на хабре до сих пор не было других материалов про Telepresence.
Если вкратце, то всё оказалось не так страшно. Все действия, которые требуют выполнения со стороны разработчика, мы разместили в текстовом файле Helm-чарта, названном
NOTES.txt
. Таким образом, разработчик после деплоя сервиса в Kubernetes видит инструкцию по запуску локального dev-окружения в логе job’а GitLab:!!! Разработка сервиса локально, в составе Kubernetes !!!
* Настройка окружения
* * Должен быть доступ до кластера через VPN
* * На локальном ПК установлен kubectl ( https://kubernetes.io/docs/tasks/tools/install-kubectl/ )
* * Получить config-файл для kubectl (скопировать в ~/.kube/config)
* * На локальном ПК установлен telepresence ( https://www.telepresence.io/reference/install )
* * Должен быть установлен Docker
* * Необходим доступ уровня reporter или выше к репозиторию https://gitlab.site.com/group/app
* * Необходимо залогиниться в registry с логином/паролем от GitLab (делается один раз):
#########################################################################
docker login registry.site.com
#########################################################################
* Запуск окружения
#########################################################################
telepresence --namespace {{ .Values.global.env }} --swap-deployment {{ .Chart.Name }}:backend --mount=/tmp/app --docker-run -v `pwd`:/app -v /tmp/app/var/run/secrets:/var/run/secrets -ti registry.site.com/group/app/backend:v8
#########################################################################
Не будем подробно останавливаться на описанных в этой инструкции шагах… за исключением последнего. Что же происходит во время запуска Telepresence?
Работа с Telepresence
При старте (по последней команде, указанной в инструкции выше) мы задаём:
- пространство имён (namespace), в котором запущен микросервис;
- имена deployment’а и контейнера, в который хотим проникнуть.
Остальные аргументы опциональны. Если наш сервис взаимодействует с Kubernetes API и для него создан ServiceAccount, нам необходимо смонтировать сертификаты/токены на свой десктоп. Для этого используется опция
--mount=true
(или --mount=/dst_path
), которая смонтирует корень (/) из контейнера в Kubernetes к нам на desktop. После этого мы можем (в зависимости от ОС и способа запуска приложения) воспользоваться «ключами» от кластера.В начале рассмотрим самый универсальный вариант запуска приложения — в Docker-контейнере. Для этого воспользуемся ключом
--docker-run
и примонтируем директорию с кодом в контейнер: -v `pwd`:/app
Обратите внимание, что здесь подразумевается запуск из каталога с проектом. Код приложения будет смонтирован в директорию
/app
в контейнере.Далее:
-v /tmp/app/var/run/secrets:/var/run/secrets
— для монтирования директории с сертификатом/токеном в контейнер.За этой опцией следует, наконец, образ, в котором будет запускаться приложение. NB: При сборке образа надо обязательно указать
CMD
или ENTRYPOINT
!Что же, собственно, произойдет дальше?
- В Kubernetes для указанного Deployment’а количество реплик будет изменено на 0. Вместо него запустится новый Deployment — с подменённым контейнером
backend
. - На десктопе запустятся 2 контейнера: первый — с Telepresence (он будет осуществлять проксирование запросов из/в Kubernetes), второй — с разрабатываемым приложением.
- Если exec’нуться в контейнер с приложением, то нам будут доступны все ENV-переменные, переданные Helm’ом при деплое, а также доступны все сервисы. Остаётся только править код в любимой IDE и наслаждаться результатом.
- В конце работы достаточно просто закрыть терминал, в котором запущен Telepresence (оборвать сессию по Ctrl+C), — на десктопе остановятся Docker-контейнеры, а в Kubernetes все вернётся в начальное состояние. Останется лишь коммитнуть, оформить MR и передать его на review/merge/… (в зависимости от ваших рабочих процессов).
В случае, если мы не хотим запускать приложение в Docker-контейнере — например, мы разрабатываем не на PHP, а на Go, и всё-таки собираем его локально, — запуск Telepresence будет ещё проще:
telepresence --namespace {{ .Values.global.env }} --swap-deployment {{ .Chart.Name }}:backend --mount=true
Если приложение обращается к Kubernetes API, потребуется смонтировать директорию с ключами. Для Linux есть утилита proot:
proot -b $TELEPRESENCE_ROOT/var/run/secrets/:/var/run/secrets bash
После запуска Telepresence без опции
--docker-run
все переменные окружения будут доступны в текущем терминале, поэтому запуск приложения необходимо делать именно в нём. NB: При использовании, например, PHP, нужно не забывать отключать для разработки различные op_cache, apc и прочие акселераторы — иначе правка кода не будет приводить к желаемому результату.
Итоги
Локальная разработка с Kubernetes — проблема, потребность в решении которой растёт пропорционально распространению этой платформы. Получая соответствующие запросы со стороны разработчиков (от наших клиентов), мы начали их решать первыми доступными средствами, которые, однако, не зарекомендовали себя на длинной дистанции. Благо, это стало очевидно не только сейчас и не только нам, поэтому в мире уже появились более подходящие средства, и Telepresence — самое известное из них (к слову, есть ещё skaffold от Google). Наш опыт его использования ещё не так велик, но уже даёт основания рекомендовать «коллегам по цеху» — попробуйте!
P.S.
Другое из цикла K8s tips & tricks:
- «Инструменты для разработчиков приложений, запускаемых в Kubernetes»;
- «Kubernetes tips & tricks: персонализированные страницы ошибок в NGINX Ingress»;
- «Перевод работающих в кластере ресурсов под управление Helm 2»;
- «О выделении узлов и о нагрузках на веб-приложение»;
- «Доступ к dev-площадкам»;
- «Ускоряем bootstrap больших баз данных».