====== GitHub Actions ======
===== Введение =====
Сервис GitHub, который позволяет автоматизировать любой процесс связанный с деплоем кода на продакшн сервер. Код проекта выполняется на виртуальных серверах GH и далее доставляется куда нужно\\
:!: more
Имеет множество готовых решений, шаблонов и т.д.\\
Файлы располагаются во вложенной директории "./GitHub/workflows/.yml", файлы в формате yml\\
Можно работать в онлайн редакторе\\
==== Workflow ====
Поток выполнения действий\\
Задачи внутри - джобы (**jobs**), которые состоят из шагов (**steps**)\\
:!: more
Workflow обычно выполняет какую то большую задачу (очевидно правильно разбивать "одна задача - один воркфлоу"), их может быть множество, могут выполняться как последовательно так и параллельно, как и джобы\\
Может быть вызван автоматически, вручную либо выполняться по расписанию\\
Пример workflow:\\
name: Print workflow
# запуск вручную
on: workflow_dispatch
jobs:
# название джобы
print_hello:
# среда для запуска джобы
runs-on: ubuntu-latest
steps:
- name: Print hello
# команда которая будет выполнена в терминале
run: echo "Hello world"
В этом примере три логических блока:
* параметры самого воркфлоу (name и on (пар-р запуска)),
* настройка джоб (название и runs-on (инструкция для докера))
* и шаги (так же название и run (команда для выполнения))
==== Jobs ====
"**Джобы выполняются** на разных VM", для выполнения в рамках одной VM есть **steps**\\
:!: Порядок выполнения jobs
Параллельное выполнение
jobs:
print_hello:
runs-on: ubuntu-latest
steps:
- name: Print hello
run: echo "Hello world!"
print_full_tech_info:
runs-on: ubuntu-latest
steps:
- name: print full_tech_info
run: sudo lshw
print_short_tech_info:
runs-on: ubuntu-latest
steps:
- name:
run: sudo lshw -short
В интерфейсе GH, данные джобы будут в одном блоке, вместе, выполнялись параллельно\\
Для последовательного выполнения используется параметр "needs"\\
jobs:
print_hello:
runs-on: ubuntu-latest
steps:
- name: print hello
run: echo "hello world"
print_full_tech_info:
needs: print_hello
runs-on: ubuntu-latest
steps:
- name: print_full_tech_info
run: sudo lshw
print_short_tech_info:
needs: print_full_tech_info
runs-on: ubuntu-latest
steps:
- name: print_short_tech_info
run: sudo lswh -short
В интерфейсе GH джобы будут размещены в разных блоках, друг за другом\\
Если одна из них завершится с ошибкой, то остальные выполняться не будут\\
Такие цепочки зависимостей можно строить и целыми workflow\\
name: my_Workflow
# секция условий выполнения текущего флоу
on:
workflow_run:
workflows: ["Run tests"]
branches: [main]
types:
# статус выполнения первого флоу
- completed
==== Actions ====
Вместо инструкции "**run**", для вызова команд, можно использовать готовые модули или решения, написанные другими разработчиками, они распространяются бесплатно на маркетплейсе GH\\
:!: пример
Например популярный экшен для загрузки кода из репозитория, "checkout@v3"\\
name: print workflow
on: workflow_dispatch
jobs:
checkout:
runs-on: ubuntu-latest
steps:
- name: checkout_repo
uses: actions/checkout@v3
==== Примеры ====
:!: Большой пример
Главный экшен "ci-production" является точкой входа, внутри запускает доп экшены\\
Выполняются на self-hosted раннерах\\
Настроено несколько окружений, содержащих свой набор переменных, задаются в настройках репозитория\\
"ci-production.yml"\\
Триггеримся на пуш тега с формате "release-"\\
Нюанс в том что сработает пуш этого тега в любую ветку а выпуск релиза предполагается наверно из мастера\\
name: ci-production
on:
push:
tags:
- 'release-*'
jobs:
build:
uses: ./.github/workflows/build.yml
secrets: inherit
deploy-stage:
needs: build
uses: ./.github/workflows/deploy.yml
with:
environment: stage
secrets: inherit
deploy-prod:
needs: deploy-stage
uses: ./.github/workflows/deploy.yml
with:
environment: prod
secrets: inherit
Для запуска тестов делаем flow с триггером на пуш в любую ветку кроме "master", в него как раз предполагается пуш релизного тега, иначе запускаются оба флоу \\
name: ci-test
on:
push:
branches:
- '**'
- '!master'
jobs:
build:
uses: ./.github/workflows/build.yml
secrets: inherit
deploy-stage:
needs: build
uses: ./.github/workflows/deploy.yml
with:
environment: stage
secrets: inherit
"build.yml"
name: build
on:
workflow_call:
jobs:
build:
runs-on:
- self-hosted
- Linux
- kube-common
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'adopt'
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v2.1.1
- name: Compile
run: sudo bash ./gradlew :compileJava
- name: Test
run: sudo bash ./gradlew :test
- name: Build
run: sudo bash ./gradlew :bootJar
- name: Inject GitHub variables
uses: rlespinasse/github-slug-action@v4
- name: Set docker tags
run: |
echo 'DOCKER_TAGS<> $GITHUB_ENV
# В этой переменной последний тег, если нет собственного то в нем будет название ветки
if [[ "$GITHUB_REF_SLUG" =~ "release-".* ]]; then
echo "registry.myapp.ru/myapp:$GITHUB_REF_SLUG-$GITHUB_SHA_SHORT"
echo "registry.myapp.ru/myapp:latest"
else
echo "registry.myapp.ru/myapp:branch-$GITHUB_REF_SLUG-$GITHUB_SHA_SHORT"
fi >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
cat $GITHUB_ENV
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: registry.myapp.ru
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push image
uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile
push: true
tags: ${{ env.DOCKER_TAGS }}
"deploy.yml"\\
Деплоится на удаленный сервер, кредлы ssh сохранены в секретах репы\\
Для применения окружения переменных "stage", нужен блок "environment"\\
name: deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
jobs:
deploy:
runs-on:
- self-hosted
- Linux
- kube-common
environment:
name: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
# Для того чтобы задеплоить именно эту версию образа, чтобы и для тестов тоже работало
# Указываем в докер-компоуз тег образа который мы добавляли на прошлом шаге
- name: Inject GitHub variables
uses: rlespinasse/github-slug-action@v4
- name: Get docker-image tags
run: |
echo 'DOCKER_TAGS<> $GITHUB_ENV
if [[ "$GITHUB_REF_SLUG" =~ "release-".* ]]; then
echo "$GITHUB_REF_SLUG-$GITHUB_SHA_SHORT"
else
echo "branch-$GITHUB_REF_SLUG-$GITHUB_SHA_SHORT"
fi >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
cat $GITHUB_ENV
# Далее добавить еще один replace в компоуз
- name: Set secrets & environment
run: |
cd ./docker/
sed -i 's//${{ vars.DATABASE_NAME }}/g' ./docker_compose_myapp.yml
sed -i 's//${{ vars.DATABASE_USER_NAME }}/g' ./docker_compose_myapp.yml
- name: Deploy on remote host
uses: cross-the-world/ssh-scp-ssh-pipelines@latest
with:
host: ${{ secrets.SSH_HOST }}
user: ${{ secrets.SSH_USER }}
pass: ${{ secrets.SSH_PASS }}
first_ssh: |
echo '===== PREPARE HOST ====='
sudo mkdir -p /opt/${{ vars.APP_DIRECTORY }}/
sudo mkdir -p /opt/pgdata/
sudo chown -R ${{ secrets.SSH_USER }}. /opt/
scp: |
echo '===== SENDING FILES ====='
'./docker/postgres' => /opt/${{ vars.APP_DIRECTORY }}/
'./docker/docker_compose_myapp.yml' => /opt/${{ vars.APP_DIRECTORY }}/
last_ssh: |
echo '===== LOGIN TO REGISTRY ====='
sudo docker login registry.myapp.io -u '${{ secrets.DOCKER_USERNAME }}' -p '${{ secrets.DOCKER_PASSWORD }}'
echo '===== RESTART DOCKER-COMPOSE ====='
cd /opt/${{ vars.APP_DIRECTORY }}/
sudo docker-compose -f docker_compose_myapp.yml down
sudo docker-compose -f docker_compose_myapp.yml up -d
==== ====