Terramateで始めるIaC CI/CDパイプライン
序論
先日IaCをオーケストレーションしてくれるツール、Terramateについて紹介しました。
この時はクイックスタートということでnullリソースを使ってTerramateの動作確認程度のハンズオンを実施しました。
今回は複数のStateファイルで分割され、CI/CDパイプラインの処理に時間がかかるようになったTerraformリソースをTerramateを活用して、変更差分があった場所のみ検知してapplyを実行するCI/CDパイプラインの構築について紹介いたします。
対象読者
- Terramateを使ったCI/CDパイプラインの構築に興味がある人
- Terraform(OpenTofu)の基礎知識がある人
- GitHub Actionsの基礎知識がある人
IaC(Terraform)導入後の課題についておさらい
Terramateの概要については私の記事や本家ドキュメントを読んでもらえますと幸いです。
ここではIaC構築で使われる代表的なツール、Terraform[1]を導入後の運用課題についてあらためて説明いたします。
大規模なインフラ構築でTerraformを導入すると必然的にTerraformリソースも大規模なものになります。
Terraformを1つの巨大なStateファイルにリソースをまとめるとTerraformの実行に時間がかかりAPI制限に引っかかる懸念も出てきます。
ブログ記事より引用[2]
ですのでTerraformを導入する際、Stateファイルを分割[3]しアプリケーションに与える影響を小さくすることが求められます。
その一方でStateファイルを複数分割するとTerraformの実行回数が増えてしまい、手動による実行が手間となります。
Terraformを導入している企業ではCI/CDパイプラインの構築もセットで実装しており、GitHub Actions[5]やGitLab CI/CD[6]、HashiCorpが提供するマネージドなサービスHCP Terraform[7]を使っている企業も多いと思います。
しかし、以前の記事でも紹介しましたが、Stateファイルを分割しCI/CDパイプライン上に実装した場合生じる新たな課題として更新された特定のStateファイル分だけを実行したいという要望があります。[8]
例えば以下のようにモジュールとしてリソースファイルを共通化し、環境ディレクトリ内でモジュールからリソースを作成する構成を構築したとします。
├── modules
│ ├── app
│ │ ├── README.md
│ │ ├── ec2.tf
│ │ ├── ecs.tf
│ │ ├── output.tf
│ │ └── variable.tf
│ ├── db
│ │ ├── README.md
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ ├── network
│ │ ├── README.md
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ └── presentation
│ ├── README.md
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
└── env
├── prod
│ ├── app #terraform apply実行ディレクトリ
│ │ ├── backend.tf
│ │ ├── ecr.tf
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── provider.tf
│ ├── db
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── provider.tf
│ ├── domain
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── provider.tf
│ ├── network
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── provider.tf
│ └── presentation
│ ├── backend.tf
│ ├── main.tf
│ ├── output.tf
│ └── provider.tf
└── stg
手動でTerraformを実行するなら更新した環境/リソースディレクトリまで下ってterraform apply
すれば問題ありません。
しかし、GitHub ActionsなどのCI/CDパイプラインでTerraformを実行する場合どのリソースのStateファイルが更新されたのか検知する方法はなく、もしprod
配下のリソースディレクトリを対象にマトリックス戦略[9]で並行実行すれば無関係なStateファイルに対してもterraform apply
を実行し、CIコンピューティングリソースを無駄に消費してしまう問題があります。
Terramateはこの問題を解決できるオーケストレーションツールであり、またCI/CDプラットフォームではありません。
すでにGitHub ActionsやGitLab CI/CDなどのCI/CDパイプラインを導入済みでもTerramateは競合することなくそれらのパイプラインツールと共存できます。
Terramateを含めたGitHub Actions
ここから主題となるGitHub Actionsワークフローの中にTerramateを導入して、特定のStateファイルが更新された箇所だけにTerraformを実行するCI/CDパイプラインを構築していきます。
公式が提供してるAWS向けのクイックスタートリポジトリがあり、これをベースにカスタマイズしていきます。
このリポジトリにはStateファイル保存先のバックエンドとしてS3+DynamoDBを作成するモジュールとGitHubリポジトリとAWSをOpenID Connect連携[10]に必要なIAMを作成するモジュールも含まれており、まずはローカル環境でこれらのリソースを作成後、作成されたS3にStateファイルを移行するようにします。
その後TerraformとOpenTofu[12]それぞれで別々にStateファイルを作成し、pushして変更箇所のみにGitHub ActionsがTerraform(OpenTofu)が実行されるかを確認します。
事前準備
以下のツールをインストールします。
- terraform
- terramate
- pre-commit
- AWS CLI
手順ではasdf[13]を使ってインストールしていますが他のパッケージマネージャーでも問題ありません。
実装手順
- テンプレート複製
準備できましたらテンプレートリポジトリを自分のGitHubアカウント内に複製します。
ルートディレクトリの.pre-commit-config.yaml
にpre-commit[14]向けルールセットが含まれていますので、インストールしておきます。
pre-commit install
- 不要Terraformファイル削除
作成後ローカルにcloneしますが以下のTerraformファイル*.tf
はすべて削除してください。手順には含まれてませんが、本来はユーザー自身がterramate generate
コマンドを実行時に生成するものですので、すでにある状態ですとエラーが発生します。
削除対象ファイル
_bootstrap\oidc-aws-github\_main.tf
_bootstrap\oidc-aws-github\backend.tf
_bootstrap\oidc-aws-github\terraform.tf
_bootstrap\terraform-state-bucket\_main.tf
_bootstrap\terraform-state-bucket\backend.tf
_bootstrap\terraform-state-bucket\terraform.tf
stacks\opentofu\empty\backend.tf
stacks\opentofu\empty\terraform.tf
stacks\terraform\vpc\backend.tf
stacks\terraform\vpc\main.tf
stacks\terraform\vpc\terraform.tf
- StateバケットおよびWorkload Identity Providerの設定
ルートディレクトリにあるconfig.tm.hcl
ではバックエンド用S3、AWSと連携するためのGitHubリポジトリ設定が定義されています。
ここで作成したいS3のバケット名と自身のGitHubリポジトリを指定します。
またTerraformのバージョンを固定していない場合はインストールしたTerraformのバージョンと競合しない設定にしておきます。
globals "terraform" {
version = ">= 1.7"
}
globals "terraform" "backend" {
bucket = "any-name-you-want"
region = "ap-northeast-1"
}
globals "aws" "oidc" {
github_repositories = [
"your-github-username-or-organization/repository-name",
]
}
globals "terraform" "providers" "aws" {
enabled = true
source = "hashicorp/aws"
version = "~> 5.48"
config = {
region = "ap-northeast-1"
}
}
- Stateファイルのローカル一時保存
Stateファイル保存用のS3を作成しますが、この時点ではStateファイルをローカルで保存させておく必要がありますので、imports/mixins/backend.tm.hcl
でコメントアウトされている以下を解除します。
- バックエンドとOpenID Connectプロバイダー作成
ここまで進んだ状態でterramate generate
コマンドを実行するとTerramateが設定ファイルで定義されたTerraformファイルを_bootstrap
とstacks
ディレクトリに生成してくれます。
注目点として項目3のbackend.tm.hcl
のおかげで、_bootstrap
ディレクトリ以下2つのモジュールにbackend.tf
を生成しないようにしていることが分かります。
$ terramate generate
Code generation report
Successes:
- /_bootstrap/oidc-aws-github
[+] _main.tf
[+] terraform.tf
- /_bootstrap/terraform-state-bucket
[+] _main.tf
[+] terraform.tf
- /stacks/opentofu/empty
[+] backend.tf
[+] terraform.tf
- /stacks/terraform/vpc
[+] backend.tf
[+] terraform.tf
Hint: '+', '~' and '-' mean the file was created, changed and deleted, respectively.
TerramateはGit連携することで差分検知しますので、GitHubにコミットしないとローカルでもTerramateは実行できません。
$ terramate run -C _bootstrap terraform init
Error: repository has uncommitted files
コミット後、terramate run -C _bootstrap terraform init
を実行するとそれぞれのモジュール内でterraform init
され、続けてterramate run -C _bootstrap terraform apply
を実行するとそれぞれのモジュールが定義したリソースが作成されます。
- StateファイルをS3へ移行
一旦S3を作成できましたら_bootstrap
ディレクトリ内のそれぞれのモジュールに存在するstack.tm.hcl
ファイルからno-backendと定義されているタグを削除します。
もう一度terramate generate
を実行すると今度はルートディレクトリのconfig.tm.hcl
で設定したS3バケットをバックエンドとして定義したbackend.tf
が生成されます。
$ terramate generate
Code generation report
Successes:
- /_bootstrap/oidc-aws-github
[+] backend.tf
- /_bootstrap/terraform-state-bucket
[+] backend.tf
Hint: '+', '~' and '-' mean the file was created, changed and deleted, respectively.
terramate run -C _bootstrap terraform init
を再度実行すれば既存StateファイルをS3へ移行するか問われますのでyesで移行させます。
- ワークフロー設定
GitHub Actionsワークフローはすでに用途に応じた3つのファイルがありますが、デフォルトリージョンがus-east-1なのとGitHub環境変数でAWSアカウントIDを指定している部分がありますのでここを直す必要があります。
環境変数設定
- リソース作成
ここまで進めましたらいよいよリソースの作成です。
ブランチを適当に切ってstacks\terraform\vpc
配下でVPCを作るTerraformファイルを作成します。
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "terramate-vpc"
}
}
作成後コミットし、mainブランチへのPRを作ります。
GitHub Actionsが動きプレビュー用のワークフローファイルが変更差分のあったterraform stacksだけplan結果が出力されます。
PRをマージしますと今度はデプロイ用のワークフローファイルがTerraformを実行します。
- 異なるStateファイルの作成
次にOpenTofuのStacks配下でリソースを作成します。こちらは元々stacks\opentofu\empty
と空のディレクトリでしたのでnullリソースを作るTerraformファイルを作成しました。
resource "null_resource" "nul_tofu" {
}
もう一度ブランチを切ってPRを出すと今度はOpenTofuのStackしか変更差分がないことを検知したTerramateはOpenTofu配下のStackのみにTerraformを実行していることが分かります。
追加でTerraform側のStackで作成したVPCに変更を書けて再コミットすると今度は2つのStackに対して変更があったことを検知したTerramateがそれぞれのStack配下でTerraformを実行いたします。
これでTerramateを使ったIaCのCI/CDパイプラインの構築ができました。
所感
Terramateを用いたIaC構築に最適なCI/CDパイプラインについて紹介しました。
以前紹介した時よりもTerramateは進化しており、クローズドベータだったマネージドクラウド版もリリースされ、ダウンロード数も1000万回を超え活発に機能開発が進んでいるようです。
ロードマップも公開されており、数多くの取り組みや今後の取り組みなどが紹介されていますので気になる人はぜひ覗いてみてください。[15]
参考文献
-
https://terramate.io/rethinking-iac/why-you-should-break-down-your-terraform-into-stacks/ ↩︎
-
https://en.wikipedia.org/wiki/Blast_radius#Use_in_software_security ↩︎
-
https://terramate.io/rethinking-iac/introducing-terramate-an-orchestrator-and-code-generator-for-terraform/ ↩︎
-
https://docs.github.com/ja/actions/using-jobs/using-a-matrix-for-your-jobs ↩︎
-
https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services ↩︎
-
https://developer.hashicorp.com/terraform/language/settings/backends/s3#multi-account-aws-architecture ↩︎
Discussion