Terraform 基礎 #3 変数・出力・ローカル値 — ハードコーディングを取り除く 3 つの道具

読了 5分

前回までのコードには、バケット名とリージョンが文字列で直書きされていました。実習では問題ありませんが、同じ構成を開発用と本番用の 2 セット作ろうとした瞬間、ファイルをコピーして文字列を直す羽目になります。その 2 つのコピーはやがて食い違い始め、コンソールクリック時代の問題がコードの上で再現されます。答えは値をコードの外に切り出すことで、Terraform はそのために変数(variable)、出力(output)、ローカル値(locals)の 3 つを用意しています。今回はこの 3 つをすべて扱います。

variable — 値の入り口 #

変数は variable ブロックで宣言します。慣例として variables.tf ファイルにまとめておきます。

variables.tf
variable "env" {
  type        = string
  description = "デプロイ環境名 (dev, prod)"
}

variable "bucket_prefix" {
  type        = string
  description = "バケット名の前半部分"
  default     = "my-terraform"
}

宣言した変数は var.名前 で使います。

main.tf
resource "aws_s3_bucket" "hello" {
  bucket = "${var.bucket_prefix}-${var.env}"
}

文字列の中に値を埋め込む ${...} の文法を補間(interpolation)と呼びます。default がある変数は値を渡さなければその値を使い、default がない変数は値が入ってくるまで実行されません。type には string のほか number、bool、そして list(string)map(string) のようなコレクション型を指定でき、型が合わない値は plan の段階で拒否されます。

値を渡す 4 つのパス #

default のない env に値を渡す方法は 4 つあります。

  1. terraform.tfvars ファイル: ディレクトリにこの名前のファイルがあれば自動的に読み込まれます。env = "dev" の 1 行で済みます。
  2. -var-file フラグ: terraform plan -var-file="prod.tfvars" のようにファイルを選んで指定します。環境別にファイルを分けておく実務パターンの基礎です。
  3. -var フラグ: terraform plan -var="env=dev" で 1 つずつ指定します。
  4. 環境変数: TF_VAR_env=dev のように TF_VAR_ 接頭辞を付けると、シェルの環境変数として渡せます。CI で主に使う方式です。

複数のパスが重なると後のものが勝ちます。環境変数が最も弱く、tfvars ファイルがその次、コマンドラインの -var が最も強いです。どのパスからも値が来ないと Terraform が対話形式で聞いてきますが、自動化を前提とする道具で対話入力に頼るのは良い習慣ではないので、tfvars ファイルを基本にすることをお勧めします。

validation — 誤った値は入り口で止めます #

envprod の代わりに production が入ってバケット名がずれる、というたぐいの事故は、変数の宣言で防げます。

variables.tf
variable "env" {
  type        = string
  description = "デプロイ環境名"

  validation {
    condition     = contains(["dev", "prod"], var.env)
    error_message = "env は dev または prod のみ許可します。"
  }
}

許可リスト外の値が入ると、plan が始まる前に error_message とともに拒否されます。値に制約がある変数なら validation を付けておくことが、数か月後のミスを防いでくれます。パスワードのような値には sensitive = true を付けられ、plan 出力で値が (sensitive value) と隠されます。ただし #5 で見るとおり、state ファイルには値がそのまま保存されるので、この隠蔽は画面出力に限った保護だという点を覚えておく必要があります。

output — 値の出口 #

作ったリソースの情報を外に取り出すには output ブロックを使います。慣例として outputs.tf にまとめておきます。

outputs.tf
output "bucket_name" {
  description = "作成されたバケット名"
  value       = aws_s3_bucket.hello.bucket
}

output "bucket_arn" {
  value = aws_s3_bucket.hello.arn
}

apply が終わるとターミナルの最後に出力され、後から terraform output コマンドで再表示できます。terraform output -raw bucket_name のように値だけ取り出せば、シェルスクリプトにつないで使えます。今は人が読む用途がすべてですが、#8 でモジュールを扱うと、output はモジュール同士が値を受け渡す通り道になります。

locals — 内部の計算に名前を付ける #

変数が外から入ってくる値だとすれば、ローカル値は中で計算して名前を付けておく値です。

main.tf
locals {
  bucket_name = "${var.bucket_prefix}-${var.env}"

  common_tags = {
    Env       = var.env
    ManagedBy = "terraform"
  }
}

resource "aws_s3_bucket" "hello" {
  bucket = local.bucket_name
  tags   = local.common_tags
}

参照は local.名前 です(宣言は locals、参照は local と単数になる点に注意します)。同じ組み合わせ式やタグのマップを複数のリソースに繰り返し書いているなら、locals に持ち上げるサインです。変数との使い分けの基準は単純です。使う人が変えるべき値なら variable、コードの中で組み立てる値なら locals です。すべてを変数にすると使う人の入力が増えるだけなので、入り口は狭く保ち、組み立ては locals に任せるほうがコードを読みやすくします。

まとめ #

今回扱った内容です。

  • 変数は variable ブロックで宣言し、var.名前 で使います。type を指定すると、形の合わない値が plan の前にはじかれます
  • 値の入力パスは tfvars ファイル、-var-file-varTF_VAR_ 環境変数の 4 つで、tfvars ファイルを基本にします
  • validation は許容範囲外の値を入り口で止め、sensitive は画面出力だけを隠します。state には値がそのまま残ります
  • output は作ったリソースの情報を取り出す出口で、モジュールの時代にはモジュール間の通り道になります
  • 外から受け取る値は variable、中で組み立てる値は locals です

次回(#4 データソースと式)では逆方向を扱います。Terraform が作っていない既存リソースの情報を読み込むデータソース、そして条件式と for 式で値を加工する方法です。

X