- PVSM.RU - https://www.pvsm.ru -

Сборка и деплой однотипных микросервисов с werf и GitLab CI

Сборка и деплой однотипных микросервисов с werf и GitLab CI - 1

Два года назад мы публиковали статью «Сборка проектов с GitLab CI: один .gitlab-ci.yml для сотни приложений [1]», а теперь расскажем о решении схожей задачи сегодня. Новый материал — о том, как можно построить CI/CD-процессы для большого количества однотипных приложений с появлением include в .gitlab-ci.yml и приходом werf на замену dapp.

Вводные

В дальнейших инструкциях, приведенных в статье, рассматривается следующая ситуация:

  • Есть большое клиентское приложение, которое разбито на множество репозиториев.
  • Каждый репозиторий представляет собой отдельное приложение, которое необходимо запускать в Kubernetes-кластере.
  • В качестве CI-системы используется GitLab CI.
  • Деплой (инфраструктура, в которую разворачивается код) описывается Helm-чартами.
  • Сборка образов и деплой в Kubernetes осуществляется с помощью werf [2].

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

Понятно, что копирование .gitlab-ci.yml, werf.yaml и .helm приносит множество проблем. Ведь любая правка в CI, сборочный процесс или описание Helm-чарта должна быть добавлена и в остальные репозитории…

Подключение шаблонов в .gitlab-ci.yml

С появлением в GitLab CE директивы include:file (с версии 11.7 [3]) стало возможным делать общий CI. Сам include появился несколько ранее (в 11.4), но он позволял подключать шаблоны только с публичных URL, что несколько ограничивало его функциональность. В документации GitLab прекрасно описаны [4] все возможности и примеры использования.

Таким образом удалось отказаться от копирования .gitlab-ci.yml между репозиториями и поддержки его актуальности. Вот пример .gitlab-ci.yml с include:

include:
 - project: 'infra/gitlab-ci'
   ref: 1.0.0
   file: base-gitlab-ci.yaml
 - project: 'infra/gitlab-ci'
   ref: 1.0.0
   file: cleanup.yaml

Настоятельно рекомендуем с осторожностью использовать имена веток в ref. Include’ы вычисляются на момент создания pipeline, поэтому ваши изменения по CI могут автоматически попасть в production pipeline в самый неподходящий момент. А вот использование в ref тегов позволяет легко версионировать описание CI/CD-процессов. При обновлении всё выглядит максимально прозрачно и можно с легкостью отследить историю изменения версий pipeline, если использовать семантическое версионирование для тегов.

Подключение .helm из отдельного репозитория

Поскольку рассматриваемые микросервисы деплоятся и запускаются одинаково, необходим одинаковый набор Helm-шаблонов. Чтобы избежать копирования каталога .helm между репозиториями, раньше мы выполняли клонирование репозитория, в котором хранились Helm-шаблоны и делали checkout на нужный тег. Выглядело это примерно так:

   - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.example.com/infra/helm.git .helm
   - cd .helm && git checkout tags/1.0.0
   - type multiwerf && source <(multiwerf use 1.0 beta)
   - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
   - werf deploy --stages-storage :local

Были также вариации с использованием git submodules, но всё это больше похоже на обходной путь…

И вот с недавним релизом werf у него появилась возможность [5] подключать чарты из внешних репозиториев. Полноценная поддержка функций пакетного менеджера в свою очередь позволила прозрачно описать зависимости для деплоя приложения.

Последовательность действий

Вернёмся к решению нашей задачи с микросервисами. Поднимем свой репозиторий для хранения Helm-чартов — например, ChartMuseum [6]. Он легко разворачивается в кластере Kubernetes:

helm repo add stable https://kubernetes-charts.storage.googleapis.com
helm install stable/chartmuseum --name flant-chartmuseum

Добавим ingress:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/force-ssl-redirect: "false"
    nginx.ingress.kubernetes.io/proxy-body-size: 10m
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
  name: chart-museum
spec:
  rules:
  - host: flant-chartmuseum.example.net
    http:
      paths:
      - backend:
          serviceName: flant-chartmuseum
          servicePort: 8080
        path: /
status:
  loadBalancer: {}

Deployment'у flant-chartmuseum необходимо поменять переменную окружения DISABLE_API на значение false. В ином случае (по умолчанию) запросы к API ChartMuseum не будут работать и нельзя будет создавать новые чарты.

Теперь опишем репозиторий, в котором будут храниться общие Helm-чарты. Структура его каталогов — следующая:

.
├── charts
│   └── yii2-microservice
│       ├── Chart.yaml
│       └── templates
│           ├── app.yaml
└── README.md

Chart.yaml может выглядеть следующим образом:

name: yii2-microservice
version: 1.0.4

В каталоге templates должны находиться все необходимые примитивы Kubernetes’а, которые понадобятся для деплоя приложения в кластер. Как вы уже, возможно, догадались, в данном случае микросервис представляет собой PHP-приложение на основе фреймворка yii2. Опишем его минимальный Deployment с двумя контейнерами nginx и php-fpm, которые собираются с помощью werf:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.global.werf.name }}
spec:
  replicas: 1
  revisionHistoryLimit: 3
  template:
    metadata:
      labels:
        service: {{ .Values.global.werf.name }}
    spec:
      imagePullSecrets:
      - name: registrysecret
      containers:
      - name: backend
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
        command: [ '/usr/sbin/php-fpm7', "-F" ]
        ports:
        - containerPort: 9000
          protocol: TCP
          name: http
        env:
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
      - name: frontend
        command: ['/usr/sbin/nginx']
{{ tuple "frontend" . | include "werf_container_image" | indent 8 }}
        ports:
        - containerPort: 80
          name: http
        lifecycle:
          preStop:
            exec:
              command: ["/usr/sbin/nginx", "-s", "quit"]
        env:
{{ tuple "frontend" . | include "werf_container_env" | indent 8 }}
---
apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.global.werf.name }}
spec:
  selector:
    service: {{ .Values.global.werf.name }}
  ports:
  - name: http
    port: 80
    protocol: TCP

Переменная .Values.global.werf.name содержит название проекта из файла werf.yaml, что позволяет получить необходимые имена сервисов и Deployment’ов.

Сделаем простейшую автоматизацию для push’а в ChartMuseum наших чартов при коммите в master-ветку. Для этого опишем .gitlab-ci.yml:

Build and push to chartmuseum:
 script:
 - for i in $(ls charts); do helm package "charts/$i"; done;
 - for i in $(find . -type f -name "*.tgz" -printf "%fn"); do curl --data-binary "@$i" http://flant-chartmuseum.example.net/api/charts; done;
 stage: build
 environment:
   name: infra
 only:
   - master
 tags:
   - my-shell-runner-tag

Версионирование чартов осуществляется с помощью изменения version в Chart.yaml. Все новые чарты автоматически будут добавлены в ChartMuseum.

Выходим на финишную прямую! В репозитории проекта в .helm/requirements.yaml прописываем зависимости для чарта:

dependencies:
- name: yii2-microservice
 version: "1.0.4"
 repository: "@flant"

… и выполняем в директории с репозиторием:

werf helm repo init
werf helm repo add flant http://flant-chartmuseum.example.net
werf helm dependency update

Получаем в ней .helm/requirements.lock. Теперь для деплоя приложения в кластер достаточно выполнять команду werf helm dependency build перед запуском werf deploy.

Для обновления описания деплоя приложения теперь необходимо пройтись по репозиториям с микросервисами и наложить небольшие патчи c изменениями хешей и тегов в requirements.yaml и requirements.lock. Данную операцию при желании также можно автоматизировать через CI: как это сделать, мы уже рассказывали в упомянутой статье [1].

Заключение

Надеюсь, описанная последовательность действий для обслуживания однотипных приложений окажется полезной инженерам, что сталкиваются с похожими проблемами. А мы будем рады поделиться и другими практическими рецептами использования werf [7]. Поэтому, если у вас есть сложности, кажущиеся непреодолимыми или просто непонятными в реализации, — смело выходите на связь в Telegram [8] или оставляйте здесь в комментариях запросы на будущие материалы.

P.S.

Читайте также в нашем блоге:

Автор: konstantin_axenov

Источник [12]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/gitlab/332349

Ссылки в тексте:

[1] Сборка проектов с GitLab CI: один .gitlab-ci.yml для сотни приложений: https://habr.com/ru/company/flant/blog/340996/

[2] werf: https://github.com/flant/werf

[3] с версии 11.7: https://about.gitlab.com/2019/01/22/gitlab-11-7-released/#include-cicd-files-from-other-projects-and-templates

[4] прекрасно описаны: https://docs.gitlab.com/ce/ci/yaml/#includefile

[5] появилась возможность: https://habr.com/ru/company/flant/blog/468049/

[6] ChartMuseum: https://github.com/helm/chartmuseum

[7] werf: https://werf.io/

[8] Telegram: https://t.me/werf_ru

[9] GitLab CI для непрерывной интеграции и доставки в production. Часть 1: наш пайплайн: https://habr.com/ru/company/flant/blog/332712/

[10] Поддержка monorepo и multirepo в werf и при чём здесь Docker Registry: https://habr.com/ru/company/flant/blog/465131/

[11] Собирать Docker-образы в werf теперь можно и по обычному Dockerfile: https://habr.com/ru/company/flant/blog/463613/

[12] Источник: https://habr.com/ru/post/469541/?utm_source=habrahabr&utm_medium=rss&utm_campaign=469541