company_banner

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,6k
    • 6
    Флант
    275,00
    Специалисты по DevOps и Kubernetes
    Поделиться публикацией

    Комментарии 6

      +1
      После запуска Telepresence без опции --docker-run

      С опцией --docker-run всё ясно-понятно: из образа будет запущен контейнер, который заместит контейнер в кластере.
      А вот про «без опции» что-то не до конца въеду. Локальная машина будет якобы контейнером в кластере, так? Что будет, если таких команд несколько запустить, начнут мешать друг другу?

        +1
        Без опции, трафик будет проксироваться на локальный порт, указанный в deployment или при запуске telepresence. Если запускать telepresence в нескольких экземлярах без опции --docker-run, то нужно следить за пересечением портов
        0
        А gitkube или ksync не пробовали? Telepresence на фоне gitkube выглядит сложным, а в сравнении с ksync больше ручных действий надо делать.
        Ну а костыль с ssh в кубике — ЗЛО! Таких идей даже показывать не стоит :)
          0

          Я вот пока обходился при разработке docker-compose. Понятное дело, хорошо иметь локальное окружение максмально близкое к проду, но что-то решение не выглядит простым, с docker-compose все на порядок проще.

            0
            Telepresence удобная штука, когда, например, надо подцепиться дебаггером к микросервису. Тормозит все в этом случае жестко, конечно, но иногда это единственный выход.
              0

              В качестве сетевой альтернативы Telepresence можно рассмотреть https://github.com/txn2/kubefwd и совсем уж простой https://github.com/kayrus/kuttle

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое