Slide 1

Slide 1 text

Amazon ECS デプロイツール ecspresso の 開発を支える「正しい抽象化」の探求 2025-11-14 YAPC::Fukuoka 2025 藤原俊一郎 (@fujiwara)

Slide 2

Slide 2 text

自己紹介 @fujiwara (X, GitHub, Bluesky) @sfujiwara (hatena, mixi2) 2011〜2024 面白法人カヤック 2025-02〜 さくらインターネット ISUCON 優勝4回 / 運営(出題)4回 github.com/kayac/ecspresso github.com/fujiwara/lambroll

Slide 3

Slide 3 text

kayac/ecspresso 1000+ Amazon ECSデプロイツール 「エスプレッソ」と読みます 2017/11〜(8年間継続開発中) 国内企業で広く利用されている 50名以上のコントリビュータ 普段の開発とメンテはほぼ @fujiwara ひとり(個人開発に近い)

Slide 4

Slide 4 text

ecspresso の特徴 Amazon ECSの管理と操作を行う IaC CLIツール create deploy rollback verify diff status scale ... 「タスク定義」 「サービス」を AWS SDK / AWS-CLI と同じ構造の JSONで管理 それ以外のリソース(VPC、LBなど)は 管理しない

Slide 5

Slide 5 text

今日のテーマ: 「正しい抽象化の探求」 ecspressoの設計思想 他の IaC ツールと比較した抽象化アプローチ 抽象化のトレードオフと持続可能性 2025年設計ナイト「ecspressoの設計思想に至る道」の続編です https://speakerdeck.com/fujiwara3/sekkeinight2025

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

責任範囲 システム構成 ECS App,nginx,Fluentd... IAM, VPC, LB, RDS... 👥 カヤック 👥 MSP ecspressoの設計思想に至る道 2017年 責任分界点を明確化 新タイトルは同じ座組でも 「ECS上全部」と「それ以外」 → ecspressoを開発 2020年 設計思想の言語化 「操作範囲をあえてECSのみに限定」 他のリソース(VPC,LB...)は別の手段で ライフサイクルが違うものの管理は 手段も分ける

Slide 8

Slide 8 text

「ecspressoの設計思想に至る道」とは 教訓:設計とは「どこで切るか」 適切な境界線は組織構成によって異なる 責任範囲と操作する範囲を一致させることが重要 変更頻度が少ないインフラ(VPC, LBなど)は Terraform / CloudFormation などで管理 変更頻度が高いアプリケーション(ECSタスク定義、サービス)は ecspresso で管理 インフラ要素のIDは tfstate, CFn Outputs/Exports から参照できる それぞれの管理はチームが分かれることも多い お互いの変更による影響を最小限に抑え、開発効率と安定性を両立

Slide 9

Slide 9 text

アーキテクチャ/実装をほかのIaCツールと比較する 「ecspressoの設計思想」に至った背景を踏まえた上で… 「AWS SDKをほぼそのまま使う薄いラッパー設計」 =あえて抽象化しないのが ecspressoの 特徴 これにより得たものを他のIaCツールと比較しながら探求していきます

Slide 10

Slide 10 text

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 な設計

Slide 11

Slide 11 text

ecspresso: JSON + テンプレート記法 JSON は AWS SDK とほぼ同じ構造 {{ must_env }} : 環境変数を参照 {{ tfstate }} : Terraformで作成し たリソースをtfstateファイルから参照 JSON と Jsonnet の両方をサポート

Slide 12

Slide 12 text

Terraform : HCLで統一した世界 HashiCorp Configuration Language JSONやYAMLがデータ構造をシリアライ ズするためのフォーマットであるのに対 し、HCLは構造化された設定フォーマッ トを構築するために特別に設計された構 文とAPIです github.com/hashicorp/hcl より引用翻訳 SDKの executionRoleArn → HCLでは execution_role_arn (統一された命名規則)

Slide 13

Slide 13 text

AWS CDK: 選べる抽象化レベルとプログラミング言語での記述 TypeScript, Python, Java, C#, Go など複数言語で記述可能 L1 Construct: AWSリソースをほぼそのまま表現(≒SDK/CFn) L2 Construct: よく使うパターンを抽象化した高レベルAPI L3 Construct: アプリケーション全体を構築するための包括的な抽象化 この発表では出てきません 最終的には CloudFormation (CFn) に変換されてからデプロイされる トラブルシューティング時には CFn の知識が必要になることも

Slide 14

Slide 14 text

AWS CDK (L1 Construct) ほぼ AWS SDK / CFn と同じものをTypeScriptで記述

Slide 15

Slide 15 text

AWS CDK (L2 Construct) L1に適切なデフォルト自動生成を追加して簡潔、型安全に記述可能 family , networkMode , requiresCompatibilities は自動設定(デフォルト値) executionRoleArn もロール自体を自動生成するので省略可能 image → ContainerImage.fromRegistry で型付き cpu / memoryLimitMiB は数値で指定 (SDK/CFnでは文字列) メソッドチェーンでリソースを構築 taskDefinition.addContainer(...)

Slide 16

Slide 16 text

AWS Copilot CLI Opinionated な設計 サービス全体の関連リソースを manifest(YAML) で一元管理 これだけでECSで動くアプリケーシ ョンをデプロイできる ALB, VPC, Subnet, NAT Gateway, Security Group...すべて自動作成 最終的には CDK 同様 CFn に変換さ れてからデプロイされる←重要

Slide 17

Slide 17 text

ECSを管理できる IaC ツール - それぞれ「良いところ」がある ecspresso: AWS SDKをほぼそのまま + テンプレート記法 概念・用語の読み替えが必要ない ドキュメントはAWS公式を見ればOK Terraform: 独自DSL(HCL)でリソースを宣言 統一された構文と命名規則で複数リソースを管理 複数のクラウドやSaaSでも同じような記述 AWS CDK: 抽象化レベルを選択、プログラミング言語で記述 L1〜L3までの抽象化レベルで柔軟に設計可能 より少ない記述(L2)、IDEサポート、コード補完で記述性が高い AWS Copilot CLI: Opinionated な設計 簡単な記述でWebサービスに必要な関連リソースを自動構築

Slide 18

Slide 18 text

抽象化アプローチの違いがもたらす実装への影響 ツールを使う側としては 「それぞれ良いところがある」 しかし開発・メンテナンスする側には大きな違いが……? そこを探求していきます

Slide 19

Slide 19 text

実例(1) ECS native Blue/Green デプロイメントへの対応 2025-07-17にECSに追加された Blue/Green デプロイメント機能 CodeDeployを使わずにECSネイティブで実現可能に これ以前はECSのみではRolling updateしかできなかった

Slide 20

Slide 20 text

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" + },

Slide 21

Slide 21 text

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日後)

Slide 22

Slide 22 text

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日後)

Slide 23

Slide 23 text

ECS Blue/Green - ecspresso vs Terraform 対応比較まとめ 抽象化の選択によって追従時の実装コストに差が出てくる deploy操作のみの対応比較 抽象化の種類 実装に必要な対応 変更行数 ecspresso AWS SDK JSON そのまま SDKの更新のみ 数行 Terraform 独自DSL (HCL) SDKとDSLの相互変換 ドキュメント・テスト追加 1,500行以上 TerraformがDSL抽象化コストを払って得ているもの 「ECS以外のリソース、他のクラウドやSaaSをも管理できる統一された世界」 このコストはコミュニティ(受益者、開発者)の大きさ故に支払える面もある ほぼ個人開発の ecspresso で選択するのは……?

Slide 24

Slide 24 text

実例(2) 複数LB対応の比較 2019-07 ECS サービスが ALB/NLB の複数LBに接続できるように ECS GA (2015-04) から4年後に対応 公開用ALBと内部用ALBを分けるユースケースに対応 管理用ALBは内部ネットワークからのみアクセス可能、など

Slide 25

Slide 25 text

複数LB対応 - API レベルでの変更点 ECSサービスの設定で loadBalancers[] に複数LBのターゲットグループを指定可能に ECS 最初のリリース時から loadBalancers は配列なので構造は変更なし "loadBalancers": [ { "containerName": "nginx", "containerPort": 80, "targetGroupArn": "arn:aws:elasticloadbalancing:****" }, + { + "containerName": "nginx", + "containerPort": 80, + "targetGroupArn": "arn:aws:elasticloadbalancing:****" + } ]

Slide 26

Slide 26 text

複数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{

Slide 27

Slide 27 text

複数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

Slide 28

Slide 28 text

複数LB - AWS Copilot CLIでの対応 2023-03 単一LBの複数のリスナーに対応 (v1.27.0) 複数LBにはまだ対応できていない http: path: '/' target_port: 80 additional_rules: - path: '/admin' target_port: 8080

Slide 29

Slide 29 text

複数LB - AWS Copilot CLIでの対応は最後までされなかった 2024-07 最後の新機能追加リリース v1.34.0 2024-11 メンテナンスモード入り発表 (開発終了) 2025-04 最後のリリース v1.34.1(Go 1.23対応のみ) 2020-01 のGAから5年も経たずに開発終了してしまった

Slide 30

Slide 30 text

抽象化の種類とそれが生む制約

Slide 31

Slide 31 text

IaCツールに共通する「抽象化」 ecspresso、Terraform、AWS CDK、Copilot CLI すべてに共通する点 各ツールは ECS に対する変更操作を「抽象化している」 例えば ECS の deploy は以下の API で実現される 1. ecs.RegisterTaskDefinition 変更された新しいタスク定義を登録 2. ecs.UpdateService サービスを新しいタスク定義に更新 3. ecs.WaitUntilServicesStable サービスが安定するまで待機 (厳密には API ではなく SDK のユーティリティ関数) 構成変更を反映するために必要になる手続きを隠蔽している

Slide 32

Slide 32 text

IaCツールに共通する「抽象化」 ecspresso, Terraform, AWS CDK(L1)は ECS(タスク定義、サービス)の構造は「抽象化していない」 AWS CDK(L2)は簡潔で安全な記述を可能にするが 「構造は変えていない」

Slide 33

Slide 33 text

どれも同じ概念を構造として表現している (タスク定義の例)

Slide 34

Slide 34 text

ところが AWS Copilot CLI は… アプリケーションの構造全体を(LB などの外部リソースを含めて)「抽象化している」 ECSが持つタスク定義、サービスなどの概念は隠蔽されている Copilot CLIのサービス != ECSサービス

Slide 35

Slide 35 text

Copilot CLIの「構造の抽象化」がもたらした制約 1サービスには1LBのみ接続可能というDSL設計 → 複数LBには最後まで対応できなかった http: path: '/' 「簡単な記述ですべてを自動構築する」という設計思想    vs 「現実にECSで運用されるサービス」=ユーザーが任意に構築したアプリケーション 現実のアプリケーションが必要とする複雑さは「簡単な記述」でカバーしきれない

Slide 36

Slide 36 text

すべてを抽象化して扱う難しさ 例: 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

Slide 37

Slide 37 text

すべてを抽象化して扱う難しさ - 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で良いのでは)

Slide 38

Slide 38 text

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/

Slide 39

Slide 39 text

2021年時点の自分の感想 https://x.com/fujiwara/status/1382504564327288835

Slide 40

Slide 40 text

ecspresso の抽象化アプローチ

Slide 41

Slide 41 text

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 が追加されたのでそれを利用

Slide 42

Slide 42 text

ecspresso が抽象化しないもの ECSの構造 (タスク定義、サービス) は抽象化しないことでコストを最小化 JSON / Jsonnet でほぼそのまま記述 ドキュメントは AWS 公式を参照すればOK 細かい機能にもすべてアクセスできる 結局実運用で必要になるのは細かい機能 例えばタスク定義の ulimits は Copilot CLI は非対応 AWS SDK / API の変更に迅速に追従 最小限の開発リソースで対応可能 実世界で「ユーザーが構築するアプリケーション」は多様で複雑 あえて構造を抽象化しないことが明確なメリット

Slide 43

Slide 43 text

構造を抽象化せずに使いやすく - 構成ファイルを自動生成 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 管理可能に 「構成ファイル」↔︎「実リソース」の双方向変換可能 なのが重要

Slide 44

Slide 44 text

構造を抽象化せずに使いやすく - Jsonnet + native functions Jsonnet(jsonnet.org): JSONのスーパーセット。より簡潔・強力な記述が可能 ただし、最終的に生成するのはあくまでJSON

Slide 45

Slide 45 text

構造を抽象化せずに使いやすく - Jsonnet + native functions 人間が書きやすく読みやすい = ecspresso にとっての DSL 人間は Jsonnet で柔軟に記述 JSON に変換して AWS SDK / API に渡す Jsonnet は生の JSON も扱える = 既存の JSON + テンプレート記法もそのまま使える 互換性維持、段階的な学習、移行が可能

Slide 46

Slide 46 text

DSL やりすぎになりがち問題への対処 DSL/汎用言語でコード化 → 複雑な構造も柔軟、簡潔に記述できる コード化による抽象化はついついやりすぎる → 読めない、保守が困難 ecspresso での解法 「JSONを escape hatch(非常口)にする」 ecspresso render (taskdef|servicedef|config) 1. Jsonnet + native function で記述した構成ファイルを読み込んで解釈 2. 生の JSON (APIに渡すもの) で標準出力に出力 複雑化したコードをリファクタしても 同一のJSONを生成できれば結果が同一なことを保証できる

Slide 47

Slide 47 text

安心してデプロイ/リファクタリングができる 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",

Slide 48

Slide 48 text

「勝ちに不思議の勝ちあり、負けに不思議の負けなし」 松浦 静山(まつら・せいざん=1760年3月7日~1841年8月15日) 野村克也監督の座右の銘としても有名 「ecspresso の設計思想に至る道」は組織構造から偶然生まれた 「あえて構造を抽象化しない設計」は、実は怠惰ゆえの産物 ECSの複雑な構造体を抽象化するのは面倒なのでサボった、という意識(当時) しかし結果的には 「ecspresso が8年間少ないリソースで継続開発でき、価値を発揮している」要因 (2020年に設計思想を言語化したときに気づいた) → 不思議の勝ち

Slide 49

Slide 49 text

「勝ちに不思議の勝ちあり、負けに不思議の負けなし」 Copilot CLI の設計思想は「開発者体験の簡素化を最優先」 Opinionated = ベストプラクティスに基づいた「正解」を提供 しかし「すべてを抽象化して扱う」設計思想がゆえに 新規構築は簡単だが、移行は困難 複雑なアプリケーションにDSL内で対応しきれない 抽象(DSL)が破れたところから具象(CFn)が漏れ出す 現実のユーザーのニーズを満たせなかった? 特にエンタープライズ領域は制約が多い 他の(生き残った)IaCツールとは抽象化アプローチが根本的に異なっていた

Slide 50

Slide 50 text

まとめ - 「正しい抽象化の探求」 「どこで切るか」が設計の本質 責任分界点を明確にする ecspresso は ECS 以外を相手にしなかったのが成功の要因 操作は抽象化してもよい、構造は抽象化するコストが高い 現実の複雑さから目を背け続けることはできない 「すべてを抽象化して簡単に」80%をカバーしても、20%には対応できない escape hatch は重要 DSL / 汎用言語はやりすぎになりがち いつでも生の構造に戻れる / 差分が見えると安心 生殺与奪の権を他人(が作ったツール)に握らせない

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

GitHub Sponsors で ecspresso 他の開発を支援していただけると嬉しいです https://github.com/sponsors/fujiwara