Флант corporate blog
System administration
DevOps
Kubernetes
April 17

Kubernetes tips & tricks: о локальной разработке и Telepresence



Нас все чаще спрашивают про разработку микросервисов в Kubernetes. Разработчики, особенно интерпретируемых языков, хотят быстро поправить код в любимой IDE и без ожидания сборки/деплоя увидеть результат — по простому нажатию на F5. И когда речь шла про монолитное приложение, достаточно было локально поднять базу данных и веб-сервер (в Docker, VirtualBox…), после чего — сразу же наслаждаться разработкой. С распиливанием монолитов на микросервисы и приходом Kubernetes, с появлением зависимостей друг от друга, всё стало немного сложнее. Чем больше этих микросервисов, тем больше проблем. Чтобы вновь насладиться разработкой, нужно поднять уже не один и не два Docker-контейнера, а иногда — даже не один десяток… В общем, на всё это может уходить достаточно много времени, поскольку требуется ещё и поддерживать в актуальном состоянии.

В разное время мы пробовали разные решения проблемы. И начну я с накопленных workarounds или попросту «костылей».

1. Костыли


Большинство IDE имеет возможность править код прямо на сервере с помощью FTP/SFTP. Такой путь весьма очевиден и мы сразу решили им воспользоваться. Суть его сводится к следующему:

  1. В pod’е у окружений для разработки (dev/review) запускается дополнительный контейнер с доступом по SSH и пробросом публичного SSH-ключа того разработчика, что будет коммитить/деплоить приложение.
  2. На 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, потребуется смонтировать директорию с ключами (https://www.telepresence.io/howto/volumes). Для 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:

+39
5.9k 64
Comments 6
Top of the day