ecspresso利用を考慮したTerraformのECS(Fargate)構築
はじめに
ecspressoでは、デプロイの過程でECSのタスク定義の新規リビジョン作成とECSサービスの更新を行います。
そのため、インフラのコード管理をTerraformで行なっている場合、ECSに関してはクラスターまでをTerraformで管理し、サービスとタスク定義は管理しないという方針が考えられます。
ただ、実際に構築・運用をしてみて後述するいくつかの課題にぶつかり、最終的に以下の方法を採ることにしました。
- Terraformではサービスを管理させる。また、初期構築時用のダミーのタスク定義を管理させる。
- ecspressoではタスク定義のみを管理させ、サービスを管理させない。
そのような結論に至った背景を説明します。
(2021/5/1追記)
ecspresso作者のfujiwaraさんにコメントをいただきましたが、ecspressoで本来想定しているユースケースはタスク定義とサービスの両方をecspressoの管理下に置くとのことなので、本記事で最終的に取り扱っている方法は特殊ケースと認識いただければと思います。
環境
- ecspresso v1.5.1
前提
説明を開始する前に、どのようなファイル構成を想定しているかを示します(説明のために簡略化はしています)。
ポイントとして、ecspressoのecs-service-def.json
やecs-task-def.json
は環境ごとにファイルを分けず、環境変数を使ってdev環境とprod環境双方で共用することを想定しています。
.
├── Terraformのリポジトリ
│ ├── dev
│ │ └── dev環境用の各種リソース
│ ├── prod
│ │ └── prod環境用の各種リソース
│ └── modules
│ └── 各種モジュール
└── アプリケーションのリポジトリ
├── .github/workflows
│ └── deploy.yml // ecspresso deployを実行
├── ecspresso
│ ├── config_dev.yaml
│ ├── config_prod.yaml
│ ├── ecs-service-def.json // 本記事最後に説明するパターン3では削除し、使用しない
│ └── ecs-task-def.json // devとprodで共用
└── src
└── アプリケーションのコード
なお、ecspressoでの環境変数の使用方法は以下を参考にしてください。
パターン1 : ECSサービスとタスク定義の初期構築をマネジメントコンソールで行う
最初に実行したのが、このパターンです。
- TerraformでECSクラスターやその他必要なリソース(ALBやIAMロール)などを構築(
terraform apply
)する - マネジメントコンソールで、ECSサービスとタスク定義を作成する
- (まだ一度も実行していなければ)
ecspresso init
を実行し、2
で作成したECSサービスとタスク定義からecs-service-def.json
やecs-task-def.json
を生成する - 以後、
ecs-service-def.json
やecs-task-def.json
を用いてecspresso deploy
を継続的に行う
いったん構築してしまえば、その後は特に問題は無いのですが、2
でマネジメントコンソールを使っているため、本番環境や検証環境といった複数環境を構築する場合や、環境構築途中で何か問題があって1
と2
を始めからやり直すことになった場合、その分だけマネジメントコンソールを操作する必要があり、あまり効率的ではありません。
パターン2 : ECSサービスとタスク定義の初期構築もTerraformで行う
マネジメントコンソールの使用を止めたのが、以下のパターンです。
- TerraformでECSクラスターやその他必要なリソース(ALBやIAMロール)などのほか、ECSサービスとタスク定義を構築(
terraform apply
)する - (まだ一度も実行していなければ)
ecspresso init
を実行し、1
で作成した既存のECSサービスとタスク定義からecs-service-def.json
やecs-task-def.json
を生成する - 以後、
ecs-service-def.json
やecs-task-def.json
を用いてecspresso deploy
を継続的に行う
マネジメントコンソールの操作が不要となり、初期構築はTerraformだけで完結します。
パターン2の課題1 : コードの二重管理
ただ、この状況だとECSサービスのコードがTerraform側とecspresso側で二重管理になっています。
そのため、ecspresso側のECSサービスのコード(ecs-service-def.json
)に何か変更を加えてデプロイした場合、Terraform側でECSサービスを含むstateに対してterraform plan
を行うと、差分となって現れてしまい、Terraformの運用が煩雑になってしまいます。
パターン2の課題2 : Terraformだけで環境を繰り返し新規構築できない
前述の課題1を解消する方法として、追加で以下を行うことが考えられます。
-
ECSサービスをTerraform管理外にする(
terraform state rm
の実行とtfファイルからの該当コード削除)
ただし、これにも問題があります。
4
が完了したTerraformのコードの状態で、環境の作り直しや、別環境の構築を行おうとすると以下の流れとなります。
- TerraformでECSクラスターやその他必要なリソース(ALBやIAMロール)など
のほか、ECSサービスとタスク定義を構築(terraform apply
)する -
ecspresso init
は前回実行済みなのでecs-service-def.json
やecs-task-def.json
は生成済み - 以後、
ecs-service-def.json
やecs-task-def.json
を用いてecspresso deploy
を継続的に行うecspresso deploy
がエラーとなる
2021/04/28 15:09:14 {クラスター名}/{サービス名} Starting deploy
2021/04/28 15:09:15 deploy FAILED. failed to describe current service status: service is not found
ecspresso deploy
は
- 新しいタスク定義を ECS に登録 (register)
- サービスに設定されているタスク定義を新しく登録したものに変更
- 他にサービス定義に変更点があればサービスの設定を更新
ecspresso advent calendar 2020 day 4 - deploy より
といったように、既存のECSサービスを読み取って更新する動きを取り、ECSサービスが無い状態から新規作成はしません。
そのため、AWS側にECSサービスが存在していないとエラーとなります。
結果、前述の4
を行った状態からそのまま環境の作り直しや、別環境の構築を行うということができません(構築そのものはできますが、ecspresso deploy
のエラー問題にぶつかってしまう)。
パターン3 ECSサービスとダミーのタスク定義の初期構築および管理をTerraformで行う
以上の課題を踏まえて、以下の方法を採ることにしました。
- Terraformではサービスを管理させる。また、初期構築時用のダミーのタスク定義を管理させる。
- ecspressoではタスク定義のみを管理させ、サービスを管理させない。
パターン3のポイント1 : ecspressoにサービスを管理させない
まず、ecspressoでは、ECSサービスの定義ファイル(ecs-service-def.json
)を指定しなくてもecspresso deploy
は正常に動作します。
region: ap-northeast-1
cluster: example-prod
service: example-prod
# service_definition: ecs-service-def.json
task_definition: ecs-task-def.json
timeout: 10m0s
そのため、ecs-service-def.json
は削除可能であり、ECSサービスの管理を一方(今回はTerraform)に寄せることができます。
パターン3のポイント2 : Terraformにダミーのタスク定義を管理させる
ECSサービスを新規作成、更新する際には、タスク定義のARNの指定が必須です。
そのため、Terraformで管理するECSサービスにもタスク定義を指定しますが、これにダミーのタスク定義のARNを指定するようにします。
// ECSサービス
resource "aws_ecs_service" "this" {
// 略
task_definition = aws_ecs_task_definition.dummy.arn
// 略
}
// ダミーのECSタスク定義
resource "aws_ecs_task_definition" "dummy" {
container_definitions = jsonencode([
{
name = "nginx"
image = "nginx:latest" // 適当なイメージを使用
// 略
])
// 略
}
これにより、初期構築時のterraform apply
でECSサービスを作成できます。
ECSサービスがAWS側に作成できているということは、ecspressoによるデプロイが前述のエラーにならずに済みます。
初期構築時にダミーのタスク定義もAWS側に作成されてしまいますが、ecspressoによるデプロイでは、ecspresso側のecs-task-def.json
に基づいて新規リビジョンのタスク定義が都度作成されますので、ダミーのタスク定義の存在が問題になることはありません。
パターン3のポイント3 : Terraformでignore_changeを使う
また、TerraformのECSサービスでは、lifecycle
のignore_changes
にtask_definition
を指定しておきます。
// ECSサービス
resource "aws_ecs_service" "this" {
// 略
task_definition = aws_ecs_task_definition.dummy.arn
// 略
lifecycle {
ignore_changes = [
task_definition,
]
}
}
ecsoressoによる継続的デプロイを開始した以降は、ECSサービス上のタスク定義指定(task_definition
)は、常にecspressoが作成した最新リビジョンのタスク定義のARNとなっています。
しかし、ignore_changes
を指定することで、TerraformでECSサービスを含むstateに対してapply
したとしても、AWS側のECSサービスのタスク定義指定がダミーのタスク定義のARNに戻ることはありません。
よって、ECSサービスのtask_definition
の更新はecspressoに任せ、それ以外の属性に関してはTerraform側だけで管理することができます。
終わりに
以上、ecspressoとTerraformの併用に関して、自分なりの方法を解説しました。
また何か課題が見つかれば記事にしていきたいと思います。
Discussion
ecspresso 作者です。記事ありがとうございます!
ecspresso はサービスの新規作成用に create というコマンドを用意しているのですが、それでは用が足りないでしょうか?
初回だけ deploy ではなくcreate を実行しなければいけないので、何度も環境を自動的に作り直しつつ ECS サービスも立てるような場合には不便、というのはありそうですが…
deploy で新規作成ができないというのは他にもハマった事例を見たことがあるので、新規作成も自動的に実行するようにできないか検討してみます。
はじめまして!
ecspressoをいつも便利に使っております!
素晴らしいツールをありがとうございます!
おっしゃる通りcreateコマンドを使う方法がありますね。
記事中では触れていなかったのですが、私の場合、以下の事象に遭遇してcreateコマンドの利用を見送った経緯がありました(長文ご容赦ください)。
前提
ecspresso createでエラー
以上となります。
createでエラーになりましたが、tfstateの読み込みの問題となると、これはdeployコマンドでも同様になると思われるので、ecspressoでのecs-service-def.jsonの利用を見送る理由にもなりますね。
お時間のある時にご確認いただけたら幸いです。
余談ですが、tfstate-lookup単体で色々検証してみようと思ったのですが、インストールできず見送りました。
ありがとうございます!
今後のリリースを楽しみにしております!
なるほど、事情を把握しました。
tfstate を分割していて、data.terraform_remote_state の先が参照できなかったのが ECS サービスを ecspresso での管理から除外した根本理由、ということなのですね。
ecspresso の想定ユースケースはタスク定義とサービスを両方管理下に置くもので、tfstate を分割していなければ create の利用におそらく問題はないと思われますので、terraform_remote_state を使えるようにできないか検討してみますね。
すみません、こちらREADMEが間違っていました。
brew install fujiwara/tap/tfstate-lookup
でインストールできるかと思います。(現時点では terraform_remote_state 先の参照はできないと思います)
そうですね、よくよく考えてみれば私のTerraformの構成の場合ではこちらが根本理由となりますので、初めから記事中にその旨も書くべきでしたね。
なるほど!本記事でのサービスを管理下に置かない手法はイレギュラーなものとわかりましたので、記事を読んだ方が誤解されないよう、冒頭にその旨を追記させていただきます。
ありがとうございます!もし使えるようになれば、大変嬉しいです!
tfstate-lookupのインストールの件もありがとうございました!