Настраиваем CI/CD с GitHub Actions и werf: инструкция для новичков

В этой статье мы рассмотрим, как настроить пайплайн CI/CD в GitHub: подготовим репозиторий, зальём туда приложение, создадим файлы конфигурации GitHub Actions, в которых опишем, как собирать наше приложение и деплоить его в кластер Kubernetes, развёрнутый под управлением Deckhouse Kubernetes Platform. Деплоить будем с помощью Open Source CLI-утилиты werf. Она помогает организовать полный цикл доставки приложений в Kubernetes и рассматривает Git как единый источник истины для состояния развёрнутого приложения. Статья рассчитана на тех, кто только начинает свой путь в мире облаков и кластеризации.

Если вы хотите узнать, как настроить CI/CD в GitLab, рекомендуем нашу предыдущую статью.

3540f44b9727fb87b70d1743e420f9f0.png

Введение

GitHub — крупнейший веб-сервис и хостинг для проектов, использующих Git в качестве системы контроля версий. Он бесплатен для проектов с открытым исходным кодом и небольших частных проектов, но также предоставляет платные фичи для корпоративных подписчиков.

Сервис появился в 2008 году и довольно быстро завоевал популярность среди разработчиков. Сейчас на нём хостятся многие Open Source-проекты, а сами создатели называют его «социальная сеть для разработчиков».

GitHub Actions была представлена в 2019 году. Это с CI/CD-платформа, позволяющая настраивать пайплайны задач для сборки, публикации и деплоя проектов. Она помогает настроить действия на различные события в репозитории, такие как новые коммиты в главной ветке, навешивание тега на ветку или коммит или выполнение определённых задач после создания нового pull request’а или Issue.

В качестве среды исполнения GitHub предоставляет три вида виртуальных машин: Linux, macOS и Windows, — на них можно выполнять задачи из созданных пайплайнов. Кроме того, можно подключить и использовать собственный раннер.

Помимо хостинга исходных кодов, GitHub предоставляет удобные способы совместной работы над проектом, трекер ошибок, вики для публикации различной информации, репозитории пакетов и registry для Docker-контейнеров, а также настройку автоматических CI/CD, называемых GitHub Actions, которые позволяют настроить сборку и развёртывание приложения в кластере Kubernetes.

Исходные требования

Для работы нам понадобятся:

  • кластер Kubernetes под управлением Deckhouse (можно взять и обычный Kubernetes-кластер);

  • проект на GitHub с приложением, подготовленным к развёртыванию в кластере.

Для развёртывания кластера под управлением Deckhouse можно воспользоваться официальной документацией.

Подготовка репозитория на GitHub

Создание нового репозитория

Основой всего станет репозиторий на GitHub, в котором будут храниться код нашего приложения и всё необходимое для настройки CI/CD.

Если у вас ещё нет аккаунта на GitHub, зарегистрируйтесь в сервисе. Обратите внимание, что с марта 2023 года GitHub начал требовать обязательную двухфакторную аутентификацию и планирует ввести её для всех пользователей до конца года.

После регистрации и входа перед нами предстанет главный дашборд сервиса, на котором отображаются последние новости, ваши активные репозитории и события людей или компаний, на которых вы подписаны.

23806004cb09a7b210d11ad74b7b2881.png

У меня включена тёмная тема оформления. Другие темы можно выбрать и включить в настройках пользователя.

Создать новый репозиторий можно, нажав либо на кнопку «New» в списке репозиториев слева, либо на кнопку «New repository» в выпадающем меню справа вверху (меню выпадает по нажатию на кнопку с плюсиком).

53a5844b52fd8c0562c32986013615bb.png

После этого откроется окно создания нового репозитория:

d0c583a383e76f0dcae0feb21cdc3169.png

Здесь нужно задать несколько настроек:

  • Owner — владелец репозитория. Если вы не добавлены ни в какие организации, вариант будет один — ваш аккаунт.

  • Repository name — имя вашего репозитория. Оно должно быть ёмким, небольшим и не содержать пробелов (их лучше заменить на дефисы). Также это имя будет именем каталога, который появится у вас на компьютере после клонирования репозитория с GitHub. Если введённое имя уже занято, система сообщит об этом.

  • Description — краткое описание проекта, отображается на странице проекта в GitHub.

  • Public или Private — закрытый или открытый репозиторий. Открытый будет доступен всем, кто зайдёт на страничку проекта на GitHub, также исходные коды можно будет скачать даже без регистрации. Закрытый, соответственно, будет доступен только вам или тем, кому вы самостоятельно выдадите необходимые права доступа (также пользователям GitHub).

  • Add a README file — автоматическое создание README-файла в формате Markdown (*.md) для нового проекта. Создаёт в корне одноимённый файл, в котором можно написать разную полезную информацию, такую как название, описание, различные инструкции и так далее. Файлы README по умолчанию отображаются в отрендеренном виде внизу страницы под содержимым репозитория или его каталога.

  • Add .gitignore — добавляет файл .gitignore, в котором можно описать файлы, не попадающие в Git-репозиторий при фиксации изменения. Обычно туда добавляются расширения служебных файлов, остающихся после сборки проекта, директории с настройками IDE (например, .idea, .vscode) и так далее.

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

c7c18bfc55fd9a8ddcf04ef287456bc9.png

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

9e7e128ab6f0e55495980ff350dbb523.png

Выберем лицензию MIT, так как она нам подходит для тестов и наглядно продемонстрирует механизм работы этого пункта при создании репозитория.

Пункты добавления README, .gitignore и LICENSE необязательны. Эти файлы можно будет добавить после создания репозитория. Мы их заполняем, чтобы в репозитории были какие-то файлы и его можно было клонировать.

После заполнения всех полей остаётся нажать на кнопку «Create repository» в самом низу формы. Через несколько секунд репозиторий будет создан.

d0da7142016e96390caf3735b1d50355.png

Прямо в центре экрана отображаются файлы проекта, сейчас это только README, файл лицензии и .gitignore. В разделе «About» указаны краткое описание и статистика проекта, а внизу под содержимым — отрендеренное содержимое README-файла.

Подготовка приложения

В качестве приложения возьмём тестовый проект, который был разработан для статьи о настройке CI/CD в GitLab. В нём представлены два микросервиса — бэкенд и фронтенд, — собираемые в отдельные контейнеры и разворачиваемые в кластере Kubernetes.

Бэкенд написан на Go и предоставляет REST-интерфейс к простому API, а фронтенд представлен простым веб-приложением на Vue.js, принимающим и отправляющим запросы на нужные эндпоинты бэкенда.

Для начала клонируем созданный репозиторий себе на машину. Сделать это можно, выбрав один из вариантов в выпадающем меню под кнопкой «Code» в правом верхнем углу:

b436e298bdfb034495d4edd89f4b5b69.png

Если вы не настраивали SSH-доступ к вашему аккаунту, клонировать репозиторий можно будет только по HTTPS.

Перейдём на своей машине в тот каталог, где будем работать с проектом, и клонируем в него репозиторий:

$ git clone git@github.com:Zhbert/habr-action-tests.git
Cloning into 'habr-action-tests'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (5/5), done.

Не забудьте поменять адрес репозитория в команде на свой!

В каталоге появится ещё один каталог, имя которого совпадает с именем репозитория. Перейдём в него и посмотрим, что внутри:

$ cd habr-action-tests
$ ls -a
.               .git               .gitignore         LICENSE             README.md

Перенесём содержимое исходного проекта в новый. Для начала склонируем репозиторий с примерами приложений в тот же каталог, где уже лежит наш новый проект:

$ git clone git@github.com:flant/examples.git
Cloning into 'examples'...
remote: Enumerating objects: 1501, done.
remote: Counting objects: 100% (570/570), done.
remote: Compressing objects: 100% (327/327), done.
remote: Total 1501 (delta 333), reused 417 (delta 218), pack-reused 931
Receiving objects: 100% (1501/1501), 2.30 MiB | 3.78 MiB/s, done.
Resolving deltas: 100% (672/672), done.

Теперь скопируем содержимое в новый проект:

$ cp -r -i ./examples/2023/09-werf-deckhouse-gitlab/. ./habr-action-tests
overwrite ./habr-action-tests/./README.md? (y/n [n]) n
not overwritten
overwrite ./habr-action-tests/./.gitignore? (y/n [n]) y

Ключ -i говорит утилите cp о том, что при перезаписи файлов нужно сначала спросить, что делать. Файл README мы оставляем новый, а вот .gitignore берём из старого проекта.

Проверим, что в новом проекте появились файлы приложения:

$ ls -la habr-action-tests
total 48
drwxr-xr-x  13 zhbert  staff   416 14 ноя 15:46 .
drwxr-xr-x  22 zhbert  staff   704 14 ноя 15:45 ..
drwxr-xr-x   3 zhbert  staff    96 14 ноя 15:46 .configs
drwxr-xr-x  12 zhbert  staff   384 14 ноя 15:39 .git
-rw-r--r--   1 zhbert  staff   548 14 ноя 15:46 .gitignore
-rw-r--r--   1 zhbert  staff   577 14 ноя 15:46 .gitlab-ci.yml
drwxr-xr-x   3 zhbert  staff    96 14 ноя 15:46 .helm
-rw-r--r--   1 zhbert  staff  1076 14 ноя 15:39 LICENSE
-rw-r--r--   1 zhbert  staff    69 14 ноя 15:39 README.md
drwxr-xr-x   8 zhbert  staff   256 14 ноя 15:46 backend
-rw-r--r--   1 zhbert  staff  1149 14 ноя 15:46 docker-compose.yml
drwxr-xr-x  12 zhbert  staff   384 14 ноя 15:46 frontend
-rw-r--r--   1 zhbert  staff   139 14 ноя 15:46 werf.yaml

Всё нормально, проект перенесён. Осталось только зафиксировать изменения в новом репозитории:

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git restore ..." to discard changes in working directory)
	modified:   .gitignore

Untracked files:
  (use "git add ..." to include in what will be committed)
	.configs/
	.gitlab-ci.yml
	.helm/
	backend/
	docker-compose.yml
	frontend/
	werf.yaml

no changes added to commit (use "git add" and/or "git commit -a")
$  git add -A
$  git commit -s -m "Add project files"
[main 7344433] Add project files
 35 files changed, 12617 insertions(+)
 create mode 100644 .configs/nginx.conf
 create mode 100644 .gitlab-ci.yml
 create mode 100644 .helm/templates/database.yaml
 create mode 100644 .helm/templates/deployment-backend.yaml
 create mode 100644 .helm/templates/deployment-frontend.yaml
 create mode 100644 .helm/templates/ingress.yaml
 create mode 100644 .helm/templates/job-db-setup-and-migrate.yaml
 create mode 100644 .helm/templates/service-backend.yaml
 create mode 100644 backend/Dockerfile
 create mode 100644 backend/cmd/main.go
 create mode 100644 backend/db/migrations/000001_create_talkers_table.down.sql
 create mode 100644 backend/db/migrations/000001_create_talkers_table.up.sql
 create mode 100644 backend/go.mod
 create mode 100644 backend/go.sum
 create mode 100644 backend/internal/app/app.go
 create mode 100644 backend/internal/common/json_logger_filter.go
 create mode 100644 backend/internal/controllers/db_controllers.go
 create mode 100644 backend/internal/services/db_service.go
 create mode 100644 docker-compose.yml
 create mode 100644 frontend/.gitignore
 create mode 100644 frontend/Dockerfile
 create mode 100644 frontend/README.md
 create mode 100644 frontend/babel.config.js
 create mode 100644 frontend/jsconfig.json
 create mode 100644 frontend/package-lock.json
 create mode 100644 frontend/package.json
 create mode 100644 frontend/public/favicon.ico
 create mode 100644 frontend/public/index.html
 create mode 100644 frontend/src/App.vue
 create mode 100644 frontend/src/assets/logo.png
 create mode 100644 frontend/src/components/HelloWorld.vue
 create mode 100644 frontend/src/main.js
 create mode 100644 frontend/vue.config.js
 create mode 100644 werf.yaml
$ git push origin main
Enumerating objects: 56, done.
Counting objects: 100% (56/56), done.
Delta compression using up to 10 threads
Compressing objects: 100% (43/43), done.
Writing objects: 100% (54/54), 124.18 KiB | 614.00 KiB/s, done.
Total 54 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:Zhbert/habr-action-tests.git
   affe140..7344433  main -> main

Теперь исходники нашего нового проекта можно посмотреть на GitHub:

5b35f7e4483f372594a866982f511be4.png

Теперь можно настраивать автоматизацию!

Настройка сборки и деплоя

Цепочка действий, которые будут выполнены по срабатыванию определённого триггера, называется workflow. Они описываются в формате YAML и располагаются в каталоге .github/workflows  в корне проекта. Но перед написанием непосредственно файлов workflow необходимо выполнить небольшую настройку.

Конфигурация доступа к кластеру

Github запускает задачи workflow в своём облаке в преднастроенных виртуальных машинах, где уже установлена kubectl, а конфигурация доступа к кластеру задаётся вручную в интерфейсе системы.

Чтобы получить конфигурацию, перейдите по ссылке kubeconfig.your-deckhouse-cluster.com, войдите в веб-интерфейс с использованием полученных на этапе установки кластера логина и пароля пользователя, для которого будет выдаваться доступ, и перейдите на вкладку «Raw Config»:

4fbd7e73b4ae04cb2365554526613aca.png

Скопируйте содержимое конфига и выполните команду:

$ echo "" | base64
YXBpVmVyc2lvbjogdjEKa2luZDogQ29uZmlnCnByZWZlcmVuY2VzOiB7fQp1c2VyczoKLSBuYW1lOiBhZG1pbi1hcGkua3ViZS56aGJlcnQucnUKICB1c2VyOgogICAgYXV0aC1wcm92aWRlcjoKICAgICAgY29uZmlnOgogICAgICAgIGNsaWVudC1pZDoga3ViZWNvbmZpZy1nZW5lcmF0b3IKICAgICAgICBjbGllbnQtc2VjcmV0OiAxbTNvQlVXaFhSc082OEVmbmVEaAogICAgICAgIGlkLXRva2VuOiBleUpoYkdjaU9pSlNVekkxTmlJc0ltdHBaQ0k2SWpnMVpEQTNaRFpoTWpSa05EVTBaVEJtT1RoaE9XTXhaVGN4TWpOaE56UXpZbUUwTW1VNVlqUWlmUS5leUpwYzNNaU9pSm9kSFJ3Y3pvdkwyUmxlQzVyZFdKbExucG9ZbVZ5ZEM1eWRTOGlMQ0p6ZFdJaU9pSkZ

Полученную строку нужно добавить в секретную переменную в настройках проекта на GitHub. Для этого переходим в раздел »Settings»:

a61404d7df26db1ebfb7faac5d825d29.png

В левом меню раскрываем вкладку «Secrets and Variables» и выбираем раздел «Actions»:

c2be907aff4be9b455ec4d6337d78295.png

В открывшемся окне переходим на вкладку «Secrets» и нажимаем кнопку «New repository secret»:

ca29a7356b22a3363981ae2f8604bfb4.png

В появившемся окне вводим название новой переменной: KUBE_CONFIG_BASE64_DATA — и её содержимое (полученную ранее строку в Base64) в поля «Name» и «Value» соответственно:

64a87b0cad4afbd1d4286061666e9f06.png

Сохраняем переменную, нажав на кнопку «Add secret». Созданная переменная появится в разделе «Repository secrets» на предыдущей странице:

3d9ab1bcd0da572dc3033553672a8981.png

Доступ к кластеру настроен.

Включение container registry

Перед тем как подготавливать файлы workflow, нужно решить, где будут храниться собранные образы. Самый простой и быстрый путь — использовать встроенный в GitHub container registry. 

Доступ к нему будет предоставлен автоматически в рамках выполнения workflow Actions, а werf получит все необходимые параметры для подключения из переданных GitHub параметров окружения.

По умолчанию доступ к registry разрешён только на чтение, поэтому перейдём в раздел «Settings»«Actions»«General»:

b4a70e9de9bd5f7c827bb9c41d152c89.png

Листаем в самый низ страницы. Там в разделе «Workflow permissions» нужно поставить галочку напротив «Read and write permissions»:

d411aa01b32a1f8f78c8c332bfba3825.png

Теперь Actions смогут получить доступ к хранилищу в процессе выполнения и положить туда собранные werf образы.

Подготовка workflow

Создадим файл .github/workflows/production_deployment.yml со следующим содержимым:

name: Production Deployment
on:
  push:
    branches: [main]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v1.2
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=http://habrapp.example.com"

Разберём подробнее содержимое:

  • name — название пайплайна. Будет отображаться в интерфейсе GitHub.

  • on — триггер, по которому будет запускаться пайплайн. В текущем случае это любой коммит в главную ветку main.

  • jobs — задачи, которые будут выполняться в рамках пайплайна. Их может быть несколько, но у нас описана только одна: converge проекта в Kubernetes.

    • name — имя задачи;

    • runs-on — тип виртуальной машины, на которой будет выполняться задача, в нашем случае это последняя версия раннера с Ubuntu;

    • steps — шаги, которые будут выполняться в процессе.

Выполняемые шаги можно описывать как простыми командами или bash-скриптом, так и с помощью уже подготовленных наборов команд. Например, выгрузку актуальной версии кода из главной ветки в рабочий каталог на раннере можно выполнить с помощью actions/checkout@v4, предоставляемой самим GitHub. По умолчанию действие checkout выполняет клонирование только последнего коммита. Однако werf не сможет переиспользовать сборочный кэш без полной истории Git-репозитория. Действие checkout умеет выгружать полную историю, для этого указывается глубина выгрузки 0.

На следующем шаге также используется подготовленное действие Converge, предоставляемое разработчиками werf. В рамках этого шага на раннер будет установлена werf, а затем выполнены сборка, публикация и деплой проекта в кластер. Здесь дополнительно передаются два параметра: переменная env, указывающая werf на тип окружения, куда деплоится приложение, и kube-config-base64-data, в который попадает содержимое секрета KUBE_CONFIG_BASE64_DATA (закодированный в Base64 kubeconfig для подключения к кластеру).

Сохраняем созданный файл, коммитим его в Git и отправляем на сервер (git push).

Запуск workflow

Так как в файле workflow указано выполнение задач сразу же после коммита в главную ветку, после отправки изменений на GitHub сразу же запустится созданный пайплайн. При этом его статус будет отображаться над списком файлов рядом с хэш-суммой последнего коммита:

118dfd8912a2a3d906241c14162f3167.png

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

Если нажать на этот значок, отобразится окошко с более подробным списком выполняемых задач:

878dc9fa4cf3bfbfc7c8488ca3146cb3.png

Название каждой соответствует названию, заданному в файле. У нас это один пайплайн «Production Deployment». При нажатии на ссылку «Details» откроется страница с подробностями выполнения шагов:

61523bd3011f5899e88148afe82b5a72.png

В левой части экрана отображаются названия Jobs, описанных в файле. В правой — состояние шагов, входящих в каждую Job. Если выполнено всё удачно, то состояние отображается серой галочкой, если завершилось ошибкой — красным крестиком. Например, если бы выполнение шага деплоя завершилось ошибкой, состояние в интерфейсе выглядело бы так:

7307584776ed7e6b763645e7e8fc9889.png

При удачном завершении состояние будет отображено так:

8ee38c0fa40f4da0fd6d075665640b3b.png

Раскроем главный шаг — Converge — и посмотрим лог выполнения:

a78a01315629329758ad46d505970098.png

Здесь видно, что werf удачно собрал и развернул приложение в кластере.

Исходные коды приложения можно найти в репозитории.

Проверка

Перейдём на адрес приложения, указанный в Ingress:

fce5e9847c8ec668fabd4d966ce2fb82.png

Приложение развёрнуто и работает!

Заключение

Мы рассмотрели вариант настройки автоматического деплоя приложения в кластер Kubernetes с помощью инструментария, предоставляемого GitHub, — Actions. В нём мы реализовали автоматический деплой по любым изменениям в главной ветке репозитория.

P. S.

Полезные ссылки:

  • Самоучитель «Как организовать CI/CD и настроить работу с Kubernetes». Самоучитель разбит на несколько частей: с примерами реальных приложений на Node.js, Java Spring Boot, Django, Python, Go, Ruby on Rails, Laravel. В самоучителе можно пройти весь путь создания приложений и их развёртывания в production.

  • Чат werf, где на вопросы отвечает команда разработки утилиты — если что-то будет непонятно или пойдёт не так.

  • Документация GitHub Actions, а также чат про Github Actions, здесь могут помочь в случае проблем с созданием или запуском workflow.

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

© Habrahabr.ru