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

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

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

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

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

  • Production;
  • Staging;
  • Review.

Далее последовательно рассматриваются задания и различные варианты их организации. Изложение построено от общего к частному. В конце статьи приведён полный набор конфигураций для готовых 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@v3
      with:
        fetch-depth: 0

    - name: Converge
      uses: werf/actions/converge@v2
      with:
        env: ANY_ENV_NAME
        kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
      env:
        WERF_SET_ENV_URL: "envUrl=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@v3
      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@v3
      with:
        fetch-depth: 0

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

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

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

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

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

- name: Converge
  uses: werf/actions/converge@v2
  with:
    env: ANY_ENV_NAME
    kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
  env:
    WERF_SET_ENV_URL: "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@v2
  with:
    kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}

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

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

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

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

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

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

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

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

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

Адрес окружения, URL для доступа к разворачиваемому в контуре приложению, который передаётся параметром 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@v3
        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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV
  
      - name: Converge
        uses: werf/actions/converge@v2
        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 WERF_SET_ENV_URL=env_url=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV

Далее файл .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@v3
      
      - 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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV
  
      - name: Dismiss
        uses: werf/actions/dismiss@v2
        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:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3
        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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV
  
      - name: Converge
        uses: werf/actions/converge@v2
        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:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3

      - name: Dismiss
        uses: werf/actions/dismiss@v2
        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:
        github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3
        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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV

      - name: Converge
        uses: werf/actions/converge@v2
        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@v3

      - name: Dismiss
        uses: werf/actions/dismiss@v2
        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@v3
        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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV
        if: contains( github.event.pull_request.labels.*.name, 'review' )

      - name: Converge
        uses: werf/actions/converge@v2
        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@v2
        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@v3

      - name: Dismiss
        uses: werf/actions/dismiss@v2
        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:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

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

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

  • revert изменений в master (рекомендованный);
  • развёртывание стабильного PR.

№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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

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

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

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

name: Cleanup container 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@v3

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

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

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

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

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

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

В werf встроен эффективный механизм очистки, который позволяет избежать переполнения container 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@v3
        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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV
        if: contains( github.event.pull_request.labels.*.name, 'review' )

      - name: Converge
        uses: werf/actions/converge@v2
        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@v2
        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@v3

      - name: Dismiss
        uses: werf/actions/dismiss@v2
        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:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=https://www.company.org"
.github/workflows/cleanup.yml
name: Cleanup container 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@v3

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

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

Детали workflow

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

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

  • Развёртывание на review контур по стратегии №1 Вручную.
  • Развёртывание на staging и production контуры осуществляется по стратегии №2 Push the Button.
  • Очистка стадий выполняется по расписанию раз в сутки.

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

.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:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3
        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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV
  
      - name: Converge
        uses: werf/actions/converge@v2
        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:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3

      - name: Dismiss
        uses: werf/actions/dismiss@v2
        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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=https://www.company.org"
.github/workflows/cleanup.yml
name: Cleanup container 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@v3

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

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

Детали workflow

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

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

  • Развёртывание на review контур по стратегии №1 Вручную.
  • Развёртывание на staging и production контуры осуществляется по стратегии №3 Tag everything.
  • Очистка стадий выполняется по расписанию раз в сутки.

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

.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:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3
        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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV
  
      - name: Converge
        uses: werf/actions/converge@v2
        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:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: >
            github.issues.removeLabel({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              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@v3

      - name: Dismiss
        uses: werf/actions/dismiss@v2
        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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=https://www.company.org"
.github/workflows/cleanup.yml
name: Cleanup container 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@v3

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

      - name: Cleanup
        uses: werf/actions/cleanup@v2
        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@v3
        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 WERF_SET_ENV_URL=envUrl=http://${github_repository_id}-${pr_id}.kube.DOMAIN >> $GITHUB_ENV

      - name: Converge
        uses: werf/actions/converge@v2
        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@v3

      - name: Dismiss
        uses: werf/actions/dismiss@v2
        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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: staging
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=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@v3
        with:
          fetch-depth: 0

      - name: Converge
        uses: werf/actions/converge@v2
        with:
          env: production
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
        env:
          WERF_SET_ENV_URL: "envUrl=https://www.company.org"
.github/workflows/cleanup.yml
name: Cleanup container 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@v3

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

      - name: Cleanup
        uses: werf/actions/cleanup@v2
        with:
          kube-config-base64-data: ${{ secrets.KUBE_CONFIG_BASE64_DATA }}
назад
далее