Перекладаючи статтю "Автоматизація (CI/CD) та зворотний проксі для Docker Elastic Beanstalk з GitHub Actions" (/@wellington.grisa/automate-ci-cd-and-reverse-proxy-a-docker-elastic-beanstalk-up-with-github-actions-346f15e28180), я хотів поглибити розуміння, щоб отримати переваги від використання як Lerna, так і GitHub Actions.
Ця стаття буде поділена на:
- налаштування Lerna
- налаштування commitlint та husky для забезпечення використання конвенційних комітів (ми побачимо переваги цього в статті)
- підготовка різних робочих процесів для повного використання GitHub Actions (при відкритті pull request, при створенні тегу, після злиття, після завершення одного з робочих процесів)
- використання тегів GitHub разом з Lerna для версій в ECR
- включення нових скриптів для динамічного розгортання пакунків
Налаштування Lerna
Це дуже просто: npx lerna init — independent
. Це створить файл lerna.json
і встановить пакет Lerna в ваші залежності для розробки.
Налаштування commitlint, husky та використання конвенційних комітів
Ідея commitlint полягає в тому, щоб забезпечити використання конвенційних комітів, і ми побачимо переваги цього пізніше.
Для цього виконайте команду: yarn add @commitlint/config-lerna-scopes @commitlint/cli @commitlint/config-conventional -D -W
.
Створіть файл у кореневій папці commitlint.config.ts
і помістіть у нього наступний вміст (якщо ви використовуєте TypeScript):
import type { UserConfig } from "@commitlint/types";
const Configuration: UserConfig = {
extends: [
"@commitlint/config-conventional",
"@commitlint/config-lerna-scopes",
],
};
module.exports = Configuration;
Наступний крок - налаштування husky
, щоб забезпечити виконання комітів відповідно до конвенцій. Виконайте:
- yarn add husky -D -W
- npx husky add .husky/commit-msg ‘npx — no — commitlint — edit ${1}’
Це створить папку.husky
з файломcommit-msg
, який налаштований для використання](https://github.com/well-doing/docker-elastic-beanstalk-up/blob/main/.husky/commit-msg)commitlint
для ваших комітів.
Це означає, що відтепер ваші коміти повинні бути виконані згідно з конвенцією, наприклад: fix(api): fix bug
. Використання Lerna scopes також дозволяє перевіряти пакунки в Lerna. Коли у вас є коміт для кількох пакунків, ви можете використати, наприклад, fix(api,ui,models): update buggy package version
. Згодом ми побачимо цікаву функцію Lerna при використанні таких конвенційних комітів.
Додаткові GitHub Workflows
checks.yml
Тепер, коли ми налаштували Lerna і забезпечили виконання комітів, час почати використовувати Lerna. Одна з особливостей Lerna полягає в тому, що вона може визначити, які пакунки змінилися з моменту, що дозволяє скористатися цією перевагою разом із checks.yml
(назва не має значення, важливий момент - робочий процес, який працює при відкритті pull request) у GitHub workflows.
on:
pull_request:
types: [opened, synchronize]
Ідея полягає в тому, щоб запускати тести щоразу, коли відкривається або синхронізується pull request. У нашому репозиторії ми створили просто echo, який імітує тести для кожного пакету, ось так:
"scripts": {
// ...інші команди
"test": "echo api TESTS PASSED"
},
Цього достатньо, щоб перевірити функцію лише запуску пакунків, які змінилися з моменту останнього разу. Створіть файл checks.yml
в .github/workflows
, який відповідатиме за кожен pull request.
Це цікаво, оскільки ви можете налаштувати гілку так, щоб її можна було зливати лише після успішного проходження перевірки.
Зображення нижче показує, як це буде виглядати у вашому PR.
Як зазначено в коміті, були запущені тільки тести для ui
Ви не можете злитись, поки перевірки не будуть успішно завершені
version.yml
Перейдемо до наступної функції Lerna, version. Ця команда визначає зміни та запитує, яка версія - patch, minor або major. З цього моменту ця команда почне створювати нові теги у вашому GitHub репозиторії. Однак з цього моменту ми почнемо отримувати переваги від наших конвенційних комітів, semantic-release, Lerna та yarn workspaces разом. Як згадувалося раніше, ідея полягає в тому, що як тільки ви почнете комітити свій код, скажімо, ви комітували виправлення для ui
, ви повинні вказати це:
fix(ui): fix ui's bug
Зробивши це, Lerna виявляє, що для api
є оновлення, яке потрібно опублікувати, і може обробити це автоматично. Ось чому давайте додамо наш скрипт версії для запуску в робочому процесі при злитті PR. Важливі частини цього робочого процесу:
name: Version
on:
pull_request:
types: [closed]
branches: ["main"]
jobs:
version:
if: ${{ github.event.pull_request.merged == true }}
# ...
steps:
- name: Version
uses: actions/checkout@v4
with:
token: ${{ secrets.DOCKER_ELASTIC_BEANSTALK_VERSION_UP }}
fetch-depth: 0
- run: |
yarn
# ... other commands
yarn lerna version --conventional-commits --no-commit-hooks --yes
- Умови if гарантують, що команда буде виконана тільки при злитті
- токен, вказаний у кроці checkout, важливий, оскільки скрипт
yarn lerna version
створить теги у вашому репозиторії, і наша ідея тут - використовувати інший робочий процес під час створення тегу (це стане зрозумілим, ми побачимо це в наступному робочому процесі). Але для того, щоб один робочий процес викликав інший в межах себе, необхідно створити PAT (Personal Access Token), це запобігає випадковому створенню рекурсивних запусків робочих процесів - fetch-depth 0 також важливий, щоб Lerna могла отримати історію репозиторію та здійснити необхідні порівняння для виявлення змін у пакунках
- команда Lerna version з параметрами
--no-commit-hooks
та--yes
, оскільки Lerna зробить коміт з іншим шаблоном, ніж наші повідомлення комітів для semantic release (ви можете змінити шаблон повідомлення коміту та додати щось на зразокchore: publish
у вашому lerna.json або додати інший параметр до вашої команди version)
deploy.yml
Це дійсно цікавий робочий процес, який буде виконуватись під час створення тегу, тому важливі частини:
name: Build
on:
push:
tags: "**"
jobs:
build:
if: ${{ (contains(github.ref, 'ui') || contains(github.ref, 'api')) }}
steps:
# ...
- name: Відправка Docker образу для API
env:
REGISTRY: ${{ steps.configure-aws.outputs.ecr-registry }}
REPOSITORY: docker-elastic-beanstalk-up
run: yarn push-docker
- назва робочого процесу буде використовуватись пізніше, у цьому випадку ми називаємо його Build
- параметр on налаштовує кожен тег, який буде відправлений, тому кожного разу, коли тег відправляється (у нашому випадку після команди
yarn lerna version
), цей робочий процес буде виконуватись - оскільки в нашому проекті є файли Docker для
ui
іapi
, ми забезпечуємо, що цей робочий процес буде виконуватись лише тоді, коли ці пакунки будуть теговані, а не коли тегуєтьсяmodels
- однак найважливішим кроком в цьому робочому процесі є скрипт, який викликається через
yarn push-docker
Цей скрипт містить версію застосунку, і його буде легко реалізувати кількома рядками, використовуючи змінні середовища:
PACKAGE_NAME="${GITHUB_REF_NAME%%@*}" # отримує api або ui
VERSION="${GITHUB_REF_NAME##*@}" # отримує версію
TAG=$PACKAGE_NAME-$VERSION # будуємо щось на зразок, api-0.0.1
build_docker_image() {
echo "Будую docker образ $REGISTRY/$REPOSITORY:$TAG"
docker build -t $REGISTRY/$REPOSITORY:$TAG -f packages/$PACKAGE_NAME.Dockerfile .
}
push_docker_image_to_ecr() {
echo "Відправка Docker образу - $REGISTRY/$REPOSITORY:$TAG"
docker push $REGISTRY/$REPOSITORY:$TAG
}
build_docker_image
push_docker_image_to_ecr
Змінна GITHUBREFNAME є посиланням на робочий процес, що було викликано, і в цьому випадку це буде версія тегу, тобто: [email protected]
. Ми будемо мати робочий процес, викликаний для кожного створеного тегу, тому якщо у нас є зміни в ui
та api
, ми отримаємо два робочих процеси: один для [email protected]
, а інший для [email protected]
. Таким чином, ми можемо використовувати ці теги для побудови наших зображень ECR, і Voilá, ми маємо версійовані образи в ECR.
deploy.yml
Це робочий процес, який відповідає за обробку останньої версії, яку ви щойно побудували. Важливі частини:
name: Deploy
on:
workflow_run:
workflows: [Build] # це назва вашого робочого процесу, який був виконаний на кроці тегування
types: [completed] # виконується лише після завершення
jobs:
deploy:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
# це необхідно, щоб забезпечити виконання лише після успішного завершення
name: Deploy
# ...інші кроки
- name: Розгортання на EB
env:
REGISTRY: ${{ steps.configure-aws.outputs.ecr-registry }}
REPOSITORY: docker-elastic-beanstalk-up
run: yarn deploy
І знову ж таки, найважливішим є скрипт, який обробляє логіку розгортання наших застосунків:
- він отримує останні теги, доступні на GitHub, які будуть використані для створення
docker-compose.yml
для розгортання застосунку - використовує ці теги для побудови зображень ECR
- оскільки наш застосунок має два взаємозалежні сервіси і тегування відбувається для кожного з них, ми перевіряємо чи існує вже образ іншого пакунку в ECR перед створенням його
docker-compose.yml
- після цього він намагається відправити на
eb deploy
, якщо є помилка, перевіряє статус, оскільки застосунок може бути не готовий або в черзі, тому ми викликаємоeb deploy
тільки коли це можливо
Одне важливе зауваження: оскільки наше розгортання виконується після робочого процесу Build, якщо ми змінюємо і api
, і ui
, це означає, що ми будемо мати два різних розгортання, що виконуються одночасно.
Проблема в тому, що оскільки наша структура використовує лише один docker-compose.yml
, нам потрібно трохи налаштувати скрипт eb deploy
, оскільки один з пакунків може вже виконувати розгортання середовища.
api зміна >
api образ > розгортання > eb виконує розгортання
ui зміна >
ui образ > розгортання > eb помилка, eb оновлює (через розгортання іншого пакунку)
if ! eb deploy docker-elastic-beanstalk-up-dev; then
STATUS=$(eb status docker-elastic-beanstalk-up-dev | grep -i status | awk '{print $2}')
echo "Статус Elastic Beanstalk: $STATUS"
if ["$STATUS" == "Ready"]; then
echo "Elastic Beanstalk готовий!"
if ! eb deploy docker-elastic-beanstalk-up-dev; then
echo "Розгортання виконується іншим пакунком, але це нормально (теоретично), оскільки він використовує останні теги."
fi
exit 0
elif ["$STATUS" == "Updating"] || ["$STATUS" == "Pending"]; then
echo "Розгортання виконується іншим пакунком, але це нормально (теоретично), оскільки він використовує останні теги."
exit 0
else
echo "Невідомий статус: $STATUS. Вихід з помилкою."
exit 1
fi
fi
Але як ви можете побачити в скрипті, це нормально, оскільки ми використовуємо останні теги з Git.
Ще одна річ, яку ми отримали безкоштовно після того, як вирішили використовувати semantic releases за допомогою конвенційних комітів, це те, що Lerna автоматично створює та оновлює журнали змін для кожного зміненого пакунку, наприклад:
Як ви можете побачити, в цьому випадку розділ Bug Fixes міститиме всі коміти, зроблені в цій ітерації/оновленні пакунку з посиланням на його зміни. Так, це все автоматично виконується в поточному налаштуванні CI/CD.
Перекладено з: Using Lerna with GitHub Actions to Deploy a versioned Full-stack Application to Elastic Beanstalk