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

Привет, хабровчане!
Однажды нам надоело сливать ветки с репозитория на виртуальную машину тестового сервера и прописывать команды создания бэкапов для всех баз данных. Также очень не хотелось из-за маленького хотфикса поднимать сервисы в 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» и обнаружим там наш новый флоу:

Если все было настроено верно, то при открытии 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 секунд вместе с подключением к серверу. Неплохо, не правда ли?
Заключение
Сегодня мы поговорили про варианты автоматизации, когда нужно дешево и быстро реализовать не самые сложные сценарии по запуску интеграционных тестов, обновлению стенда, созданию бэкапов. Безусловно, эти скрипты и сценарии можно и нужно улучшать, но я надеюсь, что хабровчане смогут использовать этот материал как небольшой пример, с которого можно начать автоматизацию рутины.
