🧑🏻‍🤝‍🧑🏻

Terramateで始めるIaC CI/CDパイプライン

2024/07/16に公開

序論

先日IaCをオーケストレーションしてくれるツール、Terramateについて紹介しました。

https://zenn.dev/yuta28/articles/terramate-empower-iac

この時はクイックスタートということで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向けのクイックスタートリポジトリがあり、これをベースにカスタマイズしていきます。

https://github.com/terramate-io/terramate-quickstart-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]を使ってインストールしていますが他のパッケージマネージャーでも問題ありません。

実装手順

  1. テンプレート複製

準備できましたらテンプレートリポジトリを自分のGitHubアカウント内に複製します。

ルートディレクトリの.pre-commit-config.yamlにpre-commit[14]向けルールセットが含まれていますので、インストールしておきます。

pre-commit install
  1. 不要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
  1. StateバケットおよびWorkload Identity Providerの設定

ルートディレクトリにあるconfig.tm.hclではバックエンド用S3、AWSと連携するためのGitHubリポジトリ設定が定義されています。
ここで作成したいS3のバケット名と自身のGitHubリポジトリを指定します。
またTerraformのバージョンを固定していない場合はインストールしたTerraformのバージョンと競合しない設定にしておきます。

config.tm.hcl
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"
  }
}
  1. Stateファイルのローカル一時保存

Stateファイル保存用のS3を作成しますが、この時点ではStateファイルをローカルで保存させておく必要がありますので、imports/mixins/backend.tm.hclでコメントアウトされている以下を解除します。

https://github.com/terramate-io/terramate-quickstart-aws/blob/f3ad92e0ee6181e393e8e3d7aac37ac4eade0a61/imports/mixins/backend.tm.hcl#L2

  1. バックエンドとOpenID Connectプロバイダー作成

ここまで進んだ状態でterramate generateコマンドを実行するとTerramateが設定ファイルで定義されたTerraformファイルを_bootstrapstacksディレクトリに生成してくれます。
注目点として項目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を実行するとそれぞれのモジュールが定義したリソースが作成されます。

  1. StateファイルをS3へ移行
    一旦S3を作成できましたら_bootstrapディレクトリ内のそれぞれのモジュールに存在するstack.tm.hclファイルからno-backendと定義されているタグを削除します。

https://github.com/terramate-io/terramate-quickstart-aws/blob/f3ad92e0ee6181e393e8e3d7aac37ac4eade0a61/_bootstrap/oidc-aws-github/stack.tm.hcl#L5

https://github.com/terramate-io/terramate-quickstart-aws/blob/f3ad92e0ee6181e393e8e3d7aac37ac4eade0a61/_bootstrap/terraform-state-bucket/stack.tm.hcl#L5

もう一度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で移行させます。

  1. ワークフロー設定

GitHub Actionsワークフローはすでに用途に応じた3つのファイルがありますが、デフォルトリージョンがus-east-1なのとGitHub環境変数でAWSアカウントIDを指定している部分がありますのでここを直す必要があります。


環境変数設定

https://github.com/terramate-io/terramate-quickstart-aws/blob/f3ad92e0ee6181e393e8e3d7aac37ac4eade0a61/.github/workflows/deploy.yml#L61

https://github.com/terramate-io/terramate-quickstart-aws/blob/f3ad92e0ee6181e393e8e3d7aac37ac4eade0a61/.github/workflows/drift-detection.yml#L56

https://github.com/terramate-io/terramate-quickstart-aws/blob/f3ad92e0ee6181e393e8e3d7aac37ac4eade0a61/.github/workflows/preview.yml#L69

  1. リソース作成
    ここまで進めましたらいよいよリソースの作成です。

ブランチを適当に切ってstacks\terraform\vpc配下でVPCを作るTerraformファイルを作成します。

vpc.tf
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "terramate-vpc"
  }
}

作成後コミットし、mainブランチへのPRを作ります。
GitHub Actionsが動きプレビュー用のワークフローファイルが変更差分のあったterraform stacksだけplan結果が出力されます。

PRをマージしますと今度はデプロイ用のワークフローファイルがTerraformを実行します。

  1. 異なるStateファイルの作成

次にOpenTofuのStacks配下でリソースを作成します。こちらは元々stacks\opentofu\emptyと空のディレクトリでしたのでnullリソースを作るTerraformファイルを作成しました。

null.tf
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://terramate.io/rethinking-iac/introducing-the-terramate-github-action/
https://terramate.io/docs/cli/automation/github-actions/

脚注
  1. https://www.terraform.io/ ↩︎

  2. https://terramate.io/rethinking-iac/why-you-should-break-down-your-terraform-into-stacks/ ↩︎

  3. Terramateでは分割した個々のStateファイルをStackと表現しています。 ↩︎

  4. https://en.wikipedia.org/wiki/Blast_radius#Use_in_software_security ↩︎

  5. https://docs.github.com/ja/actions ↩︎

  6. https://docs.gitlab.com/ee/ci/ ↩︎

  7. https://developer.hashicorp.com/terraform/cloud-docs ↩︎

  8. https://terramate.io/rethinking-iac/introducing-terramate-an-orchestrator-and-code-generator-for-terraform/ ↩︎

  9. https://docs.github.com/ja/actions/using-jobs/using-a-matrix-for-your-jobs ↩︎

  10. https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services ↩︎

  11. https://developer.hashicorp.com/terraform/language/settings/backends/s3#multi-account-aws-architecture ↩︎

  12. https://opentofu.org/ ↩︎

  13. https://asdf-vm.com/ ↩︎

  14. https://pre-commit.com/ ↩︎

  15. https://terramate.io/roadmap/tabs/2-terramate-roadmap ↩︎

GitHubで編集を提案

Discussion