Обзор задачи

В статье рассматриваются различные варианты настройки CI/CD с использованием GitHub CI/CD и werf.

Рабочий процесс в репозитории (набор GitHub workflow конфигураций) будет строиться на базе следующих заданий:

  • converge — задание сборки, публикации образов и выката приложения для одного из контуров кластера;
  • dismiss — задание удаления приложения (используется только для review окружений);
  • cleanup — задание очистки хранилища стадий и Docker registry.

Ключевыми шагами заданий будут наши комплексные GitHub Actions, werf/actions, которые сочетают в себе все необходимые шаги по подготовке окружения и выполнения требуемых werf команд. В статье будут рассмотрены примеры использования большинства из них, больше подробностей можно найти в репозитории набора.

Набор контуров в кластере Kubernetes может варьироваться в зависимости от многих факторов. В статье будут приведены различные варианты организации окружений для следующих:

Далее последовательно рассматриваются задания и различные варианты их организации. Изложение построено от общего к частному. В конце статьи приведён полный набор конфигураций для готовых workflow.

Независимо от workflow, все версии конфигураций подчиняются следующим правилам:

  • Сборка и публикация является неотъемлемой частью выката.
  • Выкат/удаление review окружений:
    • Выкат на review окружение возможен только в рамках Pull Request (PR).
    • Review окружения удаляются автоматически при закрытии PR.
  • Очистка запускается один раз в день по расписанию на master.

Для выкатов review окружения, а также staging и production окружений предложены самые популярные варианты по организации. Каждый вариант для staging и production окружений сопровождается всевозможными способами отката релиза в production.

С общей информацией по организации CI/CD с помощью werf, а также информацией по конструированию своего workflow, можно ознакомиться в общей статье

Требования

Далее в примерах статьи будут использоваться виртуальные машины, предоставляемые GitHub, с OS Linux (runs-on: ubuntu-latest). Тем не менее, все примеры также справедливы для предустановленных self-hosted runners на базе любой OS

Сборка, публикация образов и выкат приложения

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

converge:
  name: Converge
  runs-on: ubuntu-latest
  steps:

    - name: Checkout code
      uses: actions/checkout@v2
      with:
        fetch-depth: 0

    - name: Converge
      uses: werf/actions/converge@master
      with:
        env: ANY_ENV_NAME
        kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
      env:
        WERF_SET_ENV_URL: "global.env_url=ANY_ENV_URL"

Данное задание можно разбить на два независимых, но в нашем случае (сборка и публикации вызывается не на каждый коммит, а используется только совместно с выкатом) это избыточно и ухудшит читаемость конфигурации и время выполнения.

build-and-publish & deploy jobs
build-and-publish:
  name: Build and Publish
  runs-on: ubuntu-latest
  steps:

    - name: Checkout code
      uses: actions/checkout@v2
      with:
        fetch-depth: 0

    - name: Build and Publish
      uses: werf/actions/build-and-publish@master
      with:
        kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

deploy:
  name: Deploy
  needs: build-and-publish
  runs-on: ubuntu-latest
  steps:

    - name: Checkout code
      uses: actions/checkout@v2
      with:
        fetch-depth: 0

    - name: Deploy
      uses: werf/actions/deploy@master
      with:
        env: production
        kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Первый шаг, с которого начинается задание — Checkout code, добавление исходных кодов приложения. При использовании сборщика werf (основная особенность которого — инкрементальная сборка) недостаточно, так называемого, shallow clone с единственным коммитом, который создаёт action actions/checkout@v2 при использовании без параметров.

Базируясь на истории git, werf создаёт стадии. При отсутствии истории каждая сборка будет проходить без ранее собранных стадий, поэтому, крайне важно, использовать параметр fetch-depth: 0 для доступа ко всей истории для всех команд, которые используют стадии: при сборке, публикации и выкате (werf build, werf converge), запуске (werf run) и т.д.

- name: Checkout code
  uses: actions/checkout@v2
  with:
    fetch-depth: 0

Следующим шагом используется action werf/actions/converge, который объединяет все необходимые шаги, подготавливает окружение и вызывает соответствующую команду.

- name: Converge
  uses: werf/actions/converge@master
  with:
    env: ANY_ENV_NAME
    kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
  env:
    WERF_SET_ENV_URL: "global.env_url=ANY_ENV_URL"

Среди предустановленного ПО на виртуальных машинах GitHub уже установлен kubectl, поэтому пользователю остаётся только:

  • определиться с конфигурацией kubeconfig;
  • создать секретную переменную KUBE_CONFIG_BASE64_DATA с контентом файла kubeconfig (cat ~/.kube/config | base64) в Settings/Secrets проекта на GitHub.
  • прокинуть секретную переменную secrets.KUBE_CONFIG_BASE64_DATA в используемый werf action:
- name: Converge
  uses: werf/actions/converge@master
  with:
    kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Конфигурация задания достаточно проста, поэтому хочется сделать акцент на том, чего в ней нет — явной авторизации в Docker registry, вызова docker login.

В простейшем случае, при использовании встроенной Github Packages имплементации Docker registry, авторизация выполняется автоматически при вызове команды werf ci-env. В качестве необходимых аргументов используются переменные окружения GitHub, секретная переменная GITHUB_TOKEN, а также имя пользователя (GITHUB_ACTOR) инициировавшего запуск workflow.

Если необходимо выполнить авторизацию с произвольными учётными данными или с внешним Docker registry, то необходимо использовать готовый action для вашей имплементации или просто выполнить docker login перед использованием werf.

Рассмотрим оставшиеся используемые параметры на этом шаге:

- name: Converge
  uses: werf/actions/converge@master
  with:
    env: ANY_ENV_NAME
  env:
    WERF_SET_ENV_URL: "global.env_url=ANY_ENV_URL"

Для каждого контура необходимо определить окружение. В нашем случае оно определяется следующими параметрами:

  • именем (ANY_ENV_NAME) и;
  • URL (ANY_ENV_URL).

Для того, чтобы по-разному конфигурировать приложение для используемых контуров кластера в helm-шаблонах можно использовать Go-шаблоны и переменную .Values.global.env, что соответствует значению, которое задаётся в качестве параметра у action (env).

Адрес окружения является необязательным. В данной статье используется исключительно в качестве примера организации окружений и для демонстрации работы с опциями werf при использовании actions (все опции werf можно задавать через переменные окружения)

Адрес окружения, URL для доступа к разворачиваемому в контуре приложению, который передаётся параметром global.env_url, может использоваться в helm-шаблонах, например, для конфигурации Ingress-ресурсов. Для того, чтобы определить URL, используется переменная окружения WERF_SET_ENV_URL, которая соответствует вызову werf с опцией --set (WERF_SET_<ANY_NAME>).

Если для шифрования значений переменных вы используете werf, то вам также необходимо добавить encryption key в переменную WERF_SECRET_KEY в Settings/Secrets проекта и добавить секрет в секцию env

Далее будут представлены популярные стратегии и практики, на базе которых мы предлагаем выстраивать ваши процессы в GitHub Actions.

Варианты организации review окружения

Как уже было сказано ранее, review окружение — это динамический контур, поэтому помимо выката, у этого окружения также будет и очистка.

Рассмотрим базовые GitHub workflow файлы, которые лягут в основу всех предложенных вариантов.

Сначала разберём файл .github/workflows/review_deployment.yml.

.github\workflows\review_deployment.yml
name: Review Deployment
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:
  
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      
      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN
  
      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

В нём пропущено условие запуска, т.к. оно зависит от выбранного варианта организации.

От базовой конфигурации задание отличается только появившимся шагом Define environment url. На этом шаге генерируется уникальный URL для каждого PR, по которому после выката будет доступно наше приложение (при соответствующей организации helm templates).

- name: Define environment url
  run: |
    pr_id=${{ github.event.number }}
    github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
    echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN

Далее файл .github/workflows/review_deployment_dismiss.yml.

.github\workflows\review_deployment_dismiss.yml
name: Review Deployment Dismiss
on:
  pull_request:
    types: [closed]
jobs:

  dismiss:
    name: Dismiss
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
      
      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN
  
      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Данный GitHub workflow будет выполняться при закрытии PR.

on:
  pull_request:
    types: [closed]

На шаге Dismiss выполняется удаление review-релиза: werf удаляет helm-релиз и namespace в Kubernetes со всем его содержимым (werf dismiss).

Далее разберём основные стратегии при организации выката review окружения.

Мы не ограничиваем вас предложенными вариантами, даже напротив — рекомендуем комбинировать их и создавать конфигурацию workflow под нужды вашей команды

№1 Вручную

Данный вариант реализует подход описанный в разделе Выкат на review из pull request по кнопке

При таком подходе пользователь выкатывает и удаляет окружение, проставляя соответствующий лейбл (review_start или review_stop) в PR.

Он самый простой и может быть удобен в случае, когда выкаты происходят редко и review окружение не используется при разработке. По сути, для проверки перед принятием PR.

.github\workflows\review_deployment.yml
name: Review Deployment
on:
  pull_request:
    types: [labeled]
jobs:

  labels:
    name: Label taking off
    if: github.event.label.name == 'review_start'
    runs-on: ubuntu-latest
    steps:

      - name: Take off label
        uses: actions/github-script@v1
        with:
          script: "github.issues.removeLabel({...context.issue, name: '${{ github.event.label.name }}' })"

  converge:
    name: Converge
    if: github.event.label.name == 'review_start'
    runs-on: ubuntu-latest
    steps:
  
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      
      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN
  
      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\review_deployment_dismiss.yml
name: Review Deployment Dismiss
on:
  pull_request:
    types: [labeled, closed]
jobs:

  labels:
    name: Label taking off
    if: github.event.label.name == 'review_stop'
    runs-on: ubuntu-latest
    steps:
    
      - name: Take off label
        uses: actions/github-script@v1
        with:
          script: "github.issues.removeLabel({...context.issue, name: '${{ github.event.label.name }}' })"

  dismiss:
    name: Dismiss
    if: github.event.label.name == 'review_stop' || github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

В данном варианте оба GitHub workflow ожидают проставление лейбла в PR.

on:
  pull_request:
    types: [labeled]

Если событие связано с добавлением лейбла review_start или review_stop, то выполняются задания соответствующего workflow. Иначе, при проставлении произвольного лейбла — workflow запускается, но ни одно задание не выполняется и он помечается как skipped. Используя фильтрацию по статусу, можно проследить активность в review окружении.

Шаг Label taking off снимает лейбл, который инициирует запуск workflow. Он используется в качестве индикатора обработки пользовательского запроса на выкат и остановку review окружения (а бонусом, мы можем отслеживать историю изменений и выкатов по логу в PR).

labels:
  name: Label taking off 
  runs-on: ubuntu-latest
  if: github.event.label.name == 'review_stop'
  steps:

    - name: Take off label
      uses: actions/github-script@v1
      with:
        script: "github.issues.removeLabel({...context.issue, name: github.event.label.name})"

№2 Автоматически по имени ветки

Данный вариант реализует подход описанный в разделе Выкат на review из ветки по шаблону автоматически

В предложенном ниже варианте автоматический релиз выполняется для каждого коммита в PR, в случае, если имя git-ветки содержит review.

.github\workflows\review_deployment.yml
name: Review Deployment
on:
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
jobs:

  converge:
    name: Converge
    if: ${{ contains( github.head_ref, 'review' ) }}
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\review_deployment_dismiss.yml
name: Review Deployment Dismiss
on:
  pull_request:
    types: [closed]
jobs:

  dismiss:
    name: Dismiss
    if: ${{ contains( github.head_ref, 'review' ) }}
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Выкат инициируется при коммите в ветку, открытии и переоткрытии PR, что соответствует набору событий по умолчанию для pull_request:

// equal conditions

on:
  pull_request:
    types:
      - opened
      - reopened
      - synchronize

on:
  pull_request:

№3 Полуавтоматический режим с лейблом (рекомендованный)

Данный вариант реализует подход описанный в разделе Выкат на review из pull request автоматически после ручной активации

Полуавтоматический режим с лейблом — это комплексное решение, объединяющие первые два варианта.

При проставлении специального лейбла, в примере ниже review, пользователь активирует автоматический выкат в review окружения для каждого коммита. При снятии лейбла автоматически запускается удаление review-релиза.

.github\workflows\optional_review_deployment.yml
name: Optional Review Deployment
on:
  pull_request:
    types:
      - labeled
      - unlabeled
      - synchronize
jobs:

  optional_converge_or_dismiss:
    name: Optional Converge or Dismiss
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      
      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN
        if: contains( github.event.pull_request.labels.*.name, 'review' )

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        if: contains( github.event.pull_request.labels.*.name, 'review' )

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        if: "!contains( github.event.pull_request.labels.*.name, 'review' )"
.github\workflows\review_deployment_dismiss.yml
name: Review Deployment Dismiss
on:
  pull_request:
    types: [closed]
jobs:

  dismiss:
    name: Dismiss
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Выкат инициируется при коммите в ветку, добавлении и снятии лейбла в PR, что соответствует следующему набору событий для pull_request:

pull_request:
  types:
    - labeled
    - unlabeled
    - synchronize

Варианты организации staging и production окружений

Предложенные далее варианты являются наиболее эффективными комбинациями правил выката staging и production окружений.

В нашем случае, данные окружения являются определяющими, поэтому названия вариантов соответствуют названиям окончательных workflow, предложенных в конце статьи.

№1 Fast and Furious (рекомендованный)

Данный вариант реализует подходы описанные в разделах Выкат на production из master автоматически и Выкат на production-like из pull request по кнопке

Выкат в production происходит автоматически при любых изменениях в master. Выполнить выкат в staging можно только проставив соответствующий лейбл в PR.

.github\workflows\staging_deployment.yml
name: Staging Deployment
on:
  pull_request:
    types: [labeled]
jobs:

  labels:
    name: Label taking off
    if: github.event.label.name == 'staging_deploy'
    runs-on: ubuntu-latest
    steps:
      
      - name: Take off label
        uses: actions/github-script@v1
        with:
          script: "github.issues.removeLabel({...context.issue, name: '${{ github.event.label.name }}' })"

  converge:
    name: Converge
    if: github.event.label.name == 'staging_deploy'
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=http://staging-company.kube.DOMAIN"
.github\workflows\production_deployment.yml
name: Production Deployment
on:
  push:
    branches: [master]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=https://www.company.org"

Варианты отката изменений в production:

№2 Push the Button (*)

Данный вариант реализует подходы описанные в разделах Выкат на production из master по кнопке и Выкат на staging из master автоматически

Мы не рекомендуем использовать данный подход с GitHub, так как в настоящий момент система не имеет достаточного инструментария для комфортной работы с Github Workflow. Работа с конкретными коммитами и ручной запуск отчасти сводятся к использованию внешних событий, repository_dispatch, но большинство кейсов остаются без решений, что снижает ценность и зрелость данного подхода.

Выкат production осуществляется вручную на master, а выкат в staging происходит автоматически при любых изменениях в master.

.github\workflows\staging_deployment.yml
name: Staging Deployment
on:
  push:
    branches: [master]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=http://staging-company.kube.DOMAIN"
.github\workflows\production_deployment.yml
name: Production Deployment
on:
  repository_dispatch:
    types: [production_deployment]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=https://www.company.org"

Имеем следующее условие для релиза в production:

on:
  repository_dispatch:
    types: [production_deployment]

Чтобы запустить данный workflow, достаточно выполнить следующий запрос:

curl \
  --location --request POST 'https://api.github.com/repos/<company>/<project>/dispatches' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/vnd.github.everest-preview+json' \
  --header "Authorization: token $GITHUB_TOKEN" \
  --data-raw '{
    "event_type": "production_deployment",
    "client_payload": {}
  }'

Для использования данного подхода можно добавить скрипт в репозиторий проекта, использовать postman, плагин для браузера и т.д.

Варианты отката изменений в production:

  • выкат стабильного PR и инициализация выката при помощи repository_dispatch.

№3 Tag everything (*)

Данный вариант реализует подходы описанные в разделах Выкат на production из тега автоматически и Выкат на staging из master по кнопке

Мы не рекомендуем использовать данный подход с GitHub, так как в настоящий момент система не имеет достаточного инструментария для комфортной работы с Github Workflow. Работа с конкретными коммитами и ручной запуск отчасти сводятся к использованию внешних событий, repository_dispatch, но большинство кейсов остаются без решений, что снижает ценность и зрелость данного подхода.

Выкат в production выполняется при проставлении тега, а в staging осуществляется вручную на master.

.github\workflows\staging_deployment.yml
name: Staging Deployment
on:
  repository_dispatch:
    types: [staging_deployment]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=http://staging-company.kube.DOMAIN"
.github\workflows\production_deployment.yml
name: Production Deployment
on:
  push:
    tags:
    - v*
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=https://www.company.org"
on:
  repository_dispatch:
    types: [staging_deployment]

Чтобы запустить данный workflow, достаточно выполнить следующий запрос:

curl \
  --location --request POST 'https://api.github.com/repos/<company>/<project>/dispatches' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/vnd.github.everest-preview+json' \
  --header "Authorization: token $GITHUB_TOKEN" \
  --data-raw '{
    "event_type": "staging_deployment",
    "client_payload": {}
  }'

Для использования данного подхода можно добавить скрипт в репозиторий проекта, использовать postman, плагин для браузера и т.д.

Варианты отката изменений в production:

  • создание нового тега на старый коммит.

№4 Branch, branch, branch!

Данный вариант реализует подходы описанные в разделах Выкат на production из ветки автоматически и Выкат на production-like из ветки автоматически

Выкат в production происходит автоматически при любых изменениях в ветке production, а в staging при любых изменениях в ветке master.

.github\workflows\staging_deployment.yml
name: Staging Deployment
on:
  push:
    branches:
    - master
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=http://staging-company.kube.DOMAIN"
.github\workflows\production_deployment.yml
name: Production Deployment
on:
  push:
    branches:
    - production
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=https://www.company.org"

Варианты отката изменений в production:

Очистка образов

name: Cleanup Docker registry
on:
  schedule:
    - cron:  '0 6 * * *'
  repository_dispatch:
    types: [cleanup]

jobs:
  cleanup:
    name: Cleanup
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Fetch all history for all tags and branches
        run: git fetch --prune --unshallow

      - name: Cleanup
        uses: werf/actions/cleanup@master
        with:
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Первый шаг, с которого начинается задание — Checkout code, добавление исходных кодов приложения.

Большинство политик очистки в werf базируется на примитивах git (на коммите, ветке и теге), поэтому использование action actions/checkout@v2 без дополнительных параметров и действий может приводить к неожиданному удалению образов. Мы рекомендуем использовать следующие шаги для корректной работы.

- name: Checkout code
  uses: actions/checkout@v2
  
- name: Fetch all history for all tags and branches
  run: git fetch --prune --unshallow

В werf встроен эффективный механизм очистки, который позволяет избежать переполнения Docker registry и диска сборочного узла от устаревших и неиспользуемых образов. Более подробно ознакомиться с функционалом очистки, встроенным в werf, можно здесь.

Полный набор конфигураций для готовых workflow

Детали workflow

Подробнее про workflow можно почитать в отдельной статье

Конфигурации

.github\workflows\optional_review_deployment.yml
name: Optional Review Deployment
on:
  pull_request:
    types:
      - labeled
      - unlabeled
      - synchronize
jobs:

  optional_converge_or_dismiss:
    name: Optional Converge or Dismiss
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      
      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN
        if: contains( github.event.pull_request.labels.*.name, 'review' )

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        if: contains( github.event.pull_request.labels.*.name, 'review' )

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        if: "!contains( github.event.pull_request.labels.*.name, 'review' )"
.github\workflows\review_deployment_dismiss.yml
name: Review Deployment Dismiss
on:
  pull_request:
    types: [closed]
jobs:

  dismiss:
    name: Dismiss
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\staging_deployment.yml
name: Staging Deployment
on:
  pull_request:
    types: [labeled]
jobs:

  labels:
    name: Label taking off
    if: github.event.label.name == 'staging_deploy'
    runs-on: ubuntu-latest
    steps:
      
      - name: Take off label
        uses: actions/github-script@v1
        with:
          script: "github.issues.removeLabel({...context.issue, name: '${{ github.event.label.name }}' })"

  converge:
    name: Converge
    if: github.event.label.name == 'staging_deploy'
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=http://staging-company.kube.DOMAIN"
.github\workflows\production_deployment.yml
name: Production Deployment
on:
  push:
    branches: [master]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=https://www.company.org"
.github\workflows\cleanup.yml
name: Cleanup Docker registry
on:
  schedule:
    - cron:  '0 6 * * *'
  repository_dispatch:
    types: [cleanup]

jobs:
  cleanup:
    name: Cleanup
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Fetch all history for all tags and branches
        run: git fetch --prune --unshallow

      - name: Cleanup
        uses: werf/actions/cleanup@master
        with:
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Детали workflow

Подробнее про workflow можно почитать в отдельной статье

Мы не рекомендуем использовать данный подход с GitHub, так как в настоящий момент система не имеет достаточного инструментария для комфортной работы с Github Workflow. Работа с конкретными коммитами и ручной запуск отчасти сводятся к использованию внешних событий, repository_dispatch, но большинство кейсов остаются без решений, что снижает ценность и зрелость данного подхода.

Конфигурации

.github\workflows\review_deployment.yml
name: Review Deployment
on:
  pull_request:
    types: [labeled]
jobs:

  labels:
    name: Label taking off
    if: github.event.label.name == 'review_start'
    runs-on: ubuntu-latest
    steps:

      - name: Take off label
        uses: actions/github-script@v1
        with:
          script: "github.issues.removeLabel({...context.issue, name: '${{ github.event.label.name }}' })"

  converge:
    name: Converge
    if: github.event.label.name == 'review_start'
    runs-on: ubuntu-latest
    steps:
  
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      
      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN
  
      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\review_deployment_dismiss.yml
name: Review Deployment Dismiss
on:
  pull_request:
    types: [labeled, closed]
jobs:

  labels:
    name: Label taking off
    if: github.event.label.name == 'review_stop'
    runs-on: ubuntu-latest
    steps:
    
      - name: Take off label
        uses: actions/github-script@v1
        with:
          script: "github.issues.removeLabel({...context.issue, name: '${{ github.event.label.name }}' })"

  dismiss:
    name: Dismiss
    if: github.event.label.name == 'review_stop' || github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\staging_deployment.yml
name: Staging Deployment
on:
  push:
    branches: [master]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=http://staging-company.kube.DOMAIN"
.github\workflows\production_deployment.yml
name: Production Deployment
on:
  repository_dispatch:
    types: [production_deployment]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=https://www.company.org"
.github\workflows\cleanup.yml
name: Cleanup Docker registry
on:
  schedule:
    - cron:  '0 6 * * *'
  repository_dispatch:
    types: [cleanup]

jobs:
  cleanup:
    name: Cleanup
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Fetch all history for all tags and branches
        run: git fetch --prune --unshallow

      - name: Cleanup
        uses: werf/actions/cleanup@master
        with:
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Детали workflow

Подробнее про workflow можно почитать в отдельной статье

Мы не рекомендуем использовать данный подход с GitHub, так как в настоящий момент система не имеет достаточного инструментария для комфортной работы с Github Workflow. Работа с конкретными коммитами и ручной запуск отчасти сводятся к использованию внешних событий, repository_dispatch, но большинство кейсов остаются без решений, что снижает ценность и зрелость данного подхода.

Конфигурации

.github\workflows\review_deployment.yml
name: Review Deployment
on:
  pull_request:
    types: [labeled]
jobs:

  labels:
    name: Label taking off
    if: github.event.label.name == 'review_start'
    runs-on: ubuntu-latest
    steps:

      - name: Take off label
        uses: actions/github-script@v1
        with:
          script: "github.issues.removeLabel({...context.issue, name: '${{ github.event.label.name }}' })"

  converge:
    name: Converge
    if: github.event.label.name == 'review_start'
    runs-on: ubuntu-latest
    steps:
  
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      
      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN
  
      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\review_deployment_dismiss.yml
name: Review Deployment Dismiss
on:
  pull_request:
    types: [labeled, closed]
jobs:

  labels:
    name: Label taking off
    if: github.event.label.name == 'review_stop'
    runs-on: ubuntu-latest
    steps:
    
      - name: Take off label
        uses: actions/github-script@v1
        with:
          script: "github.issues.removeLabel({...context.issue, name: '${{ github.event.label.name }}' })"

  dismiss:
    name: Dismiss
    if: github.event.label.name == 'review_stop' || github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\staging_deployment.yml
name: Staging Deployment
on:
  repository_dispatch:
    types: [staging_deployment]
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=http://staging-company.kube.DOMAIN"
.github\workflows\production_deployment.yml
name: Production Deployment
on:
  push:
    tags:
    - v*
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=https://www.company.org"
.github\workflows\cleanup.yml
name: Cleanup Docker registry
on:
  schedule:
    - cron:  '0 6 * * *'
  repository_dispatch:
    types: [cleanup]

jobs:
  cleanup:
    name: Cleanup
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Fetch all history for all tags and branches
        run: git fetch --prune --unshallow

      - name: Cleanup
        uses: werf/actions/cleanup@master
        with:
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

Детали workflow

Подробнее про workflow можно почитать в отдельной статье

Конфигурации

.github\workflows\review_deployment.yml
name: Review Deployment
on:
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
jobs:

  converge:
    name: Converge
    if: ${{ contains( github.head_ref, 'review' ) }}
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Define environment url
        run: |
          pr_id=${{ github.event.number }}
          github_repository_id=$(echo ${GITHUB_REPOSITORY} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
          echo ::set-env name=WERF_SET_ENV_URL::global.env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\review_deployment_dismiss.yml
name: Review Deployment Dismiss
on:
  pull_request:
    types: [closed]
jobs:

  dismiss:
    name: Dismiss
    if: ${{ contains( github.head_ref, 'review' ) }}
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Dismiss
        uses: werf/actions/dismiss@master
        with:
          env: review-${{ github.event.number }}
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
.github\workflows\staging_deployment.yml
name: Staging Deployment
on:
  push:
    branches:
    - master
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=http://staging-company.kube.DOMAIN"
.github\workflows\production_deployment.yml
name: Production Deployment
on:
  push:
    branches:
    - production
jobs:

  converge:
    name: Converge
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@master
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "global.env_url=https://www.company.org"
.github\workflows\cleanup.yml
name: Cleanup Docker registry
on:
  schedule:
    - cron:  '0 6 * * *'
  repository_dispatch:
    types: [cleanup]

jobs:
  cleanup:
    name: Cleanup
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Fetch all history for all tags and branches
        run: git fetch --prune --unshallow

      - name: Cleanup
        uses: werf/actions/cleanup@master
        with:
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}