はじめに
これは2021年振り返りカレンダーの2日目の記事です.
前回はCloud Run上で動くサービス用にGitHub flowでのCD環境を構築する話でした.
前回の最後やいろんな記事で述べられているとおり,GitHub flowは審査が必要なネイティブアプリなど,アプリケーションの リリースタイミングをこちら側でコントロールできないケースで不都合が発生します(モノレポ想定です).
例えばあるアプリについて,後方互換のない新機能が入ったバージョンv2がmainにマージされ,stg環境に 上がっているとします. この時,prdのネイティブ以外の部分でなんらかのバグが見つかり,すぐに修正を行いたいとき,通常のサービスならば, 修正PRをmainにマージし,新規機能とともにリリースすることができます. しかしネイティブアプリの場合,アプリの審査がリリース前に挟まれるため,修正をデプロイするには, 審査を待つか,既にmainに入っている新機能をrevertするしかありません.
対策としては,ネイティブアプリのレポジトリを分けることが考えられますが, 自分のいたチームでは全員が機能開発に対してバックエンド,アプリを両方書いていたため, PRの管理やリリースバージョンの統一が面倒になるなど別の問題が想定されました.
そこで,今回はGitHub flowをやめ,GitLab flowを採用することとしました. GitLab flowの詳細については以下に詳しく書いてあります.
要点をまとめると以下のような感じです.
- mainブランチに加え,環境(stg, prdなど)ごとにブランチを用意する.
- 新規の変更はmainからブランチを切って,mainにマージする.
- 検証環境にデプロイする際はmainをそのブランチへマージし,デプロイする
- 検証が終わったら,検証ブランチを本番ブランチにマージし,デプロイ
- 本番に近いブランチは,必ずその前の全ての検証ブランチを通っているようにする
仕様
今回はクライアントはiOSアプリ,バックエンドはCloud Runに乗せている形を想定します. また,環境は一旦stg環境とprd環境の2つとします.
そして,仕様は次のような形です.
- 新規の変更はmainブランチから切り,mainブランチにマージ.また,検証用ブランチとしてstaging,本番ブランチとしてprductionを用意
- 常にmainからstgへのPRを生成し,リリースのタイミングでそれをstagingへマージし,リリースコマンドを打つ.このタイミングで,stagingからproductionへのPRを生成.
- staging上での検証が終わったら,stgからprdへのPRをマージ.アプリをビルドし,審査へ提出.審査にapiの変更が必要な場合はこのタイミングでデプロイ.
- 審査が通ったら,必要に応じてmigrationを行い,apiをデプロイ.
また,hotfixの流れは以下のような形です,
実装
ディレクトリ構成は前回と同じです. CDフローに必要なファイルなどは,release下に,環境ごとに用意します.
root/ ├── .github/workflows │ ├── release.yaml │ ├── hotfix.yaml │ └── prerelease.yaml ├── api ├── ui ├── release │ ├── dev │ └── prd └── release.sh
新しいバージョンのリリースとstagingへのデプロイ
新規バージョンのリリースの際はまず,mainからstgingブランチへ差分をマージします. この差分PRを常時生成するために, git-pr-releaseを用いました.
GitHub - x-motemen/git-pr-release: Release pull request generator
git-pr-releaseでは,ブランチ同士を比較して, 差分がある場合はPRの自動生成と差分コミットの詳細を作ってくれます.
設定ファイルは以下のような感じです. GIT_PR_RELEASE_BRANCH_STAGINGにはマージ元を,GIT_PR_RELEASE_BRANCH_PRODUCTIONにマージ先を設定します. また,GIT_PR_RELEASE_TEMPLATEには生成するPRのテンプレートを設定することができます.
# prerelease.yaml on: push: branches: [main] jobs: create-release-pr: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Setup Ruby uses: actions/setup-ruby@v1 with: ruby-version: 2.7.x - name: Create Staging release pull Request env: GIT_PR_RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }} GIT_PR_RELEASE_BRANCH_PRODUCTION: staging GIT_PR_RELEASE_BRANCH_STAGING: main GIT_PR_RELEASE_LABELS: staging GIT_PR_RELEASE_TEMPLATE: .github/workflows/prerelease_template run: | gem install -N git-pr-release -v "1.9.0" git-pr-release --no-fetch --squashed
次に,新規バージョンタグをプッシュした際の動きを実装します. その際には以下を行います.
- リリースノートの生成
- stagingからproductionへのPR生成
- staging環境へのデプロイ
リリースノートの生成は,前回と同様以下を用います.
stagingから,productionへのPR生成は,上記のrelease.yamlに以下の動作を追加し行います.
リリース用のブランチ(名前はrelease/v0.0.0)を生成したのち,そのブランチからproductionへ 向けたPRを生成しています.
# release.yaml on: push: tags: - "v*" name: Create Release jobs: build: name: Create Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 with: fetch-depth: 0 - name: Get version id: get_version run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} ############### 省略 ############### # リリース用のブランチを生成 - name: Create Release Branch id: create_release_branch env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git checkout pre-production git checkout -b release/${{ steps.get_version.outputs.VERSION }} git push origin release/${{ steps.get_version.outputs.VERSION }} # 作成したリリースブランチからprdブランチへのPRを生成 - name: Create Prd Release Pull Request id: create_release_pr uses: repo-sync/pull-request@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: pr_title: Production Release ${{ steps.get_version.outputs.VERSION }} pr_label: production-deploy destination_branch: production source_branch: release/${{ steps.get_version.outputs.VERSION }}
staging環境へのデプロイは,前回と同じく,cloud buildのトリガーを用います. また,iosアプリの方のCDについてはまた次回以降で書く予定です.
productionへのデプロイ
productionへのデプロイは,以下の2つのステップに分かれています.
- ネイティブアプリの審査提出
- apiのデプロイ
ネイティブアプリの審査への提出は常に行われますが,apiのデプロイは ケースによって審査に必須であったり必要なかったりします(バージョン整合のため).
なので,アプリの審査提出は自動化し,apiのデプロイは手動で行うこととします.
ネイティブアプリの審査提出に関しては,また次回以降で解説します.
apiのデプロイに関しては,前回と同様,release/prd下のスクリプトを叩くことで行います.
#!/bin/bash function deploy() { gcloud beta builds triggers run --tag $2 service-prd-api-trigger } $1 $@
cd release/prd sh deploy.sh deploy v0.0.0
これで通常のCDフローは完了です.
hotfix
次はhotfixへの対応です. hotfixというprefixを持つブランチがpushされた時,自動でmainとstagingブランチへのPRを生成するようにします (ぼーっとしてるとどっちかを入れ忘れるため). コードは以下のような形です.
# hotfix.yaml on: push: branches: ["hotfix/*"] env: BRANCH_NAME: $(echo ${GITHUB_REF#refs/heads/}) jobs: create_hotfix_pr: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Main Create Hotfix Pull Request id: main_create_hotfix_pr uses: repo-sync/pull-request@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: pr_title: "Main ${{ env.BRANCH_NAME }}" pr_label: "hotfix" destination_branch: main - name: Pre-Production Create Hotfix Pull Request id: preproduction_create_hotfix_pr uses: repo-sync/pull-request@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: pr_title: "Pre-Production ${{ env.BRANCH_NAME }}" pr_label: "hotfix" destination_branch: staging
これで全体の構築が完了です.
その他細かい知見など
- git-pr-releaseはsquashマージと通常マージの両方に対応可能.
- mainからstagingブランチへのマージをsquashにしてしまうと,リリースノートがsquashコミットだけになって 差分の確認がしづらくなるので,ここは通常マージにしたほうが良い(めんどいので,もっといい方法ないかな...).
- hotfixとmainでコンフリクトが起きた時の対処がよくわかっていない...
最後に
GitHub flowで起きる問題を解決した,GitLab flowの実装解説をしました. といっても今回解説したのだと,stagingにアプリが上がってるときにhotfixが必要になった場合 またリバートしなくてはならないので,本当にこの問題を対策するときはstagingやmainへのマージを慎重に行ったり,apiデプロイするようにしたりなど追加の検討が必要そうです. ただ柔軟性が高く拡張しやすいので,アプリならば最初から採用しておくのが楽かな〜という所感でした.
省略したアプリのCD周りなどについては,次回以降に書く予定です.