こんにちは。KOUKIです。
初学者向けに、Terraformの使い方を紹介しています。
今回は、TerraformのVariablesについて学びましょう。
<目次>
前回
前回は、TerraformのFunctionsについて学びました。
Variables
プログラミング言語にも馴染みがあるVariablesについてここで触れておきます。
Variablesは、以下のように宣言します。
1 2 3 4 5 6 7 8 |
# 宣言方法 variable 変数名 {} # 例 variable "ext_port" { default = "1880" description = "external port" } |
これは、以下の方法で呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 |
# 呼び出し方 var.変数名 resource "docker_container" "nodered_container" { count = length(random_string.random) // random数分作る name = join("-", ["nodered", random_string.random[count.index].result]) image = docker_image.nodered_image.latest ports { internal = 1880 external = var.ext_port // } } |
デフォルト値(1880)を渡してあるのでその値でコンテナが生成されますが、もしデフォルト値がない場合はplan呼び出し時に入力する必要があります。
1 2 3 4 5 6 7 |
# デフォルト値を消す variable "ext_port" {} # planを呼び出す terraform plan var.ext_port Enter a value: <<< 入力が求められる |
しかし「-var」オプションを付けて呼び出すことで、これを回避できます。
1 2 |
# -varオプションをつける terraform plan -var ext_port=1880 |
あるいは、環境変数にセットするのもありです。
1 2 3 4 5 6 7 8 |
# 環境変数 TF_VAR_変数名=値 export TF_VAR_ext_port=1880 terraform plan # 消し方 unset TF_VAR_ext_port |
TF_VARは接頭辞であり、この命名規則を守る必要があります。
typeもつけられます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
variable "int_port" { default = "1880" type = number description = "external port" } resource "docker_container" "nodered_container" { count = length(random_string.random) // random数分作る name = join("-", ["nodered", random_string.random[count.index].result]) image = docker_image.nodered_image.latest ports { internal = var.int_port external = var.ext_port } } |
以下は、公式サイトからの抜粋です。
string
: a sequence of Unicode characters representing some text, like"hello"
.number
: a numeric value. Thenumber
type can represent both whole numbers like15
and fractional values like6.283185
.bool
: a boolean value, eithertrue
orfalse
.bool
values can be used in conditional logic.list
(ortuple
): a sequence of values, like["us-west-1a", "us-west-1c"]
. Elements in a list or tuple are identified by consecutive whole numbers, starting with zero.map
(orobject
): a group of values identified by named labels, like{name = "Mabel", age = 52}
.
Variables Validation
Variablesには、Validationがつけられるようです。
1 2 3 4 5 6 7 8 9 10 |
variable "int_port" { default = "1880" type = number description = "external port" validation { condition = var.int_port == 1880 error_message = "The internal port must be 1880." } } |
default値を1881にして、テストしてみましょう。
1 2 3 4 5 6 7 8 9 10 |
$ terraform plan ╷ │ Error: Invalid value for variable │ │ on main.tf line 17: │ 17: variable "int_port" { │ │ The internal port must be 1880. │ │ This was checked by the validation rule at main.tf:22,3-13. |
便利ですね。
Variables/Outputs ファイル
これまで実装したVariablesやOutputsは、ファイルを分離することができます。
まずは、以下のファイル名で、ファイルを作成してください。
1 2 |
touch variables.tf touch outputs.tf |
上記のファイル名にしておくと、plan/applyコマンド実行時に自動的に読み込まれます。
作成後は、main.tfに実装したVariablesとOutputsをそれぞれのファイルに記述するだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# --- root/variables --- variable "ext_port" { default = "1880" description = "external port" validation { condition = var.ext_port <= 65535 && var.ext_port > 0 error_message = "The external port must be in the valid port range 0 - 65535." } } variable "int_port" { default = "1880" type = number description = "external port" validation { condition = var.int_port == 1880 error_message = "The internal port must be 1880." } } variable "container_count" { default = 1 type = number } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# --- root/outputs.tf --- output "IP-Address" { value = [ for i in docker_container.nodered_container[*] : join(":", [i.ip_address], i.ports[*]["external"])] description = "The IP address of the container" } output "container-name" { value = docker_container.nodered_container[*].name description = "The name of the container" } |
plan/applyコマンドを実行して、問題がないか確認してください。
1 2 |
terraform plan terraform apply -auto-approve |
terraform.tfvars
変数を宣言する方法として「.tfvars」も便利です。次のファイルを作成しましょう。
1 |
touch terraform.tfvars |
「terraform.tfvars」のファイル名にしておくと、自動的に読み込まれます。
1 2 |
# --- root/terraform.tfvars ----x ext_port = 1880 |
これで、「ext_port」変数をどのファイルからも読み取れるようになります。
もともと宣言していたext_portのdefault値を消しましょう。
1 2 3 4 5 6 7 8 9 10 |
# --- root/variables --- variable "ext_port" { // default = "1880" description = "external port" validation { condition = var.ext_port <= 65535 && var.ext_port > 0 error_message = "The external port must be in the valid port range 0 - 65535." } } |
1 2 3 4 5 6 |
# コンテナを廃棄して、再作成 terraform destroy -auto-approve && terraform apply -auto-approve # external(port)を確認 terraform show | grep external external = 1880 |
ちなみに、「–var-file」オプションを指定するとファイルを指定できます。
1 |
touch west.tfvars |
1 2 |
# --- root/west.tfvars ----x ext_port = 1890 |
1 2 3 4 5 6 7 |
# west.ftvarsファイルを指定 terraform plan --var-file west.tfvars ... ~ ports { ~ external = 1880 -> 1890 # forces replacement # (3 unchanged attributes hidden) } |
sensitiveオプション
CI/CDパイプラインなどを構築する際、実行結果としてパスワードなどsensitiveな情報を表示したくない場合があります。
このようなとき、sensitiveオプションが役に立ちます。
1 2 3 4 5 6 7 8 9 10 |
# --- root/variables --- variable "ext_port" { description = "external port" sensitive = true // 追加 validation { condition = var.ext_port <= 65535 && var.ext_port > 0 error_message = "The external port must be in the valid port range 0 - 65535." } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
terraform destroy -auto-approve && terraform apply -auto-approve ╷ │ Error: Output refers to sensitive values │ │ on outputs.tf line 2: │ 2: output "IP-Address" { │ │ To reduce the risk of accidentally exporting sensitive data that was intended to be only internal, Terraform requires that any root module │ output containing sensitive data be explicitly marked as sensitive, to confirm your intent. │ │ If you do intend to export this data, annotate the output value as sensitive by adding the following argument: │ sensitive = true |
output.tfファイルの「IP-Address」に指定したexternalを表示しようとしてエラーになりました。このように、sensitiveを指定すると重要な情報を外に出せなくなるので便利です。
このエラーを回避するために、outputにもsensitiveオプションを指定します。
1 2 3 4 5 6 7 8 9 |
# --- root/outputs.tf --- output "IP-Address" { value = [ for i in docker_container.nodered_container[*] : join(":", [i.ip_address], i.ports[*]["external"])] description = "The IP address of the container" sensitive = true // 追加 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ terraform destroy -auto-approve && terraform apply -auto-approve Apply complete! Resources: 3 added, 0 changed, 0 destroyed. Outputs: IP-Address = <sensitive> << 出力結果がsensitiveになる! container-name = [ "nodered-kcqz", ] $ terraform output IP-Address = <sensitive> container-name = [ "nodered-kcqz", ] $ terraform show | grep external external = (sensitive) |
local-exec Provisioner
terraformが動作するMachine上で、処理を実行したいときがあります。例えば、特定のディレクトリを作成したいといった場合ですね。
このような時に、local-exec Provisionerが役に立ちます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# --- root/main.tf --- terraform { required_providers { docker = { source = "kreuzwerker/docker" } } } provider "docker" {} resource "null_resource" "dockervol" { provisioner "local-exec" { command = "mkdir -p noderedvol/ && sudo chown -R 1000:1000 noderedvol/" } } |
ローカル上にnoderedvolディレクトリを作成し、権限を付与しました。
このコードを実行してみましょう。
1 2 3 4 |
terraform init terraform plan terraform apply -auto-approve Password: |
sudoコマンドを使用しているのでPasswordを聞かれますが、ローカルPCのパスワードを入力すればOKです。
「terraform apply」を実行した時、ローカルに「noderedvol」ディレクトリが作成されました。

このディレクトリをコンテナにマウントしたい場合は、docker_containerリソースにvolumesオプションを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<meta charset="utf-8"># --- root/main.tf --- ... resource "docker_container" "nodered_container" { count = var.container_count name = join("-", ["nodered", random_string.random[count.index].result]) image = docker_image.nodered_image.latest ports { internal = var.int_port external = var.ext_port } volumes { // 追加 container_path = "/data" // ローカルのパス host_path = "/Users/hoge/Desktop/terraform/noderedvol" } } |
Local Values
Local Valuesもよく使うので、ここで学んでおきましょう。
詳しく説明する前に、ext_port変数number typeからlist typeに変更し、コンテナを複数立ち上げられるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# --- root/terraform.tfvars ----x ext_port = [1880, 1881, 1882] // リストに変更 # --- root/variables --- variable "ext_port" { description = "external port" # sensitive = true type = list(number) # validation { # condition = var.ext_port <= 65535 && var.ext_port > 0 # error_message = "The external port must be in the valid port range 0 - 65535." # } } variable "container_count" { default = 3 # コンテナを3つ作成する type = number } # --- root/main.tf --- resource "docker_container" "nodered_container" { ... ports { internal = var.int_port external = var.ext_port[count.index] # リストから取得 } ... } |
この状態で、planを実行します。
1 2 3 4 |
$ terraform plan | grep external ~ external = (sensitive) + external = 1881 + external = 1882 |
ext_portとcontainer_countに指定した数(3)は同じなので、特に問題なくコマンドが実行できました。しかし、container_countを4にするとどうなるでしょうか。
1 2 3 4 5 |
# --- root/variables -- variable "container_count" { default = 4 type = number } |
1 2 3 4 5 6 7 8 9 10 11 12 |
$ terraform plan | grep external ╷ │ Error: Invalid index │ │ on main.tf line 36, in resource "docker_container" "nodered_container": │ 36: external = var.ext_port[count.index] │ ├──────────────── │ │ count.index is 3 │ │ var.ext_port is list of number with 3 elements │ │ The given key does not identify an element in this collection value: the given index is greater than or equal to the length of the collection. ╵ |
この様にエラーになりました。
次に、length関数を使って、ext_portに格納された値数分だけコンテナが作られるようにしてみましょう。
1 2 3 4 5 |
<meta charset="utf-8"># --- root/variables -- variable "container_count" { default = length(var.ext_port) type = number } |
一見問題なく見えますが、コマンドの実行はエラーになります。
1 2 3 4 5 6 7 8 |
$ terraform plan | grep external ╷ │ Error: Function calls not allowed │ │ on variables.tf line 25, in variable "container_count": │ 25: default = length(var.ext_port) │ │ Functions may not be called here. |
Variablesでは、関数を使った処理ができないのです。
これを解決するために、Local Variablesを使います。
1 2 3 4 5 6 |
# --- root/variables --- locals { container_count = length(var.ext_port) } |
Local Variablesを参照するには、「local」キーワードを使用します。
1 2 3 4 5 6 7 8 9 10 |
# --- root/main.tf --- resource "random_string" "random" { count = local.container_count # localに変更 ... } resource "docker_container" "nodered_container" { count = local.container_count # localに変更 ... } |
この状態でもう一度、planコマンドを実行します。
1 2 3 4 |
$ terraform plan | grep external ~ external = (sensitive) + external = 1881 + external = 1882 |
OKですね。
ちなみに、Local Variablesも外出しできます。
1 |
touch locals.tf |
1 2 3 4 |
# --- locals.tf --- locals { container_count = length(var.ext_port) } |
次回
次回は、Workspaceなどについて学びましょう。
Terraformまとめ
ソースコード
main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# --- root/main.tf --- terraform { required_providers { docker = { source = "kreuzwerker/docker" } } } provider "docker" {} resource "null_resource" "dockervol" { provisioner "local-exec" { command = "mkdir -p noderedvol/ && sudo chown -R 1000:1000 noderedvol/" } } resource "docker_image" "nodered_image" { # docker image name = "nodered/node-red:latest" } resource "random_string" "random" { count = local.container_count length = 4 special = false upper = false } resource "docker_container" "nodered_container" { count = local.container_count name = join("-", ["nodered", random_string.random[count.index].result]) image = docker_image.nodered_image.latest ports { internal = var.int_port external = var.ext_port[count.index] } volumes { container_path = "/data" // ローカルのパス host_path = "/Users/hoge/Desktop/terraform/noderedvol" } } |
variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# --- root/variables --- variable "ext_port" { description = "external port" # sensitive = true type = list(number) # validation { # condition = var.ext_port <= 65535 && var.ext_port > 0 # error_message = "The external port must be in the valid port range 0 - 65535." # } } variable "int_port" { default = "1880" type = number description = "external port" validation { condition = var.int_port == 1880 error_message = "The internal port must be 1880." } } variable "container_count" { default = 3 type = number } |
terraform.tfvars
1 2 |
# --- root/terraform.tfvars ----x ext_port = [1880, 1881, 1882] |
outputs.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# --- root/outputs.tf --- output "IP-Address" { value = [ for i in docker_container.nodered_container[*] : join(":", [i.ip_address], i.ports[*]["external"])] description = "The IP address of the container" sensitive = true } output "container-name" { value = docker_container.nodered_container[*].name description = "The name of the container" } |
locals.tf
1 2 3 4 |
# --- locals.tf --- locals { container_count = length(var.ext_port) } |
Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
.PHONY: init validate plan apply destroy fmt console show output init: terraform init # docker-compose run --rm terraform init validate: terraform validate # docker-compose run --rm terraform validate plan: terraform plan # docker-compose run --rm terraform plan apply: terraform apply -auto-approve # docker-compose run --rm terraform apply -auto-approve destroy: terraform destroy -auto-approve # docker-compose run --rm terraform destroy -auto-approve fmt: terraform fmt -recursive --diff # docker-compose run --rm terraform fmt -recursive --diff console: terraform console # docker-compose run --rm terraform console show: terraform show # docker-compose run --rm terraform show output: terraform output # docker-compose run --rm terraform output |
コメントを残す
コメントを投稿するにはログインしてください。