我在Terraform的v0.13.0版本上进行了一些试验【Terraform】
总结一下
我计划在本月尝试玩一下Terraoform的v0.13.0-beta版本。我将会进行一些实验,比如探索与v0.12版本的区别以及执行0.13升级命令后的差异验证。
安装
因为我正在使用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(开普敦)地区的亚马逊云服务(AWS)。
执行0.13版本升级指令。
我试着很快地完成了一下。
$ 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进行版本管理呢?
如果在这里添加了社区提供程序,就可以通过命名空间来解决并进行安装。
所以今后采用这种写法可能更好。
试一试
接下来我们将尝试上面提到的内容。
我試著安裝社群提供者。
您可以在此处查看提供商的注册表。
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" {}
我们将开始进行初始化。
$ 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"]
}
$ terraform plan -out plan.out
刷新Terraform状态以计划之前…
刷新后的状态将用于计算该计划,但不会被持久化到本地或远程状态存储。data.sops_file.demo_credential: 刷新状态…
data.aws_caller_identity.self: 刷新状态… [id=2020-06-20 23:52:16.146895 +0000 UTC]
aws_kms_key.sops: 刷新状态… [id=beddd63b-4b91-49f4-a865-3d668359cb28]
aws_kms_alias.sops: 刷新状态… [id=alias/sopsKey]
————————————————————————
已生成执行计划如下所示。
资源操作用以下符号表示:
+ 创建
Terraform将执行以下操作:
# aws_ssm_parameter.demo_credential 将被创建
+ resource “aws_ssm_parameter” “demo_credential” {
+ arn = (应用后已知)
+ id = (应用后已知)
+ key_id = “alias/aws/ssm”
+ name = “demo-credential”
+ tier = “Standard”
+ type = “SecureString”
+ value = (敏感值)
+ version = (应用后已知)
}
计划: 添加1个,更改0个,销毁0个。
————————————————————————
该计划已保存至: plan.out
要执行这些操作,请运行以下命令进行应用:
terraform apply “plan.out”
$
$
$ terraform apply “plan.out”
aws_ssm_parameter.demo_credential: 正在创建…
aws_ssm_parameter.demo_credential: 创建完成,耗时3秒 [id=demo-credential]
应用完成!资源: 1个已添加,0个已更改,0个已销毁。
您的基础架构状态已保存到下面的路径。
此状态用于修改和销毁基础架构,请妥善保管。要检查完整的状态,请使用 `terraform show` 命令。
状态路径: terraform.tfstate
输出:
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文件中,编写一个模块以引用上述内容,并在模块中使用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)
}
计数很准确,索引已经被添加进去了。
可以尝试使用module中的for_each。
描述一个类似上述的模块,用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
因为即使不写这个也能解决问题,所以这是多余的,但还是写下来吧。
对于之前创建的用户,我们提前创建了一个群组,并重新修改了代码,使得标签等值可以通过模块赋予。
// 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]
可以看到组先行创建,然后用户创建。
这样,即使在模块中,也可以使用像count和for_each这样的循环处理,并且可以使用depends_on来解决依赖关系。
我的感受
我觉得没有特别困难的地方,只需要了解一下模块,就可以使用了呢~
作为一个需要注意的细节点,使用输出时的写法可能需要稍微注意一下。
个人来说,由于社区提供商可以解析名称,所以我的开发体验大大提高了。
不要过度使用,否则可读性会下降,所以需要在使用的过程中加以斟酌。