Привет! Это вторая статья о новом проекте - telegram bot на python, по продаже товаров. В первой части описал с чего начал проект и каков был финальный результат. А сегодня хочу поговорить о CI/CD проекта.

Когда проект достиг стадии MVP (minimum valuable product) - очень хотелось вывести его в свет. Чтоб бот постоянно работал, чтоб у него появились первые пользователи (тут кто-то был слишком оптимистичен, без рекламы к боту пользователи не придут) и чтоб была возможность выкатывать остальную функциональность маленькими итерациями. Время делать CI/CD.

И снова хотел бы напомнить, что я не профессиональный DevOps, а всего лишь тестировщик. Поэтому текст ниже - это всего лишь мой опыт, а не руководство как делать правильно.

Начнем с CI

Под CI понимаю запуск тестов, сборку docker image и публикация артефактов (тех самых images) в репозиторий. Решил не использовать сторонних сервисов, а сделать все на основе GitHub.
В целом, как работать с GitHub Actions можно почитать в официальной документации, тут я попытаюсь описать не очевидные для меня вещи.

Первая не очевидная вещь - как опубликовать docker image в реестр. Я пробовал несколько actions, пробовал писать свой скрипт и, в конечном итоге, остановился на официальном Build and push Docker images:

uses: docker/build-push-action@v1
    with:
      push: true
      username: ${ GITHUB_ACTOR }
      password: $
      registry: docker.pkg.github.com
      repository: yurii-hunter/upsale/upsale
      tag_with_ref: true
      add_git_labels: true
      tag_with_sha: false

Вторая не очевидная вещь - secrets.PACKAGE_REGISTRY_TOKEN. Во примерах в интернете разработчики используют secrets.github_token - это стандартный токен, с которым пакет соберется и, даже, опубликуется в ваш реестр. Проблема лишь в том, что после публикации пакета, не срабатывается триггер у других actions. В моем случае должен был запуститься следующий workflow для CD. Чтобы триггер сработал, нужно создать новый секрет, в моем случае secrets.PACKAGE_REGISTRY_TOKEN и дать ему права на чтения/запись в реестр.

Переходим к CD

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

Добавляем свой runner в GitHub

Для этого проекта использовались сервера Digital Ocean, так что ниже издложена инструкция по добавлению Docker droplet в качестве ранера к существующему проекту:

  1. После создания дроплета Docker 5:19.03.1~3 on 18.04 заходим на него через ssh
  2. Первое, что нужно сделать - добавить пользователя, так как GitHub Runner не позволяет запустить агент от root
    $ adduser github
    
  3. Вводим пароль и заполняем информацию о пользователе или можно оставить ее пустой
  4. Добавляем пользователя в группы sudo и docker
    $ adduser github sudo
    $ sudo usermod -aG docker github
    $ newgrp docker
    
  5. Логинимся только что созданным пользователем
    $ sudo su - github
    
  6. Скачиваем и устанавливаем ранер, как указанно в инструкции Settings -> Actions -> Add runner. Имя ранера и рабочей директории можно оставить со значениями по умолчанию.
  7. Чтобы запустить ранер как сервис, останавливаем текущий скрипт Ctrl+C
  8. Устанавливаем сервиса ранера
    $ ./svc.sh install
    
  9. Запускаем ранер как сервис
    $ ./svc.sh start
    
  10. В workflow yaml файле меняем runs-on: ubuntu-latest на runs-on: self-hosted

result

Реализация Deploy Workflow

Идея такая, что должно быть два workflow. Первый - CI, срабатывает на каждый комит в master ветку. Он собирает артефакты и публикует их в репозиторий. Второй - CD, срабатывает на публикацию пакета в реестр и дальше доставляет этот пакет на специальный агент. Так как это бот-магазин, то CD workflow может быть несколько (по одному на каждый магазин). Это основная причина, почему сборку и доставку разделил на два отдельных процесса.

Ниже приведен пример как указать в yaml файле тригер на публикацию пакета. И как запускать флоу на личном ранере

on: registry_package

jobs:
  deploy:
    runs-on: 
      - self-hosted

Дальше выполняются шаги с docker-compose по обновлению прилодения. Шаги следующие: остановить приложение, забрать последнюю версию артефактов, мигрировать базу, запустить новую версию приложения.

- name: stop application
  run: docker-compose down
- name: pull images
  run: docker-compose pull
- name: migrate database
  run: docker-compose up migration
- name: start application
  run: docker-compose up -d admin bot-client

На даный момент бот постоянно работает и бытро обновляется после выхода новой версии.

Надеюсь вам было полезно и познавательно. Тема не очень простая, так что любые вопросы и замечания оставляйте в коментариях под этим постом.

Также если вам интересно следить за дальнейшим развитием этого проекта, то в telegram канале я публикую маленькие заметки по этой теме.