В прошлой статье мы рассмотрели чрезвычайно популярный инструмент для выкатки приложений Jenkins. Мы подружили его через плагины с SSH, с GitHub, построили простой пайплайн с помощью Groovy. И вроде все здорово, все работает как должно, но все равно есть ощущение, что можно сделать лучше. И действительно, наш процесс можно улучшить, перестав проводить сборку на VPS.
Ранее для сборки мы использовали агент Jenkins, который был установлен на нашем хостинге, где и происходила сборка приложения и его выкатка. Конечно, в реальных проектах существует больше одного боевого сервера, существуют промежуточные серверы – тестовые, демо, стейдж. Не всегда и везде, конечно, но когда у вас несколько серверов, то и сборку приложений можно проводить на каких-то промежуточных, а после всех тестов и проверок, рабочее собранное приложение доставлять до прода. Но у нас все по простому, сразу в прод и агент был там же.
В этой статье предлагаю воспользоваться альтернативным подходом – собирать наше приложение, не покидая репозиторий, никаких дополнительных программ, и никаких агентов. Одним из таких инструментов является GithubActions, который позволяет создавать пайплайн деплоя и настроить доставку приложения на хостинг различными способами.
Для начала конечно стоит изучить документацию и рассмотреть приведенные примеры, посмотреть раздел быстрого старта.
Из него становится ясно, что для создания собственного процесса нам нужно создать конфигурационный файл с расширением .yml
внутри нашего репозитория в каталоге .github/workflows
. Что указывать в этом файле? Это подробно рассмотрено также в документации, в соответствующем разделе.
На самом деле, если язык Groovy, используемый в Jenkins может вызывать сложность в понимании, то здесь используется YAML, и если вы хоть раз составляли какие-то конфигурационные файлы или хотя бы делали docker-compose, то у Вас не должно возникнуть с ним никаких трудностей. Поэтому, дабы не распыляться в пустую, перейдем к сути.
Чтобы развернуть какое-либо свое предложение Вам нужно указать в созданном .yml файле общие сведения о приложении и настроить правила активации пайплайна. Далее, перечисляем необходимые шаги:
сборка приложения и подготовка артефакта;
перенос артефакт на хостинг;
правила деплоя доставленного артефакта.
И перед тем, как мы приступим к созданию собственного сценария, нужно уделить внимание ключам. Мы уже создавали ключ в прошлой статье и размещали его в панели Jenkins, чтобы была связь с агентом, а также достаточно прав для запуска приложения. Чтобы безопасно хранить чувствительные данные, в GitHubActions есть специальный раздел настроек в репозитории «Actions secrets and variables». Подробнее можно изучить в документации, но простыми словами – это обычное хранилище ключ-значение, где мы добавляем в хранилище какое-либо значение, а затем можем обратиться к нему по ключу.
Начнем с общей части, описательной:
name: SimpleApp Deploy
on:
push:
branches: [ "master" ]
Мы создаем правило – начать деплой, если в ветку master произошел push. Одно простое правило, которое будет являться триггером. Если проект разрастается и в ветке “master” не всегда рабочая версия приложения (или не всегда релизная), то можно добавить дополнительное условие. Например, проверить содержит ли коммит значение [release]
.
jobs:
deploy:
if: contains(github.event.head_commit.message, '[release]')
Можно завезти отдельные теги c версиями и проверять их.
on:
push:
tags:
- v1.**
В общем, вариантов масса в зависимости от Ваших потребностей. Разные варианты и используемые команды можно изучить в документации про триггеры и события. Мы остановимся на самом простом, будем считать триггером обновление основной ветки master. Далее настраиваем необходимые шаги.
❯ Шаг 1. Сборка приложения
Тут все как и прежде – создание docker образа. Единственное, что добавляется, это упаковка его в tar архив.
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Docker image
run: docker build -t simpleapp .
- name: Save Docker image as tarball
run: docker save simpleapp -o simpleapp.tar
- name: Verify tarball exists
run: ls -lh simpleapp.tar
- name: Change file permissions
run: chmod 644 simpleapp.tar
❯ Шаг 2. Упакованный архив копируем на хостинг
- name: SSH to VPS and remove existing tarball
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
script: |
if [ -f /home/${{ secrets.VPS_USER }}/simpleapp.tar ]; then
rm /home/${{ secrets.VPS_USER }}/simpleapp.tar
fi
- name: Copy Docker image to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
source: simpleapp.tar
target: /home/${{ secrets.VPS_USER }}/
❯ Шаг 3. Подключаемся по ssh к хостингу и разворачиваем наше приложение с необходимым параметрами
- name: SSH to VPS and deploy
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
script: |
docker load -i /home/${{ secrets.VPS_USER }}/simpleapp.tar
if [ "$(docker ps -q -f name=simpleapp)" ]; then
docker stop simpleapp
fi
if [ "$(docker ps -a -q -f name=simpleapp)" ]; then
docker rm simpleapp
fi
docker run --name simpleapp -p 5144:8080 -d simpleapp
Сохраняем все в файл и пушим в наш репозиторий, тем самым вызывая триггер.
Полная версия файла
name: Docker Image CI
on:
push:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Docker image
run: docker build -t simpleapp .
- name: Save Docker image as tarball
run: docker save simpleapp -o simpleapp.tar
- name: Verify tarball exists
run: ls -lh simpleapp.tar
- name: Change file permissions
run: chmod 644 simpleapp.tar
- name: SSH to VPS and remove existing tarball
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
script: |
if [ -f /home/${{ secrets.VPS_USER }}/simpleapp.tar ]; then
rm /home/${{ secrets.VPS_USER }}/simpleapp.tar
fi
- name: Copy Docker image to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
source: simpleapp.tar
target: /home/${{ secrets.VPS_USER }}/
- name: SSH to VPS and deploy
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
script: |
docker load -i /home/${{ secrets.VPS_USER }}/simpleapp.tar
if [ "$(docker ps -q -f name=simpleapp)" ]; then
docker stop simpleapp
fi
if [ "$(docker ps -a -q -f name=simpleapp)" ]; then
docker rm simpleapp
fi
docker run --name simpleapp -p 5144:8080 -d simpleapp
Давайте пройдемся немного подробнее по каждому шагу, чтобы было понимание. Вначале мы используем actions/checkout, который предоставляет доступ в репозиторий. В случае наличии изменений в репозитории, переходим на следующий шаг – сборка контейнера, самая обычная с помощью команды docker build -t simpleapp .
После этого упакуем образ в архив .tar
и даем права, и переходим к следующему этапу, копированию. Тут мы выполняем ровно два действия. Используя SSH, сначала проверяем, имеется ли уже в нашей директории файл с таким названием. Если имеется, то удаляем, так как следом, используя scp-action, мы копируем наш архив на хостинг и переходим к следующему этапу.
Здесь также без каких либо сложностей. Используя ключи из секретов, мы снова подключаемся к VPS и загружаем наш образ в архиве. Скриптом проверяем, есть ли одноименный контейнер, и если есть, то останавливаем его и удаляем. И потом запускаем наш свежий командой docker run --name simpleapp -p 5144:8080 -d simpleapp
Понятное дело, что запуск контейнера может быть с любыми ключами и параметрами, которые только принимает и поддерживает докер, здесь для простоты оставил самый минимум. Наконец-то мы пришли к тому, что нам не нужно держать никаких Runtime на VPS, не надо хранить исходники, не надо ничего собирать прям на проде, все это происходит за пределами нашего сервера, а на сервер копируется лишь готовый к запуску образ.
У такого подхода есть одно преимущество (особенно в текущих реалиях), и оно же является недостатком. Более популярным решением является использовать какое-то внешнее хранилище, куда обычно размещают собранный образ. На хостинг явно ничего не копируется, а лишь указывается – сходи в хранилище и возьми нужный образ, и разверни из него контейнер. Кто-то для этого использует Docker Hub, многие используют хранилище GitHub. Стоит отметить, что у них сильно ограничены возможности для бесплатного тарифа, это раз. Во-вторых, с их выкрутасами и выкрутасами не РКН, все это в любой момент может отвалиться и лично я предпочитаю, в текущей обстановке, по минимуму завязываться на них, а посему использую самое просто копирование.
Но для полноты картины, давайте переделаем наше решение, подключив хранилище от GitHub. Для этого нам нужен токен от ghcr
, который должен автоматически добавиться в хранилище после авторизации. Поэтому нам надо авторизоваться там, а потом запушить туда докер образ. В моем случае, после авторизации, вернулся токен с недостаточными правами для записи в хранилище.
Поэтому идем сначала в раздел с токенами и создаем токен с достаточными правами на запись write:packages
и read:packages, repo
(если у Вас уже есть токен с такими правами, то можете его использовать), и добавляем его в хранилище секретов, как это делали в самом начале. Я добавил токен с именем GHCR_PAT
. В итоге, файл приобрел следующий вид:
Полная версия файла
name: Docker Image CI
on:
push:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to GitHub Container Registry
run: |
echo ${{ secrets.GHCR_PAT }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build Docker image
run: docker build -t ghcr.io/${{ github.repository_owner }}/simpleapp:latest .
- name: Push Docker image to GHCR
run: docker push ghcr.io/${{ github.repository_owner }}/simpleapp:latest
- name: SSH to VPS and deploy
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
script: |
echo ${{ secrets.GHCR_PAT }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker pull ghcr.io/${{ github.repository_owner }}/simpleapp:latest
if [ "$(docker ps -q -f name=simpleapp)" ]; then
docker stop simpleapp
fi
if [ "$(docker ps -a -q -f name=simpleapp)" ]; then
docker rm simpleapp
fi
docker run --name simpleapp -p 5144:8080 -d ghcr.io/${{ github.repository_owner }}/simpleapp:latest
Результат такой же.
На текущий момент мы рассмотрели самые популярные возможности деплоя наших .NET приложений, которых должно быть достаточно для запуска наших простых проектов. Несмотря на то, что наше приложение ничего из себя не представляет, мы упустили два важных момента, каждый из которых стоит рассмотреть в отдельности. Во-первых, в 2024 запускать приложение без https – это моветон. Поэтому в следующей статье будут рассмотрены различные способы получения и использования сертификатов для повышения безопасности приложения. Во-вторых, думаю стоит уделить внимание таким вещам, как метрики и логирование. Ведь мало толку запустить приложение, надо иметь возможность отслеживать, что с ним происходит, какие запросы приходят, что в этот момент делает приложение, сколько потребляет ресурсов и т.д. Думаю, что этому аспекту тоже стоит уделить пристальное внимание, если вы планируете научиться разворачивать приложения, приближенные к реальным корпоративным решениям.
Надеюсь, что данная статья помогла Вам разобраться в вопросе использования GitHub Actions для развертывания своих приложений. Если остались вопросы – можете задать в комментариях, сообщество быстро подскажет, как делать правильнее :)
Можете также подписаться на мой телеграм, чтобы быть в курсе планов выхода следующих статей.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩