Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Amazon ECS デプロイツール ecspresso の開発を支える「正しい抽象化」の探求...

Amazon ECS デプロイツール ecspresso の開発を支える「正しい抽象化」の探求 / YAPC::Fukuoka 2025

YAPC::Fukuoka 2025発表資料です

Avatar for FUJIWARA Shunichiro

FUJIWARA Shunichiro

November 13, 2025
Tweet

More Decks by FUJIWARA Shunichiro

Other Decks in Technology

Transcript

  1. 自己紹介 @fujiwara (X, GitHub, Bluesky) @sfujiwara (hatena, mixi2) 2011〜2024 面白法人カヤック

    2025-02〜 さくらインターネット ISUCON 優勝4回 / 運営(出題)4回 github.com/kayac/ecspresso github.com/fujiwara/lambroll
  2. ecspresso の特徴 Amazon ECSの管理と操作を行う IaC CLIツール create deploy rollback verify

    diff status scale ... 「タスク定義」 「サービス」を AWS SDK / AWS-CLI と同じ構造の JSONで管理 それ以外のリソース(VPC、LBなど)は 管理しない
  3. 責任範囲 システム構成 アプリケーション Consul,nginx,Fluentd... EC2 OS IAM, VPC, LB, RDS...

    👥 カヤック 👥 MSP ecspressoの設計思想に至る道 2014〜2015年 成功体験 Consul + Stretcherによる デプロイシステムを構築 100台規模のEC2オートスケールで 大成功 2016年 失敗から学ぶ 新規開発で同じ構成を採用 開発(カヤック) / インフラ管理(MSP) 責任が曖昧になって双方が苦労
  4. 責任範囲 システム構成 ECS App,nginx,Fluentd... IAM, VPC, LB, RDS... 👥 カヤック

    👥 MSP ecspressoの設計思想に至る道 2017年 責任分界点を明確化 新タイトルは同じ座組でも 「ECS上全部」と「それ以外」 → ecspressoを開発 2020年 設計思想の言語化 「操作範囲をあえてECSのみに限定」 他のリソース(VPC,LB...)は別の手段で ライフサイクルが違うものの管理は 手段も分ける
  5. 「ecspressoの設計思想に至る道」とは 教訓:設計とは「どこで切るか」 適切な境界線は組織構成によって異なる 責任範囲と操作する範囲を一致させることが重要 変更頻度が少ないインフラ(VPC, LBなど)は Terraform / CloudFormation などで管理

    変更頻度が高いアプリケーション(ECSタスク定義、サービス)は ecspresso で管理 インフラ要素のIDは tfstate, CFn Outputs/Exports から参照できる それぞれの管理はチームが分かれることも多い お互いの変更による影響を最小限に抑え、開発効率と安定性を両立
  6. ECS(を管理できる) IaC ツールを比較する ecspresso: kayac/ecspresso AWS SDKをほぼそのまま + テンプレート関数 Terraform:

    hashicorp/terraform-provider-aws 独自DSL(HCL)でリソースを宣言的に管理 プラグインアーキテクチャ/マルチクラウド対応 AWS CDK: aws/aws-cdk プログラミング言語でリソースを定義 抽象化レベルを選択可能 AWS Copilot CLI: aws/copilot-cli ECS/App Runnerを簡単にデプロイ Opinionated な設計
  7. ecspresso: JSON + テンプレート記法 JSON は AWS SDK とほぼ同じ構造 {{

    must_env }} : 環境変数を参照 {{ tfstate }} : Terraformで作成し たリソースをtfstateファイルから参照 JSON と Jsonnet の両方をサポート
  8. AWS CDK: 選べる抽象化レベルとプログラミング言語での記述 TypeScript, Python, Java, C#, Go など複数言語で記述可能 L1

    Construct: AWSリソースをほぼそのまま表現(≒SDK/CFn) L2 Construct: よく使うパターンを抽象化した高レベルAPI L3 Construct: アプリケーション全体を構築するための包括的な抽象化 この発表では出てきません 最終的には CloudFormation (CFn) に変換されてからデプロイされる トラブルシューティング時には CFn の知識が必要になることも
  9. AWS CDK (L2 Construct) L1に適切なデフォルト自動生成を追加して簡潔、型安全に記述可能 family , networkMode , requiresCompatibilities

    は自動設定(デフォルト値) executionRoleArn もロール自体を自動生成するので省略可能 image → ContainerImage.fromRegistry で型付き cpu / memoryLimitMiB は数値で指定 (SDK/CFnでは文字列) メソッドチェーンでリソースを構築 taskDefinition.addContainer(...)
  10. AWS Copilot CLI Opinionated な設計 サービス全体の関連リソースを manifest(YAML) で一元管理 これだけでECSで動くアプリケーシ ョンをデプロイできる

    ALB, VPC, Subnet, NAT Gateway, Security Group...すべて自動作成 最終的には CDK 同様 CFn に変換さ れてからデプロイされる←重要
  11. ECSを管理できる IaC ツール - それぞれ「良いところ」がある ecspresso: AWS SDKをほぼそのまま + テンプレート記法

    概念・用語の読み替えが必要ない ドキュメントはAWS公式を見ればOK Terraform: 独自DSL(HCL)でリソースを宣言 統一された構文と命名規則で複数リソースを管理 複数のクラウドやSaaSでも同じような記述 AWS CDK: 抽象化レベルを選択、プログラミング言語で記述 L1〜L3までの抽象化レベルで柔軟に設計可能 より少ない記述(L2)、IDEサポート、コード補完で記述性が高い AWS Copilot CLI: Opinionated な設計 簡単な記述でWebサービスに必要な関連リソースを自動構築
  12. ECS Blue/Green - API レベルでの変更点 ECSサービスの設定で deploymentConfiguration.strategy に BLUE_GREEN を指定可能に

    loadBalancers[].advancedConfiguration が追加 @@ -22,7 +22,7 @@ "deploymentConfiguration": { - "strategy": "ROLLING" + "strategy": "BLUE_GREEN" }, @@ -31,6 +31,11 @@ "loadBalancers": [ { + "advancedConfiguration": { + "alternateTargetGroupArn": "arn:aws:elasticloadbalancing:***", + "productionListenerRule": "arn:aws:elasticloadbalancing:***", + "roleArn": "arn:aws:iam::***:role/ECSServiceRole" + },
  13. ecspressoの対応 +133 -52 https://github.com/kayac/ecspresso/pull/861 AWS SDK Go v2 を最新に更新 B/G

    関係の struct や定数追加 実は deploy はこれだけで対応完了 ロールバック用の API 呼び出しを追加 これまではロールバック自体ができ ないので別途実装していた 進行中のデプロイメントに対して ecs.StopServiceDeployment(...) 2025-07-19 にマージ(ECS B/G発表2日後)
  14. Terraformでの対応 +1,759 -85 https://github.com/hashicorp/terraform- provider-aws/pull/43434 internal/service/ecs/service.go +540, -3 APIに追加された要素 (

    advanced_configuration など) に対応するコードを追加 ドキュメントとテストの追加 このPRはdeployのみ ロールバックは別PR #43986 (2025-08-27)で対応 +419 −31 2025-07-18 にマージ(ECS B/G発表1日後)
  15. ECS Blue/Green - ecspresso vs Terraform 対応比較まとめ 抽象化の選択によって追従時の実装コストに差が出てくる deploy操作のみの対応比較 抽象化の種類

    実装に必要な対応 変更行数 ecspresso AWS SDK JSON そのまま SDKの更新のみ 数行 Terraform 独自DSL (HCL) SDKとDSLの相互変換 ドキュメント・テスト追加 1,500行以上 TerraformがDSL抽象化コストを払って得ているもの 「ECS以外のリソース、他のクラウドやSaaSをも管理できる統一された世界」 このコストはコミュニティ(受益者、開発者)の大きさ故に支払える面もある ほぼ個人開発の ecspresso で選択するのは……?
  16. 実例(2) 複数LB対応の比較 2019-07 ECS サービスが ALB/NLB の複数LBに接続できるように ECS GA (2015-04)

    から4年後に対応 公開用ALBと内部用ALBを分けるユースケースに対応 管理用ALBは内部ネットワークからのみアクセス可能、など
  17. 複数LB対応 - API レベルでの変更点 ECSサービスの設定で loadBalancers[] に複数LBのターゲットグループを指定可能に ECS 最初のリリース時から loadBalancers

    は配列なので構造は変更なし "loadBalancers": [ { "containerName": "nginx", "containerPort": 80, "targetGroupArn": "arn:aws:elasticloadbalancing:****" }, + { + "containerName": "nginx", + "containerPort": 80, + "targetGroupArn": "arn:aws:elasticloadbalancing:****" + } ]
  18. 複数LB対応 - ecspresso / Terraformでの対応 ecspresso :「何もしない」で対応完了 ECSサービスの loadBalancers は元々配列なのでSDKも変更不要

    Terraform : load_balancer 要素の最大値=1を削除しただけ https://github.com/hashicorp/terraform-provider-aws/pull/9411 テストとドキュメントも更新 // aws/resource_aws_ecs_service.go "load_balancer": { Type: schema.TypeSet, Optional: true, ForceNew: true, - MaxItems: 1, // この制限を削除 Elem: &schema.Resource{ Schema: map[string]*schema.Schema{
  19. 複数LB - AWS Copilot CLIでの対応 Copilot CLI の GA は

    2020-01 (v0.1.0) 誕生時点で ECS 自体は複数LBに対応していた しかし Copilot はずっと複数LB非対応のまま…… Problem create another loadbalancer #3013 - 2021-11 2022-01 NLB 対応が追加 (v1.14.0) ALBとNLBをひとつずつ使うことはできる 複数のALBやNLBを使うことはできない http: path: '/' target_port: 80 nlb: port: 8080/tcp
  20. 複数LB - AWS Copilot CLIでの対応は最後までされなかった 2024-07 最後の新機能追加リリース v1.34.0 2024-11 メンテナンスモード入り発表

    (開発終了) 2025-04 最後のリリース v1.34.1(Go 1.23対応のみ) 2020-01 のGAから5年も経たずに開発終了してしまった
  21. IaCツールに共通する「抽象化」 ecspresso、Terraform、AWS CDK、Copilot CLI すべてに共通する点 各ツールは ECS に対する変更操作を「抽象化している」 例えば ECS

    の deploy は以下の API で実現される 1. ecs.RegisterTaskDefinition 変更された新しいタスク定義を登録 2. ecs.UpdateService サービスを新しいタスク定義に更新 3. ecs.WaitUntilServicesStable サービスが安定するまで待機 (厳密には API ではなく SDK のユーティリティ関数) 構成変更を反映するために必要になる手続きを隠蔽している
  22. Copilot CLIの「構造の抽象化」がもたらした制約 1サービスには1LBのみ接続可能というDSL設計 → 複数LBには最後まで対応できなかった http: path: '/' 「簡単な記述ですべてを自動構築する」という設計思想    vs

    「現実にECSで運用されるサービス」=ユーザーが任意に構築したアプリケーション 現実のアプリケーションが必要とする複雑さは「簡単な記述」でカバーしきれない
  23. すべてを抽象化して扱う難しさ 例: Copilot CLI では CDN / TLS終端 (CloudFront +

    ACM) も自動構築できる cdn: terminate_tls: true [Feature Request]: Configure SSL policy of the CDN (cdn.ssl_policy) #5932 # こう設定したいという要望 cdn: ssl_policy: TLSv1.2_2019 workaround は CFn YAML パッチを使うこと (抽象化とは…?) # copilot/environments/overrides/cfn.patches.yml - op: add path: /Resources/CloudFrontDistribution/Properties/DistributionConfig/ViewerCertificate/MinimumProtocolVersion value: TLSv1.2_2019
  24. すべてを抽象化して扱う難しさ - Copilot CLI ストレージ対応の例 manifestで管理できるもの = EFS, Ephemeral Storage,

    SNS, SQS CFnテンプレートがサービス/環境スタックに直接組み込まれる addonで対応するもの = S3, DynamoDB, Aurora Serverless copilot コマンドがCFnテンプレートを自動生成 非対応 = ElastiCache, OpenSearch Service, etc. copilot/{service}/addons/ に手動でCFnテンプレートを作成する どれも Copilot がサポートしていないカスタマイズは CFn の層で対応するしかない 現実世界のアプリケーションを相手にすると抽象が破れて具象が漏れてくる (CDK Overrides という手段もあるけど、それなら最初からCDKで良いのでは)
  25. The Law of Leaky Abstractions - 漏れのある抽象化の法則 "All non-trivial abstractions,

    to some degree, are leaky." すべての非自明な抽象化は、程度の差があれ漏れがある Joel Spolsky (2002) https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/
  26. ecspresso が抽象化するもの ECSに対する操作 (deploy, rollback, etc)は抽象化することで便利に使える ecspresso deploy 開発当初は ECS

    Rolling update のみサポート (2017) CodeDeployを使った Blue/Green デプロイが可能に (2019) ECSネイティブ Blue/Green デプロイにも対応 (2025) ecspresso rollback 開発当初は ECS にロールバック機能なし(2017) 「ひとつ前のタスク定義に戻す」操作で独自に実現 CodeDeployを使った Blue/Green デプロイのロールバックに対応 (2019) ECSネイティブ Blue/Green デプロイにも対応 (2025) ECS API 側にロールバック用 API が追加されたのでそれを利用
  27. ecspresso が抽象化しないもの ECSの構造 (タスク定義、サービス) は抽象化しないことでコストを最小化 JSON / Jsonnet でほぼそのまま記述 ドキュメントは

    AWS 公式を参照すればOK 細かい機能にもすべてアクセスできる 結局実運用で必要になるのは細かい機能 例えばタスク定義の ulimits は Copilot CLI は非対応 AWS SDK / API の変更に迅速に追従 最小限の開発リソースで対応可能 実世界で「ユーザーが構築するアプリケーション」は多様で複雑 あえて構造を抽象化しないことが明確なメリット
  28. 構造を抽象化せずに使いやすく - 構成ファイルを自動生成 ECSの生の構造はかなり複雑 ← 人間が一から書き起こすのは大変 ecspresso init --service myservice

    AWSに存在している実リソースから構成ファイル類(JSON/Jsonnet)を自動生成 直後に ecspresso deploy でもう始められる 最初の一歩「デプロイ時にイメージタグを書き換えたい」 1. "image":"foo:{{ must_env 'IMAGE_TAG' }}" に書き換え 2. IMAGE_TAG=xxx ecspresso deploy これだけで既存ECSサービスを ecspresso で IaC 管理可能に 「構成ファイル」↔︎「実リソース」の双方向変換可能 なのが重要
  29. 構造を抽象化せずに使いやすく - Jsonnet + native functions 人間が書きやすく読みやすい = ecspresso にとっての

    DSL 人間は Jsonnet で柔軟に記述 JSON に変換して AWS SDK / API に渡す Jsonnet は生の JSON も扱える = 既存の JSON + テンプレート記法もそのまま使える 互換性維持、段階的な学習、移行が可能
  30. DSL やりすぎになりがち問題への対処 DSL/汎用言語でコード化 → 複雑な構造も柔軟、簡潔に記述できる コード化による抽象化はついついやりすぎる → 読めない、保守が困難 ecspresso での解法

    「JSONを escape hatch(非常口)にする」 ecspresso render (taskdef|servicedef|config) 1. Jsonnet + native function で記述した構成ファイルを読み込んで解釈 2. 生の JSON (APIに渡すもの) で標準出力に出力 複雑化したコードをリファクタしても 同一のJSONを生成できれば結果が同一なことを保証できる
  31. 安心してデプロイ/リファクタリングができる ecspresso diff JSONレベルでの実リソースとの差分を表示 $ ecspresso diff --- arn:aws:ecs:ap-northeast-1:123456789012:service/ecspresso-test/nginx-local +++

    ecs-service-def.json @@ -38,5 +38,5 @@ }, "placementConstraints": [], "placementStrategy": [], - "platformVersion": "1.3.0" + "platformVersion": "LATEST" } --- arn:aws:ecs:ap-northeast-1:123456789012:task-definition/ecspresso-test:202 +++ ecs-task-def.json @@ -1,6 +1,10 @@ { "containerDefinitions": [ { "cpu": 0, "environment": [], "essential": true, - "image": "nginx:latest", + "image": "nginx:alpine", "logConfiguration": { "logDriver": "awslogs",
  32. 「勝ちに不思議の勝ちあり、負けに不思議の負けなし」 Copilot CLI の設計思想は「開発者体験の簡素化を最優先」 Opinionated = ベストプラクティスに基づいた「正解」を提供 しかし「すべてを抽象化して扱う」設計思想がゆえに 新規構築は簡単だが、移行は困難 複雑なアプリケーションにDSL内で対応しきれない

    抽象(DSL)が破れたところから具象(CFn)が漏れ出す 現実のユーザーのニーズを満たせなかった? 特にエンタープライズ領域は制約が多い 他の(生き残った)IaCツールとは抽象化アプローチが根本的に異なっていた
  33. まとめ - 「正しい抽象化の探求」 「どこで切るか」が設計の本質 責任分界点を明確にする ecspresso は ECS 以外を相手にしなかったのが成功の要因 操作は抽象化してもよい、構造は抽象化するコストが高い

    現実の複雑さから目を背け続けることはできない 「すべてを抽象化して簡単に」80%をカバーしても、20%には対応できない escape hatch は重要 DSL / 汎用言語はやりすぎになりがち いつでも生の構造に戻れる / 差分が見えると安心 生殺与奪の権を他人(が作ったツール)に握らせない