2023/03/09にTerraformのversion 1.4がGAになりました。主要アップデートを実際使ってみたのでレポートします。
Terraform CloudのCLI-driven runの場合でもWeb UIでPlan結果が見やすく見れるようになった
まずTerraform Cloudについておさらいです。Terraform Cloudには3種類のワークフロー(≒使い方)が存在します。
- UI/VCS-driven workflows … GitなどのVCSと連携させてコミットがマージされる度にTerraformコマンドが実行されるもの
- CLI-driven workflows … OSS版と同様
terraform xxx
コマンドを実行して使うもの - API-driven workflows … Terraform CloudのAPIを直接叩いて使うもの
このアップデートはCLI-drivenに関するものです。CLI-driven以外の方法でTerraformを実行した場合、Web UIの該当の実行(Run)ページにて、structured run outputと言われる見やすい表記でdiffを確認することができました。リソースをフィルターしたり、リソース毎にdiff詳細を表示・非表示したりといったことができます。
今回の1.4から、CLI-drivenで実行した場合でもWeb UIの該当実行ページにてstructured run outputでdiffを確認できるようになりました。1.3系最新バージョン1.3.9と1.4.0で表示の違いを確認しました。
before: v1.3.9
after: v1.4.0
こちらがstructured run outputと言われる表記です。各リソースにある「+」をクリックすることで、そのリソースのdiff詳細を開閉できます。またフィルターもありますね。
CLIでもOPAの結果が確認可能になった
OPA(Open Policy Agent)とは
汎用的なポリシーエンジンです。要は様々なツール・サービスに対して、同じ方法、同じ言語を使ってポリシー(セキュリティのルール、基準、条件)を満たしているかチェックするためのもの、です。Terraform以外にもKubernetes、Envoy、Kafka、SQLなどのチェックができるようです。
先日ネイティブサポートがGAしていた
2023/01/31に、Terraform CloudのこのOPAのネイティブサポートがGAになっていました。Terraform Cloud には以前からHashiCorp Sentinelによるポリシーチェックがサポートされていましたが、すでにOPAを使っている組織の要望を受けてこのOPAネイティブサポートの対応を行なったそうです。
※ なお、FreeプランはOPAもSentinelも未対応です。Team & Governanceプラン以上にアップグレードしましょう。(参考: Features欄のPolicy & securityをクリック)
そしてこの度、CLI-driven workflowを採用した場合のコンソールでも、OPAのチェック結果が確認できるようになりました。v1.3.9とv1.4.0での実行結果の違いを見てみましょう。
before: v1.3.9で実行
% terraform apply
(plan結果…)
# module.network.module.vpc.aws_vpc.this[0] will be created
+ resource "aws_vpc" "this" {
+ arn = (known after apply)
+ assign_generated_ipv6_cidr_block = false
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_classiclink = (known after apply)
+ enable_classiclink_dns_support = (known after apply)
+ enable_dns_hostnames = true
+ enable_dns_support = true
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_block_network_border_group = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = ""
}
+ tags_all = {
+ "Name" = (known after apply)
}
}
Plan: 22 to add, 0 to change, 0 to destroy.
Post-plan Tasks:
All tasks completed! 0 passed, 0 failed
│
│ Overall Result: Passed
------------------------------------------------------------------------
------------------------------------------------------------------------
Cost Estimation:
Waiting for cost estimate to complete...
╷
│ Error: Unknown or unexpected cost estimate state: unreachable
│
│
╵
cost estimationでエラーになったみたいに見えますね… ただ、該当Runをコンソールで確認するとOPAで引っかかったことがわかります。
after: v1.4.0で実行
% terraform apply
(plan結果…)
# module.network.module.vpc.aws_vpc.this[0] will be created
+ resource "aws_vpc" "this" {
+ arn = (known after apply)
+ assign_generated_ipv6_cidr_block = false
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_classiclink = (known after apply)
+ enable_classiclink_dns_support = (known after apply)
+ enable_dns_hostnames = true
+ enable_dns_support = true
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_block_network_border_group = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = ""
}
+ tags_all = {}
}
Plan: 22 to add, 0 to change, 0 to destroy.
Post-plan Tasks:
OPA Policy Evaluation
→→ Overall Result: FAILED
This result means that one or more OPA policies failed. More than likely, this was due to the discovery of violations by the main rule and other sub rules
2 policies evaluated
→ Policy set 1: learn-terraform-drift-and-opa (2)
↳ Policy name: friday_deploys
| × Failed
| No description available
↳ Policy name: public_ingress
| ✓ Passed
| No description available
╷
│ Error: Task Stage failed.
│
│
╵
OPAのチェック結果が確認できますね。
null_resourceがTerraform標準機能になった
null_resource
をご存知でしょうか? 基本的にこのリソースは何もしないのですが、他のリソースでは実現できないことを実現したい際にトリッキーな使い方をされることが多いです。例えば私はLambda関数をTerraformだけでデプロイする際に、null_resourceをProvisionersと組み合わせてビルドコマンドを実行するために使いました。
null_resource
はnull providerのリソースなのですが、今回の1.4でこのnull_resource
と同等のリソースがTerraformの標準リソースterraform_data
として用意されることになりました。なので今後はproviderのインストールが不要になります。
null_resource → terraform_dataへの置き換えをやってみた
前述の、Lambda関数のデプロイに使っていたnull_resource
をterraform_data
に置き換えてみました。
before: null_resource
resource "null_resource" "lambda_build" {
depends_on = [aws_s3_bucket.lambda_assets]
triggers = {
code_diff = sha256(join("", [
for file in setunion(
fileset(local.transfer_function_dir_local_path, "{*.ts,package*.json}")
, fileset(local.transfer_function_dir_local_path, "src/**/*.ts")
)
: filebase64("${local.transfer_function_dir_local_path}/${file}")
]))
}
provisioner "local-exec" {
command = "cd ${local.transfer_function_dir_local_path} && npm install"
}
provisioner "local-exec" {
command = "cd ${local.transfer_function_dir_local_path} && npm run build"
}
provisioner "local-exec" {
command = "aws s3 cp ${local.transfer_function_package_local_path} s3://${aws_s3_bucket.lambda_assets.bucket}/${local.transfer_function_package_s3_key}"
}
provisioner "local-exec" {
command = "openssl dgst -sha256 -binary ${local.transfer_function_package_local_path} | openssl enc -base64 | tr -d \"\n\" > ${local.transfer_function_package_base64sha256_local_path}"
}
provisioner "local-exec" {
command = "aws s3 cp ${local.transfer_function_package_base64sha256_local_path} s3://${aws_s3_bucket.lambda_assets.bucket}/${local.transfer_function_package_base64sha256_s3_key} --content-type \"text/plain\""
}
}
after: terraform_data
とても簡単でした。変えたのは以下だけです。
- resourceタイプを null_resource → terraform_dataに変更
- triggers argumentをtriggers_replaceに変更
- 他のリソースで
null_resource.lambda_build
をdepends_onで参照しているものがあったので、それをterraform_data.lambda_build
に変更
resource "terraform_data" "lambda_build" {
depends_on = [aws_s3_bucket.lambda_assets]
triggers_replace = {
code_diff = sha256(join("", [
for file in setunion(
fileset(local.transfer_function_dir_local_path, "{*.ts,package*.json}")
, fileset(local.transfer_function_dir_local_path, "src/**/*.ts")
)
: filebase64("${local.transfer_function_dir_local_path}/${file}")
]))
}
provisioner "local-exec" {
command = "cd ${local.transfer_function_dir_local_path} && npm install"
}
provisioner "local-exec" {
command = "cd ${local.transfer_function_dir_local_path} && npm run build"
}
provisioner "local-exec" {
command = "aws s3 cp ${local.transfer_function_package_local_path} s3://${aws_s3_bucket.lambda_assets.bucket}/${local.transfer_function_package_s3_key}"
}
provisioner "local-exec" {
command = "openssl dgst -sha256 -binary ${local.transfer_function_package_local_path} | openssl enc -base64 | tr -d \"\n\" > ${local.transfer_function_package_base64sha256_local_path}"
}
provisioner "local-exec" {
command = "aws s3 cp ${local.transfer_function_package_base64sha256_local_path} s3://${aws_s3_bucket.lambda_assets.bucket}/${local.transfer_function_package_base64sha256_s3_key} --content-type \"text/plain\""
}
}
inputとoutputのユースケースを考える
null_resource
には無くてterraform_data
にあるものとして、input
argumentとoutput
reference attributeがあります。これらの使い道を考えてみました。
まず、input単体でなら、公式ドキュメントにもあるように、変数を参照したいけど変数を参照することが禁じられているargumentに対して、間にterraform_data
をかます、という使い方があると思います。
次にoutputですが、公式ドキュメントに以下のように説明されています。
The computed value derived from the
input
argument. During a plan where output is unknown, it will still be of the same type asinput
. (参照元)
さらにinputにも以下のように説明があります。
A value which will be stored in the instance state, and reflected in the output attribute after apply. (参照元)
つまりapplyフェーズでしか値が明らかにならないattributeです。これを利用して、プロビジョニング順を制御するのに使えそうです。以下のようにoutput値を参照するリソースを定義しました。
resource "aws_s3_object" "test" {
bucket = aws_s3_bucket.lambda_assets.id
key = terraform_data.lambda_build.output
content = "test"
}
すると、terraform_dataリソースのプロビジョニングが完了した後に上記リソースのプロビジョニングが始まっていることがわかります。
terraform applyの出力の抜粋
terraform_data.lambda_build: Creation complete after 5s [id=5fe2d0b0-1602-e96b-c7f2-cff8974e7d40]
data.aws_s3_object.package: Reading...
data.aws_s3_object.package_hash: Reading...
aws_s3_object.test: Creating...
data.aws_s3_object.package: Read complete after 0s [id=kze-bucket-123456789012-lambda-assets/transfer/index.zip]
data.aws_s3_object.package_hash: Read complete after 0s [id=kze-bucket-123456789012-lambda-assets/transfer/index.zip.base64sha256.txt]
aws_s3_object.test: Creation complete after 0s [id=hoge]
当たり前ですが、以下のようにterraform_dataに依存関係のないリソースの場合は、
resource "aws_s3_object" "test" {
bucket = aws_s3_bucket.lambda_assets.id
key = "hoge"
content = "test"
}
並列でプロビジョニングされ、terraform_dataより前にプロビジョニング完了します(する場合があります)
terraform applyの出力の抜粋
aws_s3_object.test: Creating...
terraform_data.lambda_build: Creating...
terraform_data.lambda_build: Provisioning with 'local-exec'...
terraform_data.lambda_build (local-exec): Executing: ["/bin/sh" "-c" "cd ./lambdas/transfer && npm install"]
aws_s3_object.test: Creation complete after 0s [id=hoge]
aws_s3_bucket_acl.lambda_assets: Creation complete after 1s [id=kze-bucket-123456789012-lambda-assets,private]
aws_s3_bucket_policy.lambda_assets: Creation complete after 1s [id=kze-bucket-123456789012-lambda-assets]
aws_s3_bucket_public_access_block.lambda_assets: Creation complete after 1s [id=kze-bucket-123456789012-lambda-assets]
terraform_data.lambda_build (local-exec): > transfer@1.0.0 prepare
(略)
terraform_data.lambda_build: Creation complete after 4s [id=44ff1758-28f8-2e26-8989-70684e4c63eb]
プロビジョニング順を制御というとdepends_on = [terraform_data.lambda_build]
でも実現できます。どちらも初回プロビジョニング時の順番を制御することが可能です。違いは、depends_onだとterraform_dataリソースが再作成される場合には該当リソースの更新が走らない、outputの場合は走るという点です。outputはterraform_dataリソースがapply対象になるたびに、apply後に値が明らかになるattributeだからです。
まあ現時点で、このoutputによるプロビジョニングの制御を使う具体的なユースケースは?と言われると思いついていないのですが… とりうる実装法として頭に留めておきたいと思います。(とはいえわかりにくいので、使わないのが一番だと思います)
null_data_sourceのoutputとの比較
おそらくこのterraform_dataのoutputは、null_data_sourceのoutput attributeと近いと思います。が、null_data_sourceのoutputはdata sourceのreadが完了後に値が入るものだということで、微妙にタイミングが違うので、使い分けることができる、はず?(未検証です)
参考情報
- Terraform 1.4 improves the CLI experience for Terraform Cloud
- Release v1.4.0 · hashicorp/terraform
- Detect Infrastructure Drift and Enforce OPA Policies | Terraform | HashiCorp Developer
- Terraform 1.4 で導入された terraform_data リソースの使い方
- Policy as Codeを実現する Open Policy Agent / Rego の紹介 - ISID テックブログ
- 注目のOpen Policy Agent、その概要とKubernetesでの活用事例 | Think IT(シンクイット)
- New
terraform_data
managed resource to replacenull_resource
by jbardin · Pull Request #31757 · hashicorp/terraform