フューチャー技術ブログ

Terraform連載2024 テストとモックを使ってみる

Terraform連載2024を の7本目です。

はじめに

こんにちは! TIG Chill Architect の前原です。
Terraform を使ってクラウド環境とか構築する人は多くいると思います。
その中で毎回のようにどうやってテストをやるべきなのかなーとか悩んで、結果大したことも出来ずにリリースまで来てしまったということはないでしょうか。

今回は、その悩みを少しでも払拭できないかと思い、Terraform v1.6 とv1.7 で提供されたtestsmocksに触れていきたいと思います。

今までの試み

今までは、TFLintを導入し、コードチェックを行うことを実施していました。

ただし、この方式だと静的コード解析のため、より踏み込んだところまでいけませんでした。静的コード解析にプラスして動的なテストができると良いなーって感じていました。(terratestというのもありますが、導入コストが高い)

terraform test

terraform testは、実際にコードを実行し、動的にテストを行うことができます。

これによりコードの信頼性、品質を高めることができます。

module に対してのテストが強力ですが、module 以外でも利用可能です。

こんなことができる

terraform test は、以下のようなことができます(他にもできることがある)

  • terraform plan を実行し、期待する結果が得られるかを確認することができる
  • terraorm apply を実行した時に期待する結果が得られるかを確認することができる
    • 実際にリソースを作成し、削除まで行う
  • 期待する結果をconditionに書いて判定を行う
  • Data Source を取得できる

実際に動かしてみる

それでは、実際に簡単なコードを作成して動かしてみたいと思います。
Terraform のバージョンは、1.6以上にしてください。
バージョンの切り替えが面倒だなって思ったらtfenvをインストールすると少し幸せになれます。
また、クラウドは、AWS です。

S3 バケットを作成し、そのS3 バケット名が期待する名前なのかをテストするコードを書きたいと思います。
正直、このコードがテスト用途として必要か?というのはありますが、まずは慣れるというのを目的にしたいと思います。

ディレクトリは、以下の構成です。

.
├── backend.tf
├── local.tf
├── s3_bucket.tf
├── s3_bucket.tftest.hcl
└── versions.tf

コードは、以下です。
backend.tf を省略していますが、よしなにお願いします。

local.tf
locals {
project_name = "sample"
region = "ap-northeast-1"
}
s3_bucket.tf
resource "aws_s3_bucket" "test001" {
bucket = "${terraform.workspace}-${local.project_name}-test001"
tags = {
Name = "${terraform.workspace}-${local.project_name}-test001"
Environment = terraform.workspace
}
}
versions.tf
terraform {
required_version = "~> 1.7.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.41.0"
}
}
}

provider "aws" {
region = local.region
}

テストコードを作成します。
conditionに記述したS3 バケット名と実際のS3 バケット名を比較して問題ないかをチェックしています。
仮に期待していない値の場合は、error_messageの値が出力されます。

s3_bucket.tftest.hcl
run "test" {
assert {
condition = aws_s3_bucket.test.bucket == "${terraform.workspace}-${local.project_name}-test"
error_message = "S3 bucket name did not match expected"
}
}

テストが成功した時は、以下の出力となります。

$ terraform test

tests/s3_bucket.tftest.hcl... in progress
run "test"... pass
tests/s3_bucket.tftest.hcl... tearing down
tests/s3_bucket.tftest.hcl... pass

Success! 1 passed, 0 failed.

失敗した時は、以下です。

$ terraform test

tests/s3_bucket.tftest.hcl... in progress
run "test"... fail

│ Error: Test assertion failed

│ on tests/s3_bucket.tftest.hcl line 4, in run "test":
│ 4: condition = aws_s3_bucket.test.bucket == "${terraform.workspace}-${local.project_name}-test1"
│ ├────────────────
│ │ aws_s3_bucket.test.bucket is "dev-sample-test"
│ │ local.project_name is "sample"
│ │ terraform.workspace is "dev"

│ S3 bucket name did not match expected

tests/s3_bucket.tftest.hcl... tearing down
tests/s3_bucket.tftest.hcl... fail

Failure! 0 passed, 1 failed.

テストファイルをカレントディレクトリに配置していますが、ディレクトリを分けることも可能です。
例えば以下のようにすることが可能です。
testsディレクトリであればデフォルト指定されているため、読み込まれます。

.
├── backend.tf
├── local.tf
├── s3_bucket.tf
├── tests
│   └── s3_bucket.tftest.hcl
└── versions.tf

別のディレクトリ名で実行したい場合は、以下のように指定することも可能です。

terraform test -test-directory=hoge

今回は、applyを実行したのですが、planを実行したいときは、以下のようにcommand 指定します。

s3_bucket.tftest.hcl
run "test" {
command = plan
assert {
condition = aws_s3_bucket.test.bucket == "${terraform.workspace}-${local.project_name}-test"
error_message = "S3 bucket name did not match expected"
}
}

注意としては、applyを実行する際に既にリソースが作成されている場合は、以下のエラーが出力されます。

$ terraform test
sample.tftest.hcl... in progress
run "sample"... fail

│ Error: creating S3 Bucket (dev-sample-test): operation error S3: CreateBucket, https response error StatusCode: 409, RequestID: 5FC2KQ07G6SGK0W1, HostID: uhahHveFHZBJiEnSDfTsnqVd6gQBmRx+GabbLzMB2jlBkxlPGjblnyuY17MHMoo3UC2uODLSMWk=, BucketAlreadyOwnedByYou:

terraform mock

terraform test は、非常に便利で日々の運用を良くするための強力なツールであることがわかるかと思います。
ただ、ローカル開発する中で強力なキーを持たせたくないや、使用できない状況があると思います。
また、CI を回すためにも必要といったケースもあるかと思います。
そういった時に便利なterraform mockを使っていきたいと思います。

実際に動かしてみる

ディレクトリは、以下の構成です。

.
├── backend.tf
├── local.tf
├── mocked_providers.tftest.hcl
├── s3_bucket.tf
├── s3_bucket.tftest.hcl
└── versions.tf

terraform test をベースに作成しているため差分のみを記載します。
以下にモック用のプロバイダを指定します。

mocked_providers.tftest.hcl
mock_provider "aws" {
alias = "fake"
}

run "use_mocked_provider" {
providers = {
aws = aws.fake
}
}

versions.tfのprovider 定義を削除します。

versions.tf
terraform {
required_version = "~> 1.7.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.41.0"
}
}
}

モックを動かす際もterraform test コマンドを使用します。
実際に実行したいと思います。
アクセスキーを利用せずとも実行できることが確認できるかと思います。

$ terraform test

mocked_providers.tftest.hcl... in progress
run "use_mocked_provider"... pass
mocked_providers.tftest.hcl... tearing down
mocked_providers.tftest.hcl... pass
s3_bucket.tftest.hcl... in progress
run "test"... pass
s3_bucket.tftest.hcl... tearing down
s3_bucket.tftest.hcl... pass

Success! 2 passed, 0 failed.

もしモックとterraform testのように実際のリソースに対してテストを行いたい場合は、以下のプロバイダ指定で行うことができます。

mocked_providers.tftest.hcl
provider "aws" {}

mock_provider "aws" {
alias = "fake"
}

run "use_real_provider" {
providers = {
aws = aws
}
}

run "use_mocked_provider" {
providers = {
aws = aws.fake
}
}

他にもモックで返される値をよしなに変更するといったことも可能です。

どういったケースで使うのがいいのだろうか

すべてのリソースに対して無邪気にテストコードを書くのは、とても非効率です。
そのため、テストを行う場合の考慮すべきポイントを以下にまとめたいと思います。
(個人的なポイントのため、あくまで参考レベルです)

クリティカルなインフラ系のリソース

セキュリティグループ、IAMポリシー、VPC設定など、セキュリティやアプリケーションの正常な動作に直接影響を与えるクリティカルなインフラ系のリソースに対してテストを行うのが良いと思います。

依存関係のあるリソース

複数のリソース間で複雑な依存関係を持つパターン(例えば、セキュリティグループのルールが特定のリソースに依存している場合など)
これらの依存関係を正確に反映しているかを確認するときに良いと思います。

コストに大きな影響を与えるリソース

インスタンスタイプによっては、大きくコストに跳ねるため、サイズ、数などをテストすることは有効です。
これにより、予期しないコストの発生を防ぐことができます。

変更頻度の高いリソース

変更頻度が高いとミスが発生する可能性が高くなります。
そのため、それらに対してテストを行うのは有効かと思います。

モジュール

モジュールは、複数のリソースを包括的に管理するため、複雑な構成となります。
モジュールの期待する結果をテストでカバーすることができるため、再利用性、品質の向上につながるかと思います。

まとめ

いかがでしたでしょうか。
今回紹介したTerraform の機能を利用することで少しでもテストの有効性や導入のきっかけになれば幸いです。

参考