概要
Amazon ECSをTerraformで構築する際にコンテナ定義の表記方法が公式ドキュメント1に複数載っていたのですが、使い分けがよく分からなかったので調べてみました。例としてはECSを扱っていますが、TerraformでJSONを扱う時全般の話です。
jsonencode関数を使用する場合
Terraformではjsonencodeという関数を使用すると、HCLの形式でJSONをインラインで定義することが可能です。以下は、jsonencode関数を使用してタスク定義を記述した例です。
resource "aws_ecs_task_definition" "service" {
family = "service"
container_definitions = jsonencode([
{
name = "awesome-container"
image = "epic-image:latest"
cpu = 10
memory = 512
essential = true
portMappings = [
{
containerPort = 80
hostPort = 80
}
]
}
])
}
jsonencode関数はTerraform実行時に内部で記述内容を検証して型チェックやエスケープを行うので、安全にJSONデータを生成することが可能です2。例として、上のタスク定義のCPUの部分でcpu = "hoge"
のように文字列を入れると、整数値を入れろと怒られます。
Error: ECS Task Definition container_definitions is invalid: decoding JSON: json: cannot unmarshal string into Go struct field ContainerDefinition.Cpu of type int64
with aws_ecs_task_definition.service,
on ecs.tf line 121, in resource "aws_ecs_task_definition" "service":
121: container_definitions = jsonencode([
122: {
123: name = "awesome-container"
124: image = "epic-image:latest"
125: cpu = "hoge"
126: memory = 512
127: essential = true
128: portMappings = [
129: {
130: containerPort = 80
131: hostPort = 80
132: }
133: ]
134: }
135: ])
また、jsonencode関数ではTerraform内の変数や他のリソースの属性を動的にJSON内に埋め込むことも可能です。以下は、image_name
とimage_version
という変数を定義し、コンテナ定義のimage属性の値として参照している例です。
variable "image_name" {
default = "epic-image"
}
variable "image_version" {
default = "latest"
}
resource "aws_ecs_task_definition" "service" {
family = "service"
container_definitions = jsonencode([
{
name = "awesome-container"
image = "${var.image_name}:${var.image_version}"
cpu = 10
memory = 512
essential = true
portMappings = [
{
containerPort = 80
hostPort = 80
}
]
}
])
}
上記のように、${var.image_name}:${var.image_version}
という式を使用することで、image_name
とimage_version
の値がJSON内に動的に組み込まれます。
ヒアドキュメントを使う場合
JSONに限らず、Terraformで複数行の文字列を記述する場合はヒアドキュメントというものを使うことが出来ます。ヒアドキュメント自体はUNIX系のOSで使用されているものなので、シェルスクリプト等を書いたことのある人なら馴染みがあるかと思います。
以下の例のように<<
の後にTASK_DEFINITION
のような識別子を記述すると、それ以降の行からTASK_DEFINITION
が現れる行までの間に書かれた一連の内容が文字列として扱われます。
resource "aws_ecs_task_definition" "service" {
family = "service"
container_definitions = <<TASK_DEFINITION
[
{
"name": "awesome-container",
"image": "epic-image:latest",
"cpu": 10,
"memory": 512,
"essential": true,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80
}
]
}
]
TASK_DEFINITION
}
こちらの手法もjsonencode関数を使用する場合と同様にインラインでJSONを記述できますが、単純に記述されたとおりの文字列をJSONとして扱うだけなので、実行時にJSONの型チェックが効かなかったり、jsonencode関数のように動的に変数を埋め込むといったこともできません。また、インデントが崩れたりシンタックスハイライトも表示されないので、エディタ上での視認性も低いです。ただし、インデントは<<-
を使うと整えることが出来ます3。
ちなみに、HashiCorp社は、このヒアドキュメントをJSONで使用する方法は推奨していません4。
Don't use "heredoc" strings to generate JSON or YAML. Instead, use the jsonencode function or the yamlencode function so that Terraform can be responsible for guaranteeing valid JSON or YAML syntax.
が、実際のTerraformのAPI参照のドキュメントの例では使用されていました。なぜ非推奨の書き方を公式ドキュメントに書いているのか理解に苦しみますが、JSON以外で文字列を使用するケースでは使いどころがあるかと思います。
外部のJSONファイルに定義する場合
コンテナ定義はJSON形式で別のファイルに記述して、Terraform内からそのファイルを参照することが可能です。以下はservice.json
というファイルに定義した例です。
resource "aws_ecs_task_definition" "service" {
family = "service"
container_definitions = file("task-definitions/service.json")
この方法ではコンテナ定義は純粋なJSONファイルなので、エディタ上でJSONの構文チェックを実施することが可能です。
また、コンテナ定義が大規模で複雑な場合や、同様のコンテナを複数の場所で再利用したい場合にはこのように外部ファイルとして定義した方がコードの可読性と再利用性を高められます。
上の例ではfile関数を使用していますが、templatefile関数を使用すると、jsonencode関数と同様に動的にTerraform内の変数を埋め込むことが可能です5。埋め込む変数は第二引数にオブジェクトとして指定します。
resource "aws_ecs_task_definition" "service" {
family = "service"
container_definitions = templatefile(
"task-definitions/service.json",
{
cpu = var.cpu
}
)
}
{
"name": "awesome-container",
"image": "epic-image:latest",
"cpu": ${cpu},
"memory": 512,
"essential": true,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80
}
]
}
まとめ
ヒアドキュメントはHashiCorpも非推奨の書き方のようなので、jsonencode関数を使用するか、外部ファイルに定義するやり方がよさそうです。(なんで非推奨の書き方を公式ドキュメントに載せてるんだ...)
参考文献
-
Terraform Registry. "aws_ecs_task_definition". 2023. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition. (参照 2023-12-25). ↩
-
HashiCorp Developer. "jsonencode - Functions - Configuration Language". 2023. https://developer.hashicorp.com/terraform/language/functions/jsonencode. (参照 2023-12-25). ↩
-
Jeff Brown Tech. "Terraform Heredoc & JsonEncode: Best Practices". 2022. https://jeffbrown.tech/terraform-heredoc-jsonencode/. (参照 2023-12-25). ↩
-
HashiCorp Developer. "Strings and Templates - Configuration Language". 2023. https://www.terraform.io/language/expressions/strings#generating-json-or-yaml. (参照 2023-12-25). ↩
-
HashiCorp Developer. "templatefile - Functions - Configuration Language". 2023. https://developer.hashicorp.com/terraform/language/functions/templatefile. (参照 2023-12-25). ↩