こんにちは。KOUKIです。
初学者向けに、Terraformの使い方を紹介しています。
今回は、TerraformのWorkspaceなどについて学びましょう。
<目次>
前回
前回は、Variablesについて学びました。
Min / Max Functions
TerraformのFuncionsには、Min / Maxがあります。これらは、普通のプログラミングでもよく見かける特定の範囲内の最小値 / 最大値を取得する関数です。
1 2 3 4 5 6 7 8 9 10 |
terraform console > max(1,2,3) 3 > min(1,2,3) 1 > max([10,20,30]) > max([10,20,30]...) 30 > min([10,20,30]...) 10 |
これらの関数は、Variablesの中でも呼び出すことができます。lengthは不可なので、不思議ですよね。
1 2 3 4 5 6 7 8 9 10 11 |
# --- root/variables --- variable "ext_port" { description = "external port" # sensitive = true type = list(number) validation { condition = max(var.ext_port...) <= 65535 && min(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 |
# --- root/main.tf -- resource "docker_container" "nodered_container" { ... volumes { container_path = "/data" // ローカルのパス host_path = "/Users/hoge/Desktop/terraform/noderedvol" } } |
しかし、Terraformの組み込みのValuesを使うと上記の様に直接記述する必要がなくなります。
以下、公式サイトよりからの抜粋です。
path.module
is the filesystem path of the module where the expression is placed.path.root
is the filesystem path of the root module of the configuration.path.cwd
is the filesystem path of the current working directory. In normal use of Terraform this is the same aspath.root
, but some advanced uses of Terraform run it from a directory other than the root module directory, causing these paths to be different.terraform.workspace
is the name of the currently selected workspace.
「path.XXXX」でパスが取得できる様ですね。
1 2 3 4 5 6 7 8 |
$ terraform console > path.module "." > path.cwd "/Users/hoge/Desktop/terraform" > path.root "." |
これを使って、先ほどのコードを書き換えてみましょう。
1 2 3 4 5 6 7 8 9 |
<meta charset="utf-8"># --- root/variables --- resource "docker_container" "nodered_container" { ... volumes { container_path = "/data" // ローカルのパス host_path = "${path.cwd}/noderedvol" } } |
文字列の中でコードを展開するには、${} で囲めば大丈夫です。
Mapとlookup Function
TerraformにはMap型とそのkeyからvalueを検索するlookup Functionがあります。
1 2 3 4 5 |
$ terraform console > lookup({dev = "image1", prod = "image2"}, "dev") "image1" > lookup({dev = "image1", prod = "image2"}, "prod") "image2" |
これを使うと環境切り替えなどコードなどを簡単に実装できそうです。
イメージの切り替え例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# --- root/variables --- variable "env" { type = string description = "Env to deploy to" default = "dev" } variable "image" { type = map(any) description = "image for container" default = { dev = "nodered/node-red:latest" prod = "nodered/node-red:latest-minimal" } } # --- root/main.tf --- resource "docker_image" "nodered_image" { # docker image name = lookup(var.image, var.env) } |
デフォルトは、devなので開発環境で立ち上がります。
1 2 |
$ terraform plan | grep name + name = "nodered/node-red:latest" |
しかし、prodに変更すると本番環境の状態で立ち上がるはずです。
1 2 |
$ terraform plan -var="env=prod" | grep name + name = "nodered/node-red:latest-minimal" |
環境の切り替えは、CI/CDパイプラインを作るときに役立ちそうです。
Portの切り替え例
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 |
# --- root/terraform.tfvars ----x ext_port = { dev = [1980, 1981] prod = [1880, 1881] } # --- root/variables --- variable "ext_port" { description = "external port" # sensitive = true # type = list(number) type = map(any) validation { condition = max(var.ext_port["dev"]...) <= 65535 && min(var.ext_port["dev"]...) >= 1980 error_message = "The external port must be in the valid port range 0 - 65535." } validation { condition = max(var.ext_port["prod"]...) < 1980 && min(var.ext_port["prod"]...) >= 1880 error_message = "The external port must be in the valid port range 0 - 65535." } } # --- locals.tf --- locals { container_count = length(lookup(var.ext_port, var.env)) } # --- root/main.tf --- resource "docker_container" "nodered_container" { ... ports { internal = var.int_port external = lookup(var.ext_port, var.env)[count.index] } ... } |
開発/本番環境でPortを分けました。
1 2 3 4 5 6 7 |
$ terraform plan | grep external + external = 1980 + external = 1981 $ terraform plan -var="env=prod" | grep external + external = 1880 + external = 1881 |
Workspaces
Terraformには、GitのブランチみたいなWorkspacesという概念があります。
操作コマンド
下記のコマンドで、Workspaceのリストを表示しましょう。
1 2 |
$ terraform workspace list * default |
初期の段階ではdefaultのみ存在し、どのWorkspaceを指しているかを示す「*」が確認できます。
そして、WorkSpacesを作成するには、以下のコマンドを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# dev workpsaceを作成 terraform workspace new dev terraform workspace list default * dev # prod workpsaceを作成 terraform workspace new prod terraform workspace list default dev * prod |
Workspacesを作成すると「terraform.tfstate.d」フォルダ配下に作成したWorkspaceと同じ名前のフォルダが作成されます。

Workspaceを切り替えるには、以下のコマンドを実行します。
1 2 3 4 5 6 |
terraform workspace select dev terraform workspace list default * dev prod |
「dev」環境でnoderedコンテナを立ち上げてみましょう。
1 2 3 |
terraform apply --auto-approve -var="env=dev" Apply complete! Resources: 6 added, 0 changed, 0 destroyed. |
もう一度、「terraform.tfstate.d」フォルダ配下を確認すると「dev」フォルダ配下に「terraform.tfstate」が生成されたことが確認できます。

Prod環境に切り替えるとprodフォルダ配下に生成されるはずなので、ぜひお試しください。
Reference
Workspaceは、「terraform.workspace」キーワードで取得することが可能です。
1 2 3 |
$ terraform console > terraform.workspace "dev" |
このキーワードは、スクリプトの中から使用することができます。
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 |
# --- root/main.tf --- resource "docker_image" "nodered_image" { # docker image # name = lookup(var.image, var.env) name = lookup(var.image, terraform.workspace) } resource "docker_container" "nodered_container" { ... # name = join("-", ["nodered", random_string.random[count.index].result]) name = join("-", ["nodered", terraform.workspace, random_string.random[count.index].result]) ports { ... # external = lookup(var.ext_port, var.env)[count.index] external = lookup(var.ext_port, terraform.workspace)[count.index] } ... } # --- locals.tf --- locals { # container_count = length(lookup(var.ext_port, var.env)) container_count = length(lookup(var.ext_port, terraform.workspace)) } |
下記のコマンドで、動作確認をしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ terraform workspace show dev $ terraform plan | grep external + external = 1980 + external = 1981 $ terraform workspace select prod Switched to workspace "prod". $ terraform plan | grep external + external = 1880 + external = 1881 |
lookupの代わりにMapを使おう!
lookupで値参照していた箇所を、MapのKeyアクセスに変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# --- root/main.tf -- resource "docker_image" "nodered_image" { # docker image # name = lookup(var.image, terraform.workspace) name = var.image[terraform.workspace] } ... resource "docker_container" "nodered_container" { ... ports { internal = var.int_port # external = lookup(var.ext_port, terraform.workspace)[count.index] external = var.ext_port[terraform.workspace][count.index] } ... } # --- locals.tf --- locals { # container_count = length(lookup(var.ext_port, terraform.workspace)) container_count = length(var.ext_port[terraform.workspace]) } |
こちらの方が記述が少なくてすみますし、読みやすいですね。
以下のコマンドで、noderedコンテナが作成されるか確認します。
1 2 3 4 5 6 7 8 9 10 11 |
terraform plan terraform apply -auto-approve Apply complete! Resources: 6 added, 0 changed, 0 destroyed. Outputs: IP-Address = <sensitive> container-name = [ "nodered-dev-f7a8", "nodered-dev-pxev", ] |
次回
次回は、TerraformのModulerについて学びましょう。
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 46 47 |
# --- 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 = lookup(var.image, terraform.workspace) name = var.image[terraform.workspace] } 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", terraform.workspace, random_string.random[count.index].result]) image = docker_image.nodered_image.latest ports { internal = var.int_port # external = lookup(var.ext_port, terraform.workspace)[count.index] external = var.ext_port[terraform.workspace][count.index] } volumes { container_path = "/data" // ローカルのパス host_path = "${path.cwd}/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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# --- root/variables --- # variable "env" { # type = string # description = "Env to deploy to" # default = "dev" # } variable "image" { type = map(any) description = "image for container" default = { dev = "nodered/node-red:latest" prod = "nodered/node-red:latest-minimal" } } variable "ext_port" { description = "external port" # sensitive = true # type = list(number) type = map(any) validation { condition = max(var.ext_port["dev"]...) <= 65535 && min(var.ext_port["dev"]...) >= 1980 error_message = "The external port must be in the valid port range 0 - 65535." } validation { condition = max(var.ext_port["prod"]...) < 1980 && min(var.ext_port["prod"]...) >= 1880 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 3 4 5 |
# --- root/terraform.tfvars ----x ext_port = { dev = [1980, 1981] prod = [1880, 1881] } |
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 5 |
# --- locals.tf --- locals { # container_count = length(lookup(var.ext_port, terraform.workspace)) container_count = length(var.ext_port[terraform.workspace]) } |
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 39 40 41 42 43 44 45 46 47 48 49 50 |
.PHONY: init validate plan apply destroy fmt console show output ws-list ws-new ws-select 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 ws-list: terraform workspace list # docker-compose run --rm terraform workspace list ws-new: terraform workspace new ${name} # docker-compose run --rm terraform workspace new ${name} ws-select: terraform workspace select ${name} # docker-compose run --rm terraform workspace select ${name} |
コメントを残す
コメントを投稿するにはログインしてください。