Деплой при помощи GitHub Actions

Конечно же, это простейший сценарий, но он вполне подходит для деплоя личных проектов, и экономит массу времени. Предполагается использование MacOS на локальной машине, но также должно подойти и для Linux (кроме команды pbcopy). Итак, далее по порядку.

Конфигурация SSH на локальной машине

Создадим ключ SSH для работы с GitHub. Для этого необходимо выполнить в терминале:

ssh-keygen -t rsa -b 4096 -f ~/.ssh/github

Программа запросит ввод passphrase – делать этого не нужно, просто нажимаем на Enter.

Полученные ключи при желании в дальнейшем можно будет использовать для деплоя нескольких приложений на разных VDS.

Конфигурация SSH на VDS

На локальной машине копируем публичный ключ SSH в буфер обмена командной:

cat ~/.ssh/github.pub | pbcopy

Далее, подключаемся к VDS при помощи ssh, и открываем файл с ключами в текстовом редакторе, например:

nano ~/.ssh/authorized_keys

Вставляем под существующими ключами строку с новым ключом из буфера обмена. Сохраняем, в случае nano – комбинациями клавиш Ctrl-O (сохранить), Ctrl-X (выйти).

Настройка "секретов" репозитория на GitHub

В скрипте для деплоя нам понадобится "секретная" информация – логин, пароль, IP-адрес VDS. Чтобы скрыть эти данные от посторонних, необходимо сохранить их в виде "секретов" на GitHub.

В репозитории приложения, которое мы будем деплоить, открываем раздел “Settings → Secrets and Variables → Actions” и нажимаем на кнопку “New repository secret”.

Скопируем приватный ключ в буфер обмена командой:

cat ~/.ssh/github | pbcopy

В поле "Name" вводим SSH_PRIVATE_KEY. В поле "Secret" вставляем приватный ключ из буфера обмена.

Screenshot 1

Аналогично, создаём ещё два "секрета": SSH_HOST с IP-адресом VDS, и SSH_USER с именем пользователя для входа по SSH.

Итак, всё подготовлено, осталось только создать непосредственно действие, которое и будет деплоить наше веб-приложение.

Создание GitHub Action

Создадим в нашем проекте файл .github/workflows/deploy.yml. Имя файла может быть любое, но он должен быть размещён в директории .github/workflows/ и иметь расширение yml.

Приведу пример для проекта, который запускается на VDS при помощи Docker Compose. На самом деле, как бы ни работал проект, в данном сценарии будет отличаться самая последняя строка – run на шаге Deploy.

name: Continuous Integration

on:
  push:
    branches:
      - main

concurrency:
  group: main
  cancel-in-progress: true

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Configure SSH
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          SSH_HOST: ${{ secrets.SSH_HOST }}
          SSH_USER: ${{ secrets.SSH_USER }}
        run: |
          mkdir -p ~/.ssh/
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/github
          chmod 600 ~/.ssh/github
          cat >>~/.ssh/config <<END
          Host target
            HostName $SSH_HOST
            User $SSH_USER
            IdentityFile ~/.ssh/github
            LogLevel ERROR
            StrictHostKeyChecking no
          END
      - name: Run Deploy
        run: |
          ssh target "cd /usr/projects/django-project && docker compose down && git pull && docker compose up -d --build --force-recreate"

Вкратце, что здесь происходит:

  • "on → push → branches: main" говорит GitHub, что скрипт нужно выполнить при пуше кода в ветку main.
  • пункт concurrency настроен таким образом, что если вы запушите что-то новое, пока не закончилось выполнение ранее запущенного действия, выполнение будет отменено и действие запустится заново.
  • на шаге "jobs → deploy → steps → name: Configure SSH" создается виртуальная машина с Ubuntu, и настраиваются ключи SSH на ней для подключения к нашему VDS.
  • и последний шаг – "Run Deploy" – самый интересный. Здесь на виртуальной Ubuntu запускается SSH, которая подключается к нашему VDS и выполняет необходимые для обновления и перезапуска проекта команды. В данном примере, это:
    • cd /usr/projects/django-project: перейти в директорию проекта;
    • docker compose down : остановить проект;
    • git pull: получить последние изменения репо, как раз в результате пуша которых и запустилось действие.
    • docker compose up -d --build --force-recreate: запустить проект с принудительной сборкой контейнеров.

Для удобства, все эти команды можно вынести, например, в Makefile:

up_prod:
    docker compose -f docker-compose.prod.yml up -d --build
down:
    docker compose -f docker-compose.prod.yml down
update:
    make down
    docker system prune -f -a --volumes
    git pull
    make up_prod

Тогда строку Run Deploy в deploy.yml можно сократить до ssh target "cd /usr/projects/django-project && make update".

Отмечу команду docker system prune -f -a --volumes в приведенном выше Makefile. Она принудительно удаляет все контейнеры и временные файлы Docker, так как, если этого не делать, Docker может довольно быстро забить систему своими временными файлами, особенно при частых перезапусках проекта. Это увеличивает время сборки, однако позволяет не думать о свободном месте на диске VDS, тем более, когда его и так не много.

Теперь, когда всё готово, достаточно сделать коммит в ветку main и отправить изменения на GitHub: действие будет запущено автоматически! За ходом его выполнения можно наблюдать на вкладке "Actions" репозитория.

Screenshot 2

Подведение итогов

GitHub Actions позволяет автоматизировать все те действия, которые выполняются "руками" при деплое проекта. Это освобождает значительное количество времени, а также позволяет сохранить в системе контроля версий порядок перезапуска проекта при его обновлении – это даже лучше, чем просто документация!

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

Справочные материалы

Отправить сообщение

С помощью формы ниже, вы можете связаться с автором сайта. Пожалуйста, укажите ваш ник в Телеграме или e-mail, чтобы я смог вам ответить!