羊をめぐるブログ

趣味の色々について書きます

GCP + ネイティブアプリでのGitLab flow構築

はじめに

これは2021年振り返りカレンダーの2日目の記事です.

前回はCloud Run上で動くサービス用にGitHub flowでのCD環境を構築する話でした.

serenard.hatenablog.com

前回の最後やいろんな記事で述べられているとおり,GitHub flowは審査が必要なネイティブアプリなど,アプリケーションの リリースタイミングをこちら側でコントロールできないケースで不都合が発生します(モノレポ想定です).

例えばあるアプリについて,後方互換のない新機能が入ったバージョンv2がmainにマージされ,stg環境に 上がっているとします. この時,prdのネイティブ以外の部分でなんらかのバグが見つかり,すぐに修正を行いたいとき,通常のサービスならば, 修正PRをmainにマージし,新規機能とともにリリースすることができます. しかしネイティブアプリの場合,アプリの審査がリリース前に挟まれるため,修正をデプロイするには, 審査を待つか,既にmainに入っている新機能をrevertするしかありません.

対策としては,ネイティブアプリのレポジトリを分けることが考えられますが, 自分のいたチームでは全員が機能開発に対してバックエンド,アプリを両方書いていたため, PRの管理やリリースバージョンの統一が面倒になるなど別の問題が想定されました.

そこで,今回はGitHub flowをやめ,GitLab flowを採用することとしました. GitLab flowの詳細については以下に詳しく書いてあります.

postd.cc

要点をまとめると以下のような感じです.

  • mainブランチに加え,環境(stg, prdなど)ごとにブランチを用意する.
  • 新規の変更はmainからブランチを切って,mainにマージする.
  • 検証環境にデプロイする際はmainをそのブランチへマージし,デプロイする
  • 検証が終わったら,検証ブランチを本番ブランチにマージし,デプロイ
  • 本番に近いブランチは,必ずその前の全ての検証ブランチを通っているようにする

仕様

今回はクライアントはiOSアプリ,バックエンドはCloud Runに乗せている形を想定します. また,環境は一旦stg環境とprd環境の2つとします.

そして,仕様は次のような形です.

  1. 新規の変更はmainブランチから切り,mainブランチにマージ.また,検証用ブランチとしてstaging,本番ブランチとしてprductionを用意
  2. 常にmainからstgへのPRを生成し,リリースのタイミングでそれをstagingへマージし,リリースコマンドを打つ.このタイミングで,stagingからproductionへのPRを生成.
  3. staging上での検証が終わったら,stgからprdへのPRをマージ.アプリをビルドし,審査へ提出.審査にapiの変更が必要な場合はこのタイミングでデプロイ.
  4. 審査が通ったら,必要に応じてmigrationを行い,apiをデプロイ.

また,hotfixの流れは以下のような形です,

  1. stgからhotfixブランチを切り,stgにマージし動作確認.確認が終わったら,ブランチをmainとstgにマージ.
  2. stgをprdにマージし,審査&デプロイ.

実装

ディレクトリ構成は前回と同じです. 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環境へのデプロイ

リリースノートの生成は,前回と同様以下を用います.

zenn.dev

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周りなどについては,次回以降に書く予定です.