ecspresso advent calendar 2020 day 19 - Terraform tfstate
Amazon ECS のデプロイツールである ecspresso の利用法をまとめていく ecspresso Advent calendar 19日目です。
定義ファイルに記述される外部リソース
ecspresso が使用するサービス定義とタスク定義ファイルには、関連する外部リソースの ID や ARN (Amazon Resource Names) が必要になることがあります。
次の例では、サービス定義ファイルにLBターゲットグループのARNと、VPCセキュリティーグループとサブネットのIDが記述されています。
{
"launchType": "FARGATE",
"loadBalancers": [
{
"containerName": "nginx",
"containerPort": 80,
"targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:12345789012:targetgroup/EC2Co-Defau-SA0YF35VBLCJ/b491afabf44765de"
}
],
"networkConfiguration": {
"awsvpcConfiguration": {
"assignPublicIp": "ENABLED",
"securityGroups": [
"sg-043eab2d606362f03"
],
"subnets": [
"subnet-0089ed3e1bdff1fc9",
"subnet-0d750adbd139f411d"
]
}
}
}
ecspresso ではこれらの外部リソースは管理していないため、ID を知ることができません。しかし ID や ARN の値を定義ファイルに直接記述してしまうと、メンテナンス性や記述性を落とす原因になります。
Terraform State ファイルを読み込む
Terraform は HashiCorp が開発しているクラウドインフラの構成管理をコードで行う OSS です。AWS における IaC (Infrastructure as Code) ツールとしては、公式の CloudFormation と双璧をなす存在でしょう。
ecspresso では、Terraform の State ファイル (tfstate) を読み込み、そこに記述してあるリソースの名前から ID や ARN などの属性を参照できます。
この機能を使用するためには、設定ファイルに terraform.tfstate ファイルへのパスを記述します。パスは設定ファイルの位置からの相対パスです。
plugins:
- name: tfstate
config:
path: terraform.tfstate
ここでは必要なリソースを管理するために、Terraform の tf ファイルを次のように用意したとします。
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_vpc" "ecspresso-demo" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "az-a" {
cidr_block = "10.0.0.0/24"
vpc_id = aws_vpc.ecspresso-demo.id
}
resource "aws_subnet" "az-c" {
cidr_block = "10.0.1.0/24"
vpc_id = aws_vpc.ecspresso-demo.id
}
data "aws_security_group" "http" {
id = "sg-043eab2d606362f03"
}
resource "aws_lb_target_group" "ecspresso-demo" {
name = "EC2Co-Defau-SA0YF35VBLCJ"
port = 80
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.ecspresso-demo.id
}
ここでは例として、セキュリティグループのみ Data source として定義してあります。
定義ファイルでの tfstate 参照
定義ファイルのテンプレート関数 tfstate
を使用すると、tfstate ファイル内のリソースを Terraform のリソース名指定形式で参照できます。最初に示したサービス定義ファイルを、tfstate 参照を使用して書き換えると次のようになります。
{
"launchType": "FARGATE",
"loadBalancers": [
{
"containerName": "nginx",
"containerPort": 80,
"targetGroupArn": "{{ tfstate `aws_lb_target_group.ecspresso-demo.arn` }}"
}
],
"networkConfiguration": {
"awsvpcConfiguration": {
"assignPublicIp": "ENABLED",
"securityGroups": [
"{{ tfstate `data.aws_security_group.http.id` }}"
],
"subnets": [
"{{ tfstate `aws_subnet.az-a.id` }}",
"{{ tfstate `aws_subnet.az-c.id` }}"
]
}
}
}
リソースの属性は id や arn 以外にも tfstate 内に存在している値であれば、すべて参照してテンプレートへの展開が可能です。
これで定義ファイルから生の ID や ARN がなくなり、すべて名前で解決ができるようになりました。
実際に ecspresso render --service-definition
でレンダリングしたり、ecspresso diff
で ECS 上のサービスの状態と差分がないかを確認できます。
tfstate 参照の利点
ID を直接記述するのと比べて、tfstate を参照する利点は次のようなものが上げられます。
見た目が分かりやすい
ランダムな文字列である ID よりも、人間がみた場合に分かりやすい記述になります。
仮に tfstate 内に存在しない名前を記述した場合はテンプレートの展開が失敗するため、typo してもデプロイする前に容易に発見できます。
別の環境でも同じリソース名であれば定義ファイルを変更しなくてよい
例えばステージング環境と本番環境で実際のリソースが別に存在しているような場合に便利です。
環境ごとに異なる tfstate が同じリソース名で管理されていれば、ecspresso から参照する tfstate を変えるだけで実際のリソース ID を環境によって異なる値にできるため、定義ファイルを共用できます。
tfstate 参照 Tips
配列形式リソース名の扱い
Terraform のリソースを for_each
などで記述すると、リソースを特定するための名前は aws_subnet.az["a"]
のように配列の参照形式になることがあります。これをそのまま tfstate 関数に渡すために次のように記述すると、JSON としての構造が壊れてしまいます。
"{{ tfstate `aws_subnet.az["a"].id` }}"
5日目 テンプレート記法 で説明したとおり、このように構造が壊れても ecspresso での読み込みには問題ないものの、エディタで編集する時に困ることがあります。
そのため、正規の Terraform の記法ではありませんが、利便性のために ecspresso では配列形式の記述でダブルクォートとシングルクォートを同様に扱うようになっています。
"{{ tfstate `aws_subnet.az['a'].id` }}"
tfstate を実際に参照してどんな値が入ってるか確認したい
tfstate は JSON 形式なので、人間が直接読むことはできます。ただし、Terraform でリソースを指定するアドレスを文字列で検索してリソースにたどりつくことはできないため、確認に便利な方法を2点紹介します。
terraform state list / show
を使う
terraform state
コマンドの list
, show
を使うと、 tfstate 内の要素を確認できます。
$ terraform state list -state=terraform.tfstate
data.aws_security_group.http
aws_lb_target_group.ecspresso-demo
aws_subnet.az-a
aws_subnet.az-c
aws_vpc.ecspresso-demo
$ terraform state show -state=terraform.tfstate aws_subnet.az-a
# aws_subnet.az-a:
resource "aws_subnet" "az-a" {
arn = "arn:aws:ec2:ap-northeast-1:123456789012:subnet/subnet-0d750adbd139f411d"
assign_ipv6_address_on_creation = false
availability_zone = "ap-northeast-1a"
availability_zone_id = "apne1-az4"
cidr_block = "10.0.0.0/24"
id = "subnet-0d750adbd139f411d"
map_public_ip_on_launch = false
owner_id = "314472643515"
tags = {}
vpc_id = "vpc-0f24baede5950eb2d"
timeouts {}
}
tfstate-lookup
を使う
ecspresso で tfstate 参照を実現するためのパッケージが github.com/fujiwara/tfstate-lookup/tfstate です。
tfstate-lookup
コマンドを使うことで、tfstate 内のリソースの一覧と参照が可能です。
ecspresso が実際に利用しているのは terraform state
ではなく tfstate-lookup/tfstate
なので、こちらを使用するほうが実際にテンプレートで展開される要素を確実に確認できます。また、tfstate を読み込むことだけに特化している分、より高速です。
$ tfstate-lookup -s terraform.tfstate
data.aws_security_group.http
aws_lb_target_group.ecspresso-demo
aws_subnet.az-a
aws_subnet.az-c
aws_vpc.ecspresso-demo
$ tfstate-lookup -s terraform.tfstate aws_subnet.az-a
{
"arn": "arn:aws:ec2:ap-northeast-1:123456789012:subnet/subnet-0d750adbd139f411d",
"assign_ipv6_address_on_creation": false,
"availability_zone": "ap-northeast-1a",
"availability_zone_id": "apne1-az4",
"cidr_block": "10.0.0.0/24",
"id": "subnet-0d750adbd139f411d",
"ipv6_cidr_block": "",
"ipv6_cidr_block_association_id": "",
"map_public_ip_on_launch": false,
"outpost_arn": "",
"owner_id": "123456789012",
"tags": {},
"timeouts": {
"create": null,
"delete": null
},
"vpc_id": "vpc-0f24baede5950eb2d"
}
速度の比較 (Terraform v0.14.2, tfstate-lookup v0.0.14)
$ time terraform state list -state=terraform.tfstate > /dev/null
real 0m0.811s
user 0m0.742s
sys 0m0.158s
$ time tfstate-lookup -s terraform.tfstate > /dev/null
real 0m0.019s
user 0m0.018s
sys 0m0.010s
20日目は、タスク定義とサービス定義を Jsonnet というテンプレート言語で生成する方法を説明します。
Discussion