Сервис GitHub, который позволяет автоматизировать любой процесс связанный с деплоем кода на продакшн сервер. Код проекта выполняется на виртуальных серверах GH и далее доставляется куда нужно
Имеет множество готовых решений, шаблонов и т.д.
Файлы располагаются во вложенной директории «./GitHub/workflows/.yml», файлы в формате yml
Можно работать в онлайн редакторе
Поток выполнения действий
Задачи внутри - джобы (jobs), которые состоят из шагов (steps)
Workflow обычно выполняет какую то большую задачу (очевидно правильно разбивать «одна задача - один воркфлоу»), их может быть множество, могут выполняться как последовательно так и параллельно, как и джобы
Может быть вызван автоматически, вручную либо выполняться по расписанию
Пример workflow:
name: Print workflow # запуск вручную on: workflow_dispatch jobs: # название джобы print_hello: # среда для запуска джобы runs-on: ubuntu-latest steps: - name: Print hello # команда которая будет выполнена в терминале run: echo "Hello world"
В этом примере три логических блока:
«Джобы выполняются на разных VM», для выполнения в рамках одной VM есть steps
Параллельное выполнение
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
Вместо инструкции «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<<EOF' >> $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<<EOF' >> $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/<DATABASE_NAME>/${{ vars.DATABASE_NAME }}/g' ./docker_compose_myapp.yml sed -i 's/<DATABASE_USER_NAME>/${{ 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