はじめに
この記事は Wano Group Advent Calendar 2024 の10日目の記事です。
本稿では、私たちのチームが GitHub Actions を使用してどのようにデプロイを実施しているかについて記載します。
Goのモノレポ構成について
私たちのチームでは、Go言語を中心として利用しており、モノレポ(monorepo)で管理しています。この構成に至るまでには様々な試行錯誤があり、以下のような特徴を持つ構成へと発展させてきました。
リポジトリの基本構造
projectフォルダの下に各モジュールが並んでいます。
それぞれgo.mod
を持っています。
project
├── README.md
├── zzzz
├ ├── go.mod
├── yyy
└── zzzz
マイクロサービスを進めていたり分散モノリス化してしまっているわけではなく、純粋にデプロイライフサイクルの違うアプリケーション群をある程度の論理的な塊感を持って分けています。
業務ドメインロジックとの関わりが深いものに関しては、npmモジュールやPythonなどの非Goプロジェクトも含むこともあります。
(あまりにもデプロイライフサイクルの違うものや、非機能的な非ドメイン要素に属するものは別のリポジトリで管理しています。)
workflow_dispatchとは
デプロイにはGitHub Actionsの機能であるworkflow_dispatchをGitHub CLI経由で実行しています。
workflow_dispatchは、GitHub Actionsの機能の一つで、手動でワークフローを実行できる仕組みです。この機能の特徴は、
- 実行時にパラメータを渡すことが可能
- 特定のブランチやタグを指定してデプロイできる
- 複数のワークフローを連携させやすい
この柔軟性により、モノレポ内の特定のプロジェクトを選択的にデプロイすることが可能になります。
実際のデプロイフロー
実際のデプロイは、GitHub CLIを使用して以下のように実行しています。
gh workflow run deploy -r <branch/tag> --field env=<production/stage> --field app=<対象の傘下プロジェクト>
例えば、A
、B
、C
という3つのデプロイ論理単位があり、Aだけデプロイする場合、以下のようにデプロイします。
gh workflow run deploy -r feature/add-emoji-deploy --field env=stage --field app=A
このコマンドにより、特定のブランチから特定の環境に、選択したプロジェクト群のみをデプロイしています。
GitHub CLIで発火されているものはなにか
上記のコマンドを受けるworkflow側では、以下のように、親/イベント起点となるdeploy.yml
とそれによって呼ばれる_call.deploy_A.yml
のようなデプロイ単位によって別れています。
どれを発火させるか決めるのはdeploy.yml
であり、_call.deploy_A.yml
などは個別のデプロイジョブの単なる集合体となっています。
.github
├── actions
│ └── shared-action
├── pull_request_template.md
└── workflows
├── _call.deploy_A.yml
├── _call.deploy_B.yml
├── _call.deploy_C.yml
├── _call.deploy_OTHER.yml
├── deploy.yml
├── set-aws-deploy-secrets-dispatch.sh
└── test-result-slack.yml
親となるGitHub Action
name: 'deploy'
run-name: デプロイ ${{ github.event.inputs.env }} by @${{ github.actor }} ( ${{ github.event.inputs.app }} )
on:
workflow_dispatch:
inputs:
env:
description: 'production / stage'
required: true
app:
description: 'デプロイ対象'
default: 'A,B,C,OTHER'
env:
# 組織シークレット
...
jobs:
call_A:
uses: ./.github/workflows/_call.deploy_A.yml
if: contains(github.event.inputs.app, 'A')
with:
MY_ENV: ${{ github.event.inputs.env }}
secrets: inherit
call_B:
uses: ./.github/workflows/_call.deploy_B.yml
if: contains(github.event.inputs.app, 'B')
with:
MY_ENV: ${{ github.event.inputs.env }}
secrets: inherit
call_C:
uses: ./.github/workflows/_call.deploy_OTHER.yml
if: contains(github.event.inputs.app, 'OTHER')
with:
MY_ENV: ${{ github.event.inputs.env }}
secrets: inherit
call_OTHER:
uses: ./.github/workflows/_call.deploy_C.yml
if: contains(github.event.inputs.app, 'C')
with:
MY_ENV: ${{ github.event.inputs.env }}
secrets: inherit
on-request:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJSON(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ env.MY_SLACK_WEBHOOK_DEPLOY }}
SLACK_TITLE: 🦾 A デプロイ開始 ${{ github.event.inputs.env }}
SLACK_COLOR: '#6f7a72'
SLACK_MESSAGE: |
env : ${{ github.event.inputs.env }}
app : ${{ github.event.inputs.app }}
on-result:
runs-on: ubuntu-20.04
needs: [call_A, call_B, call_OTHER , call_C , ]
if: ${{ always() }}
steps:
- name: checkout
uses: actions/checkout@v4
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJSON(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ env.MY_SLACK_WEBHOOK_DEPLOY }}
SLACK_TITLE: 🚀 A ${{ github.event.inputs.env }} デプロイ結果
SLACK_COLOR: '#42555c'
SLACK_MESSAGE: |
対象ワークフロー
A: ${{ needs.call_A.result == 'success' && '✅' || needs.call_A.result == 'skipped' && 'skipped' || '🔥' }}
B: ${{ needs.call_B.result == 'success' && '✅' || needs.call_B.result == 'skipped' && 'skipped' || '🔥' }}
OTHER: ${{ needs.call_OTHER.result == 'success' && '✅' || needs.call_OTHER.result == 'skipped' && 'skipped' || '🔥' }}
C: ${{ needs.call_C.result == 'success' && '✅' || needs.call_C.result == 'skipped' && 'skipped' || '🔥' }}
if: contains(github.event.inputs.app, 'B')
この if構文が特徴で、これによってGutHub CLIからのデプロイ命令をフィルタしています。
上記のdeployによって呼ばれる子YAMLは以下です。
workflow_call
でいくつかのオプション等を受け取れる他は普通のjobの羅列ですので割愛します。
name: _call.B
on:
workflow_call:
inputs:
ENV:
required: true
type: string
env:
REGION: ap-northeast-1
....
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 12
env:
GOCACHE: /tmp/.cache-go
strategy:
matrix:
environment:
- TITLE: (1) xxxxxx
DEPLOY: |
....
最後に - GitHub Actions × モノレポの評価
1年ほどGitHub Actionでこのデプロイ方法を運用してきてどうだったか。
メリット
-
コードの一元管理
- 全てのプロジェクトが1つのリポジトリに集約され、依存関係の把握が容易
- デプロイスクリプトの共通化が可能
-
開発効率の向上
- プロジェクト間の変更の影響を即座に確認可能
- 共通コードの更新が一括で行える
デメリット
-
キャッシュの制限
- エンタープライズプランでも1リポジトリあたり10GBという制限
- 大規模なモノレポではすぐに限界に達する可能性
-
対策
- 頻繁なリリースが必要なプロジェクトを特定し、優先的にキャッシュを活用
- 必要に応じてキャッシュのクリーンアップを実施
...キャッシュ周りがなんかもうちょっとならないかなあ、という感想ですね。
簡単ですが本稿を終わります。
明日はnokaznさんです。乞うご期待。