🤢

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

2022/07/16に公開

はじめに

これは今さらな2021年振り返りカレンダーの2日目の記事です.
トゥイラーも良かったらフォローしていただけると嬉しいです🐦

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

https://zenn.dev/sheep96/articles/dc82e54ef7c005

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

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

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

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

https://postd.cc/gitlab-flow

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

  • 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を用いました.

https://github.com/x-motemen/git-pr-release

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環境へのデプロイ

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

https://zenn.dev/seita/articles/d1dba77043be8fd50eeb

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

Discussion