Upgrade to Pro — share decks privately, control downloads, hide ads and more …

terraform plan 結果の検証を自動化するぞ! with Conftest / Testing terraform plan with Conftest

terraform plan 結果の検証を自動化するぞ! with Conftest / Testing terraform plan with Conftest

下記LT会にて発表した資料です。

自動化大好きエンジニアLT会 - vol.5 - connpass
https://rakus.connpass.com/event/224448/

スライド内リンク一覧
---

8ページ
- Conftest https://www.conftest.dev/
- Open Policy Agent https://www.openpolicyagent.org/

12ページ
- hashicorp/terraform-json https://github.com/hashicorp/terraform-json

20ページ
- Flatten nested JSON using jq https://stackoverflow.com/questions/37540717/flatten-nested-json-using-jq/37555908#37555908

21ページ
- tfjson package https://pkg.go.dev/github.com/hashicorp/[email protected]#Plan

26ページ
- TerraformのレビューをConftestで自動化する - Speaker Deck https://speakerdeck.com/ryokbt/terraformfalserebiyuwoconftestdezi-dong-hua-suru
- Terraform x OPA/Conftest の tips - Speaker Deck https://speakerdeck.com/ryokbt/conftest-false-tips
- ConftestでOpenPolicyAgent/Regoを使いTerraformのコードにポリシーを適用してみる - febc技術メモ https://febc-yamamoto.hatenablog.jp/entry/2019/06/11/221017

27ページ
- hashicorp/terraform-json https://github.com/hashicorp/terraform-json
- korosuke613/tfplantesting https://github.com/korosuke613/tfplantesting

28ページ
- Slidev https://sli.dev/

Futa HIRAKOBA

November 02, 2021
Tweet

More Decks by Futa HIRAKOBA

Other Decks in Programming

Transcript

  1. terraform plan
    結果の
    検証を自動化するぞ!
    with Conftest
    2021/11/2
    (火)自動化大好き LT
    会 #5
    平木場 風太
    ` `

    View full-size slide

  2. 自己紹介
    平木場 風太 - Futa Hirakoba
    🌋 出身
    -
    鹿児島
    🏢 勤め先
    -
    サイボウズ株式会社
    🧑‍💻
    役割
    - Engineering Productivity (
    生産性向上エンジニア)
    🍣 好きな食べ物
    -
    辛麺
    🛠 GitHub - @korosuke613
    🐥 Twitter - @shitimi_613


    最近は AWS
    や Terraform
    を触ることが多いです。
    他には Docker Desktop
    一部有料化に伴う対応や CircleCI Server
    更新(2.x -> 3.x
    )なんかもしてます。
    2 / 28
    [1]
    1.
    写真は桝元のトマト辛麺。

    View full-size slide

  3. Terraform
    とは?
    いわゆる Infrastructure as Code
    ツール
    コードでインフラ(AWS
    など)を構築する
    terraform plan
    で現在の状態と設定を比較して今後の操作を出力する
    plan
    の例(Launch Template
    の AMI ID
    を変更する場合)
    # ...aws_launch_template.runner will be updated in-place
    ~ resource "aws_launch_template" "runner" {
    id = "lt-00925d96xxxxxxxxx"
    ~ image_id = "ami-0df8ce117xxxxxxxx" -> "ami-03b8979cfxxxxxxxx"
    ~ latest_version = 35 -> (known after apply)
    name = "ghes-xxxx-action-runner"
    tags = {
    "Environment" = "ghes-xxxx"
    "Name" = "ghes-xxxx-action-runner"
    }
    # (9 unchanged attributes hidden)
    # (5 unchanged blocks hidden)
    }
    今回はこの plan
    結果を自動で検証したいという内容です。
    3 / 28
    ` `

    View full-size slide

  4. 背景
    生産性向上チームはオートスケールする GitHub Actions Self-hosted Runner
    環境を AWS
    で構築している 。
    4 / 28
    [1]
    1.
    オートスケールする GitHub Actions
    セルフホストランナーを構築した話, https://www.slideshare.net/miyajan/github-actions-
    250042631

    View full-size slide

  5. オートスケールする GitHub Actions
    セルフホストラ
    ンナー環境の特徴
    Terraform Module
    を用意して GitHub Organization
    ごとに環境を管理している
    そのため、モジュールに変更を加えると大量のリソースが変更される
    例えば EC2 AutoScalingGroup
    の Launch Configuration
    で指定する AMI ID
    を変えるだけ
    で大量の変更が出る
    Org
    が 7
    つの場合: Plan: 7 to add, 21 to change, 7 to destroy.
    AMI
    を更新するたびに全ての変更を確認するのつらい 🥺
    5 / 28
    ` `

    View full-size slide

  6. こういった変更はある程度パターン化されている
    操作ごとの変更の数
    変更されるリソースの種類
    変更されるリソースの名前
    etc…
    => plan
    結果はある程度自動で検証できるのでは 🤔
    6 / 28

    View full-size slide

  7. plan
    結果をテストする方法
    いろいろなツールがある。
    HashiCorp Sentinel
    HashiCorp
    謹製
    調べてもあんま情報出てこない
    Conftest (Open Policy Agent)
    Terraform
    に限らずあらゆる構造化された内容をテストできる
    割と情報出てくる
    Go (terraform-json)
    Go
    の terraform-json
    モジュールで plan
    内容を解析してテストできる
    =>
    今回は
    Conftest
    を使ってみることとした
    7 / 28

    View full-size slide

  8. Conftest
    さまざまな構造化された設定データをテストをするためのツール
    Open Policy Agent
    の Rego
    言語でポリシー(

    テストコード)を作成する
    JSON
    や YAML
    、HCL2
    、Dockerfile
    などに対応しており、Terraform
    以外にも広く使える
    ポリシー自体のテストも可能
    8 / 28
    [1]
    [2]
    1. https://www.conftest.dev/
    2.
    ポリシーエンジン。Conftest
    は内部的に Open Policy Agent
    を使っている。https://www.openpolicyagent.org/

    View full-size slide

  9. ポリシー作成の流れ
    1.
    何をポリシーとしたいかを考える
    2. plan
    結果を json
    に出力
    3. plan
    結果を参考にポリシーを作成 &
    テスト
    ポリシーが完成したら実際に plan
    結果を検証する。問題なければ繰り返し使える!
    9 / 28

    View full-size slide

  10. 1.
    何をポリシーとしたいか考える
    今回は AMI
    を更新する際の検証について考える。

    -
    例えば EC2 AutoScalingGroup
    の Launch Configuration
    で指定する AMI ID
    を変えるだけで大量の変更が出る
    - Org
    が 7
    つの場合: `Plan: 7 to add, 21 to change, 7 to destroy.`
    - AMI
    を更新するたびに全ての変更を確認するのつらい 🥺
    最低限以下が確認できていれば十分だと判断。
    特定のリソースの種類であること
    特定のリソース名であること
    特定の操作の数が正しいこと
    create
    が 7

    update
    が 21

    delete
    が 7

    10 / 28
    ` `
    ` `
    ` `

    View full-size slide

  11. 2. plan
    結果を json
    に出力
    plan
    結果を conftest
    で扱うために json
    形式で plan
    結果を出力する。
    ❯ terraform plan -out plan.out
    ❯ terraform show -json plan.out > plan.json
    -out
    オプションで plan
    結果をファイルに出力する
    show
    コマンドで -out
    で出力したファイルを人間が読める形式で標準出力する
    -json
    オプションで show
    コマンドの結果を json
    形式で標準出力する
    11 / 28
    ` `
    ` ` ` `
    ` ` ` `

    View full-size slide

  12. plan.json
    (一部抜粋)
    "resource_changes": [
    {
    "address": "module.foo.null_resource.aliased",
    "module_address": "module.foo",
    "mode": "managed",
    "type": "null_resource",
    "name": "aliased",
    "provider_name": "null.aliased",
    "change": {
    "actions": [ "create" ],
    "before": null,
    "after": { "triggers": null },
    "after_unknown": { "id": true }
    }
    },
    ...
    ]
    resource_changes
    リソースごとの変更内容
    変更されないリソースの情報も入る
    actions
    リソースに対する操作
    create
    update
    delete
    read
    (data
    リソースを作成する場合)
    no-op
    (何も変更がない場合)
    詳しくは hashicorp/terraform-json
    を参照
    12 / 28
    ` `
    ` `
    ` `
    ` `
    ` `
    ` `
    ` `
    [1]
    1. https://github.com/hashicorp/terraform-json

    View full-size slide

  13. 3. plan
    結果を参考にポリシーを作成 &
    テスト
    Rego
    という言語を用いてポリシーを作成する
    作成時は plan
    結果を参考にしながら作るのが楽
    ポリシーのテストコードも書く
    13 / 28

    View full-size slide

  14. 特定のリソースの種類であること
    deny[msg] {
    #
    変更されるリソースのアドレスと種類、操作の種類を取得
    resource_address := input.resource_changes[_].address
    resource_type := input.resource_changes[_].type
    resource_action := input.resource_changes[_].change.actions[_]
    # action
    がno-op
    でないことを確認
    resource_action != "no-op"
    #
    リソースの種類が許可されたものでないことを確認
    not allow_resource_type(resource_type)
    msg = sprintf(
    "Allow only resource types. address: `%v`, type: `%v`",
    [resource_address, resource_type]
    )
    }
    #
    許可するリソースの種類を定義
    allow_resource_type(type) {
    type == "aws_lambda_function"
    }
    allow_resource_type(type){
    type == "aws_autoscaling_group"
    }
    allow_resource_type(type){
    type == "aws_launch_configuration"
    }
    allow_resource_type(type) {
    type == "aws_launch_template"
    }
    拒否時のメッセージ
    ❯ conftest test plan.json
    FAIL - plan.json - main - Allow only resource types. address: `aws_instance.foo`, type: `aws_instance`
    14 / 28

    View full-size slide

  15. 特定のリソース名であること
    deny[msg] {
    #
    変更されるリソースのアドレスと名前、操作の種類を取得
    resource_address := input.resource_changes[_].address
    resource_name := input.resource_changes[_].name
    resource_action := input.resource_changes[_].change.actions[_]
    # action
    がno-op
    でないことを確認
    resource_action != "no-op"
    #
    リソースの名前が許可されたものでないことを確認
    not allow_resource_name(resource_name)
    msg = sprintf(
    "Allow only resource names. address: `%v`, name: `%v`",
    [resource_address, resource_name]
    )
    }
    #
    許可するリソースの名前を定義
    allow_resource_name(name) {
    name == "runner"
    }
    allow_resource_name(name) {
    name == "scale_up"
    }
    allow_resource_name(name) {
    # name
    に "register_offline_runner"
    #
    が含まれていることを確認
    contains(name, "register_offline_runner")
    }
    拒否時のメッセージ
    ❯ conftest test plan.json
    FAIL - plan.json - main - Allow only resource names. address: `aws_launch_configuration.foo`, name: `foo`
    15 / 28

    View full-size slide

  16. 特定の操作の数が正しいこと
    delete
    の場合
    deny[msg]{
    #
    操作 delete
    の数を取得
    delete_actions_num = count([action |
    action := input.resource_changes[_].change.actions[_]
    action == "delete"
    ])
    # delete action
    の数が 7
    であることを確認
    delete_actions_num != 7
    msg = sprintf("The number of delete actions is not correct. `%v`", [delete_actions_num])
    }
    拒否時のメッセージ
    ❯ conftest test plan.json
    FAIL - plan.json - main - The number of delete actions is not correct. `2`
    16 / 28

    View full-size slide

  17. ポリシー自体のテスト
    「特定の操作の数が正しいこと」に対するテスト(一部)
    test_deny_number_of_actions {
    #
    ポリシーに引っかかる場合のテスト
    deny["The number of update actions is not correct. `1`"] with input as {"resource_changes": [
    {"change": {"actions": ["delete"]}},
    {"change": {"actions": ["update"]}},
    ]}
    #
    ポリシーに引っかからない場合も確認する
    not deny["The number of update actions is not correct. `7`"] with input as {"resource_changes": [
    {"change": {"actions": ["delete"]}},
    {"change": {"actions": ["delete"]}},
    # ...
    (合計 7
    つの `delete`

    ]}
    }
    ❯ conftest verify --report fails
    data.main.test_deny_number_of_actions: PASS (985.769µs)
    --------------------------------------------------------------------------------
    PASS: 1/1
    --report fails
    をつけておくとテスト失敗時にスタックトレースが出てきて便利。
    17 / 28
    ` `

    View full-size slide

  18. ポリシーできた!!
    実際に試してみる
    18 / 28

    View full-size slide

  19. 実際の plan
    結果を conftest
    で検証してみる
    冒頭で紹介した EC2
    で利用する AMI ID
    を変更する際の実際の plan
    結果を conftest
    で検証してみる。
    ❯ terraform plan -out plan.out
    ...
    ❯ terraform show -json ./plan.out > plan.json
    ❯ conftest test plan.json
    結果が返ってこない
    19 / 28
    1. CPU: 2.6GHz 6
    コア Intel Core i7
    、メモリ: 32GB
    の MacBook Pro
    で実行

    View full-size slide

  20. 実際の plan
    結果を conftest
    で検証してみる
    冒頭で紹介した EC2
    で利用する AMI ID
    を変更する際の実際の plan
    結果を conftest
    で検証してみる。
    ❯ terraform plan -out plan.out
    ...
    ❯ terraform show -json ./plan.out > plan.json
    ❯ conftest test plan.json
    結果が返ってこない
    … 15
    分もかかった 😇
    19 / 28
    [1]
    1. CPU: 2.6GHz 6
    コア Intel Core i7
    、メモリ: 32GB
    の MacBook Pro
    で実行

    View full-size slide

  21. 実際の plan
    結果を conftest
    で検証してみる
    冒頭で紹介した EC2
    で利用する AMI ID
    を変更する際の実際の plan
    結果を conftest
    で検証してみる。
    ❯ terraform plan -out plan.out
    ...
    ❯ terraform show -json ./plan.out > plan.json
    ❯ conftest test plan.json
    結果が返ってこない
    … 15
    分もかかった 😇
    調べたら json
    のサイズが 3.8MB
    もあった…
    そりゃ終わらんわけだ
    ❯ ls -lh plan.json | awk '{print $5}'
    3.8M
    19 / 28
    [1]
    1. CPU: 2.6GHz 6
    コア Intel Core i7
    、メモリ: 32GB
    の MacBook Pro
    で実行

    View full-size slide

  22. plan
    結果の json
    が大きくなる理由を考える
    json
    の全ての value
    数を数えてみる と…
    ❯ cat plan.json \
    | jq '[leaf_paths as $path | {"key": $path | join("."), "value": getpath($path)}] | from_entries | length'
    48462
    20 / 28
    [1]
    1. Flatten nested JSON using jq, https://stackoverflow.com/questions/37540717/flatten-nested-json-using-jq/37555908#37555908

    View full-size slide

  23. plan
    結果の json
    が大きくなる理由を考える
    json
    の全ての value
    数を数えてみる と…
    ❯ cat plan.json \
    | jq '[leaf_paths as $path | {"key": $path | join("."), "value": getpath($path)}] | from_entries | length'
    48462
    なんと 48,462
    個 😱
    ファイルサイズが 3.8MB
    になるのも納得。
    20 / 28
    [1]
    1. Flatten nested JSON using jq, https://stackoverflow.com/questions/37540717/flatten-nested-json-using-jq/37555908#37555908

    View full-size slide

  24. plan
    結果はリソースの変更だけじゃない?
    plan
    結果の json
    は resource_changes
    以外にもさまざまな情報を持っている。
    key
    名 説明 value
    の数
    format_version json
    フォーマットのバージョン 1
    terraform_version Terraform
    のバージョン 1
    variables variable
    の情報 2
    planned_values plan
    適用後のリソースの情報 10047
    resource_changes resource
    と data
    に関する変更 (ただし no-op
    を含む)
    17192
    resource_drift Terraform
    外の変更 0
    output_changes output
    に関する変更 15
    prior_state plan
    前のリソースの情報 14711
    configuration provider
    や module
    の設定など 6493
    21 / 28
    ` `
    [1]
    ` `
    ` ` ` ` ` `
    ` `
    1.
    右を参考にした -> tfjson package - github.com/hashicorp/terraform-json - pkg.go.dev,
    https://pkg.go.dev/github.com/hashicorp/[email protected]#Plan

    View full-size slide

  25. plan
    結果の json
    を軽くする
    1. resource_changes
    のみを取り出す
    2.
    さらに resource_changes
    の中でも変更がある( no-op
    以外)
    リソースのみを取り出す
    ❯ cat plan.json \
    | jq .resource_changes \
    | jq '{resource_changes: map(select( .change.actions != ["no-op"]))}' \
    > plan_slim.json
    plan_slim.json
    のサイズを調べる。
    ❯ cat plan_slim.json \
    | jq '[leaf_paths as $path | {"key": $path | join("."), "value": getpath($path)}] | from_entries | length'
    1868
    ❯ ls -lh plan_slim.json | awk '{print $5}'
    361K
    元が 3.8MB
    だったので、大体 1/10
    に削減できた。 これで 15
    分も待たずにすみそう。
    22 / 28
    ` `
    ` ` ` `
    ` `

    View full-size slide

  26. 再度検証してみる
    ポリシー一覧(再掲)
    -
    特定のリソースの種類であること
    -
    特定のリソース名であること
    -
    特定の操作の数が正しいこと
    - `create`
    が 7

    - `update`
    が 21

    - `delete`
    が 7

    ❯ conftest test plan_slim.json
    5 tests, 5 passed, 0 warnings, 0 failures, 0 exceptions
    今度は 1
    秒もかからず実行できた。
    これで AMI
    の更新が楽になるぞ!
    23 / 28

    View full-size slide

  27. Conftest
    の導入によって期待できること
    plan
    結果の検証コストを減らせる
    人手によるミスを減らせる
    apply
    の自動化を比較的安全にできる

    実は今日時点でまだリポジトリに導入できていません(そこまで持っていく時間が足りなかった)。
    24 / 28

    View full-size slide

  28. Conftest
    使ってみての感想
    🌞 慣れれば簡単にポリシーを書ける
    🌞 ポリシーのテストも簡単に書ける
    🌞 比較的メジャーなので検索すると事例を見つけやすい
    ☔️
    慣れるまでの学習コストは高い
    ☔️
    ポリシーをガチガチにすると管理がめんどい
    クリティカルな部分のみ検証する?
    何を確認して何を確認しないかのバランスがむずい
    ☁️
    読み込ませるファイルが大規模すぎると検証が終わらなくなる
    必要な部分のみ抽出などで対応は可能
    ☁️
     一部の問題は Terraform
    の構成・運用を変えるなど別の対応を検討した方がいいかも
    変更リソースが多すぎる問題など
    25 / 28
    [1]
    1.
    テスト全般に言えそうだけど

    View full-size slide

  29. 参考資料
    Conftest
    を使う上で以下の資料を参考にさせていただきました。
    Terraform
    のレビューを Conftest
    で自動化する - Speaker Deck
    Terraform x OPA/Conftest
    の tips - Speaker Deck
    Conftest
    で OpenPolicyAgent/Rego
    を使い Terraform
    のコードにポリシーを適用してみる
    - febc
    技術メモ
    26 / 28
    [1]
    [2]
    [3]
    1. https://speakerdeck.com/ryokbt/terraformfalserebiyuwoconftestdezi-dong-hua-suru
    2. https://speakerdeck.com/ryokbt/conftest-false-tips
    3. https://febc-yamamoto.hatenablog.jp/entry/2019/06/11/221017

    View full-size slide

  30. 余談: hashicorp/terraform-json
    モジュールを使うこと
    で Go
    でも検証できる
    hashicorp/terraform-json
    で terraform show -json
    が型定義されている
    terraform-json
    を使うことで Go
    を使ってポリシー(テストコード)を書くことができる
    デバッグがしやすい
    Go
    が書ける人は学習コスト低い
    もちろん IDE
    サポートも充実
    OPA
    が書けるならそちらの方が書きやすいかも
    色々試してみたコードは korosuke613/tfplantesting
    で雑に公開しています。
    27 / 28
    [1] ` `
    [2]
    1. https://github.com/hashicorp/terraform-json
    2. https://github.com/korosuke613/tfplantesting

    View full-size slide

  31. まとめ
    terraform plan
    結果の検証を自動化するために Conftest
    を使ってみた
    簡単なポリシーを書くだけで検証のコストと人手によるミスを減らせるのは嬉しい
    apply
    自動化も検討できる
    読み込ませるファイルが大きすぎる場合は、必要な部分だけ抽出した方がいい
    Go
    の hashicorp/terraform-json
    モジュールを使って検証するのも良さそう
    ちなみに、このスライドは Slidev (https://sli.dev/)
    というツールを使って Markdown + HTML + CSS
    で作成しました。
    28 / 28
    ` `

    View full-size slide