TL; DR
今月にTerraoformのv0.13.0-betaがリリースされたのですこし遊んでみようと思います。
v0.12系との違いや0.13upgrade
コマンドの実行による差分の検証などをやっていこうと思います。
インストール
わたしはtfenv
を使っているのでtfenv経由でインストールします。
Dockerイメージも既にきているのでDocker利用されている方はご利用ください。
$ tfenv list-remote | grep 0.13
0.13.0-beta2
0.13.0-beta1
$
$
$ tfenv install 0.13.0-beta2
[INFO] Installing Terraform v0.13.0-beta2
[INFO] Downloading release tarball from https://releases.hashicorp.com/terraform/0.13.0-beta2/terraform_0.13.0-beta2_darwin_amd64.zip
#################################################################################################################################################### 100.0%
[INFO] Downloading SHA hash file from https://releases.hashicorp.com/terraform/0.13.0-beta2/terraform_0.13.0-beta2_SHA256SUMS
tfenv: tfenv-install: [WARN] No keybase install found, skipping OpenPGP signature verification
Archive: tfenv_download.C6RPXq/terraform_0.13.0-beta2_darwin_amd64.zip
inflating: /usr/local/Cellar/tfenv/1.0.2/versions/0.13.0-beta2/terraform
[INFO] Installation of terraform v0.13.0-beta2 successful
[INFO] Switching to v0.13.0-beta2
Error parsing command-line flags: flag provided but not defined: -version
tfenv: tfenv-use: [ERROR] 'terraform --version' failed. Something is seriously wrong
おや?なんかエラーが出ていますね。
どうやらv0.13系から-version
のオプションが無効になりversion
(ハイフン抜き)になったようですね。
$ tfenv list
0.13.0-beta2
* 0.12.26 (set by /usr/local/Cellar/tfenv/1.0.2/version)
0.12.17
$
$ terraform --version
Terraform v0.12.26
$
$
$ tfenv use 0.13.0-beta2
[INFO] Switching to v0.13.0-beta2
Error parsing command-line flags: flag provided but not defined: -version
tfenv: tfenv-use: [ERROR] 'terraform --version' failed. Something is seriously wrong
$
$
$ tfenv list
* 0.13.0-beta2 (set by /usr/local/Cellar/tfenv/1.0.2/version)
0.12.26
0.12.17
$
$
$ terraform version
Terraform v0.13.0-beta2
tfenv側で未対応のためエラーになるみたいですね。
ちなみに-json
オプションが追加されたのでこんな感じで出力することもできます。
$ terraform version -json
{
"terraform_version": "0.13.0-beta2",
"terraform_revision": "",
"provider_selections": {},
"terraform_outdated": false
}
切り替えはできたので進めていきます。
変更点
v0.13系で個人的に大きな変更点は下記です。
-
Automatic installation of third-party providers
- コミュニティプロバイダーも名前解決を実施してくれる
-
depends_on for modules
-
module
ブロックでもdepends_on
が利用できる
-
-
count and for_each for modules
-
module
ブロックでもcount
やfor_each
が利用できる
-
あとはv0.12系へのアップグレード同様にterraform 0.13upgrade
コマンドも準備されているのでそれによる変更点などみていこうと思います。
わたしは利用する予定が無いのですが、バックエンドとしてS3を利用する際にaf-south-1
アフリカ(ケープタウン)リージョンが利用できるようになったみたいです。
0.13upgradeコマンドの実行
さくっとやってみました。
$ terraform 0.13upgrade
This command will update the configuration files in the given directory to use
the new provider source features from Terraform v0.13. It will also highlight
any providers for which the source cannot be detected, and advise how to
proceed.
We recommend using this command in a clean version control work tree, so that
you can easily see the proposed changes as a diff against the latest commit.
If you have uncommited changes already present, we recommend aborting this
command and dealing with them before running this command again.
Would you like to upgrade the module in the current directory?
Only 'yes' will be accepted to confirm.
Enter a value: yes
-----------------------------------------------------------------------------
Upgrade complete!
Use your version control system to review the proposed changes, make any
necessary adjustments, and then commit.
$
$
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
versions.tf
nothing added to commit but untracked files present (use "git add" to track)
versions.tf
が出力されました。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
required_version = ">= 0.13"
}
こっちでTerraformのバージョン管理が推奨なんですかね?
ここにコミュニティプロバイダーは追加してあげると名前空間で解決してインストールできるみたいです!
なので今後はこういう書き方をしていく方がよさそうですね。
やってみる
では上で挙げたものを試していきます。
コミュニティプロバイダーをインストールしてみる
プロバイダーのレジストリはここから確認できます。
赤枠の**USE PROVIDER**をクリックすると記述方法が出てくるので先ほどの`0.13upgrade`コマンドで生成された`versions.tf`に記述を追加します。terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
sops = {
source = "carlpett/sops"
version = "0.5.1"
}
}
required_version = ">= 0.13"
}
またconfig.tf
にもプロバイダーつかうで、という宣言が必要なので同様に追加してあげます。
provider "aws" {
region = "us-east-1"
version = "~> 2.67.0"
}
provider "sops" {} // ここ
data "aws_caller_identity" "self" {}
ではinitしていきます。
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 2.67.0"...
- Finding carlpett/sops versions matching "0.5.1"...
- Installing hashicorp/aws v2.67.0...
- Installed hashicorp/aws v2.67.0 (signed by HashiCorp)
- Installing carlpett/sops v0.5.1...
- Installed carlpett/sops v0.5.1 (self-signed, key ID 1468AC14E6819667)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/plugins/signing.html
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
なんかよさげ。
ディレクトリをみてみましょう。
$ tree .terraform
.terraform
└── plugins
├── registry.terraform.io
│ ├── carlpett
│ │ └── sops
│ │ └── 0.5.1
│ │ └── darwin_amd64
│ │ └── terraform-provider-sops_v0.5.1
│ └── hashicorp
│ └── aws
│ └── 2.67.0
│ └── darwin_amd64
│ └── terraform-provider-aws_v2.67.0_x4
└── selections.json
10 directories, 3 files
おお!それっぽいのがきてる!!!
使えるのかさくっとやってみます。
sops用のkmsキーを作成します。
resource "aws_kms_key" "sops" {
description = "For sops key"
enable_key_rotation = true
deletion_window_in_days = 7
}
resource "aws_kms_alias" "sops" {
target_key_id = aws_kms_key.sops.id
name = "alias/sopsKey"
}
output "sops_key" {
value = aws_kms_key.sops.arn
}
これを利用して暗号化されたymlファイルを作成します。
$ export SOPS_KMS_ARN="arn:aws:kms:us-east-1:0123456789012:key/beddd63b-4b91-49f4-a865-3d668359cb28"
$
$ sops credential.yml
$
$ sops -d credential.yml
demo_credential: IyHQTp3CHsDs+AFG
$
$ ls -l credential.yml
-rw-r--r-- 1 tetsu staff 1013 6 21 08:56 credential.yml
作成後下記をconfig.tf
に追記してもう一度terraform init
を実行します。
data "sops_file" "demo_credential" {
source_file = "credential.yml"
}
これで準備はできたのでパラメータストアにクレデンシャルを保存してみます。
resource "aws_ssm_parameter" "demo_credential" {
name = "demo-credential"
type = "SecureString"
key_id = "alias/aws/ssm"
value = data.sops_file.demo_credential.data["demo_credential"]
}
plan && apply
$ terraform plan -out plan.out
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
data.sops_file.demo_credential: Refreshing state...
data.aws_caller_identity.self: Refreshing state... [id=2020-06-20 23:52:16.146895 +0000 UTC]
aws_kms_key.sops: Refreshing state... [id=beddd63b-4b91-49f4-a865-3d668359cb28]
aws_kms_alias.sops: Refreshing state... [id=alias/sopsKey]
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_ssm_parameter.demo_credential will be created
+ resource "aws_ssm_parameter" "demo_credential" {
+ arn = (known after apply)
+ id = (known after apply)
+ key_id = "alias/aws/ssm"
+ name = "demo-credential"
+ tier = "Standard"
+ type = "SecureString"
+ value = (sensitive value)
+ version = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
This plan was saved to: plan.out
To perform exactly these actions, run the following command to apply:
terraform apply "plan.out"
$
$
$ terraform apply "plan.out"
aws_ssm_parameter.demo_credential: Creating...
aws_ssm_parameter.demo_credential: Creation complete after 3s [id=demo-credential]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
Outputs:
sops_key = arn:aws:kms:us-east-1:0123456789012:key/beddd63b-4b91-49f4-a865-3d668359cb28
moduleでcountを使ってみる
count
を利用しつつ複数のユーザーを作成してみます。
下記のmoduleを参照させて作っていきます。
resource "aws_iam_user" "default" {
name = var.name
force_destroy = false
tags = {
Group = var.iam_group
}
}
ルートディレクトリにあるmain.tf
にて上記を参照するようなmoduleを記述し、その中でcountを利用してみます。
module "count_user" {
count = 2
source = "./modules/user"
name = "hoge-${format("%02d", count.index + 1)}"
iam_group = "hogehoge"
}
こんな感じですね。特に工夫はないです。
terraform plan
するとこんな感じです。
# module.count_user[0].aws_iam_user.default will be created
+ resource "aws_iam_user" "default" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "hoge-01"
+ path = "/"
+ tags = {
+ "Group" = "hogehoge"
}
+ unique_id = (known after apply)
}
# module.count_user[1].aws_iam_user.default will be created
+ resource "aws_iam_user" "default" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "hoge-02"
+ path = "/"
+ tags = {
+ "Group" = "hogehoge"
}
+ unique_id = (known after apply)
}
ちゃんとcountが効いてindexが追加されています。
moduleでfor_eachを使ってみる
上記と同様のmoduleを参照してfor_each
でユーザーを作成するようなものを記述します。
locals {
forEach = {
demo01 = "fuga"
demo02 = "piyo"
}
}
module "for_each_user" {
for_each = local.forEach
source = "./modules/user"
name = each.value
iam_group = "fugapiyo"
}
同様にterraform plan
を投げてみます。
# module.for_each_user["demo01"].aws_iam_user.default will be created
+ resource "aws_iam_user" "default" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "fuga"
+ path = "/"
+ tags = {
+ "Group" = "fugapiyo"
}
+ unique_id = (known after apply)
}
# module.for_each_user["demo02"].aws_iam_user.default will be created
+ resource "aws_iam_user" "default" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "piyo"
+ path = "/"
+ tags = {
+ "Group" = "fugapiyo"
}
+ unique_id = (known after apply)
}
ちゃんとkey:value
が出力されているのがわかります。
moduleでdepends_onを使ってみる
これは書かなくても解決してくれるので蛇足になるのですが、一応書いておきましょう。
先ほどのユーザー作成に対して先行してグループが作られ、かつtag等にmoduleから値が付与できるように書き直しました。
// IAMユーザー作成
module "count_user" {
count = 2
source = "./modules/user"
name = "hoge-${format("%02d", count.index + 1)}"
iam_group = module.count_group.group_name
depends_on = [module.count_group.group_name]
}
module "for_each_user" {
for_each = local.forEach
source = "./modules/user"
name = each.value
iam_group = module.for_each_group.group_name
depends_on = [module.for_each_group.group_name]
}
// IAMグループの作成
module "count_group" {
source = "./modules/group"
group = "hogehoge"
policy_arns = {
default = module.gengeral_policy.default_policy
}
}
module "for_each_group" {
source = "./modules/group"
group = "fugapiyo"
policy_arns = {
default = module.gengeral_policy.default_policy
}
}
デプロイしてみる
一通り書き終わりましたのでひとまずv0.12系で確認してみます。
$ terraform -v
Terraform v0.12.26
$
$
$ terraform plan
Error: Reserved argument name in module block
on main.tf line 10, in module "count_user":
10: count = 2
The name "count" is reserved for use in a future version of Terraform.
Error: Reserved argument name in module block
on main.tf line 16, in module "count_user":
16: depends_on = [module.count_group.group_name]
The name "depends_on" is reserved for use in a future version of Terraform.
Error: Reserved argument name in module block
on main.tf line 20, in module "for_each_user":
20: for_each = local.forEach
The name "for_each" is reserved for use in a future version of Terraform.
Error: Reserved argument name in module block
on main.tf line 26, in module "for_each_user":
26: depends_on = [module.for_each_group.group_name]
The name "depends_on" is reserved for use in a future version of Terraform.
Terraformによる予約語だよ?と怒られてしまいましたね。
ではv0.13系だとどうでしょうか?
$ terraform version
Terraform v0.13.0-beta2
+ provider registry.terraform.io/hashicorp/aws v2.67.0
$
$
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
data.aws_caller_identity.self: Refreshing state...
module.gengeral_policy.data.aws_iam_policy_document.basic_policy: Refreshing state...
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
<= read (data resources)
Terraform will perform the following actions:
=======================省略=======================
Plan: 9 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
先ほどv0.12系で出ていたエラーもなくなり、問題なさそうなのでterraform apply
してみましょう。
$ terraform apply "plan.out"
module.gengeral_policy.data.aws_iam_policy_document.basic_policy: Reading... [id=296661728]
module.gengeral_policy.data.aws_iam_policy_document.basic_policy: Read complete after 0s [id=296661728]
module.for_each_group.aws_iam_group.default: Creating...
module.count_group.aws_iam_group.default: Creating...
module.gengeral_policy.aws_iam_policy.default: Creating...
module.for_each_group.aws_iam_group.default: Creation complete after 1s [id=fugapiyo]
module.count_group.aws_iam_group.default: Creation complete after 1s [id=hogehoge]
module.for_each_user["demo02"].aws_iam_user.default: Creating...
module.count_user[1].aws_iam_user.default: Creating...
module.for_each_user["demo01"].aws_iam_user.default: Creating...
module.count_user[0].aws_iam_user.default: Creating...
module.for_each_user["demo01"].aws_iam_user.default: Creation complete after 2s [id=fuga]
module.gengeral_policy.aws_iam_policy.default: Creation complete after 3s [id=arn:aws:iam::881745222256:policy/basicPolicy]
module.count_group.aws_iam_group_policy_attachment.default["default"]: Creating...
module.for_each_group.aws_iam_group_policy_attachment.default["default"]: Creating...
module.count_user[0].aws_iam_user.default: Creation complete after 2s [id=hoge-01]
module.for_each_user["demo02"].aws_iam_user.default: Creation complete after 2s [id=piyo]
module.count_user[1].aws_iam_user.default: Creation complete after 2s [id=hoge-02]
module.count_group.aws_iam_group_policy_attachment.default["default"]: Creation complete after 3s [id=hogehoge-20200620131313923200000001]
module.for_each_group.aws_iam_group_policy_attachment.default["default"]: Creation complete after 3s [id=fugapiyo-20200620131313943400000002]
グループが先行して作られその後ユーザーが作成されているのがわかります。
このようにmoduleでもcount
やfor_each
といった繰り返し処理が可能になり、依存関係をこちら側で解決するようにできるdepends_on
が利用できるようになりました。
所感
特にこれといって難しさはないので、moduleでも使えるようになったんだなあ〜くらいでよいと思います。
つまづきポイントとしてはoutput
して利用する際の書き方は少し注意が必要かなと思いました。
個人的にはコミュニティプロバイダーが名前解決できるようなってくれたのでDXがめっちゃ上がりました。
あまり使い過ぎても可読性が下がるので見極めつつ利用していけばよいかと思います