Автоматизация глазами разработчика: Github Actions для стартапа

от автора

Основное преимущество работы в стартапе – разнообразные задачи, с которыми приходится разбираться в крайне короткие сроки за минимальный бюджет. И такие условия позволяют находить и придумывать интересные решения, которые не приняты за стандарт большими компаниями.

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

Логотип GitHub Actions
Логотип GitHub Actions

Привет, хабровчане!

Однажды нам надоело сливать ветки с репозитория на виртуальную машину тестового сервера и прописывать команды создания бэкапов для всех баз данных. Также очень не хотелось из-за маленького хотфикса поднимать сервисы в Docker, поднимать сам Docker, пересобирать контейнер и запускать тесты. Мы провели исследование не только в чертогах разума, но даже на второй странице результатов поисковиков. И мы пришли к решению, которое нас более чем устроило как по цене, так и по возможностям – Github Actions.

Итак, в нашем распоряжении имеется микросервисная архитектура (ну куда же без неё в наше непростое время), состоящая из 10 контейнеров, среди которых есть базы данных на PostgreSQL, сервисы бэкенда на .NET Core, сервис Nginx, небольшой Telegram-бот для быстрого сбора данных о возможных проблемах в решении, а также один контейнер со статической страничкой, который до релиза мы хотим вынести за пределы не только архитектуры, но и виртуальной машины в целом. Естественно, все это работает внутри системы Docker. Также имеется проект с тестами, которыми мы проверяем работоспособность всех основных модулей решения.

Запуск автотестов

Начнем с создания небольшого флоу для запуска наших тестов на гитхабе. Мы нашли множество примеров реализации запуска Unit-тестов для .NET, но одними Unit-тестами сыт не будешь: большая часть нашего решения покрыта интеграционными тестами Поэтому мы реализовали сборку наших основных сервисов и запуск тестов прямо на Github.

Приводим пример такого флоу с комментариями:

# Имя флоу name: .NET Core  # Когда действие запустится (триггеры) on:   push:     # при push в master     branches: [ master ]    pull_request:     # при создании pull request на master     branches: [ master ]  # Что будем делать (экшены) jobs:   # Имя действия, придумываем сами   integration-tests:     # На какой ОС будет работать виртуальная машина     # Можно выбрать Ubuntu, Windows Server или macOS     runs-on: ubuntu-latest     # Шаги действия     steps:       # Шаг 1: собираем сервисы в режиме тестирования       - uses: actions/checkout@v2       - name: Build the stack         run: docker-compose -f docker-compose.prod.yml -f docker-compose.test.yml up -d --build         # Шаг 2: собираем проект с тестами       - name: Build tests         run: dotnet build GOT.Tests        # Шаг 3: запускаем тесты с небольшой детализацией       - name: Run tests         run: dotnet test LOT.Tests --verbosity normal

После сохранения файла в репозитории по пути RepositoryName/.github/workflows/FileName.yml мы можем перейти на вкладку «Actions» и обнаружим там наш новый флоу:

.NET Core - наш новый флоу
.NET Core — наш новый флоу

Если все было настроено верно, то при открытии pull request на master или при коммите на master (за последнее имеется отдельный котел в известном месте) Github запустит флоу, который пойдет по шагам и сначала соберет и запустит ваши контейнеры, а затем соберет и запустит проект с тестами, попутно выводя всю информацию из консоли своей виртуальной машины прямо в вашем браузере. При желании можно прикрутить бота Github, который будет сообщать о проблемах с тестами, заведет новые ишью, если тест не был пройден, а также сообщит вам в Slack/Telegram о проблемах. Все ограничено лишь фантазией и сроками. Тесты – это хорошо, но как насчет автоматизации доставки?

Автоматизация доставки

Но и тут у Github Actions есть все, что необходимо, но для полного понимания концепции стоит провести небольшой экскурс по возможностям Github:

  • Github Packages – используется для хранения сборок. В нашем случае мы используем его как внешний Docker registry. Подробности можно посмотреть тут.

  • Secrets – «секреты» на уровне репозитория или организации. С их помощью можно доверить Github такие ценные вещи, как логины, пароли, токены. Преимущество секретов в том, что даже в случае утраты доступа к аккаунту никто не получит доступа к секретам: Github выведет только их название, сами значения после нажатия “Save” останутся секретом для всех. Больше о секретах.

  • Github Actions от сторонних разработчиков. Маркетплейс различных экшенов растет очень быстро, с каждым месяцем появляются сотни различных интеграций и возможностей для разработчиков: Jira, Azure, Telegram, Slack и так далее. На момент написания статьи в маркетплейсе было более 9000 экшенов.

Будем использовать все эти возможности, чтобы реализовать полноценный CI для обновления тестового стенда.

Подбираем подходящие экшены

Нам понадобятся экшены для логина в Github Packages, для сборки образов микросервисов, для публикации образов в Github Packages. Также нам пригодится экшен, умеющий устанавливать SSH-соединение к нашей виртуальной машине и выполнять произвольные команды.

После небольшого поиска по маркетплейсу и изучения документации был подобран следующий список экшенов:

  • docker/setup-qemu-action@v1 – надстройка для виртуализации

  • docker/setup-buildx-action@v1 – модуль Docker для сборки образов

  • docker/login-action@v1 – экшен для логина в Docker Registry (в нашем случае – Github Packages)

  • docker/build-push-action@v2 – экшен сборки и публикации образа

  • appleboy/ssh-action@master – экшен для инициализации подключения по SSH и выполнения скрипта 

Настраиваем секреты

Один из немногих случаев, когда можно сделать скрин из приватного production-репозитория.

"Секреты" репозитория
«Секреты» репозитория

Небольшие пояснения:

REGISTRY_TOKEN – токен для авторизации в Github Packages. Его можно получить тут.

SERVER_HOST – IP-адрес сервера в виде «192.168.100.100».

SERVER_KEY – PEM-ключ для подключения к серверу. Обычно выдается провайдером вашей виртуальной машины.

SERVER_PORT – порт сервера для SSH-соединения. По-умолчанию это 22.

SERVER_USERNAME – имя пользователя, под которым авторизовываемся на виртуальной машине.

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

Самое время готовить стенд: напишем docker-compose и залогинимся в Github Packages. После сборки и публикации образов в Github Packages нам необходимо подключиться к удаленной машине, выгрузить и запустить новую сборку. Для этого будем использовать практически тот же docker-compose файл, что и для локальной сборки и запуска, но немного отредактируем его. Во-первых, в качестве «image» будем использовать ссылку на Github Packages, где у нас будет опубликована сборка. Во-вторых, уберем из docker-compose-файла все параметры, которые касаются именно сборки: тут они нам попросту не нужны, так как сервер отныне не отвечает за сборку.

По итогу у нас получился примерно такой файл (некоторые параметры исправлены на «template»):

version: "3.4" services:   template-server:     image: ghcr.io/template-inc/prod-template-server:latest     environment:       DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"       DOCKER: "true"       EMAIL_SERVER: "http://template-email-server"     restart: always     links:       - template-db     networks:       template-network:     template-db:     image: postgres:11     volumes:       - db-volume:/var/lib/postgresql/data     environment:       POSTGRES_PASSWORD: ${DB_PASSWORD}       POSTGRES_DB: ${DB_NAME}     restart: always     networks:       template-network:    template-nginx:     image: ghcr.io/template-inc/prod-template-nginx:latest     restart: always     networks:       template-network:     depends_on:       - template-server     ports:       - "90:80"   networks:   template-network:  volumes:   db-volume:

Маленькое примечание: ссылка на image не должна содержать заглавных букв. Даже если имя репозитория или логин содержат заглавные буквы, в ссылке приведите их к lower-case формату. Иначе возникнут проблемы.

Опубликуем этот файл прямо в стартовом каталоге виртуальной машины, чтобы не усложнять скрипт запуска. Для примера назовем его «docker-compose.prod-ci.yml». Также, раз мы уже на машине, сразу залогинимся в Github Packages:

«docker login ghcr.io --username yourGithubUsername --password yourGithubToken»

Важно использовать именно username, а не электронную почту. Почему-то для Github это важно и первый раз мы прогорели на этом несмотря на то, что получили сообщение об успешной авторизации. На этом работа с сервером окончена.

Пишем workflow

Просто собираем все наши знания, которые получили по ходу чтения документации соответствующих экшенов, и агрегируем их в одном файле:

name: 'build and deploy test server' on:   release:     types: [published]   workflow_dispatch: jobs:   build:     name: 'Build & Publish'     runs-on: ubuntu-latest     steps:       - name: "Checkout repository"         uses: actions/checkout@v2                - name: "Set up QEMU"         uses: docker/setup-qemu-action@v1                - name: "Set up Docker Buildx"         uses: docker/setup-buildx-action@v1        - name: "Login to GitHub Registry"         uses: docker/login-action@v1          with:           registry: ghcr.io           username: "yourGithubUsername"           password: ${{ secrets.REGISTRY_TOKEN }}                  - name: "Build&Deploy template-server"         uses: docker/build-push-action@v2         with:           push: true           tags: |             ghcr.io/${{ github.repository_owner }}/prod-template-server:${{ github.event.release.tag_name }}             ghcr.io/${{ github.repository_owner }}/prod-template-server:latest           secrets: |             "ASPNETCORE_ENVIRONMENT=Release"           build-args: |             build_mode=Release                    - name: "Build&Deploy template-nginx"         uses: docker/build-push-action@v2         with:           push: true           tags: |             ghcr.io/${{ github.repository_owner }}/prod-template-nginx:${{ github.event.release.tag_name }}             ghcr.io/${{ github.repository_owner }}/prod-template-nginx:latest           build-args: |             build_mode=Release        - name: "Run deploy on server"         uses: appleboy/ssh-action@master         with:           host: ${{ secrets.SERVER_HOST }}           username: ${{ secrets.SERVER_USERNAME }}           key: ${{ secrets.SERVER_KEY }}           port: ${{ secrets.SERVER_PORT }}           script: |             sudo docker-compose -f docker-compose.prod-ci.yml -p prod pull             sudo docker-compose -f docker-compose.prod-ci.yml -p prod up -d

Сохраним этот файл в репозитории по пути .github/workflows/test-server-ci.yml.

На этом настройка закончена. Переходим на вкладку «Actions» и видим новый экшен «build and deploy». Триггерами этого экшена выступают два действия: публикация нового релиза и ручной запуск.

Ручной запуск экшена можно осуществить прямо со страницы экшена. Для этого нажимаем «Run workflow», выбираем ветвь, с которой будет осуществляться сборка, и запускаем.

Запуск наших экшенов
Запуск наших экшенов

Для срабатывания триггера нового релиза зайдем в раздел «Releases» репозитория и создадим новый релиз, нажав «Draft a new release»:

Кнопка создания нового релиза
Кнопка создания нового релиза

Заполним основную информацию про новый релиз:

Форма описания нового релиза
Форма описания нового релиза

 Сразу после нажатия «Publish release» сработает триггер и начнет выполнять все те шаги, которые мы описали: виртуальная машина Github’а клонирует себе репозиторий, залогинится в Github Packages, установит необходимые для Docker инструменты, соберет образы, опубликует их, затем подключится к нашей виртуальной машине и выполнит скрипт, который подключится к Github Packages, стянет последние сборки сервисов и развернет их. А за всем процессом можно зорко следить прямо в консоли запуска на Github. Разве это не прекрасно?

Бэкапы баз данных 

Какие шансы, что вы не потеряете свои данные в случае, если совершаете по 2-3 обновления в неделю, многие из которых мигрируют базу?

Спойлер: шансов немного
Спойлер: шансов немного

Поэтому практически сразу встал вопрос о том, как автоматизировать создание бэкапов баз данных. Тем более, когда их несколько, а варианты с готовыми системами управления либо дорогие, либо очень дорогие. И вновь на помощь приходит Github Actions!

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

Начнем с разметки каталогов на сервере: в корне создадим каталог db_backup, внутри него — template-db и template-identity-db

В каталоге db_backup создадим маленький скрипт:

#db username dbUsername="<your db username>"  # container additionals containerPrefix="prod_" containerPostfix="_1"  #container names templateDb="template-db" templateIdentityDb="template-identity-db"  #create template-db backup cd $templateDb docker exec -t $containerPrefix$templateDb$containerPostfix pg_dumpall -c -U $dbUsername > dump_$(date +%Y-%m-%d_%H_%M_%S).sql echo "Backup of" $templateDb "created success" cd ../  #create template-identity-db backup cd $templateIdentityDb  docker exec -t $containerPrefix$templateIdentityDb$containerPostfix pg_dumpall -c -U $dbUsername > dump_$(date +%Y-%m-%d_%H_%M_%S).sql echo "Backup of" $templateIdentityDb "created success" cd ../  echo "All database backups created success!"

Это максимум, который удалось реализовать за 15 минут, загуглив общий синтаксис sh-скриптов. Но этого нам хватит. Сохраним файл под именем create_backups.sh.

Теперь переходим к репозиторию и создадим новый workflow по аналогии выше:

name: 'Create prod database backups' on:   # Allows you to run this workflow manually from the Actions tab   workflow_dispatch:      # Run action every day   schedule:   - cron: "0 0 * * *"  jobs:   build:     name: 'Build & Publish'     runs-on: ubuntu-latest     steps:       - name: "Run backup script on server"         uses: appleboy/ssh-action@master         with:           host: ${{ secrets.SERVER_HOST }}           username: ${{ secrets.SERVER_USERNAME }}           key: ${{ secrets.SERVER_KEY }}           port: ${{ secrets.SERVER_PORT }}           script: |             cd db_backup/             sudo bash create_backups.sh

К слову, тут появился новый для этого материала тип триггера – schedule. Данный вид триггеров запускается по расписанию, а вот время запуска описано в формате «cron» (подробнее): многим ненавидимый, но в то же время прекрасный формат, позволяющий прописывать правило времени.

Итак, что мы имеем по итогу: раз в сутки (в полночь по UTC) на Github срабатывает триггер и запускает экшен, который, в свою очередь, устанавливает соединение с нашим сервером и выполняет скрипт запуска нашего бэкап-скрипта. Бэкап-скрипт пробегается по контейнерам с базами данных и создает бэкапы в соответствующих каталогах. Также у нас есть возможность создать резервные бэкапы вручную со страницы соответствующего workflow. Все, что вам останется – придумать, куда отправлять бэкапы на долгосрочное хранение.

К слову, автоматизированное создание бэкапов из 5 контейнеров занимает у нас 13-15 секунд вместе с подключением к серверу. Неплохо, не правда ли?

Заключение

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


ссылка на оригинал статьи https://habr.com/ru/articles/595627/