由于Terraform的许可证变更,我曾经担心未来的动向,但IBM宣布收购HashiCorp后,我感到有所安心。我将继续关注相关动向。
本文将介绍Terraform的内置函数templatefile
。
什么是templatefile
函数?
templatefile
函数用于读取指定路径下的模板文件,并将其内容渲染为模板,从而动态生成文本文件或配置文件。这在需要为不同环境使用稍有不同的文件时非常有用。
https://developer.hashicorp.com/terraform/language/functions/templatefilehttps://developer.hashicorp.com/terraform/language/functions/templatefile
在AWS环境中,templatefile
函数有几种应用方式,下面将介绍几个示例。
1.用户数据
用户数据用于EC2实例启动时的初始设置或自动执行脚本。我们可以使用templatefile
函数来生成这些用户数据。
- 目录结构和文件内容如下:
project/
├── modules/
│ └── ec2/
│ ├── main.tf
│ └── init.tpl
└── env/
├── prod/
│ └── ec2/
│ └── main.tf
└── stage/
└── ec2/
└── main.tf
- modules/ec2/main.tf
在user_data
中使用templatefile
函数。我们指定同一目录下的模板文件init.tpl
,并将packages
和file_content
作为变量传递。
provider "aws" {
region = "ap-northeast-1"
}
variable "packages" {
type = list(string)
}
variable "file_content" {
type = any
}
resource "aws_instance" "example" {
ami = "ami-061a125c7c02edb39"
instance_type = "t2.micro"
iam_instance_profile = "MySessionManagerRole"
user_data = templatefile("${path.module}/init.tpl", {
packages = join(" ", var.packages)
file_content = var.file_content
})
}
- modules/ec2/init.tpl
这是用于user_data
的模板文件,采用cloud-config格式。在本示例中,我们使用了write_files
和runcmd
,但也可以通过users
来创建用户。
通过在init.tpl
中设置cloud_final_modules
,可以确保即使在删除了用户数据生成的文件或卸载了软件包后,系统在重启时仍会重新创建文件或重新安装软件包。
注意:如果修改了这个模板文件并重新执行terraform apply
以更改现有EC2实例的配置,用户数据会被修改,但不会被执行。若要执行新的用户数据,需要在EC2实例上运行cloud-init clean
以重置cloud-init缓存,然后重新启动实例。
#cloud-config
cloud_final_modules:
- [scripts-user, always]
- [write-files, always]
write_files:
- path: /etc/welcome.txt
permissions: '0644'
owner: root:root
content: |
${file_content}
runcmd:
- yum install -y ${packages}
- env/prod/ec2/main.tf
在prod
环境的EC2实例中,指定了要创建的文件内容和安装的软件包。
module "ec2-instance" {
source = "../../../modules/ec2"
packages = ["amazon-cloudwatch-agent", "git"]
file_content = <<EOF
Welcome to prod server!
EOF
}
- env/stage/ec2/main.tf
除了packages
和file_content
外,其余内容与env/prod/main.tf
相同。
module "ec2-instance" {
source = "../../../modules/ec2"
packages = ["git"]
file_content = <<EOF
Welcome to stage server!
EOF
}
在env/prod/ec2/
目录下执行terraform apply
以创建EC2实例。以下是prod
环境中EC2实例的用户数据、文件和软件包的确认结果。
prod
环境EC2实例
同样地,在 env/stage/ec2/
目录下执行 terraform apply
。
以下是 stage 环境 EC2 实例的用户数据、文件和软件包的检查结果。
stage环境EC2实例
2.IAM 策略与角色
在 main.tf
文件中描述 IAM 策略和角色会导致文件逐渐变大,每次添加新的策略或角色时,查找目标策略或角色会变得更加麻烦。
为了避免这种情况,我们可以使用 templatefile
函数。
- 目录结构
project/
├── modules/
│ └── iam/
│ ├── main.tf
│ ├── iam_policy.json.tpl
│ └── iam_role.json.tpl
└── env/
├── prod/
│ └── iam/
│ └── main.tf
└── stage/
└── iam/
└── main.tf
- modules/ec2/main.tf
在 aws_iam_policy
资源的 policy
和 aws_iam_role
资源的 assume_role_policy
中使用 templatefile
函数。
指定同一目录下的模板文件 iam_policy.json.tpl
和 iam_role.json.tpl
,其中 iam_policy.json.tpl
使用 actions
和 resources
作为变量,iam_role.json.tpl
使用 service
作为变量。
variable "policies" {
description = "List of IAM policies"
type = list(object({
name = string
actions = list(string)
resources = list(string)
}))
}
variable "roles" {
description = "List of IAM roles"
type = list(object({
name = string
service = string
policies = list(string)
}))
}
resource "aws_iam_policy" "example" {
for_each = { for p in var.policies : p.name => p }
name = each.value.name
policy = templatefile("${path.module}/iam_policy.json.tpl", {
actions = jsonencode(each.value.actions)
resources = jsonencode(each.value.resources)
})
}
resource "aws_iam_role" "example" {
for_each = { for r in var.roles : r.name => r }
name = each.value.name
assume_role_policy = templatefile("${path.module}/iam_role.json.tpl", {
service = each.value.service
})
}
resource "aws_iam_role_policy_attachment" "example" {
for_each = { for r in var.roles : r.name => r }
role = aws_iam_role.example[each.key].name
policy_arn = lookup({ for p in var.policies : p.name => aws_iam_policy.example[p.name].arn }, each.value.policies[0])
}
- modules/iam/iam_policy.json.tpl
这是用于 policy
的模板文件。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ${actions},
"Resource": ${resources}
}
]
}
- modules/iam/iam_role.json.tpl
这是用于 assume_role_policy
的模板文件。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "${service}"
},
"Action": "sts:AssumeRole"
}
]
}
- env/prod/iam/main.tf
在 prod
环境中描述要创建的 IAM 策略和角色。
module "iam" {
source = "../../../modules/iam"
policies = [
{
name = "prod_policy_1"
actions = ["s3:ListBucket", "s3:GetObject"]
resources = ["arn:aws:s3:::prod-bucket-name", "arn:aws:s3:::prod-bucket-name/*"]
},
{
name = "prod_policy_2"
actions = ["ec2:DescribeInstances"]
resources = ["*"]
}
]
roles = [
{
name = "prod_role_1"
service = "ec2.amazonaws.com"
policies = ["prod_policy_1"]
},
{
name = "prod_role_2"
service = "lambda.amazonaws.com"
policies = ["prod_policy_2"]
}
]
}
- env/stage/iam/main.tf
在 stage
环境中描述要创建的 IAM 策略和角色。
module "iam" {
source = "../../../modules/iam"
policies = [
{
name = "stage_policy_1"
actions = ["s3:ListBucket", "s3:GetObject"]
resources = ["arn:aws:s3:::stage-bucket-name", "arn:aws:s3:::stage-bucket-name/*"]
},
{
name = "stage_policy_2"
actions = ["ec2:DescribeInstances"]
resources = ["*"]
}
]
roles = [
{
name = "stage_role_1"
service = "ec2.amazonaws.com"
policies = ["stage_policy_1"]
},
{
name = "stage_role_2"
service = "lambda.amazonaws.com"
policies = ["stage_policy_2"]
}
]
}
在 `env/prod/iam/` 目录下执行 `terraform apply` 以创建 IAM 策略和角色。
以下是 `prod` 环境 IAM 策略和角色的检查结果。
prod環境IAM-1
prod環境IAM-2
同样,在 env/stage/iam/
目录下执行 terraform apply
。
以下是 stage
环境 IAM 策略和角色的检查结果。
stage環境IAM-1
stage環境IAM-2
3.Terraform 配置文件
Terraform 配置文件的扩展名为 .tf
,包括定义基础设施资源的 main.tf
、声明变量的 variables.tf
和定义输出值的 outputs.tf
等文件。
通过使用 templatefile
函数,可以动态地生成 Terraform 配置文件(.tf 文件)。
※ 由于 Terraform 会读取目录中的所有 .tf
文件,因此只要扩展名是 .tf
,文件名可以自由更改。
- 目录结构
project/
├── modules/
│ └── s3/
│ ├── main.tf
│ └── s3.tf.tpl
└── env/
├── prod/
│ └── s3/
│ └── main.tf
└── stage/
└── s3/
└── main.tf
- modules/s3/main.tf
在 local_file
资源的 content
中使用 templatefile
函数。
指定同一目录下的模板文件 s3.tf.tpl
,其中 bucket_name
是模板中的变量。
provider "aws" {
region = "ap-northeast-1"
}
variable "bucket_names" {
type = list(string)
}
variable "file_path" {
type = string
}
resource "local_file" "s3_buckets" {
count = length(var.bucket_names)
filename = "${var.file_path}/s3_${var.bucket_names[count.index]}.tf"
content = templatefile("${path.module}/s3.tf.tpl", {
bucket_name = var.bucket_names[count.index]
})
}
- modules/s3/s3.tf.tpl
这是用于 content
的模板文件。
resource "aws_s3_bucket" "${bucket_name}" {
bucket = "${bucket_name}"
acl = "private"
}
- env/prod/s3/main.tf
在 prod
环境中描述要创建的 S3 存储桶。
module "s3" {
source = "../../../modules/s3"
bucket_names = ["prod-bucket-example-1", "prod-bucket-example-2", "prod-bucket-example-3"]
file_path = "."
}
- env/stage/s3/main.tf
在 stage
环境中描述要创建的 S3 存储桶。
module "s3" {
source = "../../../modules/s3"
bucket_names = ["stage-bucket-example-1", "stage-bucket-example-2", "stage-bucket-example-3"]
file_path = "."
}
在 `env/prod/s3/` 目录下执行 `terraform apply` 时,将会创建 Terraform 配置文件(`prod-bucket-example-1.tf`、`prod-bucket-example-2.tf`、`prod-bucket-example-3.tf`)。
然后再次执行 `terraform apply`,Terraform 会读取这些创建的文件并创建 S3 存储桶。
以下是 `prod` 环境 S3 存储桶的检查结果。
prod環境s3
同样,在 env/stage/s3/
目录下执行 terraform apply
两次。
以下是 stage
环境 S3 存储桶的检查结果。
stage環境S3
最后
本文介绍了 templatefile
函数的三种应用方法,但它还有许多其他的使用场景。
希望本文能为使用 Terraform 的大家提供参考,并帮助大家发现 templatefile
函数的新应用方法。