Terraform は、インフラを正確に、しかもバージョン管理された形ですでに記述しています。それなのに、なぜコードから誰もシステムを理解できないのでしょうか。.tf ファイルが答えるのは「何が存在し、どう設定されているか」ですが、構成図を読む人が知りたいのは別のことだからです。トラフィックはどこから入るのか、何が何と通信するのか、データはどこにあるのか、何が外部に公開されているのか、といったことです。
このガイドでは、Terraform から人が読める図を作る方法を説明します。terraform graph がその図にならない理由、リソースをフレームとカードに対応付ける方法、plan JSON を使うべき場面、省くもの、そしてプルリクエストのレビューで結果を活用する方法です。
コードも terraform graph も図にならない理由
小さな本番スタックでも、複数のファイルとモジュールにまたがって 100 個ほどのリソースになるのは珍しくありません。その多くは接着剤のようなリソースです。IAM ポリシーのアタッチメント、ルートテーブルの関連付け、セキュリティグループのルール、ターゲットグループのアタッチメントなどです。「インターネットからデータベースに到達できるか」という問いの答えはその中にありますが、十数個の参照に散らばっています。
terraform graph もこれを解決しません。このコマンドは DOT 言語でグラフを出力し、Terraform 1.7 以降のデフォルト出力は、コマンドリファレンスの言葉を借りれば、リソースの依存関係の順序だけを表す簡略化されたグラフです。-type=plan のようなオプションは、Terraform がプランを作るときに使う、より完全なグラフの近似を示します。Terraform 1.16 以降は -format=mermaid で DOT の代わりに Mermaid を出力できます。
依存関係グラフが答えるのは「何が何より先に存在する必要があるか」です。ECS サービスは IAM ロール、ターゲットグループ、タスク定義に依存するので、矢印はサービスから前提条件に向かいます。これはトラフィックの流れとは逆向きであることが多く、接着剤のリソースもデータベースと同じだけの場所を占めます。プランのデバッグには役立ちますが、構成図ではありません。
例として使うスタック
AWS 上の典型的な Web アプリです。2 つのアベイラビリティーゾーンにパブリックサブネットとプライベートサブネットを持つ VPC、Application Load Balancer、Fargate 上の ECS サービス、RDS 上の PostgreSQL データベース、アップロード用の S3 バケットで構成されます。設定の抜粋は次のとおりです。
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.11.0/24"
availability_zone = "eu-west-1a"
}
resource "aws_lb" "web" {
name = "web"
load_balancer_type = "application"
subnets = [aws_subnet.public_a.id, aws_subnet.public_b.id]
security_groups = [aws_security_group.alb.id]
}
resource "aws_ecs_service" "app" {
name = "app"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
launch_type = "FARGATE"
desired_count = 2
network_configuration {
subnets = [aws_subnet.private_a.id, aws_subnet.private_b.id]
security_groups = [aws_security_group.app.id]
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = "app"
container_port = 8080
}
}
resource "aws_db_instance" "main" {
identifier = "app-db"
engine = "postgres"
instance_class = "db.t4g.medium"
allocated_storage = 50
username = "app"
manage_master_user_password = true
multi_az = true
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
}
Terraform を図に対応付ける
作業の大半は、どのリソースを図の要素にし、どのリソースを他の要素の詳細にするかを決めることです。
| Terraform では | 図では |
|---|---|
aws_vpc、aws_subnet、および vpc_id / subnet_id の参照 |
フレーム:パブリックサブネットとプライベートサブネットを囲む VPC |
リスナーとターゲットグループを伴う aws_lb |
ロードバランサーのカード 1 枚。リスナーのポートはエッジに書く |
クラスターとタスク定義を伴う aws_ecs_service |
必要数を添えたコンピューティングのカード 1 枚 |
サブネットグループを伴う aws_db_instance |
プライベートサブネット内のデータベースのカード 1 枚。Multi-AZ を明記 |
バージョニングとアクセスブロックを伴う aws_s3_bucket |
オブジェクトストレージのカード 1 枚 |
| セキュリティグループのルール | エッジ上のポートとプロトコル |
| リソース間の参照 | トラフィックが流れる向きに描く接続 |
depends_on |
読み手に必要なことを伝えるなら破線 |
IAM のロール、ポリシー、アタッチメント、random_*、time_*、null_resource |
省くか、カード上の短いメモにする |
.tf ファイルか plan JSON か
設定そのものから作業することも、プランから作業することもできます。
terraform plan -out plan.out
terraform show -json plan.out > plan.json
.tf ファイルは手早く、何も実行する必要がありません。ただし count と for_each は展開されず、変数は不明のままで、リモートモジュールも中身が見えません。plan JSON ではそれらがすべて解決されており、resource_changes のリストには、プランが各リソースに何をするかが書かれています。["create"]、["update"]、["delete"]、置き換えなら ["delete", "create"] です。そのため、変更のレビューには plan JSON のほうが適した入力になります。ファイルの扱いには注意してください。terraform show のドキュメントは、state 内の機密値が JSON 出力に平文で現れると警告しています。
図
ダイアグラムの内容
- Users
- app.example.com
- VPC
- Public subnets · 2 AZs
- Application LB
- Private subnets · 2 AZs
- ECS service ×2
- PostgreSQL, Multi-AZ
- Uploads bucket
- HTTPS 443
- alias record
- HTTP 8080
- PostgreSQL 5432
- HTTPS 443, uploads
図の読み方は次のとおりです。
- 入口。 ユーザーはポート 443 の HTTPS で Application Load Balancer に到達します。DNS のエイリアスレコードがそこを指しています。
- ネットワークの境界。 VPC には 2 つのフレームがあります。ロードバランサー用のパブリックサブネットと、それ以外のすべてのためのプライベートサブネットです。プライベートサブネット内のものには、インターネットから到達できません。
- 経路。 ロードバランサーはポート 8080 で ECS サービスに転送します。サービスは 5432 で PostgreSQL と通信し、アップロードを HTTPS で S3 に書き込みます。
- メモ。 各カードを背後のリソースアドレスに対応付け、意図的に省いたものを列挙しているので、読み手はコードを探せます。
まず削り、Terraform が知らないことを足す
思い切って削りましょう。IAM の接着剤、ルートテーブルの関連付け、ロググループ、ルールごとのセキュリティグループリソースはコードの中にあれば十分です。多くの読み手にとっては、デプロイ可能なものやデータストアごとにカード 1 枚が適切な粒度です。
そのうえで、Terraform からはわからないことを書き足します。
- 誰がシステムを使うのか、トラフィックがどこから入るのか。
- 流れの向き。 参照はリソースから、それが必要とするものへ向かいます。トラフィックは逆向きに流れることがよくあります。
- ポートとプロトコル。セキュリティグループのルールから拾い、エッジに書きます。
- 外部サービス。アプリが呼び出しているものの、この設定では管理されていないサービスです。
- 障害対応で重要な事実。Multi-AZ、バックアップ、必要数などです。
プルリクエストのレビューで使う
プルリクエストのプランから生成した図は、長い diff の代わりに変更の形をレビュアーに見せてくれます。特に次の点に注目してください。
- 新しいパブリックな入口。ロードバランサー、パブリックサブネット、0.0.0.0/0 に開いたセキュリティグループなどです。
- 状態を持つリソースの置き換え。 データベースやバケットに対する
["delete", "create"]には、2 人目のレビュアーが必要です。 - サブネット間を移動するリソース、またはプライベートネットワークの外に出るリソース。
- 新しいデータストア。オーナー、バックアップ、データマップへの記載が必要です。
生成した図と、手を入れて仕上げた構成図は分けて管理します。生成した図は 1 つの変更をレビューするための補助で、仕上げた図はシステムを説明するドキュメントです。
最新の状態を保つ
仕上げた図はコードと同じように扱います。オーナーを決め、モジュールの README からリンクし、変更によって入口、データストア、ネットワークの境界が増えたら更新します。腐らない構成図では、まさにそのためのワークフローを紹介しています。同じスタックのネットワーク面は AWS VPC 構成図のガイドを、2 つのリージョンで動かす場合は AWS マルチリージョンアーキテクチャ図を参照してください。
Archboard で作る
- Google または GitHub でサインインし、図を作成します。
- 最初の下書きには、
.tfファイルか plan JSON を無料の Terraform を図に変換 ツールに貼り付けます。VPC とサブネットはフレームに、リソースはカードに、参照は接続になります。plan JSON から予定された変更に印を付け、ターゲットグループのアタッチメントのような接着剤は接続に畳み込みます。編集を続けるには Archboard で開く を選びます。 - 手で描く場合は、ネットワークパックの VPC フレームと 2 つの サブネット フレームを追加し、パブリックサブネットにクラウドパックの クラウドロードバランサー を、プライベートサブネットに コンピューティング と マネージドデータベース のノードを置きます。
- バケット用にクラウドパックの オブジェクトストレージ ノード、ユーザー用にネットワークパックの インターネット ノード、エイリアスレコード用に DNS ノードを追加します。
- エッジにポートとプロトコルのラベルを付け、カードとリソースアドレスの対応を書いた基本パックの Markdown ブロックを追加します。
この例で使うダイアグラムノードは 10 個で、Free プランの範囲に収まります。スタックを CloudFormation で定義している場合は、CloudFormation を図に変換 ツールがテンプレートに対して同じことをします。
よくある質問
terraform graph で構成図を作れますか? 作れるのは、DOT 形式、または Terraform 1.16 以降なら Mermaid 形式の依存関係グラフです。プランのデバッグには役立ちますが、トラフィックではなく構築順序を示し、接着剤のリソースも含むため、人が構成図として読めるようにするには手を入れる必要があります。
図は .tf ファイルと plan JSON のどちらから描くべきですか?
モジュールをざっと見るなら .tf ファイル、変更をレビューするなら plan JSON を使います。プランでは個数、モジュール、予定されたアクションが解決されているからです。
セキュリティグループはどう表せばよいですか? 独立した箱ではなく、保護対象のリソース間のエッジに、ポートとプロトコルとして書きます。
何を省くべきですか?
IAM のアタッチメント、ルートテーブルの関連付け、random_id のような補助リソース、そして読み手によるトラフィック、データ、公開範囲の理解を変えないものすべてです。
図をコードと同期させるにはどうすればよいですか? 変更によって入口、データストア、ネットワークの境界が増えたら下書きを生成し直し、同じプルリクエストの中で仕上げた図を更新します。