コネヒト開発者ブログ

コネヒト開発者ブログ

Draft Releaseデプロイフローを作成してみました

こんにちは。サーバーサイドエンジニアの岡田です

みなさんはどんなデプロイフローを採用していますでしょうか?今回は自分が取り組んでいたデプロイフローのちょっとした改善を皆さんに紹介したいと思います!

きっかけ

現状コネヒトではgdpを使ったデプロイ方法を採用しており、手元のコマンドからデプロイするのが標準になっています。

tech.connehito.com

元々自分はgit-pr-releaseを使ったワンクリックデプロイを以前の現場で使っており、手元でコマンドを打たなければいけないのが地味にめんどうだなあと感じていました。 そこでワンクリックでもっとラクしてデプロイしたい!と思ったのが今回のデプロイフローを考案したきっかけになります!

それ以外にも手元のコマンドからデプロイする事に感じていた課題感

  • 手元でコマンド打つ場合、打ち間違いやコマンド履歴等から意図せずデプロイしてしまう可能性
  • devデプロイ〜本番リリースの間(ビルドの7〜8分)地味にブランチを切り替え辛い(リリース前にmainブランチにcheckoutが必要なため)
  • 新しい人が入社してきた場合にgdp&hubのインストールと設定が必要になる
  • 本番デプロイ後にリリースノートを公開(gdp publish)するのを忘れてしまう

そしてこのデプロイフローが生まれた背景

当初はgit-pr-releaseを導入すれば簡単にワンクリックデプロイ出来るようになる!と考えていました。しかしいざgit-pr-releaseを導入しようと調べてみると、難しい背景がいくつか見つかりました。

ちなみにgit-pr-releaseとは

git-pr-releaseとは、本番環境向けの対象ブランチへのリリースPRを作成&リリース内容を一覧にして作成してくれるgemライブラリです。導入も簡単なので使える環境であればぜひ使ってみて欲しいです

こちらの記事が非常に参考になります!

songmu.jp

まずgit-pr-releaseが想定しているブランチモデルは

  • 本番用のmainブランチと開発用のdevelopブランチが並走する形で、developブランチからfeatureブランチを切ってmainブランチへマージしていく、いわゆるgit flowの簡易版の様なフローが前提
  • mainブランチへのマージが本番デプロイへのトリガーとして想定されている
  • 環境構成:本番環境(main)、dev環境(develop)

git-pr-releaseが想定しているブランチモデル

対してコネヒトでは

  • GitHub Flowを踏襲しており、本番用のmainブランチからfeatureブランチを切ってmainブランチへマージしていくフローになっている
  • 最新のmainブランチから切ったtagのpushが本番デプロイへのトリガーになっている
  • 環境構成:本番環境(最新のtag)、dev環境(featureブランチ)

コネヒトのブランチモデル

なのでもしgit-pr-releaseを採用する場合はブランチ戦略自体を見直す必要があり、流石にそれは影響範囲が大きすぎるかつ現実的ではありませんでした。そこで今あるブランチ戦略を変えずにもっと簡単にデプロイを実現する方法として考えたのがこの「Draft Releaseデプロイフロー」です。

そして実際にできたデプロイフローがこちら

いつものようにPRをマージします

Slackにdev環境へのデプロイ完了とドラフトリリースの作成が通知されます

作成されたドラフトリリースを

Publishすると

tagが切られ本番デプロイが走ると言った感じです

ビフォーアフター

既存のgdpを使ったデプロイフロー

  1. mainブランチからfeatureブランチを切る
  2. feature PRをmainブランチへマージ(dev環境へデプロイ)
  3. dev環境へのデプロイが完了したら、手元でgdpコマンドからタグを切ってpush
    1. 手元のmainを最新化(git checkout main && git pull origin main && git pull --tags)
    2. リリース内容確認(gdp deploy -d)
    3. タグを切って本番デプロイ(gdp deploy)
  4. 本番環境へのデプロイが完了したら、手元でgdpを使ってリリースノートを公開
    1. リリースノート内容確認(gdp publish -d)
    2. リリースノート公開(gdp publish)

デプロイフローBefore

今回作成したDraft Releaseデプロイフロー

  1. mainブランチからfeatureブランチを切る
  2. feature PRをmainブランチへマージ(dev環境へデプロイ)
  3. dev環境へのデプロイが完了したら、GitHub上から下書きリリースノートの内容を確認し、問題なければ公開(合わせてタグも切られるのでそのまま本番デプロイ)

デプロイフローAfter

改善ポイント

  • 手元でコマンド打つ場合、打ち間違いやコマンド履歴等から意図せずデプロイしてしまう可能性
    • GUI上から確認できるので、意図せずデプロイしてしまうといったことを防げる
  • devデプロイ〜本番リリースの間(ビルドの7〜8分)地味にブランチを切り替え辛い(リリース前にmainブランチにcheckoutが必要なため)
    • devデプロイ後は別のブランチに切り替えてすぐ作業の続きができるように
  • 新しい人が入社してきた場合にgdp&hubのインストールと設定が必要になる
    • 今回の対応で不要に
  • 本番デプロイ後にリリースノートを公開(gdp publish)するのを忘れてしまう
    • 必ずリリースノートが作成されるフローに

実際のコード

呼び出し元

ドラフトリリース作成ワークフロー

下記コードを各PJのリポジトリに展開するだけでドラフトリリースが作成されます!

on:
  push:
    branches:
      - main

permissions:
  contents: write

jobs:
  deploy:
    # デプロイ処理
    ...

  create_draft_release:
    if: ${{ success() }}
    needs: [ deploy ]
    uses: {Reusable Workflow用リポジトリ}/reusable-workflow/.github/workflows/create_draft_release.yml@main
    secrets:
      slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}

呼び出される側

ドラフトリリース作成ワークフロー

ドラフトリリース作成actionの実行と、Slack通知を行っています。

name: Create Draft Release
on:
  workflow_call:
    secrets:
      slack-webhook-url:
        required: true

jobs:
  start:
    runs-on: ubuntu-latest
    steps:
      - name: Action create-draft-release
        id: create-draft-release
        uses: {Reusable Workflow用リポジトリ}/reusable-workflow/.github/actions/create-draft-release@main

      - name: Notify slack of create draft release
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          fields: repo,workflow
          author_name: ${{ github.actor }}
          text: |
            ドラフトリリースが作成されました。<${{ steps.create-draft-release.outputs.draft-release-link }}|${{ steps.create-draft-release.outputs.draft-release-tag }}>
            <${{ github.server_url }}/${{ github.repository }}/releases|リリース一覧>
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.slack-webhook-url }}

実装時のポイント

Reusable Workflowとはいわゆる再利用可能なワークフローの事で、各リポジトリで共通のワークフローを実行することができます! 作成方法はシンプルでon.workflow_callを定義したymlファイルを作成するだけです。

on:
  workflow_call:

今回Reusable Workflowとして実装した理由ですが、Reusable Workflowと後述のaction.ymlの大きな違いとしては実行環境に左右されるかどうかだと考えています。

Reusable Workflowの場合はワークフロー(1つ以上のジョブ)でしか実行できないので前後の処理や実行環境に左右されません。逆にaction.ymlではジョブの一ステップとして実行されるので、必要であれば actions/checkout@v2actions/setup-node@v2 など使って実行環境を整える必要があります。

そして今回のDraft Release作成処理は完全に処理が独立しているので、リポジトリ毎の実行環境に左右されずに横展開しやすいReusable Workflowで実装しました

Reusable Workflow使用時の注意点としては実行されるコードはあくまで呼び出し元で実行されるので

  • actions/checkout を使用する場合は呼び出し元のブランチにチェックアウトされる
  • Reusable Workflowのリポジトリ内にあるファイルは参照できない

と言った点に注意が必要です!上記点を解決するために次のaction.ymlへとつながります

詳しくは公式ドキュメントを確認してください!

docs.github.com

ドラフトリリース作成処理本体

こちらがメインのドラフトリリース作成処理です。実行している処理はシンプルにGitHub CLIを使ったリリースノートの作成と削除を行なっています。(削除はリリースノートの自動生成を使いたいため一度削除→再作成しています)

name: Create Draft Release Action
description: Create draft release with calver tag

outputs:
  draft-release-link:
    description: Create draft release link.
    value: ${{ steps.create-draft-release.outputs.draft-release-link }}
  draft-release-tag:
    description: Create draft release tag.
    value: ${{ steps.create-draft-release.outputs.draft-release-tag }}

runs:
  using: "composite"
  steps:
    - name: Create Draft Release
      id: create-draft-release
      env:
        TZ: "Asia/Tokyo"
        GH_TOKEN: ${{ github.token }}
      shell: bash
      run: |
        # 最新のタグ取得
        latest_tag=$(gh release list --repo '${{ github.repository }}' | awk -F'\\t' '$2=="Latest" { print $3 }')
        echo "latest_tag:${latest_tag}"

        # calver形式での次バージョンのタグ取得
        next_tag=$(/bin/bash ${{ github.action_path }}/calver.sh $latest_tag)
        echo "next_tag:${next_tag}"

        # draft release取得
        draft_tag=$(gh release list --repo '${{ github.repository }}' | awk -F'\\t' '$2=="Draft" { print $3 }' | head -n 1)
        echo "draft_tag:${draft_tag}"

        # draft release が既に存在していれば、削除してから再作成(リリースノートの自動生成を使いたいため)
        if [ "$draft_tag" != "" ]; then
          gh release delete $draft_tag --repo '${{ github.repository }}' -y
        fi
        draft_release_link=$(
          gh release create $next_tag \\
            --repo '${{ github.repository }}' \\
            --title "Release ${next_tag}" \\
            --latest --draft --generate-notes
        )
        echo "draft-release-link=${draft_release_link}" >> "${GITHUB_OUTPUT}"
        echo "draft-release-tag=${next_tag}" >> "${GITHUB_OUTPUT}"
        echo $draft_release_link

実装時のポイント

action.ymlとはいわゆるメタデータ構文と言われているもので、ワークフロー内の処理を切り出したり共通化することができます! 普段よく使っているようなactions/checkoutのようなアクションを作成できるイメージです。作成方法はこちらもシンプルでaction.ymlまたはaction.yamlのファイル名で作成するだけです

action.ymlではパブリックリポジトリにあるアクションはもちろん、同一リポジトリ内のアクションからプライベートリポジトリのアクションも実行可能です。

    # パブリックのaction
    uses: actions/create-draft-release@main
    # 同一リポジトリ内のaction
    uses: ./.github/actions/create-draft-release
    # プライベートリポジトリのaction
    uses: {owner}/{repo}/.github/actions/create-draft-release@main

詳しくは公式ドキュメントと

docs.github.com

こちらの記事が参考になりました!

developer.mamezou-tech.com

calver形式でのタグ作成シェルスクリプト

このシェルスクリプトを作成した背景ですが、前提としてコネヒトではcalver形式でのバージョン管理を採用しています。

タグ作成にあたってできるだけ既存と同じようなcalver形式のタグを作成する必要があったのですが、現状これと同じようなタグを作成するツールが見つかりませんでした。そこで元々のgdpコマンドで行なっていたタグ作成ロジックをそのままコピーしてきて、このシェルスクリプトを作成することにしました。(GitHub Actionsとの相性や実行の手軽さも考えてシェルスクリプトにしました)

#!/bin/bash
set -eu

##############################
## Get next calver
## @ref <https://github.com/Connehito/gdp/blob/main/format.go>
##############################
today=$(date +'%Y%m%d')
regexp="(.*)([0-9]{8})\\.(.+)"

version=${1:-$today}

if [[ $version =~ $regexp ]]; then
    if [[ $today = ${BASH_REMATCH[2]} ]]; then
        minor=${BASH_REMATCH[3]}
        next=$(($minor + 1))
        echo "${BASH_REMATCH[1]}${today}.${next}"
        exit 0
    fi
    echo "${BASH_REMATCH[1]}${today}.1"
    exit 0
fi
echo "${today}.1"
exit 0

おわりに

同じような環境でデプロイフローに悩みを感じでいる方の参考になれば嬉しいです