これは「「はじめに」の Advent Calendar 2021」14日目の記事です。
TerraformでECSリソースを作成
terraformでECSのリソースを作る場合、例えばコードは以下のような感じになります。(※リソースは省略しているので、以下のコードそのままではapply
は出来ません。ご了承ください)
resource "aws_ecs_cluster" "foo" {
name = "foo"
}
resource "aws_ecs_task_definition" "web" {
family = "web"
container_definitions = jsonencode([
{
name = "web"
image = "example/web:1.2.3"
cpu = 128
memory = 512
essential = true
portMappings = [
{
containerPort = 80
hostPort = 80
}
]
}
])
}
resource "aws_ecs_service" "foo" {
name = "web"
cluster = aws_ecs_cluster.foo.id
task_definition = aws_ecs_task_definition.web.arn
desired_count = 1
iam_role = aws_iam_role.foo.arn
}
ECS Taskの問題点
- Taskが利用するDockerイメージを更新したい
- TaskのCPU,Memoryを変更したい
- 環境変数、Secretsなどを追加したい
これらの場合、Task定義aws_ecs_task_definition
を更新する必要があります。
例として、イメージのタグを変更する場合
(略)
resource "aws_ecs_task_definition" "web" {
family = "web"
container_definitions = jsonencode([
{
name = "web"
# 1.2.3 → 1.2.4
image = "example/web:1.2.4"
cpu = 128
memory = 512
essential = true
portMappings = [
{
containerPort = 80
hostPort = 80
}
]
}
])
}
(略)
このtfをapply
すると、Task定義は再作成されるという差分が出ます。
-/+ aws_ecs_task_definition.web (new resource required)
...
これは、AWSではTask定義はリビジョンごとに別リソースとして扱っており、terraformはAWSのAPIを呼び出していることに起因します。
同じFamilyだとしても別リビジョンは別リソースなので、古いリビジョンを消して新しいリビジョンを作成することになります。
この問題は、terraformのissueでも議論されています。
過去のリビジョンを保持しておきたい場合、terraformでTask定義を更新するのができなくなります。
解決策としては、
- stateを作成直後に消してterraform管理外にする
-
lifecycle.ignore_changes
にcontainer_definitions
を追加することで更新をしない- 更新が必要な場合は、
ignore_changes
をコメントアウトしてapply
する
- 更新が必要な場合は、
などがありますが、どちらもハック的なワークアラウンドとなります。
ECS Serviceの問題
aws_ecs_service
もtaskのARNが変わるため、差分が出てしまいます。
ま\Task定義を変更しない場合でも、差分が出るときがあります。
デプロイを別のCIツール(CodePipelineやGithub Actionsなど)で実行していることが多いと思われます。
この場合、そもそもterraformで作成したTask定義のリビジョンと実際に利用されているTask定義のリビジョンに差分が出ており、気づかずにそのままapplyすると稼働中のアプリケーションが先祖還りするリスクがあります。
CIでデプロイされている場合など、最新のTask定義を常に参照したい場合、以下のようなコードにすることで実現できます。
(略)
data "aws_ecs_task_definition" "web" {
task_definition = aws_ecs_task_definition.web.family
}
resource "aws_ecs_service" "foo" {
name = "web"
cluster = aws_ecs_cluster.foo.id
task_definition = "${aws_ecs_task_definition.web.family}:${max(aws_ecs_task_definition.web.revision, data.aws_ecs_task_definition.web.revision)}"
desired_count = 1
iam_role = aws_iam_role.foo.arn
}
これは、以下のリンクを参考にしています。
デプロイしたリビジョンを、Parameter Store等に入れて同じようにdata source
として参照する方法も有効です。
ECSサービスをDataSourceとして使えば、デプロイ中のRevisionをそのまま参照することができそうですが・・・(自己参照的なことになるけど、できるのかな・・・)
task_definition
を ignore_changes
に入れることでそもそも差分を出さないようにすることは出来ますが、ignore_changes
に頼りすぎるのは可能であれば避けるべきでしょう。
ECS Serviceはそもそもdesired_count
というAutoScalingで変更されうるリソースを持っていたりするので、terraformでECS Serviceを管理するとdesired_count
はignore_changes
に追加せざるを得ない状況です。このあたり、Serviceとしてどのリソースを使うのか、という情報と実際に動いているServiceの状態を別々に管理したさがありますね・・・
※ AWSに Task定義は ecs_task_family
というリソースがあったり、 ecs_service_deployment
というリソースがあればこういった問題は起きづらい気がします。