TerraformでCycleエラーが起きてるリソースだけを画像で表示して修正する

aws_security_groupのみを利用してSecurity Groupを構築をしていたら、plan時に20個近くのがリソースがCycleエラーになってしまいとても消耗してしまったので、これ以上被害者が増えないようにと本エントリーを投下します。
2019.09.20

最近、Terraformが好きだ。 もこ@札幌オフィスです。

aws_security_groupのみを利用してSecurity Groupを構築をしていたら、plan時に20個近くのリソースがCycleエラーになってしまいとても消耗してしまったので、これ以上被害者が増えないようにと本エントリーを投下します。

そもそもCycle エラーとは

リソースの相互参照によってエラーが発生します。

例えば下記のようなコードを実行してみます。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_security_group" "example-A" {
  ingress {
    from_port = 22
    protocol = "tcp"
    to_port = 22
    security_groups = [ aws_security_group.example-C.id ] 
  }
}

resource "aws_security_group" "example-B" {
  ingress {
    from_port = 22
    protocol = "tcp"
    to_port = 22
    security_groups = [ aws_security_group.example-A.id ]
  }
}


resource "aws_security_group" "example-C" {
  ingress {
    from_port = 22
    protocol = "tcp"
    to_port = 22
    security_groups = [ aws_security_group.example-B.id ]
  }
}

実行結果

Error: Cycle: aws_security_group.example-B, aws_security_group.example-C, aws_security_group.example-A

上記コードですと、 example-Bexample-A を参照、 example-Cexample-B を参照するまでは正常ですが、

example-Aexample-C を参照してしまっており、間接的とはなっていますが相互参照となってしまっています。

修正方法

上記のような場合ですと、Example-AのSecurity Groupを少し修正し、 aws_security_group_rule を利用してSecurity Groupを指定することによってエラー解決が可能です。

resource "aws_security_group" "example-A" {
}

resource "aws_security_group_rule" "example-A-rule" {
  type = "ingress"
  from_port = 22
  protocol = "tcp"
  to_port = 22
  source_security_group_id = aws_security_group.example-C.id
  security_group_id = aws_security_group.example-A.id
}

エラーが起きてるリソースを普通にグラフで出力する

Terraformのコマンドでgraphを出力することも出来ます。

実行にはdotコマンドが必要となっていますので、インストールします。

brew install graphviz

もしくは

pip install graphviz

インストール後、下記コマンドでCycleエラーが起こっているリソースをハイライトしたグラフ画像を出力することが出来ます。

terraform graph -draw-cycles | dot -Tsvg > graph.svg

出力した結果はこんな感じ

薄っすらと左側に赤くなっているのがSecurity Group部分で、Cycleエラーが起こっている部分となっています。(あえて画像を小さくしています)

この画像から追うのはしんどい、エラーが起きてる箇所だけを表示してみる

上記画像とにらめっこしていたら、隣の席のCloudFormationマンに「TerraformしんどそうwCloudFormationだと勝手にいい感じにしてくれるよw」と煽られてしまいましたが、今からCloudFormationで書き直すのはしんどかったので、ひとまずエラーが起こっている部分だけを抽出してみます。

terraform graph -draw-cycles

で出力される形は下記のようになっています。

(例)

digraph {
        compound = "true"
        newrank = "true"
        subgraph "root" {
                "[root] aws_security_group.hogehoge" [label = "aws_security_group.hogehoge", shape = "box"]
                "[root] aws_security_group.hugahuga" -> "[root] aws_security_group.hugahuga" [color = "red", penwidth = "2.0"]
                ...省略
        }
}

Cycleエラーになっているリソースに対しては [color = "red", penwidth = "2.0"] が付与されていることがなんとなくわかるかと思います。

grepでCycleエラーが起きているリソースだけを取得してみます。 適当に"red"の部分だけでgrepしてみましょう。

$ terraform graph -draw-cycles | grep "red"
"[root] aws_security_group.hogehoge" -> "[root] aws_security_group.hogehoge1" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge1" -> "[root] aws_security_group.hogehoge2" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge2" -> "[root] aws_security_group.hogehoge3" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge3" -> "[root] aws_security_group.hogehoge4" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge4" -> "[root] aws_security_group.hogehoge5" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge5" -> "[root] aws_security_group.hogehoge6" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge6" -> "[root] aws_security_group.hogehoge7" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge7" -> "[root] aws_security_group.hogehoge8" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge8" -> "[root] aws_security_group.hogehoge9" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge9" -> "[root] aws_security_group.hogehoge10" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge10" -> "[root] aws_security_group.hogehoge11" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge11" -> "[root] aws_security_group.hogehoge12" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge12" -> "[root] aws_security_group.hogehoge13" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge13" -> "[root] aws_security_group.hogehoge14" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge14" -> "[root] aws_security_group.hogehoge15" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge15" -> "[root] aws_security_group.hogehoge16" [color = "red", penwidth = "2.0"]
"[root] aws_security_group.hogehoge16" -> "[root] aws_security_group.hogehoge" [color = "red", penwidth = "2.0"]

これでエラーが起きているリソースだけをGrep出来ましたので、graphvizで表示できる形にしていきます。

subgraph "root" { の中にgrepした結果を入れていきます

digraph {
        compound = "true"
        newrank = "true"
        subgraph "root" {
        "[root] aws_security_group.hogehoge" -> "[root] aws_security_group.hogehoge" [color = "red", penwidth = "2.0"]
        "[root] aws_security_group.hogehoge1" -> "[root] aws_security_group.hogehoge1" [color = "red", penwidth = "2.0"]
        "[root] aws_security_group.hogehoge2" -> "[root] aws_security_group.hogehoge2" [color = "red", penwidth = "2.0"]
        ...略
        }
}

適当なファイルで保存して、早速グラフにして開いてみましょう。

cat grepgraph | dot -Tpng > graph.png

これでとても見やすくなりました。めでたしめでたし。

grepでゴリ押したりしていましたが、Goでワンライナーでリソースを絞り込めるスクリプトが公開されました!是非こちらも試してみてください(2020/03/19追記)

(小ネタ) terraform graph で該当するリソースだけ絞り込むスクリプト

まとめ

aws_security_group_rule を利用して相互参照を解消することによって Cycle Errorを修正することが可能!

terraform graphコマンドを利用するとTerraformのリソース一覧をグラフ(画像)にして表示することが出来る!

Cycle エラーには [color = "red", penwidth = "2.0"] が含まれているので、Grepして無駄なものを省いてあげればCycleエラーになっているリソースだけを可視化することが可能!

誰かのお役に立てれば幸いです。