Terraformのworkspaceを使いこなしたい
Terraformのworkspace(旧environment)について、いまいち理解しきれていない部分があったので整理しておくことにした。まずは普通にworkspaceを使った時にどのような動きになるのかを確認した。そして、応用としてworkspace毎に別のテナントを使ったり、workspaceによってリソースの名前を変えるというテクニックについて検証した。なお、Terraformのバージョンは v0.10.5 を、プロバイダーはOpenStackを用いている。
単純にworkspaceを分けて使う
まずは何も工夫せず、純粋にworkspaceを分けて使う時に何が起こるのかを確認する。プロバイダーを定義して…
provider "openstack" {
user_name = "admin"
tenant_name = "admin"
password = "XXXXXXXX"
auth_url = "http://controller.repl.info:35357/v3"
region = "RegionOne"
domain_name = "Default"
}
次に、リソースとしてセキュリティグループを1つ定義しておく。
resource "openstack_networking_secgroup_v2" "secgroup_1" {
name = "secgroup_1"
description = "My neutron security group"
}
この時点ではworkspaceは分けていない。標準のDefaultのままである。ここでapplyする。
~/s/g/t/terraform_research ❯❯❯ terraform apply master ✱ ![◼](https://s.w.org/images/core/emoji/13.1.0/72x72/25fc.png)openstack_networking_secgroup_v2.secgroup_1: Creating...
description: "" => "My neutron security group"
name: "" => "secgroup_1"
region: "" => "<computed>"
tenant_id: "" => "<computed>"
openstack_networking_secgroup_v2.secgroup_1: Creation complete after 0s (ID: 407f90d2-6622-4c07-94c9-5a8612e5fd8f)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
当然だが、リソースが作成される。リソースの状態は./terraform.tfstateに保存される(以下のサンプルでserialが2になっているのは、sg.tfを追加する前に一度applyしたため)。
$ grep serial terraform.tfstate
"serial": 2,
一度リソースをdestroyして、workspaceを用意していく。標準ではDefaultのみ。
terraform workspace list
* default
stagingという名前のworkspaceを追加する。
$ terraform workspace new staging
Created and switched to workspace "staging"!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
一覧にstagingが追加され、terraform.tfstate.d/staging ディレクトリが作成される。
$ terraform workspace list
default
* staging
applyするとどうなるか。
$ terraform apply
openstack_networking_secgroup_v2.secgroup_1: Creating...
description: "" => "My neutron security group"
name: "" => "secgroup_1"
region: "" => "<computed>"
tenant_id: "" => "<computed>"
openstack_networking_secgroup_v2.secgroup_1: Creation complete after 0s (ID: 1ed6c56a-c4b8-484c-863d-a6f91097e4cc)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
作成時の画面はいつもと同じだが、terraform.tfstateの位置が変わる。標準だとCWDに作られるが、workspaceを指定している時は terraform.tfstate.d/${workspace}/terraform.tfstate になる。つまり、 terraform.tfstate.d/staging/terraform.tfstate だ。
次にworkspaceとしてproductionを作成して、そちらでapplyしてみる。workspaceが変わったので、tfstateも変わり、terraform.tfstate.d/production/terraform.tfstate になる。別々のtfstateで管理しているので、リソースも複数作成されていることになる。
$ openstack security group list | grep secgroup_1
| 1ed6c56a-c4b8-484c-863d-a6f91097e4cc | secgroup_1 | My neutron security group | 2c79c734a32649629e7c68529638f0b4 |
| b6930893-ff5a-4383-b56c-3662e950f58c | secgroup_1 | My neutron security group | 2c79c734a32649629e7c68529638f0b4 |
セキュリティグループを確認すると、secgroup_1が2つあることが確認できる。このように、workspaceは状態の管理を分けていることがわかる。
workspace毎にテナントを変える
前節ではworkspaceの動きについて確認した。こうなると、少し実践的なことを試してみたくなる。本節では、workspace毎に別々のテナントを使う、ということをやってみる。前節では同じテナントに同じ名前のリソースを作っていたが、これは管理上わかりにくいと感じる。リソースの名前はテナント内ではユニークであることが望ましいだろう。となると、別々のテナントにするという手が思いつく。
workspace毎にどうやってテナントを変えたらよいか。Terraformではworkspace名をtfファイル内で参照することができるので、これを利用する。
provider.tfを以下のように変更する。これにより、staging workspaceを選択した場合は「repl-staging」テナントが使われ、production workspaceを選択した場合は「repl-production」が使われる。
diff --git a/provider.tf b/provider.tf
index 693a8c1..9259c1b 100644
--- a/provider.tf
+++ b/provider.tf
@@ -1,6 +1,6 @@
provider "openstack" {
user_name = "admin"
- tenant_name = "admin"
+ tenant_name = "repl-${terraform.workspace}"
password = "XXXXXXXX"
auth_url = "http://controller.repl.info:35357/v3"
region = "RegionOne"
後はそれぞれテナントを用意してやればよいので、作業ログなどは省略する。作業スペース毎にテナントを作っているので、OpenStack内では完全に分離されている。かなり安全といえるだろう。しかし、テナントの切り替えが必要であること、workspace毎にテナントを用意しておく必要があることなどから少し手間がかかるデメリットもある。
workspaceによってリソース名を変える
前節ではworkspace毎にテナントを分けるテクニックを検証した。本節では、同じテナントを使うがリソースがどのworkspaceに属しているのか認識できるようにするテクニックを検証する。同じテナントで複数のworkspaceを運用して不便なのは、同じ名前のリソースができるからである。つまり、workspaceによってリソース名が異なればよい。
modules/sg/sg.tfとして、以下のようなモジュールを用意する。ポイントはnameの部分で、workspaceがproductionであれば変数として渡したnameを使い、そうでなければ”${workspace}-${name}”という、workspace名をprefixにつけた名前となる。
variable "name" {}
variable "description" {}
resource "openstack_networking_secgroup_v2" "secgroup" {
name = "${terraform.workspace == "production" ? var.name : format("%s-%s", terraform.workspace, var.name)}"
description = "${var.description}"
}
モジュールを呼び出した側は以下のようになる。
module "secgroup_1" {
source = "modules/sg"
name = "secgroup_1"
description = "My neutron security group"
}
stagingでapplyすると、staging-secgroup_1という名前でセキュリティグループが作成されている。
$ terraform apply
module.secgroup_1.openstack_networking_secgroup_v2.secgroup: Creating...
description: "" => "My neutron security group"
name: "" => "staging-secgroup_1"
region: "" => "<computed>"
tenant_id: "" => "<computed>"
module.secgroup_1.openstack_networking_secgroup_v2.secgroup: Creation complete after 0s (ID: 61b3f382-37ee-48bc-ae2a-d1366a446c4d)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Copy
productionでapplyした場合はprefixがつかず、secgroup_1という名前で作成される。
$ terraform apply
module.secgroup_1.openstack_networking_secgroup_v2.secgroup: Creating...
description: "" => "My neutron security group"
name: "" => "secgroup_1"
region: "" => "<computed>"
tenant_id: "" => "<computed>"
module.secgroup_1.openstack_networking_secgroup_v2.secgroup: Creation complete after 0s (ID: f5329ff5-c443-4bdb-b5f7-ce107c05b28b)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
このようにworkspaceとmoduleを組み合わせることで、同じ設定でもproductionとstagingで別々の名前のリソースを用意できることがわかった。workspace分のリソースが作られることになるが、テナントの管理に比べると楽だろう。
Terraformの追加構文にはかなり多くの関数が用意されている。モジュールでうまく隠蔽して、良い感じのInfrastructure as Codeを実現していきたい。