はじめに
こんにちは、株式会社hacomono プラットフォーム部のおりちゃんこと居石(@hetre70914)です。
2/1からhacomonoにジョインしたばかりで、日々新しい環境で刺激を受けながらプラットフォームとして求められることはなんだろう?を突き詰めています。
プライベートでは冬季は白馬に籠り、エンジニアリングも趣味のスキーも全力で楽しんでいます。
Terraformの課題
hacomonoの既存インフラはTerraformで管理しています。
プラットフォーム基盤としての将来を考えると、Terraformのみでは以下のような課題が起きると考えています。
terraform apply
の実行時間増加- tfstate間の依存関係複雑化
- コードの重複に伴う構成変更の抜け漏れ
そこでTerramateを利用し、このような課題を解決できないかと考えました。
Terramateとは?
Terramateは複数のstateに跨るTerraformの管理を便利にしてくれるCLIツールで
- コード生成
- オーケストレーション (依存関係の解決や並列実行)
- スクリプトの定義
などを行うことが可能です。
実行環境やダッシュボードを提供するTerramate Cloudもありますが、ここではTerramate CLIについてのみ言及します。
Terramateの仕様
差分適用
Stack
TerramateではStackと呼ばれる単位でリソースを管理し、基本的には 1 Stack = 1 tfstate となります。
Stackはひとつのディレクトリで、その中に stack.tm.hcl というファイルで設定が記述されます。
stack_1 ├── main.tf ├── provider.tf ├── backend.tf └── stack.tm.hcl
stack.tm.hclの内容は以下のようになっており、 terramate create stack_1
で自動生成されます。
stack { name = "dev" description = "dev" id = "f960092d-5148-4671-a929-8fac47a938cb" }
またStackはネストすることもでき、以下のような階層構造も可能です。
stacks └── dev ├── shared │ ├── cluster │ │ ├── main.tf │ │ └── stack.tm.hcl │ ├── database │ │ ├── main.tf │ │ └── stack.tm.hcl │ └── network │ ├── main.tf │ └── stack.tm.hcl └── stack.tm.hcl
オーケストレーション
Terramateはgit diffから差分となるStackのみを検出し実行してくれるため、実行時間を短縮することが可能です。
また依存関係を明示することで、実行順序の制御や並列実行を制御することが可能です。
コード差分の適用は terramate run --changed -- terraform apply
を実行します。
Terramateが前回適用時からのGit差分を取得し、変更のあるStackのみ適用してくれます。
またこの際に terramate run --changed --parallel 5 -- terraform apply
とすると、依存関係のないStack同士を並列適用することも可能です。
またStack間に依存関係がある場合、依存先から順に実行されます。
実行順序の確認は terramate list --run-order
コマンドを使います。
依存関係を構成する要素は以下の2つとなります。
- Stackがネストしている場合、子は親に依存 (親の方が先に実行)
- config.tm.hcl にbefore, afterで明示
例えば /stacks/cluster/stack.tm.hcl に以下のような記述をした場合、
stack { name = "cluster" ... after = [ "/stacks/dev/shared/network", "/stacks/dev/shared/database" ] }
terramate list --run-order
の結果は以下となります。
stacks/dev stacks/dev/shared/database stacks/dev/shared/network stacks/dev/shared/cluster
一方で after = ...
の記述を削除した場合、依存関係がなくなりフラットになります。
stacks/dev stacks/dev/shared/cluster stacks/dev/shared/database stacks/dev/shared/network
コード生成
import
Terramateにはコード生成機能があります。
コードをDRYに保ちつつ、生成したコードはリポジトリに残るため可読性も損ないません。
例えば以下のようなディレクトリ構成を想定します。
. ├── imports │ └── generate_backend.tf └── stacks └── stack_1 ├── main.tf ├── import.tm.hcl └── stack.tm.hcl
generate_backend.tm.hcl は以下の通りです。
generate_hcl "backend.tf" { content { terraform { backend "s3" { region = "ap-northeast-1" bucket = "terramate-example-bucket" key = "dev/terraform.tfstate" encrypt = true use_lockfile = true } } } }
import.tm.hcl は以下の通りです。
import { source = "/imports/generate_backend.tm.hcl" }
terramate generate
を実行すると、 ./stacks/stack_1/backend.tf が生成されます。
変数の使用
コード生成には変数も使用することが可能です。
以下のようなディレクトリ構造を想定します。
. ├── imports │ └── generate_backend.tm.hcl └── stacks └── dev ├── shared │ └── network │ ├── main.tf │ ├── import.tm.hcl │ ├── configu.tm.hcl │ └── stack.tm.hcl └── stack.tm.hcl
ここでは変数を config.tm.hcl に定義し、imports.tm.hcl でコードを生成する際に使用します。
またStackに親子関係がある場合、親の変数は子に受け継がれます。
./imports/generate_backend.tm.hcl
generate_hcl "backend.tf" { content { terraform { backend "s3" { region = global.terraform.backend.s3.region bucket = global.terraform.backend.s3.bucket key = "${global.env.value}/${terramate.stack.name}/terraform.tfstate" encrypt = global.encrypt.enabled use_lockfile = true } } } }
./stacks/dev/config.tm.hcl
globals "terraform" "backend" "s3" { region = "ap-northeast-1" bucket = "terramate-example-bucket" } globals "env" { value = "dev" }
./stacks/dev/shared/network/config.tm.hcl
globals "encrypt" { enabled = true }
ここで terramate generate
実行で生成される./stacks/dev/shared/network/backend.tfは以下の通りです。
// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT terraform { backend "s3" { bucket = "terramate-example-bucket" encrypt = true key = "dev/network/terraform.tfstate" region = "ap-northeast-1" use_lockfile = true } }
ここで全て紹介はしませんが、以下のような機能もありコード生成でできる幅もかなり広いです。
スクリプト
Terramateは特定のワークフローをscriptとして作成することができます。
※ こちら2025.03.12時点ではexperimental featureとなっております。
experimental featureを扱うため、リポジトリのルートに terraform.tm.hcl を作成します。
terramate { config { experiments = [ "scripts" ] } }
Stackに含まれる config.tm.hcl にスクリプトを定義します。
script "deploy" { description = "Run Terraform deployment" lets { provisioner = "terraform" } job { name = "deploy-dev" commands = [ [let.provisioner, "init"], [let.provisioner, "validate"], ["tfsec", "."], [let.provisioner, "apply", "-auto-approve"], ] } }
terramte script list
でスクリプト一覧が見られ、 terramate script run deploy
で実行することができます。
またStackが親子関係にある場合、親にスクリプトを定義すると子のStackに対しても実行されるようになります。
terramate script tree
を実行すると、スクリプトのツリー構造を見ることが可能です。
運用するなら?
Terramateの仕様について紹介してきました。
最後にもし本番導入するとするならどのような運用方法にするか考えてみたのでご紹介します。
ディレクトリ構造
ディレクトリ構成は以下の通りです。
. ├── imports # 生成ファイルの定義 (backend, provider) ├── modules # Terraformモジュール └── stacks # Stack ├── dev # 開発環境向け │ ├── shared # テナント共通のリソース │ │ ├── network │ │ └── cluster... │ └── tenant # テナント個別に作成するリソース │ ├── tenant1 │ └── tenant2... └── prd ├── shared │ ├── network │ └── cluster... └── tenant ├── tenant1 └── tenant2...
まずStackですが、影響範囲を最低限に抑えるためライフサイクルごとに作成する方針をとっています。
またコード生成でbackendやproviderを作成し、Terraformやプロバイダのバージョン差異が起きないようにします。
一方で運用の簡便さと可読性を担保するため、リソース定義には適用せずTerraformのモジュールを用います。
Git Hooks
terramate generate
の実行忘れなどでリポジトリに生成後のコードが存在しないと、可読性を損なうことがあります。
そのためGitHooksを使って漏れを防ぐ必要があります。
pre-commit
terramate fmt terramate generate
pre-push
terramate generate # stacks配下にのみ生成ファイルができる想定 git diff --exit-code stacks
ローカル実行
開発環境など、コミット前に適用したいケースは多々あります。
ただしTerramateはデフォルトで未コミットのファイルがリポジトリにある場合、
Error: repository has untracked files
というエラーが発生してしまいます。
その場合は ./terramate.tm.hcl に disable_safeguards プロパティを設定してください。
terramate { config { experiments = [ "scripts" ] disable_safeguards = [ // ローカルからの実行を考慮し、未コミットの適用を許可 "git-untracked", "git-uncommitted" ] } }
ただしこの設定はプロジェクトルートのみ有効で、「dev Stack以下でのみ許可」といった設定ができません。
多少ワークアラウンド感はありますが、普段はコメントアウトしておき、手元から適用したい場合のみ外すといった運用が必要になると思います。
おわりに
Terramateをご紹介させていただきました。
コード生成によるDRY化、Stackのオーケストレーション、Terraformの可読性を下げすぎない、といった点でかなり使い勝手の良いツールだなと感じました。
一方で生成ファイルがリモートリポジトリから漏れないよう工夫する必要がある、disable_safeguardsがStack単位でON/OFFを切り分けられないといった問題もありました。
ただ総合的にはTerraformの管理運用が楽になりそうだと感じています。
hacomonoでは事業の圧倒的成長に向け、プラットフォーム基盤を作り上げていきます。
共に作り上げる仲間を募集しておりますので、もし少しでも興味を持っていただけたならご連絡くださいませ。
株式会社hacomonoでは一緒に働く仲間を募集しています。
エンジニア採用サイトや採用ウィッシュリストもぜひご覧ください!