AWS

terraformコードから構成図を出力する方法3選

sho

はじめに

西藤です。

私が参画させていただいているプロジェクトにおいては、AWSクラウドでのインフラ構成管理にTerraformを活用することが多く、これによりIaC(Infrastructure as Code)を実現し、その恩恵として再現性の高いインフラ構築が可能になっています。

しかし、実際のビジネスのニーズにおいては「IaCでコード化できている」というだけでは不十分で、Terraformのコードに不慣れなメンバーにも、インフラ構成を理解してもらう必要はどうしても出てきます。

そしてその際には、Terraformの言語、HCL(HashiCorp Configuration Language)で書かれた内容を説明する資料を作成するなどしても良いですが、構成図を作ることが一番ニーズを満たすことが多いです。

ただし、構成図とIaCとが別々に存在してしまうと、二重管理になってしまったり、構成図が古くなってしまうという問題があります。コードやインフラを変更した際に速やかに構成図も取得できるようになると理想です。

そこで、今回は、Terraformのコードから、インフラ構成図を作成する方法を検討してみましたので、その結果を記事にまとめます。

前提

今回の記事では、以下の前提を設定します。

  • Terraformバージョン:1.5.3
  • AWS Providerバージョン:5.9.0

そして、Terraformのコードによって以下のような構成でインフラを構築したものとします。

  • VPCひとつ
  • パブリックサブネットひとつとプライベートサブネットひとつをAZ-aに配置
  • パブリックサブネットひとつとプライベートサブネットひとつをAZ-cに配置
  • EC2インスタンスをパブリックサブネットにひとつ配置
  • そのほかEC2インスタンスがインターネット上からアクセスできるようなネットワーク設定

この構成を前提に、上記のような構成図を作成する方法を検討していきます。(上記の図は人の手で作成したものです)

選択肢1:terraform graphコマンド

まずはじめに、検討したいのがTerraformの標準コマンドです。
Terraformには、terraform graphというコマンドがあり、これを実行すると、Terraformのコードから、DOT形式可視化用のデータを出力してくれます。

https://developer.hashicorp.com/terraform/cli/commands/graph

上記の構成のTerraformコードを実行した結果、以下のようなデータが出力されます。

terraform graph            
digraph {
        compound = "true"
        newrank = "true"
        subgraph "root" {
                "[root] aws_instance.sample-ec2 (expand)" [label = "aws_instance.sample-ec2", shape = "box"]
                "[root] aws_internet_gateway.sample (expand)" [label = "aws_internet_gateway.sample", shape = "box"]
                "[root] aws_route_table.private (expand)" [label = "aws_route_table.private", shape = "box"]
                "[root] aws_route_table.public (expand)" [label = "aws_route_table.public", shape = "box"]
                "[root] aws_route_table_association.private-a (expand)" [label = "aws_route_table_association.private-a", shape = "box"]
                "[root] aws_route_table_association.private-c (expand)" [label = "aws_route_table_association.private-c", shape = "box"]
                "[root] aws_route_table_association.public-a (expand)" [label = "aws_route_table_association.public-a", shape = "box"]
                "[root] aws_route_table_association.public-c (expand)" [label = "aws_route_table_association.public-c", shape = "box"]
                "[root] aws_security_group.sample-ec2 (expand)" [label = "aws_security_group.sample-ec2", shape = "box"]
                "[root] aws_security_group_rule.sample-ec2-all (expand)" [label = "aws_security_group_rule.sample-ec2-all", shape = "box"]
                "[root] aws_security_group_rule.sample-ec2-http (expand)" [label = "aws_security_group_rule.sample-ec2-http", shape = "box"]
                "[root] aws_subnet.private-a (expand)" [label = "aws_subnet.private-a", shape = "box"]
                "[root] aws_subnet.private-c (expand)" [label = "aws_subnet.private-c", shape = "box"]
                "[root] aws_subnet.public-a (expand)" [label = "aws_subnet.public-a", shape = "box"]
                "[root] aws_subnet.public-c (expand)" [label = "aws_subnet.public-c", shape = "box"]
                "[root] aws_vpc.sample (expand)" [label = "aws_vpc.sample", shape = "box"]
                "[root] provider[\"registry.terraform.io/hashicorp/aws\"]" [label = "provider[\"registry.terraform.io/hashicorp/aws\"]", shape = "diamond"]
                "[root] aws_instance.sample-ec2 (expand)" -> "[root] aws_security_group.sample-ec2 (expand)"
                "[root] aws_instance.sample-ec2 (expand)" -> "[root] aws_subnet.public-a (expand)"
                "[root] aws_internet_gateway.sample (expand)" -> "[root] aws_vpc.sample (expand)"
                "[root] aws_route_table.private (expand)" -> "[root] aws_vpc.sample (expand)"
                "[root] aws_route_table.public (expand)" -> "[root] aws_internet_gateway.sample (expand)"
                "[root] aws_route_table_association.private-a (expand)" -> "[root] aws_route_table.private (expand)"
                "[root] aws_route_table_association.private-a (expand)" -> "[root] aws_subnet.private-a (expand)"
                "[root] aws_route_table_association.private-c (expand)" -> "[root] aws_route_table.private (expand)"
                "[root] aws_route_table_association.private-c (expand)" -> "[root] aws_subnet.private-c (expand)"
                "[root] aws_route_table_association.public-a (expand)" -> "[root] aws_route_table.public (expand)"
                "[root] aws_route_table_association.public-a (expand)" -> "[root] aws_subnet.public-a (expand)"
                "[root] aws_route_table_association.public-c (expand)" -> "[root] aws_route_table.public (expand)"
                "[root] aws_route_table_association.public-c (expand)" -> "[root] aws_subnet.public-c (expand)"
                "[root] aws_security_group.sample-ec2 (expand)" -> "[root] aws_vpc.sample (expand)"
                "[root] aws_security_group_rule.sample-ec2-all (expand)" -> "[root] aws_security_group.sample-ec2 (expand)"
                "[root] aws_security_group_rule.sample-ec2-http (expand)" -> "[root] aws_security_group.sample-ec2 (expand)"
                "[root] aws_subnet.private-a (expand)" -> "[root] aws_vpc.sample (expand)"
                "[root] aws_subnet.private-c (expand)" -> "[root] aws_vpc.sample (expand)"
                "[root] aws_subnet.public-a (expand)" -> "[root] aws_vpc.sample (expand)"
                "[root] aws_subnet.public-c (expand)" -> "[root] aws_vpc.sample (expand)"
                "[root] aws_vpc.sample (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/aws\"]"
                "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_instance.sample-ec2 (expand)"
                "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_route_table_association.private-a (expand)"
                "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_route_table_association.private-c (expand)"
                "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_route_table_association.public-a (expand)"
                "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_route_table_association.public-c (expand)"
                "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_security_group_rule.sample-ec2-all (expand)"
                "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_security_group_rule.sample-ec2-http (expand)"
                "[root] root" -> "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)"
        }
}

ただし、これだけでは、人の目で理解するには、少し難しいです。Terraformの公式ドキュメントにおいてもGraphvizを使って可視化する方法が紹介されています。

terraform graphコマンドの出力結果を、Graphvizを使って可視化すると、以下のような構成図が作成できます。

terraform graph | dot -Tsvg > graph.svg

このように、Terraformのコードからグラフ画像を作成することができました。リソースの依存関係を理解する上では便利かもしれません。
しかし、冒頭で示していたような構成図とは目的が違っているので、次の方法を検討します。

選択肢2:InfraMap

次に検討するのがInfraMapです。

https://github.com/cycloidio/inframap

これは、Terraformのコードから、インフラ構成図を作成するツールです。

AWSのほか、各クラウドプロバイダーに対応しており、リソースによってはアイコンも表示された形で出力されます。

tfstateファイルを読み込む形で実行されるため、Terraform Cloudなどで管理されている場合は、tfstateファイルをダウンロードしておく必要があります。
ダウンロードした上で、標準のオプションで実行し、Graphvizのdotコマンドを使って可視化すると、以下のような構成図が作成できます。

inframap --tfstate terraform.tfstate | dot -Tpng > inframap.png

・・・かなりシンプルな図となってしまいました。VPCやサブネットなどの概念が出力された図の中に現れておりません。(※1)

どうやらリソース間のセキュリティグループのつながりを表すときには強みを発揮するようなのですが、今回のパターンではあまり合致しませんでした。
ただし、EC2インスタンスについてはアイコンで表示されているところもあり、より大きなインフラを構築しているときには、その中のリソースを把握するのに便利かもしれません。

※1:下記にもissueとして挙げられているのですが、長らく動きがないので、優先度は低いのかもしれません。

AWS Groupping (VPC/Subnets/Region) #6

選択肢3:Pluralith

最後に紹介するのは、Pluralithです。

https://www.pluralith.com

こちらもTerraformで構成されたインフラを可視化するツールです。

上記2つと同様にコマンドラインベースでの実行となりますが、公式サイト上でのサインアップが必要です。

サインアップやCLIのインストール、APIキーの取得のセットアップ周りは公式のガイダンスに任せるものとして、ここでは実際に実行してみた際の使用感の紹介とします。

Terraformのプロジェクトディレクトリ内で、以下のコマンドを実行します。

pluralith graph

すると、しばらく読み込み処理が走ったのちブラウザにリダイレクトされます。

% pluralith graph                                
⠿ Initiating Graph ⇢ Posting Diagram To Pluralith Dashboard

→ Authentication
  ✔ API key is valid, you are authenticated!

→ Plan
  ✔ Local Execution Plan Generated
  ✔ Local Plan Cache Created
  ✔ Secrets Stripped
  - Cost Calculation Skipped

→ Graph
  ✔ Local Diagram Generated
  ✔ Diagram Posted To Pluralith Dashboard

  → Diagram Pushed To: https://app.pluralith.com/#/orgs/xxxxxxxxx/projects/pluralith-local-project/runs/xxxxxxx/

このようにTerraformにおいて直近での変更があった箇所をハイライトした形で全体の構成図が表示されています。(この画像の場合は、EC2インスタンスが再作成されたということを表しています)

また、左のメニューから"View"を表示するとすべてのアイコンがアクティブになった形で表示されます。

エクスポート機能もあり、PNG形式でダウンロードできます。
出力した際の画像は次のようなものとなります。

冒頭に私が作成した構成図の例を示しましたが、ちょっと作るのが大変で一部省いていたところがありましたが...このpluralithから出力した画像は、その部分も含めて作成されており、十分に使えるものとなっています。

そのほかにも、コスト計算の切り口で表示したり、セキュリティグループの依存関係を表示したりと、様々な機能があります。

ただし、今回の目的としては、コードの変更に伴い構成図を出力出来るかという観点でした。
このpluralithはウェブサービスとして提供されているため、サインアップが必要だったり、ブラウザを介して出力結果を見る必要があるという点でどうしても手数が多くなってしまうところが難点です。

各CIサービスとの連携も提供されているので、それらをうまく業務のワークフローに組み入れながら有効活用していくのが良いのかもしれません。

まとめ

以上、terraform公式によって紹介されている標準コマンドから、構成図出力に特化したWebサービスまで、いくつかの方法を試してみました。
出力の粒度や、出力結果の見やすさなど、それぞれに特徴があり一長一短であったというのが正直なところです。

まだまだ、Terraformのコードから構成図を作成する方法は検討の余地があると思いますので、今後も検討していきたいと思います。

今回の記事が同様のニーズを持った方々の参考になれば幸いです。

AUTHOR
sho
sho
記事URLをコピーしました