本記事は
AWSアワード記念!夏のアドベントカレンダー
14日目の記事です。
🎆🏆
13日目
▶▶ 本記事 ▶▶
15日目
🏆🎆
カラスに襲われたためしばらくその付近には近づいていません。中村です。
Terraformのライセンス変更があったためこれからどうなるのか心配していましたが、IBMがHashiCorpを買収することが発表されたため落ち着きそうで安心しています。今後も動向を注視していきます。
本記事ではTerraformの組み込み関数templatefileについてご紹介します。
templatefile関数とは
指定されたパスにあるテンプレートファイルを読み取り、その内容をテンプレートとしてレンダリングすることでテキストファイルや設定ファイルを動的に生成することができます。
そのため環境毎に少し異なるファイルを使用する必要がある場合などに非常に便利な関数です。
developer.hashicorp.com
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を変数にしています。
- user_dataでtemplatefile関数を使用します。
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:を設定することで、ユーザーデータを利用して作成したファイルを削除したり、パッケージをアンインストールしたとしても再起動時には再作成・再インストールすることができます。
※既存のEC2インスタンスの構成を変更するために、このテンプレートファイルを修正してterraform applyを再実行した場合、ユーザーデータが修正されますが実行はされません。 新しいユーザーデータを実行したい場合には、EC2インスタンス上でcloud-init cleanを実行してcloud-initのキャッシュをリセットしたうえで、再起動する必要があります。
- init.tplにcloud_final_modules:を設定することで、ユーザーデータを利用して作成したファイルを削除したり、パッケージをアンインストールしたとしても再起動時には再作成・再インストールすることができます。
- 今回の例ではwrite_files:、runcmd:を使用していますが、users:でユーザー作成することも可能です。
- user_dataで使用するテンプレートファイルです。cloud-config形式にしています。
#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/でterrafom applyを実行してEC2インスタンスを作成します。
以下はprod環境のEC2インスタンスのユーザーデータ・ファイル・パッケージの確認結果です。

同様にenv/stage/ec2/でterrafom applyを実行します。
以下は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を変数にしています。
- aws_iam_policyリソースのpolicyと、aws_iam_roleリソースのassume_role_policyでtemplatefile関数を使用します。
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/でterrafom applyを実行してIAMポリシー、ロールを作成します。
以下はprod環境IAMポリシー、ロールの確認結果です。


同様にenv/stage/iam/でterrafom applyを実行します。
以下はstage環境IAMポリシー、ロールの確認結果です。


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を変数にしています。
- local_fileリソースのcontentでtemplatefile関数を使用します。
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/でterrafom applyを実行するとTerraform設定ファイル(prod-bucket-example-1.tf、prod-bucket-example-2.tf、prod-bucket-example-3.tf)が作成されます。
そして再度terraform applyを実行すると作成されたファイルを読み取り、S3バケットを作成します。
以下はprod環境s3バケットの確認結果です。

同様にenv/stage/s3/でterrafom applyを2回実行します。
以下はstage環境s3バケットの確認結果です。

最後に
templatefile関数の活用方法を3つご紹介しましたが、ほかにも様々な活用方法があるかと思います。
本記事がTerraformを使用する皆様の参考となり、templatefile関数の新しい活用方法を見つけるための助けとなりましたら幸いです。