kubernetes, playground, микросервисы и немного магии

в 7:06, , рубрики: devops, kubernetes, playground

В жизни любого DevOps-инженера возникает необходимость создать playground для команды разработки. Как всегда он должен быть умным, шустрым и потреблять минимальное количество ресурсов. В этой статье я хочу рассказать о том, как я решал проблему создания такого зверя для микросервисного приложения на kubernetes.

kubernetes, playground, микросервисы и немного магии - 1

Входящие условия и требования

Немного о том, что представляет из себя система для которой необходимо было создать playground:

  • Kubernetes, bare-metal кластер;
  • Простой api-шлюз на базе nginx;
  • MongoDB в качестве БД;
  • Jenkins в качестве CI-сервера;
  • Git на Bitbucket;
  • Два десятка микросервисов, которые могут общаться между собой (через api-шлюз), с базой и с пользователем.

kubernetes, playground, микросервисы и немного магии - 2

Требования, которые мы смогли сформулировать при активном общении с тимлидом:

  • Минимизация потребления ресурсов;
  • Минимизация изменений в коде сервисов для работы на playground;
  • Возможность параллельной разработки нескольких сервисов;
  • Возможность разработки нескольких сервисов в одном пространстве;
  • Возможность демонстрации изменений заказчикам до деплоя на staging;
  • Все разрабатываемые сервисы могут работать с одной БД;
  • Минимизация усилий разработчика для разворачивания тестируемого кода.

Размышления на тему

С самого начала было ясно, что наиболее логичным для создания параллельных пространств в k8s логичнее всего использовать родной инструмент виртуальных кластеров, или в терминологии k8s — namespaces. Задачу, так же, упрощает тот факт, что все взаимодействия внутри кластера производятся по коротким именам предоставляемым kube-dns, что означало, что запуск структуры можно произвести в отдельном namespace без потери связности.
У данного решения есть только одна проблема — необходимость разворачивать в namespace все имеющиеся сервисы, что долго, неудобно и потребляет большое количество ресурсов.

Namespace и DNS

При создании любого сервиса k8s создаёт DNS-запись вида <service-name>.<namespace-name>.svc.cluster.local. Данный механизм позволяет общение через короткие имена внутри одного namespace благодаря изменениям вносимым в resolv.conf каждого запускаемого контейнера.

В обычном состоянии он выглядит вот так:
search <namespace-name>.svc.cluster.local svc.cluster.local cluster.local
nameserver 192.168.0.2
options ndots:5

Т.е к сервису в том же namespace можно обратится по имени <service-name>, в соседних namespace по имени <service-name>.<namespace-name>

Обходим систему

В этот момент в голову приходит простая мысль "База общая, маршрутизацией запросов к сервисам занимается api-шлюз, почему бы не заставить его ходить сначала к сервису в своём namespace, а в случае его отсутствия в default?"
Да, подобное решение можно было организовать настройками namespace (мы же помним, что это nginx), но подобное решение вызовет разницу в настройках на pg и на прочих кластерах, что неудобно и может вызвать ряд проблем.

Так что, был выбран метод замены строки
search <namespace-name>.svc.cluster.local svc.cluster.local cluster.local
На
search <namespace-name>.svc.cluster.local svc.cluster.local cluster.local default.svc.cluster.local
Такой подход обеспечит автоматический переход в namespace default при отсутствии необходимого сервиса в своём namespace.

kubernetes, playground, микросервисы и немного магии - 3

Подобного результата можно добиться в кластере следующим образом. Kubelet добавляет параметры search в контейнер из resolve.conf хост-машины, так что достаточно просто дописать в /etc/resolv.conf каждой ноды строку:

search default.svc.cluster.local

Если же вы не желаете, чтобы ноды ресолвили адреса сервисов, то можно использовать параметр --resolv-conf при запуске kubelet, что позволит указать любой другой файл вместо /etc/resolv.conf. Например файл /etc/k8s/resolv.conf с той же строкой.

Дело техники

Дальнейшее решение достаточно просто, нужно, только, принять следующие соглашения:

  • Новые фичи разрабатываются в отдельных ветках вида play/<feature-name>
  • Для работы с несколькими сервисами в рамках одной фичи названия веток должны совпадать в репозиториях всех задействованных сервисов.
  • Всю работу по деплою выполняет Jenkins автоматически
  • Для тестов фича-ветки доступны по адресу <feature-name>.cluster.local

Настройки ssl-offloader

Конфиг nginx для перенаправления запросов к api-gw в соответствующих namespace

 server_name ~^(?<namespace>.+).cluster.local;
location / {
  resolver 192.168.0.2;
  proxy_pass http://api-gw.$namespace.svc.cluster.local;
}

Jenkins

Для автоматизации процесса развёртывания используется плагин Jenkins Pipeline Multibranch Plugin.

В настройках проекта указываем собирать только ветки соответствующие шаблону play/* И добавляем Jenkinsfile в корень всех проектов, с которыми будет работать сборщик.
Для обработки используется groovy-скрипт, целиком приводить его не буду, только пара примеров. Остальной деплой принципиально ничем не отличается от обычного.

Получение имени ветки:

def BranchName() {
    def Name = "${env.BRANCH_NAME}" =~ "play[/]?(.*)"
    Name ? Name[0][1] : null
}

Минимальная конфигурация namespace требует развёрнутого api-шлюза, поэтому добавляем вызов проекта создающего namespace и разворачивающего в него api-шлюз:

 def K8S_NAMESPACE = BranchName()
 build job: 'Create NS', parameters: [[$class: 'StringParameterValue', name: 'K8S_NAMESPACE', value: "${K8S_NAMESPACE}"]]
 build job: 'Create api-gw', parameters: [[$class: 'StringParameterValue', name: 'K8S_NAMESPACE', value: "${K8S_NAMESPACE}"]]

Заключение

Серебряной пули не существует, но мне так и не удалось найти не только best practices, но и описаний того, как организованы песочницы у других, поэтому решил поделиться методом, который использовал при создании песочницы на базе k8s. Возможно это не идеальный способ, так что с радостью приму замечания или рассказы о том, как данная проблема решена у вас.

Автор: demonight

Источник

Поделиться

* - обязательные к заполнению поля