Github Actions уже достаточно давно и плотно используются нашими разработчиками, дошли и у меня руки, что бы поближе познакомиться с этим сервисом от горячолюбимого Microsoft, так как в Github они появились уже после покупки Github этой прекрасной компанией.
По сути, Github Actions весьма схож с TravisCI, но более тесно интегрирован в сам Github, вплоть до того, что интерфейс Actions доступен прямо из Github:
Кратко рассмотрим основные возможности, а в следующих частях задеплоим self-hosted runners в Kubernetes кластер и построим CI/CD с использованием Github Actions и ArgoCD.
Содержание
Стоимость Github Actions
См. документацию тут>>>.
Использование GitHub Actions бесплатно как для free-репозиториев, так и для paid-планов, но в рамках определённых ограничений:
| Product | Storage | Minutes (per month) |
|---|---|---|
| GitHub Free | 500 MB | 2,000 |
| GitHub Pro | 1 GB | 3,000 |
| GitHub Free for organizations | 500 MB | 2,000 |
| GitHub Team | 2 GB | 3,000 |
| GitHub Enterprise Cloud | 50 GB | 50,000 |
У нас GitHub Team, значит на месяц мы получаем 2 гигабайта хранилища и 3000 минут бесплатно.
При этом стоимость билдов на Linux, macOS или упаси боже Windows тоже разная:
| Operating system | Minute multiplier |
|---|---|
| Linux | 1 |
| macOS | 10 |
| Windows | 2 |
Т.е. на macOS мы могли бы использовать всего 300 минут билдов.
А каждая дополнительная минута стоит дополнительных денег:
| OS | Resources | Price per extra minute |
| Linux | 2 cores, 7 GB | $ 0,008 |
| Windows | 2 cores, 7 GB | $0,016 |
| MacOS | 2 cores, 7 GB | $ 0,08 |
| Self-hosted | – | free |
Также, Github Actions могут работать как в Github Cloud, так и в виде self-hosted runners, что может решить вопрос с доступами к Kubernetes-кластерам, так как постоянного пула IP у Github нет, соответственно появляется проблема с настройкой SecurityGroup/firewalls.
Сам Github предлагает лепить костыли периодически загружать json-файл со списком обновлённых сетей (btw, работают Github Actions в Microsoft Azure), и городить лишнюю автоматизацию для этого банально лень.
Github Actions: обзор
В Actions билд происходит следующим образом (см. Introduction to GitHub Actions):
- event (например, пул-реквест или коммит в репозиторий, см. список тут>>>) триггерит запуск workflow, а workflow содержит jobs
- job содержит список steps, а каждый step содержит один или более actions
- actions выполняются на одном и том же runner, и несколько таких actions могут выполняться в рамках одного workflow одновременно
Основные компоненты:
- runner: сервер самого Github или self-hosted, на котором выполняется job
- workflow: описанная процедура, включающая в себя одну или более job, которая триггеррится неким event
- jobs: набор steps, которые выполняются на одном и том же runner. Если в одном workflow несколько jobs — по-умолчанию они запускаются параллельно, но могут быть настроены на выполенние по очереди и зависеть от результатов выполнения друг друга.
- steps: задача, выполняющая команды или actions. Так как steps в одной job выполняются на одном runner — они могут обмениваться данными между собой.
- actions: основные «исполняемые блоки» — могут представлять собой набор уже готовых задач, или выполнять обычные команды
Структура workflow файла
Кратко рассмотрим структуру файла для workflow:
name: имя флоуon: event, при котором будет запущен workflowjobs: список задач этого workflow<JOB_NAME>runs-on: ранrunnerнер, на котором задача будет выполнятьсяsteps: шаги в рамках этой job, используяusesилиrunuses: имя action для запускаrun: обычная команда для запуска
Добавление workflow файла
Напишем простой файл.
В корне репозитория создаём каталог .github/workflows/ — тут будут храниться все файлы флоу, которые могут потом триггерится разными events:
[simterm]
$ mkdir -p .github/workflows
[/simterm]
В нём создаём файл, например .github/workflows/actions-test.yaml:
name: actions-test
on: [push]
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: echo "Hello, world"
Сохраняем и пушим в репозиторий:
[simterm]
$ git add .github/workflows/actions-test.yaml && git commit -m "Test flow" && git push
[/simterm]
В репозитории переходим в Actions, и видим его выполнение:
Events
В Events можно описывать достаточно сложные условия, по которым будет принимать решение о выполнении workflow.
Такими условиями может быть коммит или пул-реквест в репозитории, расписание или определённое событие вне Github, которое выполняет webhook к репозиторию, при этом можно задать несколько событий, при наступлении которых будет запущен workflow.
Например, можно для разных event указать разные бранчи:
name: actions-test
on:
push:
branches:
- master
pull_request:
branches:
- test-branch
...
Или задать выполнение по крону, см. Scheduled events:
name: actions-test
on:
schedule:
- cron: '* * * *'
Ручной запуск — workflow_dispatch
Можно настроить воркфлоу на ручной запуск, добавив в on условие workflow_dispatch:
name: actions-test
on:
workflow_dispatch
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: echo "Hello, world"
После чего в Actions появится кнопка для запуска с выбором бранча:
Кроме того, сюда же можно добавить набор вводных данных, используя inputs, которые на время выполнения будут сохранены в контексте github.event и доступны в виде переменных:
name: actions-test
on:
workflow_dispatch:
inputs:
userName:
description: "Username"
required: true
default: "Diablo"
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: echo "Username: ${{ github.event.inputs.username }}"
- run: echo "Actor's username: ${{ github.actor }}"
Тут в ${{ github.event.inputs.username }} мы берём значение из workflow_dispatch.inputs.userName, а в github.actor получаем метаданные Github Actions:
Например, таким образом можно будет передавать Docker image tag для деплоя с ArgoCD.
Webhooks: create
См. полный список событий в Webhook events.
Кроме push, который мы уже использовали, можно настроить запуск воркфлоу на любое событие в репозитории.
Например, что бы запускать флоу при создании нового бранча или тега — используем create:
name: actions-test
on:
create
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: |
echo "Event name: ${{ github.event_name }}"
echo "Actor's username: ${{ github.actor }}"
Тут ещё используем ${{ github.event_name }}, что бы отобразить имя триггера.
Создаём бранч и пушим его:
[simterm]
$ git checkout -b a-new-branch Switched to a new branch 'a-new-branch' $ git push -u origin a-new-branch
[/simterm]
Проверяем:
Переменные окружения
Также, Github Actions поддерживают работу с переменными окружения.
Имеется набор дефолтных переменных, см. Default environment variables, плюс можно задать свои на уровне всего workflow в jobs, на уровне определённой job или step.
При этом надо учитывать «особенности» обращения к переменным, см. About environment variables:
- context variable —
${{ env.VARNAME }}: подстановка значения выполняется на уровне процессинга workflow-файла до отправки на runner, используем везде кромеrun, например при проверке условий сif(см. ниже) - environment variable —
$VARNAME: подстановка значения выполняется на runner во время выполненияrun - для создания переменной окружения во время выполнения job — используем файл, заданный в
$GITHUB_ENV— значения из него будут доступны всем step далее
Пример:
name: vars-test
on:
push
env:
VAR_NAME: "Global value"
jobs:
print-vars:
runs-on: ubuntu-latest
steps:
# using own varibales
- name: "Test global var as $VAR_NAME"
run: echo "Test value $VAR_NAME"
- name: "Test global var as ${{ env.VAR_NAME }}"
run: echo "Test value ${{ env.VAR_NAME }}"
# using default variables
- name: "Test job var as $GITHUB_REPOSITORY"
run: echo "Test value $GITHUB_REPOSITORY"
# this will be empty, as default variables are not in the context
- name: "Test job var as ${{ env.GITHUB_REPOSITORY }}"
run: echo "Test value ${{ env.GITHUB_REPOSITORY }}"
# using 'dynamic' variables
- name: "Set local var"
run: echo "local_var=local value" >> $GITHUB_ENV
- name: "Print local var as $local_var"
run: echo "$local_var"
- name: "Print local var as ${{ env.local_var }}"
run: echo "${{ env.local_var }}"
И результат:
Secrets
Ну и куда же без секретов?
Документация тут>>>.
Добавляются секреты в настройках репозитория > Secrets:
Добавляем использование, причём можно как создать переменную, которая будет хранить секрет, так и обращаться к секрету напрямую:
name: actions-test
on:
push
env:
TEST_ENV: ${{ secrets.TEST_SECRET }}
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: |
echo "Test secret: ${{ secrets.TEST_SECRET }}"
echo "Test secret: ${{ env.TEST_ENV }}"
Запускаем:
Условия и if
Actions поддерживает проверку условий для jobs, используя оператор if за которым следует выражение, см. About contexts and expressions.
Например:
name: actions-test
on:
push
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- id: 'zero'
run: echo "${{ github.actor }}"
- id: 'one'
run: echo "Running because of 'github.actor' contains a 'setevoy' string"
if: "contains(github.actor, 'setevoy')"
# this will not run
- id: 'two'
run: echo "Skipping because of 'github.actor' contains a 'setevoy' string"
if: "!contains(github.actor, 'setevoy')"
- id: 'three'
run: echo "Running because of Step Two was skipped"
if: steps.two.conclusion == 'skipped'
- id: 'four'
run: echo "Running because of commit message was '${{ github.event.commits[0].message }}'"
if: contains(github.event.commits[0].message, 'if set')
- id: 'five'
run: echo "Running because of previous Step was successful and the trigger event was 'push'"
if: success() && github.event_name == 'push'
Тут мы используем github context, функцию contains(), операторы != и &&, steps context для проверки условий.
Результат:
needs — зависимости job
Кроме использования if: success() в steps, можно прописать зависимости самих jobs используя needs:
name: actions-test
on:
push
jobs:
init:
runs-on: ubuntu-latest
steps:
- run: echo "An init job"
build:
runs-on: ubuntu-latest
steps:
- run: echo "A build job" && exit 1
needs: 'init'
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "A deploy job"
if: always()
needs: ['init', 'build']
Тут в build job ожидаем выполнения джобы init, а в джобе deploy — ждём и init, и build, а используя if: always() запускаем её независимо от результатов выполнения предыдущих двух задач:
Actions
И последним рассмотрим «основной» компонент Github Actions — собственно, сами Actions.
Actions позволяют в job и step использовать уже созданные скрипты и утилиты из Github Actions Marketplace или готовые образы с Docker Hub, см. Finding and customizing actions.
В примере ниже используем actions/checkout@v2 для клонирования репозитория на runner-агент и omegion/argocd-app-actions для синхронизации ArgoCD приложения (см. ArgoCD: обзор, запуск, настройка SSL, деплой приложения).
Приложение ArgoCD
Подготовим тестовое приложение:
Обновляем argocd-cm ConfigMap, т.к. у admin по-умолчанию нет прав на использование токенов:
... data: accounts.admin: apiKey,login ...
Логинимся:
[simterm]
$ argocd login dev-1-18.argocd.example.com Username: admin Password: 'admin' logged in successfully Context 'dev-1-18.argocd.example.com' updated
[/simterm]
Генерируем токен для юзера admin:
[simterm]
$ argocd account generate-token eyJ***3Pc
[/simterm]
Github Actions workflow для ArgoCD
Добавляем секрет с токеном:
Создаём флоу:
name: "ArgoCD sync"
on: "push"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: "Clone reposiory"
uses: actions/checkout@v2
with:
repository: "argoproj/argocd-example-apps.git"
ref: "master"
- name: "Sync ArgoCD Application"
uses: omegion/argocd-app-actions@master
with:
address: "dev-1-18.argocd.example.com"
token: ${{ secrets.ARGOCD_TOKEN }}
appName: "guestbook"
Пушим в репозиторий, и проверяем:
И приложение в ArgoCD задеплоено:
Готово.




















