💡

Terraform を使いこなすための Tips 集

に公開

Terraform を使いこなすための Tips 集

こんにちは。エン・ジャパン株式会社のプラットフォームエンジニアリンググループ(PEG)に所属している山﨑です。

先日、Terraform の v1.13.0 がリリースされ、ようやく terraform stack が利用できるようになったと思い、少し嬉しかったのですが、残念ながらローカルではまだ利用できないようですね。

弊社では、IaC として Terraform や CDK を利用しておりますが、最近は Terraform を利用することが多く、利用者も増えてきました。基本的な使い方を知っていればリソースのデプロイまでは問題なく行えると思いますが、利用頻度が高くなると、初期化の際にエラーが起きて少し調べる必要があったり、tfstate を直接操作する必要が出てきたりと、色々なかゆいところが出てくると思います。本記事では、知っておくと役に立ちそうな機能や Tips を紹介しますので、Terraform 開発の参考にしていただければ幸いです。

terraform init

Terraform を初期化するコマンドで、.terraform フォルダが作成されます。

Terraform を触っていてややこしくなったら、.terraform フォルダを削除して初期化し直しましょう。.terraform フォルダには初期化した際にダウンロードしたモジュールやプロバイダーのデータが保存されており、バージョンが変わる度に新しいモジュールやプロバイダーがダウンロードされるため、利用しない時は .terraform を削除しておいても良いと思います。私の場合、複数のプロジェクトを管理している関係で、数十〜数百 GB ほどの容量を使ってストレージを圧迫していたのですが、Provider Plugin Cache を使うと上手く削減できます。

terraform init -upgrade

モジュールやプロバイダーをバージョンアップした際に利用します。

古いバージョンが .terraform フォルダに残るので、terraform init -upgrade は利用せず、.terraform フォルダを削除して terraform init だけでも良いです。

以下は v5.29.0 から v6.11.0 にバージョンアップしたあとのディレクトリ構造で、古いバージョンが残ります。(500 MB くらいあります)

.
└── providers
    └── registry.terraform.io
        └── hashicorp
            └── aws
                ├── 5.29.0
                │   └── darwin_arm64
                │       └── terraform-provider-aws_v5.29.0_x5
                └── 6.11.0
                    └── darwin_arm64
                        ├── LICENSE.txt
                        └── terraform-provider-aws_v6.11.0_x5

terraform init --reconfigure

tfstate ファイルの場所(キーなど)が変わったときに使います。

.terraform/ を削除して terraform init するのと同じで、既存の tfstate は無視します。

terraform init --migrate-state

tfstate ファイルの場所(キーなど)が変わったときに使います。

既存の tfstate を維持しつつ、新しい場所に tfstate をコピーします。
移動ではなくコピーのため、元の場所に古い tfstate が残ってしまうため注意が必要です。
不要であれば、忘れずに削除するようにしましょう。

terraform plan

プラン結果(差分)を出力するときに使います。

そのままではコンソールに出力されるので、ファイルに出力する際は以下のコマンドを使ってください。

terraform plan -out=planfile

プラン結果をファイルとして保存して Apply 時に利用すれば、そのあと状態が変わったとしてもそのまま Apply するリスクが減りますし、CI/CD で使われることが多いです。尚、プランファイルを使って Apply する場合は以下のコマンドです。

terraform apply planfile

terraform state

terraform state list

現在どのリソースがデプロイされているか確認するときに便利です。

私は次の terraform state show でリソースの詳細を確認するときの前段として利用しています。

aws_s3_bucket.this
aws_s3_bucket_public_access_block.this
aws_s3_bucket_server_side_encryption_configuration.this
aws_s3_bucket_versioning.this

terraform state show

tfstate に保存されているリソースの詳細が分かります。

# リソース
terraform state show aws_s3_bucket.this

# モジュールのリソース
terraform state show module.s3.bucket_arn

# データソース
terraform state show data.aws_s3_bucket.this

以下は出力例です。

$ terraform state show aws_s3_bucket.this

# aws_s3_bucket.this:
resource "aws_s3_bucket" "this" {
    arn                         = "arn:aws:s3:::bucket-name"
    bucket                      = "bucket-name"

terraform state pull

terraform を操作する際にバックアップとして利用することがよくあります。

不安だったらバックアップを取っておきましょう。

terraform state pull > backup.tfstate

戻す時は terraform state push backup.tfstate です。

terraform test

tests フォルダを作ってその中にテストファイル(xxx.tftest.hcl)を置いておけば、terraform test でテストできます。tests 以外の名称のフォルダであれば、terraform test --test-directory="xxx" でテストが可能です。

弊社では共通利用のモジュールを開発する際にテストコードを含めるようにしています。

テストの機能については記事がいくつか出ていますが、それを実運用でどう活用していくかに関してはほとんど情報がないため、ある程度運用できるようになったら記事を出したいと思います。

(インフラのテストは難しい。。)

Input variable validation

変数に対して制約をかけることが可能です。

特定の条件を満たす変数を指定させたい場合に便利です。

単一の変数を評価

v1.9.0 より前のバージョンでは単一の変数のみを評価できました。

variable "xxx" {
  type        = string
  description = "yyy"
  default     = "zzz"
  
  validation {
    condition     = can(regex("^[a-zA-Z0-9_]+$", var.xxx))
    error_message = "xxx は英数字とアンダースコアのみです"
  }
}

複数の変数を評価

v1.9.0 で自身以外の変数も参照して評価をできるようになりました。

v1.9.0 になるまでは複数の変数を指定できず、check ブロックなどを利用する必要がありましたが、v1.9.0 からは複数の変数を指定できるようになりました。ただし、1つの変数の検証に複数の変数が含まれるとスパゲッティコードになりがちなため、複数変数を評価する場合は check ブロックを使うなど、分けた方が管理上良いかもしれません。

variable "xxx" {
  type        = string
  description = "yyy"
  default     = "zzz"
  
  validation {
    condition     = can(regex("^[a-zA-Z0-9_]+$", var.xxx)) && can(regex("^[a-zA-Z0-9_]+$", var.yyy))
    error_message = "xxx と yyy は英数字とアンダースコアのみです"
  }
}

Ephemeral values

Terraform はシークレットを利用すると tfstate に保存されるという欠点がありました。それを解消するのが Ephemeral values です。使い方には少しコツがいりますが、tfstate にシークレットが保存されなくなるため、シークレットも Terraform で管理しやすくなります。

Ephemeral values を使わない場合

以下は resource ブロックで random_password を作成し、それを aws_secretsmanager_secret_version で利用するコードとその結果です。

resource "random_password" "db_password" {
  length = 16
  override_special = "!#$%&*()-_=+[]{}<>:?"
}


resource "aws_secretsmanager_secret" "db_password" {
  name = "db_password"
}

resource "aws_secretsmanager_secret_version" "db_password" {
  secret_id           = aws_secretsmanager_secret.db_password.id
  secret_string    = random_password.db_password.result
}
  # aws_secretsmanager_secret_version.db_password will be created
  + resource "aws_secretsmanager_secret_version" "db_password" {
      + arn                      = (known after apply)
      + has_secret_string_wo     = (known after apply)
      + id                       = (known after apply)
      + region                   = "ap-northeast-1"
      + secret_id                = (known after apply)
      + secret_string_wo         = (write-only attribute)
      + secret_string_wo_version = 1
      + version_id               = (known after apply)
      + version_stages           = (known after apply)
    }

  # random_password.db_password will be created
  + resource "random_password" "db_password" {
      + bcrypt_hash      = (sensitive value)
      + id               = (known after apply)
      + length           = 16
      + lower            = true
      + min_lower        = 0
      + min_numeric      = 0
      + min_special      = 0
      + min_upper        = 0
      + number           = true
      + numeric          = true
      + override_special = "!#$%&*()-_=+[]{}<>:?"
      + result           = (sensitive value)
      + special          = true
      + upper            = true
    }

シークレットが保存されます。

"secret_string": "3E1o1MN*VOvrts7B", # シークレットが tfstate に残る
"secret_string_wo": null,
"secret_string_wo_version": null,

Ephemeral values を使う場合

random_password を ephemeral にすると、以下のコードとなり、secret_string_wo が (write-only attribute) となって tfstate にシークレットが保存されなくなります。

ephemeral "random_password" "db_password" {
  length = 16
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

resource "aws_secretsmanager_secret_version" "db_password" {
  secret_id           = aws_secretsmanager_secret.db_password.id
  secret_string_wo    = ephemeral.random_password.db_password.result
  secret_string_wo_version = 1
}
  + resource "aws_secretsmanager_secret_version" "db_password" {
      + arn                      = (known after apply)
      + has_secret_string_wo     = (known after apply)
      + id                       = (known after apply)
      + region                   = "ap-northeast-1"
      + secret_id                = (known after apply)
      + secret_string_wo         = (write-only attribute)
      + secret_string_wo_version = 1
      + version_id               = (known after apply)
      + version_stages           = (known after apply)
    }

シークレットが保存されなくなります。

"secret_string": "",  # シークレットが tfstate に残らない
"secret_string_wo": null,
"secret_string_wo_version": 1,

use_lockfile

バックエンドに Amazon S3 を利用している場合の話です。

同時に tfstate ファイルを操作すると不整合が起きるため、従来は DynamoDB で tfstate の排他制御を行ってきました。しかし、この方法では DynamoDB を用意したり、読み取り権限が必要だったりと、構成が複雑になります。use_lockfile を使えば DynamoDB が不要になりますし、現在は DynamoDB の利用が deprecated なので use_lockfile を使いましょう。

terraform {
  backend "s3" {
    bucket       = "xxx"
    key          = "yyy"
    use_lockfile = true     # これを追加
  }
}

注意点

tfstate が保存されるパスに tflock ファイルが保存されるようになるので、それを操作する権限が必要になります。

  • s3:GetObject
  • s3:PutObject
  • s3:DeleteObject

おまけ

AWS Provider v6

v6 系から region パラメータが追加されました。これによって、従来 Provider ブロックでリージョンの設定をしていたものが不要になりました。

v5 系までの複数リージョン指定

provider "aws" {
  alias = "xxx"
  region = "yyy"
}

resource "aaa" {
  provider = "xxx"
  ...
}

v6 系の複数リージョン指定

resource "aaa" {
  region = "yyy"
  ...
}

注意点

AWS Provider を v6 系に上げる必要があり、以下のようなバージョン指定をしていると v5 系しか利用できないので注意です。

# v5.100 以降の v5 系のみ利用可能(v6 系は利用不可)
required_version = "~> 5.100"

まとめ

Terraform で役に立つ機能についてざっと紹介しました。Terraform は後方互換性を保ちつつ、比較的高い頻度でバージョンアップを行っているため、新しい機能が知らない間に増えたりしています。時々、どんな機能が追加されたのかをチェックしておくと、使い勝手が悪くて Terraform を使わなかった部分も Terraform に置き換えられ、完全な IaC を実現できるようになるかもしれません。

参考

エン・ジャパン テックブログ

Discussion