博報堂テクノロジーズでインフラエンジニア・ITアーキテクトをしている森です。
はじめに
CI/CDとは何か。『Continuous Integration/Continuous Integration(継続的インテグレーション/継続的デリバリー)』を略したものです。CI/CDとネット検索するとさぞかし難解な説明が多く見つかるかと思います。CI/CDの説明はそちらに譲ることとし、エンタープライズなシステム開発における実務的な局面での CI/CDとは、”自動リリース”とほぼ同義で扱われると考えています。本記事ではエンタープライズにおけるシステム開発での実務的な内容にフォーカスしたCI/CDについて紹介します。
本記事の目的
パブリッククラウドで最大のシェアを誇るAWSにおいても、エンタープライズにおける実務的なCI/CD(つまり、”自動リリース”)のベストプラクティスといえる情報はあまり見かけないように思います。私自身は長年の開発現場で試行錯誤を繰り返し現時点で辿り着いた「エンタープライスシステムにおけるCI/CDのベストプラクティス」があります。こういった情報がベストプラクティスの一例などとして公開されていればいいのになぁ、と思い、同じように悩まれているITアーキテクト・インフラエンジニアの方々の一助になればと考え、この記事を作成しました。
エンタープライズシステムの構成
エンタープライズシステムにおいては、通常、1つのプロダクトにおいて複数面を準備します。俗に3ランドスケープと言われるような、本番環境・検証環境・開発環境の3面の環境が立てられることが多いです。(もちろん、開発規模によっては、2面であったり、4面以上となる場合もありますが。)
CI/CD においては1環境(1面)だけにリリースする視点ではなく、3面(それ以外の面数でも)に対して、開発サイクルを踏まえどのように”自動リリース”するかということを検討する必要があります。
CI/CDは2つの分類が存在する
CI/CD と一口に言っても実装においては【インフラのCI/CD】と【アプリケーションのCI/CD 】の大きく2分類に分けられます。私の経験上、実務的なタスク管理においてCI/CDがひとまとめのタスクとして扱われてしまいがちなようです。そのタスクをアサインされた経験の浅い現場担当者がどう手をつけたらよいか戸惑っている様子を何度も見てきました。この2つはそれぞれ関連する要素もあるものの(これがまた混乱を与える要素になってしまうのですが..)、【インフラのCI/CD】と【アプリケーションのCI/CD 】は別々で実装するものであることは意識する必要があります。今回、本記事では、まずは前編として 【インフラのCI/CD】について記載する こととします。【アプリケーションのCI/CD 】については続編として執筆する予定です。
【インフラのCI/CD】を導入するための前提について
【インフラのCI/CD】 を 導入するためには、インフラ構築の方法としてWeb管理コンソールやCLIではなく、 IaC(Infrastructure as Code)を採用することが前提となります。
AWSにおいては、IaCの選択肢として以下3つが考えられます。
1.CloudFormation
2.AWS SDK
3.Terraform
CloudFormation はAWSのマネージドサービスで、同じくAWSのマネージドサービスである Git機能:CodeCommit 、CI/CD機能:CodePipeline とともに採用することでAWSのサービスのみで完結させられるという管理のしやすさなどが大きなメリットです。※ただし、 CodeCommit は2024年に新規利用停止になりました。
Terraform はマルチクラウドに対応しているため、学習効率がよいといったメリットがあります。
【ご参考】この3つの違いは以下の記事も参考にしてください
IaCのコード管理
現在コード管理は Git を利用することが多いと思います。Git のリモートリポジトリツールはいくつか選択肢がありますが、有名な SaaS サービスとして GitHub があります。私のチームでも GitHub をよく採用しています。
ただし、GitHub については、管理コンソールへのIP制限ができない、通常は個人発行アカウントであること、PAT(Personal access tokens)に対するリポジトリ単位の権限制限ができない、など、セキュリティに関する仕様上の注意事項があり、これらについては利用者側で十分意識して対策を取って使うべきサービスだと考えています。
Terraformのディレクトリ構成
前述の通り、3ランドスケープ構成とした場合に、各環境ごとのTerraformコードはどのようにすみ分けて管理を行うかは検討事項になります。Gitのブランチ戦略を踏まえると、env 配下にdev、stg、prd のフォルダを作成し、その配下に環境ごとのコードファイルを置くのがよいです。このとき環境ごとの差分は主に環境識別子、URL等となり、これらの情報は local.tf に集約すると管理しやすいでしょう。ディレクトリ構成のイメージを例として挙げます。
【インフラのCI/CD】のツール
IaC のコード管理を GitHub で行うとなれば、同じサービス内の GitHub Actions は親和性があり、【インフラのCI/CD】のツールの選択肢として有力です。
Gitのブランチ戦略
CI/CD(【インフラのCI/CD】、【アプリケーションのCI/CD 】とも)の実装はGitのブランチ戦略と密接に関わります。よって、CI/CDの設計にあたり、ブランチ戦略を決定したうえで整合を取る必要があります。
IaCにTerraform を採用する場合の【インフラのCI/CD】においては、ブランチ戦略: Trunk-based development が最も適していると考えており、私のチームでも多く採用しています。インフラとアプリのブランチ戦略は合わせる必要はなく、インフラ・アプリそれぞれのCI/CDの実装に応じてブランチ戦略を検討すればよいでしょう。
Trunk-based development を採用した場合【インフラのCI/CD】は大きく以下の流れで実装します。
採用する技術スタックのまとめ
私のチームの多くのプロダクトで以下を組み合わせた方式を採用しており、実際に運用として安定しています。
- IaC: Terraform
- コード管理: GitHub
- CICD: GitHub Actions
【インフラのCI/CD】の実装
ここから具体的な実装の話に入ります。
以降、Terraform や GitHub Actions の仕様を理解されている前提として説明します。これらの仕様に関する説明は本記事では割愛しますので、必要に応じて仕様をご確認ください。特にGitHub Actions については公式ドキュメントも存在しますが、高機能が故、仕組みも複雑で、慣れるまで難解に感じることがあるかもしれません(私もそうでした)。
有難いことに、体系的に理解できるGitHub CI/CD実践ガイドという書籍が昨年出版されています。
【インフラのCI/CD】における GitHub Actions の実装は2段階で行います。具体的には、.github/workflows 配下の YAML ファイルを2つ作成することで、ワークフローを2つ実装します。この2段階のワークフローもGitのブランチ戦略と密接に関わります。
- 開発用ブランチ(ex.feature/develop)からマスターブランチ(main)へのプルリクエストをトリガーとして変更対象の環境を判定させ、対象の環境に対して terraform plan を実行します。
- (terraform plan でエラーがなかった場合)マスターブランチ(main)への merge がトリガーとして、 対象の環境に対して terraform apply を実行します。
それぞれ、具体的に説明します。
1.の処理(YAMLファイル)について
ファイル名は .github/workflows/terraform_plan.yaml などとします。
トリガー
マスターブランチ(main)へのプルリクエストをトリガーとします。
on:
pull_request:
branches: [ main ]
ジョブ①:変更対象の環境を判定
前述に例を挙げたように3ランドスケープのフォルダを dev、stg、prd とします。変更があった環境を判定するために、シェルコマンドを実行します。この結果、例えば、dev 配下の main.tf と stg 配下の main.tf を変更したのであれば、dev、stg が結果として得られます。
ジョブのYAMLを示します。
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check diff
id: check-diff
env:
DIFF_PATH: ${{ github.ref == 'refs/heads/main' && 'HEAD~' || 'origin/main' }}
run: |
diff_files="$(git diff --stat --name-only ${{ env.DIFF_PATH }})"
diff=$(echo "$diff_files" \
| grep 'env/' \
| sed -r 's/env\/([^\/]+)\/.*/\1/' \
| uniq \
| jq -R . \
| jq -sc)
echo "diff envs: $diff"
echo "diff=${diff}" >> $GITHUB_OUTPUT
# DIFF_PATHを可変とすることで、2.の処理(YAMLファイル)と共通化できるようにしています
# ・プルリクエストがマージされた場合 → HEAD~(直前のコミットとの比較)
# ・マージされていない場合 → origin/main(mainブランチとの比較)
判定した環境を matrix へ設定し(上記の例だと、dev、stg が設定される)、strategyで並列実行させます。
AWSへアクセスするためのアクセスキーをシークレットから取得して設定しておきます。
if: ${{ needs.check_diff.outputs.diff != '[]' && needs.check_diff.outputs.diff != '' }}
strategy:
fail-fast: false
matrix:
env: ${{ fromJson(needs.check_diff.outputs.diff) }}
defaults:
run:
working-directory: env/${{ matrix.env }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
ジョブ②:terraform plan までの一連の処理
terraform plan までの一連のアクションを実行します
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@[version]
with:
terraform_version:[version]
- name: terraform init
run: terraform init
- name: Terraform plan
run: terraform plan -no-color -out=tfplan
実行後、正常終了すれば、GitHub Actions のワークフロー履歴からこのようなイメージの結果画面が表示されます。クリックして開くことで terraform plan の結果を確認することができます。

2.の処理(YAMLファイル)について
ファイル名は github/workflows/terraform_apply.yaml などとします。
トリガー
マスターブランチ(main)へのマージとします。
on:
push:
branches: [ main ]
ジョブ①:変更対象の環境を判定
変更があった環境を判定します。1.の処理と同様のコードを実行します。前述を参照ください。
ジョブ②:terraform apply までの一連の処理
terraform apply までの一連のアクションを実行します
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v[version]
with:
terraform_version:[version]
- name: terraform init
run: terraform init
- name: terraform plan
run: terraform plan -no-color -out=tfplan
- name: terraform apply
run: terraform apply -auto-approve -no-color tfplan
実行結果の確認方法は、1.の処理と同様です。
運用上の留意点
このような実装をした場合に、実務の運用で気を付けるべき点が2つあります。
- AWSマネージメントコンソールから手動で環境変更を行った場合に先祖返りを起こすリスクがあります。IaCを導入する場合には基本的に手動での環境変更は行わないべきですが、実務上どうしても必要な局面が出てきてしまう場合があります。このような場合には、手動変更を行ったことをチームメンバーへ周知し、かつ忘れないよう記録を残すなどして早いタイミングでIaCへ追従させる、といった対策を取るようにしています。
- Pull requests による terraform plan の実行後に、別の Pull requests が先に マスターブランチ(main)への merge された場合について。コンフリクトが発生しなければ merge はできるものの、merge したマスターブランチ(main)に対して terraform apply が実行されるため、意図しない結果となる可能性があります。 terraform plan から時間を空けて マスターブランチ(main)へ merge する場合には、変更をpullしたうえで再度 terraform plan を行うように注意しています。
いずれの場合にも、マスターブランチ(main)へ merge した後は必ず結果を確認することが重要です。
エンタープライズシステムでの【インフラのCI/CD】採用是非
前述の通り、採用の大きな前提としてIaCを導入していることがあります。IaCの導入には賛否があり、その議論は本題から逸れるためここでは触れません。もしIaCを導入するのであれば、【インフラのCI/CD】は同時に採用すべきでしょう。
- リリース履歴がすべて残る、リリースはレビューを必須にするなど管理性の向上・ガバナンス向上に役立つ
- CI/CDの実装がワンパターンに揃えやすいため、他システム(他プロダクト)へほとんどそのまま転用可能で運用負担も少ない
以上となります。
続編の【アプリケーションのCI/CD 】についても、なるべく早く公開すべく執筆を進めていきます。


