<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ecs on Tarragon</title><link>https://tarrragon.github.io/blog/tags/ecs/</link><description>Recent content in Ecs on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/ecs/index.xml" rel="self" type="application/rss+xml"/><item><title>運算平台上 IaC — ECS 與 EKS</title><link>https://tarrragon.github.io/blog/infra/05-core-services/compute-ecs-eks/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/05-core-services/compute-ecs-eks/</guid><description>&lt;p>運算是業務程式碼的執行載體。infra 這層描述的是「運算容量與接線」— 它跑在哪些 subnet、套用哪個 IAM role、掛到哪個 load balancer 的 target group、以及容量怎麼隨負載擴縮。實際跑什麼版本的程式碼由部署流程決定，這個邊界讓 infra 變更與應用發布各走各的節奏 — infra apply 不會因此改動映像，部署 pipeline 不會因此改動 subnet。&lt;/p>
&lt;p>核心服務的部署順序由依賴方向決定（被依賴的先建），運算在這個&lt;a href="https://tarrragon.github.io/blog/infra/05-core-services/deployment-order-database/" data-link-title="部署順序與資料庫上 IaC" data-link-desc="核心服務的依賴圖決定部署順序，資料庫作為第一批上層服務需要最謹慎的 IaC 描述 — 涵蓋 RDS 接線、連線管理、read replica 與端點暴露">四層依賴結構&lt;/a>裡位於第三層：它引用底層的 subnet、security group 與 IAM role，同時被上層的 load balancer target group 引用。所以運算資源的 IaC 定義裡，subnet ID、security group ID、IAM role ARN 都應該是引用而非硬編碼 — 底層重建時上層才會自動跟上。&lt;/p>
&lt;h2 id="ecs-vs-eks-選型">ECS vs EKS 選型&lt;/h2>
&lt;p>ECS 與 EKS 都能跑容器，差異在控制平面的維運模型與生態適配。選型看的是團隊能力與業務需求，而非功能多寡 — 兩者都能達成「容器跑在私有 subnet、用 IAM role 存取資源、掛到 ALB 接收流量」這個基本目標。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>ECS&lt;/th>
 &lt;th>EKS&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>控制平面維運&lt;/td>
 &lt;td>AWS 完全代管&lt;/td>
 &lt;td>AWS 代管 API server，附加元件自行管理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>學習曲線&lt;/td>
 &lt;td>低（AWS 原生概念）&lt;/td>
 &lt;td>高（Kubernetes 生態）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨雲可攜&lt;/td>
 &lt;td>低（AWS 專屬）&lt;/td>
 &lt;td>高（Kubernetes 標準）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>IaC 工具鏈&lt;/td>
 &lt;td>全部用 Terraform AWS provider&lt;/td>
 &lt;td>Terraform 建 cluster，workload 走 Helm&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適合場景&lt;/td>
 &lt;td>AWS 單雲、團隊無 K8s 經驗&lt;/td>
 &lt;td>已有 K8s 能力或需要其生態時&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>ECS 的控制平面由 AWS 代管，service、task definition、target group 都是 AWS 原生資源，Terraform 的 provider 直接描述，心智負擔低。它的 Fargate 啟動類型更進一步 — 連 EC2 instance 都不用管，只描述 task 要多少 CPU 和記憶體，AWS 負責排程到底層主機。&lt;/p>
&lt;p>EKS 的控制平面是受管的 Kubernetes，IaC 描述的是 cluster 本身與 node group，workload（Deployment、Service）則走 Kubernetes manifest 或 Helm chart。這代表 infra 工具鏈跨越了 Terraform 與 Kubernetes 兩套系統 — Terraform 負責 cluster 基礎設施，kubectl / Helm 負責工作負載，兩者的 state 與變更流程是分開的。&lt;/p>
&lt;p>團隊已有 Kubernetes 能力或需要其生態（service mesh、自訂排程器、多雲部署、社群的 operator 生態）時，EKS 的複雜度才值得承擔。否則 ECS 的低負擔是預設起點。一個自測方式：團隊選了 EKS 但只用到最基本的 Deployment + Service，沒有碰 service mesh、CRD 或跨雲，那等於承擔了 Kubernetes 的維運成本卻沒用到它的回報——退回 ECS 通常更合理。&lt;/p>
&lt;h3 id="fargate-vs-ec2-launch-type">Fargate vs EC2 launch type&lt;/h3>
&lt;p>ECS 的執行模式再分 EC2 launch type 和 Fargate launch type。EC2 launch type 需要自己管理 EC2 instance 組成的 capacity provider — AMI 更新、instance 擴縮、OS 層安全修補都是團隊的責任。Fargate 由 AWS 代管運算實例，不需要配 capacity provider、不需要管 AMI，進一步降低運維面。&lt;/p></description><content:encoded><![CDATA[<p>運算是業務程式碼的執行載體。infra 這層描述的是「運算容量與接線」— 它跑在哪些 subnet、套用哪個 IAM role、掛到哪個 load balancer 的 target group、以及容量怎麼隨負載擴縮。實際跑什麼版本的程式碼由部署流程決定，這個邊界讓 infra 變更與應用發布各走各的節奏 — infra apply 不會因此改動映像，部署 pipeline 不會因此改動 subnet。</p>
<p>核心服務的部署順序由依賴方向決定（被依賴的先建），運算在這個<a href="/blog/infra/05-core-services/deployment-order-database/" data-link-title="部署順序與資料庫上 IaC" data-link-desc="核心服務的依賴圖決定部署順序，資料庫作為第一批上層服務需要最謹慎的 IaC 描述 — 涵蓋 RDS 接線、連線管理、read replica 與端點暴露">四層依賴結構</a>裡位於第三層：它引用底層的 subnet、security group 與 IAM role，同時被上層的 load balancer target group 引用。所以運算資源的 IaC 定義裡，subnet ID、security group ID、IAM role ARN 都應該是引用而非硬編碼 — 底層重建時上層才會自動跟上。</p>
<h2 id="ecs-vs-eks-選型">ECS vs EKS 選型</h2>
<p>ECS 與 EKS 都能跑容器，差異在控制平面的維運模型與生態適配。選型看的是團隊能力與業務需求，而非功能多寡 — 兩者都能達成「容器跑在私有 subnet、用 IAM role 存取資源、掛到 ALB 接收流量」這個基本目標。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>ECS</th>
          <th>EKS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>控制平面維運</td>
          <td>AWS 完全代管</td>
          <td>AWS 代管 API server，附加元件自行管理</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>低（AWS 原生概念）</td>
          <td>高（Kubernetes 生態）</td>
      </tr>
      <tr>
          <td>跨雲可攜</td>
          <td>低（AWS 專屬）</td>
          <td>高（Kubernetes 標準）</td>
      </tr>
      <tr>
          <td>IaC 工具鏈</td>
          <td>全部用 Terraform AWS provider</td>
          <td>Terraform 建 cluster，workload 走 Helm</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>AWS 單雲、團隊無 K8s 經驗</td>
          <td>已有 K8s 能力或需要其生態時</td>
      </tr>
  </tbody>
</table>
<p>ECS 的控制平面由 AWS 代管，service、task definition、target group 都是 AWS 原生資源，Terraform 的 provider 直接描述，心智負擔低。它的 Fargate 啟動類型更進一步 — 連 EC2 instance 都不用管，只描述 task 要多少 CPU 和記憶體，AWS 負責排程到底層主機。</p>
<p>EKS 的控制平面是受管的 Kubernetes，IaC 描述的是 cluster 本身與 node group，workload（Deployment、Service）則走 Kubernetes manifest 或 Helm chart。這代表 infra 工具鏈跨越了 Terraform 與 Kubernetes 兩套系統 — Terraform 負責 cluster 基礎設施，kubectl / Helm 負責工作負載，兩者的 state 與變更流程是分開的。</p>
<p>團隊已有 Kubernetes 能力或需要其生態（service mesh、自訂排程器、多雲部署、社群的 operator 生態）時，EKS 的複雜度才值得承擔。否則 ECS 的低負擔是預設起點。一個自測方式：團隊選了 EKS 但只用到最基本的 Deployment + Service，沒有碰 service mesh、CRD 或跨雲，那等於承擔了 Kubernetes 的維運成本卻沒用到它的回報——退回 ECS 通常更合理。</p>
<h3 id="fargate-vs-ec2-launch-type">Fargate vs EC2 launch type</h3>
<p>ECS 的執行模式再分 EC2 launch type 和 Fargate launch type。EC2 launch type 需要自己管理 EC2 instance 組成的 capacity provider — AMI 更新、instance 擴縮、OS 層安全修補都是團隊的責任。Fargate 由 AWS 代管運算實例，不需要配 capacity provider、不需要管 AMI，進一步降低運維面。</p>
<p>Fargate 的代價是三個面向：單位成本較高（同規格的 vCPU/記憶體比 EC2 貴約 20-40%）、不支援 GPU workload、啟動延遲稍長（cold start 約 30-60 秒，EC2 已有 instance 時近乎即時）。多數 web API 和非 GPU 的背景工作的初始選擇是 Fargate — 省掉的運維時間通常抵得過溢價。流量穩定且需要成本最佳化時再切回 EC2 launch type，屆時增加的是 capacity provider 的設定與 instance 管理。量級參考：一個持續運行 2 vCPU / 4GB 的 Fargate task 月費約 $70，同規格 EC2 t3.medium 約 $30。月費差距在服務數量少時不顯著，當 task 數量超過 10-20 個且流量穩定時，切回 EC2 launch type 的節省量才值得投入切換工程。</p>
<p>後續 HCL 範例以 ECS Fargate 示意，EKS 的接線骨架（subnet、IAM、target group）相近，差異落在編排層的資源類型。</p>
<h2 id="task-definition描述容器規格與接線">Task definition：描述容器規格與接線</h2>
<p>Task definition 是 ECS 描述「一個工作單元長什麼樣」的宣告：要跑哪個容器映像、給多少 CPU 和記憶體、開哪些 port、用哪個 IAM role、log 送到哪裡。它是運算 IaC 的核心資源。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_ecs_task_definition&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  family</span>                   <span class="o">=</span> <span class="s2">&#34;api-${var.env}&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  requires_compatibilities</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;FARGATE&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  network_mode</span>             <span class="o">=</span> <span class="s2">&#34;awsvpc&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  cpu</span>                      <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">task_cpu</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  memory</span>                   <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">task_memory</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">  execution_role_arn</span>       <span class="o">=</span> <span class="k">aws_iam_role</span><span class="p">.</span><span class="k">ecs_execution</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">  task_role_arn</span>            <span class="o">=</span> <span class="k">aws_iam_role</span><span class="p">.</span><span class="k">api_task</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">  container_definitions</span> <span class="o">=</span> <span class="k">jsonencode</span><span class="p">([</span>{
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">    name</span>  <span class="o">=</span> <span class="s2">&#34;api&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">    image</span> <span class="o">=</span> <span class="s2">&#34;${var.ecr_repo_url}:${var.image_tag}&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">    portMappings</span> <span class="o">=</span><span class="n"> [{ containerPort</span> <span class="o">=</span><span class="n"> 8080, protocol</span> <span class="o">=</span> <span class="s2">&#34;tcp&#34;</span> }<span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">    logConfiguration</span> <span class="o">=</span> {
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">      logDriver</span> <span class="o">=</span> <span class="s2">&#34;awslogs&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">      options</span> <span class="o">=</span> {
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">        &#34;awslogs-group&#34;</span>         <span class="o">=</span> <span class="k">aws_cloudwatch_log_group</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">name</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">        &#34;awslogs-region&#34;</span>        <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">region</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">        &#34;awslogs-stream-prefix&#34;</span> <span class="o">=</span> <span class="s2">&#34;api&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">      }
</span></span><span class="line"><span class="ln">21</span><span class="cl">    }
</span></span><span class="line"><span class="ln">22</span><span class="cl">  }<span class="p">])</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">}</span></span></code></pre></div><p>這段定義裡有三個刻意的設計：</p>
<p><strong>映像版本解耦</strong>：<code>var.image_tag</code> 在 infra 的 <code>tfvars</code> 裡給一個穩定的預設值（如 <code>latest</code> 或某個基線版本），部署管線覆寫這個值推新版本。infra apply 不會因此改動映像、部署 pipeline 不會因此改動 subnet — 兩者的變更頻率與審查強度不同，混在一起會讓快的等慢的。如果每次部署新版本都要改 infra 的 Terraform code 並跑 apply，代表映像版本跟 infra 沒有解耦——應該讓部署管線直接用 <code>aws ecs update-service</code> 或修改 task definition 的 image tag，不走 Terraform。</p>
<p><strong>兩個 IAM role 的分工</strong>：<code>execution_role_arn</code> 是 ECS 代理用來拉映像和寫 log 的身分 — 它的權限是 ECS 平台層級的，跟業務邏輯無關。<code>task_role_arn</code> 是容器內的應用程式碼在執行期取得的身分 — 它的權限對應業務需求，例如讀寫某個 S3 bucket 或呼叫某個 SQS queue。兩者混在同一個 role 上，就是把平台權限跟業務權限混在一起，違反最小權限（見<a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>）。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_iam_role&#34; &#34;api_task&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  name</span>               <span class="o">=</span> <span class="s2">&#34;api-task-${var.env}&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  assume_role_policy</span> <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="k">aws_iam_policy_document</span><span class="p">.</span><span class="k">ecs_assume</span><span class="p">.</span><span class="k">json</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_iam_role_policy&#34; &#34;api_task&#34;</span> {
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">  role</span>   <span class="o">=</span> <span class="k">aws_iam_role</span><span class="p">.</span><span class="k">api_task</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">  policy</span> <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="k">aws_iam_policy_document</span><span class="p">.</span><span class="k">api_permissions</span><span class="p">.</span><span class="k">json</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">}
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">data</span> <span class="s2">&#34;aws_iam_policy_document&#34; &#34;api_permissions&#34;</span> {
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="k">statement</span> {
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">    actions</span>   <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;s3:GetObject&#34;, &#34;s3:PutObject&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">    resources</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;${aws_s3_bucket.uploads.arn}/*&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  }
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="k">statement</span> {
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">    actions</span>   <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;sqs:SendMessage&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">    resources</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_sqs_queue</span><span class="p">.</span><span class="k">notifications</span><span class="p">.</span><span class="k">arn</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  }
</span></span><span class="line"><span class="ln">20</span><span class="cl">}</span></span></code></pre></div><p><strong>Log 接線</strong>：<code>logConfiguration</code> 把容器的 stdout/stderr 導向 CloudWatch Logs，log group 名稱引用的是同一份 IaC 裡宣告的資源 — 這正是<a href="/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性與 log</a> 說的「監控跟資源同生命週期」。</p>
<h2 id="ecs-service部署模式與網路接線">ECS service：部署模式與網路接線</h2>
<p>ECS service 控制「要跑幾個 task、怎麼部署新版本、掛到哪個 target group」。它是 task definition 的執行實例管理者。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_ecs_service&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  name</span>            <span class="o">=</span> <span class="s2">&#34;api-${var.env}&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  cluster</span>         <span class="o">=</span> <span class="k">aws_ecs_cluster</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  task_definition</span> <span class="o">=</span> <span class="k">aws_ecs_task_definition</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  desired_count</span>   <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">api_desired_count</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  launch_type</span>     <span class="o">=</span> <span class="s2">&#34;FARGATE&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">network_configuration</span> {
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">    subnets</span>          <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="k">s</span> <span class="k">in</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">private</span> <span class="err">:</span> <span class="k">s</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">    security_groups</span>  <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">    assign_public_ip</span> <span class="o">=</span> <span class="kt">false</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  }
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">load_balancer</span> {
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">    target_group_arn</span> <span class="o">=</span> <span class="k">aws_lb_target_group</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">    container_name</span>   <span class="o">=</span> <span class="s2">&#34;api&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">    container_port</span>   <span class="o">=</span> <span class="m">8080</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  }
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="k">deployment_circuit_breaker</span> {
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">    enable</span>   <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">    rollback</span> <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  }
</span></span><span class="line"><span class="ln">24</span><span class="cl">}</span></span></code></pre></div><p><code>network_configuration</code> 把 task 放進 private subnet 並套用 security group — 它決定了這些容器在網路拓撲裡的位置（見<a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三：網路地基</a>）。<code>assign_public_ip = false</code> 讓容器不拿公網 IP，對外流量經由 NAT 出去、入站流量經由 ALB 進來。</p>
<p><code>deployment_circuit_breaker</code> 是 ECS 的內建保護：部署新版本時如果 task 持續啟動失敗（health check 不過、容器 crash），ECS 會自動回滾到上一版。這個行為需要明確開啟、預設是關的 — 關著的話，壞版本的 task 會反覆啟動失敗，新版始終上不來但舊版也不會回來，服務陷入降級狀態。</p>
<h2 id="連線管理運算到資料庫的接線">連線管理：運算到資料庫的接線</h2>
<p>運算到資料庫之間有一段常被略過的接線：連線管理。無狀態運算水平擴張時，每個 task 各自開連線到 RDS，容易把資料庫的連線數打滿。RDS 的連線上限由 instance class 決定（例如 <code>db.r6g.large</code> 約 1000 個連線），而一個跑了 50 個 task 的 ECS service，每個 task 開 20 個連線就到上限了。</p>
<p>出現「擴運算反而拖垮 DB」的訊號時，要引入連線池或受管的連線代理。RDS Proxy 在運算與 RDS 之間代理連線，把運算端的大量短命連線收斂成少量長期連線再進資料庫。它也可以寫進 IaC 並輸出端點給運算引用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_db_proxy&#34; &#34;main&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  name</span>                   <span class="o">=</span> <span class="s2">&#34;api-proxy-${var.env}&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  engine_family</span>          <span class="o">=</span> <span class="s2">&#34;POSTGRESQL&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  role_arn</span>               <span class="o">=</span> <span class="k">aws_iam_role</span><span class="p">.</span><span class="k">rds_proxy</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  vpc_subnet_ids</span>         <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="k">s</span> <span class="k">in</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">private</span> <span class="err">:</span> <span class="k">s</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  vpc_security_group_ids</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">rds_proxy</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">auth</span> {
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">    auth_scheme</span> <span class="o">=</span> <span class="s2">&#34;SECRETS&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">    secret_arn</span>  <span class="o">=</span> <span class="k">aws_secretsmanager_secret</span><span class="p">.</span><span class="k">db_password</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  }
</span></span><span class="line"><span class="ln">12</span><span class="cl">}
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">output</span> <span class="s2">&#34;db_endpoint&#34;</span> {
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">  value</span> <span class="o">=</span> <span class="k">aws_db_proxy</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">endpoint</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">}</span></span></code></pre></div><p>運算端的連線字串指向 proxy 端點而非 RDS 端點。proxy 的 security group 允許來自運算 security group 的流量，proxy 到 RDS 的流量則由 proxy 自己的 security group 對 RDS security group 的規則控制 — 安全邊界多了一層但更清晰。</p>
<h2 id="auto-scaling容量隨負載擴縮">Auto-scaling：容量隨負載擴縮</h2>
<p>ECS service 的 <code>desired_count</code> 是靜態的起始容量。要讓容量隨負載動態調整，需要加上 Application Auto Scaling。它的責任是在負載上升時長出更多 task、負載下降時縮回去省錢。</p>
<p>auto-scaling 的核心決策是「用什麼指標觸發擴縮」。常見的指標分兩類：</p>
<table>
  <thead>
      <tr>
          <th>指標類型</th>
          <th>典型指標</th>
          <th>適用情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>資源利用率</td>
          <td>CPU utilization、memory utilization</td>
          <td>運算密集型服務，CPU 與負載正相關</td>
      </tr>
      <tr>
          <td>業務吞吐量</td>
          <td>ALB request count per target</td>
          <td>I/O 密集型服務，CPU 低但併發高</td>
      </tr>
  </tbody>
</table>
<p>CPU utilization 是最直覺的指標，但它在 I/O 密集型服務上會失準 — 一個等待外部 API 回應的 task，CPU 很低但已經沒有多餘的能力處理新請求。這時用 ALB 的 request count per target（每個 task 平均處理幾個請求）更能反映真實負載。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_appautoscaling_target&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  max_capacity</span>       <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">api_max_count</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  min_capacity</span>       <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">api_min_count</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  resource_id</span>        <span class="o">=</span> <span class="s2">&#34;service/${aws_ecs_cluster.main.name}/${aws_ecs_service.api.name}&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  scalable_dimension</span> <span class="o">=</span> <span class="s2">&#34;ecs:service:DesiredCount&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  service_namespace</span>  <span class="o">=</span> <span class="s2">&#34;ecs&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_appautoscaling_policy&#34; &#34;api_cpu&#34;</span> {
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">  name</span>               <span class="o">=</span> <span class="s2">&#34;api-cpu-${var.env}&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">  policy_type</span>        <span class="o">=</span> <span class="s2">&#34;TargetTrackingScaling&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">  resource_id</span>        <span class="o">=</span> <span class="k">aws_appautoscaling_target</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">resource_id</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">  scalable_dimension</span> <span class="o">=</span> <span class="k">aws_appautoscaling_target</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">scalable_dimension</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">  service_namespace</span>  <span class="o">=</span> <span class="k">aws_appautoscaling_target</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">service_namespace</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="k">target_tracking_scaling_policy_configuration</span> {
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">    target_value</span>       <span class="o">=</span> <span class="m">60</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">predefined_metric_specification</span> {
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">      predefined_metric_type</span> <span class="o">=</span> <span class="s2">&#34;ECSServiceAverageCPUUtilization&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    }
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">    scale_in_cooldown</span>  <span class="o">=</span> <span class="m">300</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">    scale_out_cooldown</span> <span class="o">=</span> <span class="m">60</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  }
</span></span><span class="line"><span class="ln">24</span><span class="cl">}</span></span></code></pre></div><p><code>target_value = 60</code> 表示目標 CPU 平均維持在 60% — 留 40% 的餘裕應對突發。<code>scale_out_cooldown</code> 設短（60 秒），讓擴張反應快；<code>scale_in_cooldown</code> 設長（300 秒），避免負載短暫下降就立刻縮容、結果下一波流量來了又要重新擴張。</p>
<p>設了 auto-scaling 後要定期看 scaling activity log 確認它在正確的時機擴縮。從來沒觸發過有兩種可能：<code>min_capacity</code> 已經高於實際需求（資源浪費），或 target value 設太高（來不及擴）。</p>
<p><code>max_capacity</code> 是成本護欄 — 設一個你能接受的上限，避免異常流量（爬蟲、攻擊、上游重試風暴）把 task 數推到遠超預期的帳單。運行期的成本優化在 <a href="/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">devops 模組八：成本管理</a> 展開。</p>
<p>規模放大後，auto-scaling 的行為模式會改變。<a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">Pokémon GO 上線時實際流量達預估的 50 倍</a>，這類突發不是 auto-scaling 能事前規劃的——50 倍的 headroom 會讓平日成本不合理。Niantic 的 infra 層前提是 GKE 把容器啟動時間降到秒級，讓 surge 反應成為可能；同時依賴 Google CRE 即時補 node 容量。<a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">Zoom COVID 期間的 30 倍突發</a> 則是結構性成長——日活從 1000 萬升到 3 億後不會回落，容量規劃的 baseline 需要永久重新校準。兩個案例的共同教訓是：auto-scaling 的 <code>max_capacity</code> 設定要預留突發空間，但極端突發的處理靠的是平台能力（容器化的快速啟動）和 vendor 支援（managed service 的彈性），不是 IaC 配置能獨立解決的。</p>
<p>多叢集治理是另一個規模維度。<a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">Riot Games 用 246 個 EKS cluster 跨多遊戲多地區</a>，每個遊戲一個獨立叢集（避免跨遊戲互相影響），搭配 Terraform 做 IaC、Karpenter 做 node lifecycle，年省 1000 萬美金。infra 層的教訓是：當運算叢集數量從個位數長到數十甚至數百，叢集本身變成需要 IaC 治理的資源——叢集的建立、版本升級、安全基線都要標準化。<a href="/blog/backend/05-deployment-platform/cases/conde-nast-platform-modernization-eks/" data-link-title="5.C2 Condé Nast：EKS 平台整併與標準化" data-link-desc="多地區異質 Kubernetes 平台整併為統一控制面的案例。">Condé Nast 的 EKS 平台整併</a>也印證了同樣的模式：多團隊各自維護異質 K8s 叢集會造成安全基線不一致，整併到統一平台後把 kube2iam（有 race condition 風險）換成 IRSA（OIDC federation），消除了 node-level 的 credential 共用。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/02-identity-credentials/" data-link-title="模組二：身分與憑證地基 — IAM 與 OIDC" data-link-desc="IAM role / policy 設計、最小權限，以及用 OIDC 短期憑證取代長期 access key">模組二：身分與憑證地基</a>：execution role 與 task role 的最小權限設計</li>
<li>→ <a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三：網路地基</a>：運算放在 private subnet、security group 接線</li>
<li>→ <a href="/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性與 log</a>：log group 與 task definition 同生命週期</li>
<li>→ <a href="/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">devops 模組八：成本管理</a>：auto-scaling 的成本護欄與 spot/Fargate Spot 混用</li>
</ul>
]]></content:encoded></item><item><title>ECS Fargate 成本分析與優化</title><link>https://tarrragon.github.io/blog/infra/05-core-services/ecs-fargate-cost-optimization/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/05-core-services/ecs-fargate-cost-optimization/</guid><description>&lt;p>Fargate 把運算的維運面外包給 AWS — 不需要管 EC2 instance、不需要管 AMI 更新、不需要管 capacity provider 的擴縮邏輯。這份簡化的代價是單位成本較高。當服務規模小或流量不穩定時，Fargate 的簡化值回票價；當服務規模穩定且持續運行時，EC2 launch type 的單位成本優勢會累積到值得切換的量級。本篇的目標是讓讀者能判斷自己的服務在成本曲線的哪個位置、以及有哪些槓桿可以調。&lt;/p>
&lt;h2 id="fargate-計價模型">Fargate 計價模型&lt;/h2>
&lt;p>Fargate 按 task 的 vCPU 時數和記憶體時數分別計費，從 task 啟動（pull image 完成、進入 RUNNING）到停止。計費的最小粒度是一分鐘，不足一分鐘按一分鐘算。&lt;/p>
&lt;p>以 ap-northeast-1（東京）為例的單價（截至撰寫時的量級參考，實際以 AWS 定價頁為準）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>資源&lt;/th>
 &lt;th>單價（每小時）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1 vCPU&lt;/td>
 &lt;td>~$0.05056&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1 GB RAM&lt;/td>
 &lt;td>~$0.00553&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>一個 1 vCPU / 2 GB 的 task 持續運行一個月（730 小時）的費用約為 $0.05056 × 730 + $0.00553 × 2 × 730 ≈ $44.97。這個數字是所有後續比較的基線。&lt;/p>
&lt;p>Fargate 的計費粒度還有一個常被忽略的面向：task 規格只能從 AWS 預定義的 vCPU/memory 組合中選。如果應用只需要 0.3 vCPU / 512 MB，最小可選的配置是 0.25 vCPU / 0.5 GB，但如果需要 0.3 vCPU / 1 GB，就得選 0.5 vCPU / 1 GB — 多付了 0.2 vCPU 的費用。這個「階梯式浪費」在小規格 task 上比例最高。&lt;/p>
&lt;h2 id="fargate-vs-ec2-launch-type-的成本比較">Fargate vs EC2 launch type 的成本比較&lt;/h2>
&lt;p>EC2 launch type 的成本結構不同：付的是 EC2 instance 的時數（不管上面跑幾個 task），加上 ECS 本身不收費。省的是 Fargate 的 markup，多的是 instance 管理（AMI 更新、capacity provider 設定、instance 閒置時仍計費）。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>Fargate 月費&lt;/th>
 &lt;th>EC2（t3.medium）月費&lt;/th>
 &lt;th>差異&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1 task, 1 vCPU / 2 GB, 持續&lt;/td>
 &lt;td>~$45&lt;/td>
 &lt;td>~$30（共享 instance）&lt;/td>
 &lt;td>+50%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5 tasks, 各 0.5 vCPU / 1 GB&lt;/td>
 &lt;td>~$113&lt;/td>
 &lt;td>~$30（1 台 t3.medium 裝得下）&lt;/td>
 &lt;td>+277%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>20 tasks, 各 1 vCPU / 2 GB&lt;/td>
 &lt;td>~$900&lt;/td>
 &lt;td>~$240（4 台 t3.xlarge）&lt;/td>
 &lt;td>+275%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>流量波動大，尖峰 10 tasks / 離峰 1&lt;/td>
 &lt;td>~$180（加權平均）&lt;/td>
 &lt;td>~$150（需預留尖峰容量）&lt;/td>
 &lt;td>+20%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>幾個判讀要點：&lt;/p>
&lt;ul>
&lt;li>task 數量少且持續運行時，Fargate 的溢價比例最高（+50% 到 +277%），但絕對金額小（$15-$80/月的差距），不值得為此承擔 instance 管理的維運負擔&lt;/li>
&lt;li>task 數量多且持續運行時，EC2 的絕對節省量開始可觀（$660/月），這時候切換的維運成本有回報&lt;/li>
&lt;li>流量波動大時，Fargate 的優勢是按需計費 — 離峰時 task 數降下來就停止計費，EC2 instance 閒置時仍然計費。波動越大，Fargate 的成本效益越接近或超過 EC2&lt;/li>
&lt;/ul>
&lt;h2 id="fargate-spot">Fargate Spot&lt;/h2>
&lt;p>Fargate Spot 使用 AWS 的閒置容量，價格約為 on-demand 的 30%（折扣幅度 ~70%），代價是 AWS 可以隨時回收容量、task 會收到 SIGTERM 後被終止。&lt;/p></description><content:encoded><![CDATA[<p>Fargate 把運算的維運面外包給 AWS — 不需要管 EC2 instance、不需要管 AMI 更新、不需要管 capacity provider 的擴縮邏輯。這份簡化的代價是單位成本較高。當服務規模小或流量不穩定時，Fargate 的簡化值回票價；當服務規模穩定且持續運行時，EC2 launch type 的單位成本優勢會累積到值得切換的量級。本篇的目標是讓讀者能判斷自己的服務在成本曲線的哪個位置、以及有哪些槓桿可以調。</p>
<h2 id="fargate-計價模型">Fargate 計價模型</h2>
<p>Fargate 按 task 的 vCPU 時數和記憶體時數分別計費，從 task 啟動（pull image 完成、進入 RUNNING）到停止。計費的最小粒度是一分鐘，不足一分鐘按一分鐘算。</p>
<p>以 ap-northeast-1（東京）為例的單價（截至撰寫時的量級參考，實際以 AWS 定價頁為準）：</p>
<table>
  <thead>
      <tr>
          <th>資源</th>
          <th>單價（每小時）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1 vCPU</td>
          <td>~$0.05056</td>
      </tr>
      <tr>
          <td>1 GB RAM</td>
          <td>~$0.00553</td>
      </tr>
  </tbody>
</table>
<p>一個 1 vCPU / 2 GB 的 task 持續運行一個月（730 小時）的費用約為 $0.05056 × 730 + $0.00553 × 2 × 730 ≈ $44.97。這個數字是所有後續比較的基線。</p>
<p>Fargate 的計費粒度還有一個常被忽略的面向：task 規格只能從 AWS 預定義的 vCPU/memory 組合中選。如果應用只需要 0.3 vCPU / 512 MB，最小可選的配置是 0.25 vCPU / 0.5 GB，但如果需要 0.3 vCPU / 1 GB，就得選 0.5 vCPU / 1 GB — 多付了 0.2 vCPU 的費用。這個「階梯式浪費」在小規格 task 上比例最高。</p>
<h2 id="fargate-vs-ec2-launch-type-的成本比較">Fargate vs EC2 launch type 的成本比較</h2>
<p>EC2 launch type 的成本結構不同：付的是 EC2 instance 的時數（不管上面跑幾個 task），加上 ECS 本身不收費。省的是 Fargate 的 markup，多的是 instance 管理（AMI 更新、capacity provider 設定、instance 閒置時仍計費）。</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>Fargate 月費</th>
          <th>EC2（t3.medium）月費</th>
          <th>差異</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1 task, 1 vCPU / 2 GB, 持續</td>
          <td>~$45</td>
          <td>~$30（共享 instance）</td>
          <td>+50%</td>
      </tr>
      <tr>
          <td>5 tasks, 各 0.5 vCPU / 1 GB</td>
          <td>~$113</td>
          <td>~$30（1 台 t3.medium 裝得下）</td>
          <td>+277%</td>
      </tr>
      <tr>
          <td>20 tasks, 各 1 vCPU / 2 GB</td>
          <td>~$900</td>
          <td>~$240（4 台 t3.xlarge）</td>
          <td>+275%</td>
      </tr>
      <tr>
          <td>流量波動大，尖峰 10 tasks / 離峰 1</td>
          <td>~$180（加權平均）</td>
          <td>~$150（需預留尖峰容量）</td>
          <td>+20%</td>
      </tr>
  </tbody>
</table>
<p>幾個判讀要點：</p>
<ul>
<li>task 數量少且持續運行時，Fargate 的溢價比例最高（+50% 到 +277%），但絕對金額小（$15-$80/月的差距），不值得為此承擔 instance 管理的維運負擔</li>
<li>task 數量多且持續運行時，EC2 的絕對節省量開始可觀（$660/月），這時候切換的維運成本有回報</li>
<li>流量波動大時，Fargate 的優勢是按需計費 — 離峰時 task 數降下來就停止計費，EC2 instance 閒置時仍然計費。波動越大，Fargate 的成本效益越接近或超過 EC2</li>
</ul>
<h2 id="fargate-spot">Fargate Spot</h2>
<p>Fargate Spot 使用 AWS 的閒置容量，價格約為 on-demand 的 30%（折扣幅度 ~70%），代價是 AWS 可以隨時回收容量、task 會收到 SIGTERM 後被終止。</p>
<p>適用條件：task 能在 120 秒內優雅停止、應用有重試機制或上游有 load balancer 自動移除不健康的 target。批次處理、背景 worker、可中斷的佇列消費者是典型的 Spot 候選。對外直接服務的 API 通常混合部署 — 基線容量用 on-demand、彈性擴張部分用 Spot。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_ecs_service&#34; &#34;api&#34;</span> {<span class="c1">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">  # ...
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">capacity_provider_strategy</span> {
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">    capacity_provider</span> <span class="o">=</span> <span class="s2">&#34;FARGATE&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">    weight</span>            <span class="o">=</span> <span class="m">1</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    base</span>              <span class="o">=</span> <span class="m">2</span><span class="c1">  # 至少 2 個 on-demand task 保底
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>  }
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="k">capacity_provider_strategy</span> {
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">    capacity_provider</span> <span class="o">=</span> <span class="s2">&#34;FARGATE_SPOT&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">    weight</span>            <span class="o">=</span> <span class="m">3</span><span class="c1">  # 擴張時 3/4 的 task 用 Spot
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>  }
</span></span><span class="line"><span class="ln">14</span><span class="cl">}</span></span></code></pre></div><p><code>base = 2</code> 確保至少有兩個 on-demand task 在線（不會被回收），<code>weight</code> 比例讓後續擴張的 task 優先使用 Spot。中斷發生時 ECS 會自動在 on-demand 上補充，但補充需要時間（task 啟動 + health check 通過），這段期間服務容量會短暫下降。</p>
<h2 id="compute-savings-plans">Compute Savings Plans</h2>
<p>Compute Savings Plans 是對 Fargate（和 EC2、Lambda）的預付承諾折扣：承諾每小時固定消費 X 美元的運算量，換取 1 年或 3 年的折扣（1 年約 -20%、3 年約 -40%，視具體方案）。</p>
<p>關鍵判斷：承諾量（$/hr）設在實際用量的多少比例。保守做法是設在過去 3 個月最低用量的 80% — 這部分幾乎確定會用到，享受折扣；超過承諾量的部分自動按 on-demand 計費，不會浪費。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 查過去 90 天的 Fargate 用量趨勢</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">aws ce get-cost-and-usage <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --time-period <span class="nv">Start</span><span class="o">=</span>2026-03-01,End<span class="o">=</span>2026-06-01 <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --granularity MONTHLY <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --metrics <span class="s2">&#34;UnblendedCost&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --filter <span class="s1">&#39;{&#34;Dimensions&#34;:{&#34;Key&#34;:&#34;SERVICE&#34;,&#34;Values&#34;:[&#34;Amazon Elastic Container Service&#34;]}}&#39;</span></span></span></code></pre></div><p>Savings Plans 跟 Fargate Spot 可以疊加：Spot task 的費用也能用 Savings Plans 折抵。先用 Savings Plans 降低基線成本，再用 Spot 降低彈性擴張的成本，兩層折扣疊起來可以把 Fargate 的實際單價壓到接近 EC2 on-demand。</p>
<h2 id="task-規格的-rightsizing">Task 規格的 rightsizing</h2>
<p>Fargate task 的 vCPU 和記憶體配置如果設得過大，多出來的資源每小時都在計費。rightsizing 的目標是讓 task 規格貼合實際使用量，但留足安全餘裕。</p>
<h3 id="量測實際使用量">量測實際使用量</h3>
<p>開啟 CloudWatch Container Insights 後，每個 task 的 CPU 和記憶體使用量會自動上報。觀察 7-14 天的 p95 值：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 查 ECS service 過去 7 天的 CPU p95</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">aws cloudwatch get-metric-statistics <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --namespace ECS/ContainerInsights <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --metric-name CpuUtilized <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --dimensions <span class="nv">Name</span><span class="o">=</span>ServiceName,Value<span class="o">=</span>api <span class="nv">Name</span><span class="o">=</span>ClusterName,Value<span class="o">=</span>prod <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --start-time 2026-06-19T00:00:00Z <span class="se">\
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="se"></span>  --end-time 2026-06-26T00:00:00Z <span class="se">\
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="se"></span>  --period <span class="m">3600</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="se"></span>  --statistics p95</span></span></code></pre></div><h3 id="判斷調整方向">判斷調整方向</h3>
<table>
  <thead>
      <tr>
          <th>p95 使用率</th>
          <th>判斷</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU &lt; 30%</td>
          <td>過度配置，浪費明顯</td>
          <td>降一級 vCPU</td>
      </tr>
      <tr>
          <td>CPU 30-70%</td>
          <td>合理範圍，有足夠餘裕應對尖峰</td>
          <td>維持</td>
      </tr>
      <tr>
          <td>CPU &gt; 80%</td>
          <td>餘裕不足，尖峰時可能觸發 throttling</td>
          <td>升一級 vCPU 或增加 task 數</td>
      </tr>
      <tr>
          <td>Memory &lt; 40%</td>
          <td>過度配置</td>
          <td>降一級 memory</td>
      </tr>
      <tr>
          <td>Memory &gt; 80%</td>
          <td>OOM kill 風險</td>
          <td>升一級 memory</td>
      </tr>
  </tbody>
</table>
<p>調整後觀察 3-5 天確認沒有效能退化再進入下一輪。每次只調一個維度（CPU 或 memory），避免同時改兩個變數無法歸因。</p>
<h3 id="fargate-可選的規格組合">Fargate 可選的規格組合</h3>
<p>Fargate 的 vCPU 和 memory 不能任意搭配。常用的組合：</p>
<table>
  <thead>
      <tr>
          <th>vCPU</th>
          <th>可選 Memory 範圍</th>
          <th>典型用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0.25</td>
          <td>0.5 / 1 / 2 GB</td>
          <td>輕量 sidecar、cron job</td>
      </tr>
      <tr>
          <td>0.5</td>
          <td>1 / 2 / 3 / 4 GB</td>
          <td>小型 API、worker</td>
      </tr>
      <tr>
          <td>1</td>
          <td>2 / 3 / 4 / 5 / 6 / 7 / 8 GB</td>
          <td>標準 API、中型 worker</td>
      </tr>
      <tr>
          <td>2</td>
          <td>4 ~ 16 GB</td>
          <td>高負載 API、批次處理</td>
      </tr>
      <tr>
          <td>4</td>
          <td>8 ~ 30 GB</td>
          <td>資料處理、ML inference</td>
      </tr>
  </tbody>
</table>
<p>選的時候從最小的「能跑」組合開始，用 Container Insights 量測後再調。常見的浪費是把所有 task 都設成 1 vCPU / 2 GB — 一個只用 0.1 vCPU / 256 MB 的 sidecar 也配了同樣的規格。</p>
<h2 id="何時從-fargate-切到-ec2">何時從 Fargate 切到 EC2</h2>
<p>切換的判斷不只看成本差額，還要看維運能力。EC2 launch type 需要管理：AMI 更新（安全 patch）、instance draining（rolling update 時把 task 遷走再關 instance）、capacity provider 的擴縮邏輯、instance 的 security group 與 IAM role。</p>
<table>
  <thead>
      <tr>
          <th>判斷維度</th>
          <th>留在 Fargate</th>
          <th>切到 EC2</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>月費差額</td>
          <td>&lt; $200</td>
          <td>&gt; $500 且持續 3 個月</td>
      </tr>
      <tr>
          <td>團隊維運能力</td>
          <td>沒有專人管 instance</td>
          <td>有平台工程師或 DevOps</td>
      </tr>
      <tr>
          <td>流量型態</td>
          <td>波動大、有明顯離峰</td>
          <td>穩定、24/7 持續運行</td>
      </tr>
      <tr>
          <td>GPU 需求</td>
          <td>不需要</td>
          <td>需要（Fargate 不支援 GPU）</td>
      </tr>
      <tr>
          <td>啟動速度</td>
          <td>可接受 cold start</td>
          <td>需要 &lt;1s 啟動（EC2 instance 已在線）</td>
      </tr>
  </tbody>
</table>
<p>混合部署是常見的中間路線：基線容量用 EC2（成本低、啟動快），尖峰彈性用 Fargate Spot（按需、不需預留）。這需要同時維護兩種 capacity provider，複雜度較高。</p>
<h2 id="成本監控">成本監控</h2>
<p>把 ECS 的成本歸因到服務層級需要兩個機制：task 層的 tag propagation 和 Cost Explorer 的 tag 維度。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_ecs_service&#34; &#34;api&#34;</span> {<span class="c1">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">  # ...
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span><span class="n">  propagate_tags</span> <span class="o">=</span> <span class="s2">&#34;SERVICE&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  tags</span> <span class="o">=</span> {
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">    service</span>     <span class="o">=</span> <span class="s2">&#34;payment-api&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    env</span>         <span class="o">=</span> <span class="s2">&#34;prod&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">    cost-center</span> <span class="o">=</span> <span class="s2">&#34;cc-payments&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  }
</span></span><span class="line"><span class="ln">10</span><span class="cl">}</span></span></code></pre></div><p><code>propagate_tags = &quot;SERVICE&quot;</code> 讓 service 的 tag 自動傳播到每個 task，Cost Explorer 就能按 <code>service</code> 或 <code>cost-center</code> 維度拆分 Fargate 費用。這跟<a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣</a>的 tagging 規範對齊 — tag 是成本可見性的地基。</p>
<p>定期（月初或月中）檢查 Cost Explorer 的 Fargate 費用趨勢：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">aws ce get-cost-and-usage <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --time-period <span class="nv">Start</span><span class="o">=</span>2026-06-01,End<span class="o">=</span>2026-06-26 <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --granularity DAILY <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --metrics <span class="s2">&#34;UnblendedCost&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --group-by <span class="nv">Type</span><span class="o">=</span>TAG,Key<span class="o">=</span>service <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --filter <span class="s1">&#39;{&#34;Dimensions&#34;:{&#34;Key&#34;:&#34;SERVICE&#34;,&#34;Values&#34;:[&#34;Amazon Elastic Container Service&#34;]}}&#39;</span></span></span></code></pre></div><p>費用突然跳升時，先看是 task 數增加（auto-scaling 觸發）還是單價變化（Savings Plans 過期或 Spot 中斷後自動回補為 on-demand）。這兩者的處理方式不同：前者檢查 scaling policy、後者檢查 Savings Plans 到期日和 Spot 回收頻率。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/05-core-services/compute-ecs-eks/" data-link-title="運算平台上 IaC — ECS 與 EKS" data-link-desc="容器運算平台的 IaC 描述：ECS 與 EKS 選型、task definition 與映像版本解耦、IAM task role 分離、auto-scaling 策略">運算平台上 IaC</a>：ECS vs EKS 選型、Fargate 的定位</li>
<li>→ <a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣</a>：tagging 與成本可見性的地基</li>
<li>→ <a href="/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">devops 模組八：成本管理</a>：運行期的 RI / Spot / rightsizing 策略</li>
</ul>
]]></content:encoded></item><item><title>ECS</title><link>https://tarrragon.github.io/blog/infra/knowledge-cards/ecs/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/knowledge-cards/ecs/</guid><description>&lt;p>ECS（Elastic Container Service）的核心職責是把容器映像排程到運算資源上執行，並管理它們的生命週期 — 健康檢查、失敗重啟、滾動更新。它是 AWS 上容器工作負載的預設起點，心智負擔低於 Kubernetes（EKS），但編排彈性也較受限。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>ECS 在核心服務層裡的角色是「應用程式的執行載體」。它跑在 VPC 的 private subnet 裡，用 IAM task role 存取其他 AWS 資源，前面掛 ALB 接收流量。IaC 描述 ECS 時，重點在「接線」（subnet、security group、IAM role、target group）而非容器映像版本 — 映像版本由 CI/CD 在部署期注入。&lt;/p>
&lt;p>ECS 的執行模式分 EC2 launch type（自己管運算實例、要管 AMI 更新與 capacity provider）和 Fargate launch type（AWS 代管運算、不需管實例）。Fargate 進一步降低運維面，代價是單位成本較高（同規格約多 20-40%）且不支援 GPU workload。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下狀況指向 ECS 相關問題：&lt;/p>
&lt;ul>
&lt;li>Task 頻繁被 kill 後重啟 — 健康檢查失敗或 OOM，先看 task 的 stopped reason 和 CloudWatch log&lt;/li>
&lt;li>部署後新版本遲遲不上線 — rolling update 的 minimum healthy percent 設太高，新 task 啟動空間不足&lt;/li>
&lt;li>Task 無法拉到 ECR image — 通常是 private subnet 沒有 NAT 或 VPC Endpoint 到 ECR&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>使用 ECS 時要決定：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Launch type&lt;/strong>：Fargate（低運維、較高成本）還是 EC2（低成本、要管實例）。多數 web API 的初始選擇是 Fargate，流量穩定後再評估 EC2&lt;/li>
&lt;li>&lt;strong>Task IAM role&lt;/strong>：task execution role（拉 image 和寫 log 用）和 task role（應用程式存取其他 AWS 資源用）是兩個不同的 role，不要混用&lt;/li>
&lt;li>&lt;strong>映像版本解耦&lt;/strong>：task definition 裡的 image tag 由 CI/CD 部署期注入，infra code 不寫死版本號&lt;/li>
&lt;li>&lt;strong>Auto-scaling 指標&lt;/strong>：用 CPU / memory 還是 ALB request count，取決於服務是計算密集還是 IO 密集&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — ECS task 跑在 private subnet 裡&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — ECS service 套用 security group 控制入站&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM&lt;/a> — task role 與 execution role 是 ECS 的兩個身分接線&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB&lt;/a> — 流量透過 ALB target group 導入 ECS task&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>ECS（Elastic Container Service）的核心職責是把容器映像排程到運算資源上執行，並管理它們的生命週期 — 健康檢查、失敗重啟、滾動更新。它是 AWS 上容器工作負載的預設起點，心智負擔低於 Kubernetes（EKS），但編排彈性也較受限。</p>
<h2 id="概念位置">概念位置</h2>
<p>ECS 在核心服務層裡的角色是「應用程式的執行載體」。它跑在 VPC 的 private subnet 裡，用 IAM task role 存取其他 AWS 資源，前面掛 ALB 接收流量。IaC 描述 ECS 時，重點在「接線」（subnet、security group、IAM role、target group）而非容器映像版本 — 映像版本由 CI/CD 在部署期注入。</p>
<p>ECS 的執行模式分 EC2 launch type（自己管運算實例、要管 AMI 更新與 capacity provider）和 Fargate launch type（AWS 代管運算、不需管實例）。Fargate 進一步降低運維面，代價是單位成本較高（同規格約多 20-40%）且不支援 GPU workload。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下狀況指向 ECS 相關問題：</p>
<ul>
<li>Task 頻繁被 kill 後重啟 — 健康檢查失敗或 OOM，先看 task 的 stopped reason 和 CloudWatch log</li>
<li>部署後新版本遲遲不上線 — rolling update 的 minimum healthy percent 設太高，新 task 啟動空間不足</li>
<li>Task 無法拉到 ECR image — 通常是 private subnet 沒有 NAT 或 VPC Endpoint 到 ECR</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>使用 ECS 時要決定：</p>
<ul>
<li><strong>Launch type</strong>：Fargate（低運維、較高成本）還是 EC2（低成本、要管實例）。多數 web API 的初始選擇是 Fargate，流量穩定後再評估 EC2</li>
<li><strong>Task IAM role</strong>：task execution role（拉 image 和寫 log 用）和 task role（應用程式存取其他 AWS 資源用）是兩個不同的 role，不要混用</li>
<li><strong>映像版本解耦</strong>：task definition 裡的 image tag 由 CI/CD 部署期注入，infra code 不寫死版本號</li>
<li><strong>Auto-scaling 指標</strong>：用 CPU / memory 還是 ALB request count，取決於服務是計算密集還是 IO 密集</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — ECS task 跑在 private subnet 裡</li>
<li><a href="/blog/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — ECS service 套用 security group 控制入站</li>
<li><a href="/blog/infra/knowledge-cards/iam/" data-link-title="IAM（Identity and Access Management）" data-link-desc="雲端平台的授權系統，回答「某個身分能不能對某個資源做某件事」">IAM</a> — task role 與 execution role 是 ECS 的兩個身分接線</li>
<li><a href="/blog/infra/knowledge-cards/alb/" data-link-title="ALB" data-link-desc="Application Load Balancer — 流量進入系統的第一站，負責 listener 路由、健康檢查與 TLS 終結">ALB</a> — 流量透過 ALB target group 導入 ECS task</li>
</ul>
]]></content:encoded></item></channel></rss>