

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 最佳化 Amazon ECS 服務自動擴展
<a name="capacity-autoscaling-best-practice"></a>

Amazon ECS 服務是受管任務集合。每個服務都有相關聯的任務定義、所需任務計數以及選用的置放策略。

Amazon ECS 服務自動擴展功能透過應用程式自動擴展服務實現。應用程式自動擴展服務使用 CloudWatch 指標作為擴展指標的來源。該服務也會使用 CloudWatch 警示來設定關於何時縮減或橫向擴充服務的閾值。

您需提供擴展閾值。您可以設定稱為*目標追蹤擴展*的指標目標。您也可以指定稱為*步進擴展*的閾值。

設定應用程式自動擴展之後，該服務會持續為應用程式計算最適的任務數量目標值。當需要透過橫向擴充或縮減來調整任務數量時，該服務也會即時通知 Amazon ECS 進行相應變更。

若要有效使用服務自動擴展功能，您必須選擇適當的擴展指標。我們將在以下章節中探討如何選擇指標。

## 描述應用程式特性
<a name="capacity-autoscaling-app"></a>

若要適當地擴展應用程式，您必須了解縮減與橫向擴充應用程式的相關條件。

本質上，如果預測需求會超出容量，則應橫向擴充應用程式。相反地，如果資源超過需求，則應縮減應用程式以節省成本。

### 識別使用率指標
<a name="capacity-autoscaling-app-utilizationmetric"></a>

若要有效進行擴展，您必須識別能表示使用率或飽和度的指標。此指標需具備以下特性，才能有效用於擴展操作。
+ 該指標必須與需求相關聯。當資源保持穩定但需求變更時，指標值也必須隨之變化。需求增加或減少時，指標應隨之上升或下降。
+ 指標數值必須與容量成比例縮減。當需求保持不變時，增加更多資源必須使指標數值產生相應比例的變化。例如，將任務數量加倍，應使指標降低 50%。

識別使用率指標的最佳方法是透過生產前環境 (例如預備環境) 中的負載測試進行識別。市面上有許多商業與開源的負載測試解決方案可供使用。這類解決方案通常既能產生模擬負載，也能模擬真實使用者流量。

開始進行負載測試時，您應先為應用程式的使用率指標建置儀表板。這些指標包括 CPU 使用率、記憶體使用率、I/O 操作、I/O 佇列深度與網路輸送量。您可以透過 CloudWatch Container Insights 等服務收集這些指標。您也可以搭配使用 Amazon Managed Service for Prometheus 與 Amazon Managed Grafana 來收集這些指標。在此過程中，請務必收集並繪製應用程式的回應時間或工作完成率等指標。

進行負載測試時，請從較低的請求量或任務插入率開始。維持此速率數分鐘，讓應用程式完成暖機。接著，緩慢提高速率並穩定維持幾分鐘。重複此循環，逐步提高速率，直至應用程式的回應時間或完成時間過慢，無法達到服務水準目標 (SLO) 為止。

進行負載測試時，請檢查每個使用率指標。隨負載增加而上升的指標，是作為最佳使用率指標的首要候選對象。

然後，識別達到飽和的資源。同時，也請檢查使用率指標，觀察哪一項會率先在高位趨於平緩。或者，觀察哪項指標會最先達到峰值，而後導致應用程式當機。例如，若 CPU 使用率在您新增負載時從 0% 增加至 70-80%，而在您新增更多負載之後，會保持在該層級，則表示 CPU 已飽和。根據 CPU 架構，該使用率可能永遠不會達到 100%。例如，假設記憶體使用率隨著新增負載而增加，然後應用程式在達到任務或 Amazon EC2 執行個體記憶體限制時突然當機。這種情況通常意味著記憶體資源已完全耗盡。應用程式可能會消耗多種資源。因此，請選擇代表先耗盡之資源的指標。

最後，將任務數量或 Amazon EC2 執行個體數量加倍後，再次進行負載測試。此時假設關鍵指標的增長率或下降率為先前的一半。若情況確實如此，則表示該指標與容量成比例。這正是適用於自動擴展的良好使用率指標。

現在請考量下列假設案例。假設對應用程式進行負載測試，發現 CPU 使用率最終在每秒 100 個請求的負載下達到 80%。當您新增更多負載時，CPU 使用率不會再增加。不過，這樣確實會讓應用程式回應速度變慢。然後，再次執行負載測試，將任務數量加倍，但將速率保持在先前的峰值。如果發現平均 CPU 使用率降至約 40%，則平均 CPU 使用率是適合作為擴展指標的良好候選對象。另一方面，如果增加任務數量後，CPU 使用率仍維持在 80%，則平均 CPU 使用率並不適合作為擴展指標。此時便需進一步探尋合適的監控指標。

### 常見應用模型與擴展屬性
<a name="capacity-autoscaling-app-common"></a>

您可以在 AWS上執行各種軟體。許多工作負載是企業自行開發的，而其他工作負載則是以熱門的開放原始碼軟體為基礎。無論其來源為何。我們觀察到服務存在一些常見的設計模式。如何有效擴展，很大程度上取決於所採用的模式。

#### 高效的 CPU 密集型伺服器
<a name="capacity-autoscaling-app-common-cpu"></a>

高效的 CPU 密集型伺服器幾乎不使用 CPU 與網路輸送量以外的資源。每個請求均可由應用程式單獨處理。請求不依賴其他服務，例如資料庫。應用程式可以處理數十萬個並行請求，並可高效利用多顆 CPU 來達成此目標。每個請求要麼由記憶體負荷低的專用執行緒處理，要麼由每個 CPU 上執行的非同步事件迴圈處理。應用程式的每個副本都具備同等的請求處理能力。在 CPU 耗盡前，唯一可能耗盡的資源是網路頻寬。在 CPU 密集型服務中，即使處於峰值輸送量，記憶體使用率也僅佔可用資源的一小部分。

對此類應用程式，您可以使用 CPU 型自動擴展。這類應用程式在擴展面向享有最大的彈性。您可以透過提供更大的 Amazon EC2 執行個體或 Fargate vCPU 來進行垂直擴展，也可以透過新增副本來進行水平擴展。無論是新增副本數量，還是將執行個體大小加倍，都會將相對於容量的平均 CPU 使用率減半。

如果為此應用程式使用 Amazon EC2 容量，建議將其置放於運算最佳化執行個體上，例如 `c5` 或 `c6g` 系列。

#### 高效率的記憶體密集型伺服器
<a name="capacity-autoscaling-app-common-memory"></a>

高效率的記憶體密集型伺服器會為每個請求配置大量記憶體。在最大並行數下，但未必是最大輸送量時，記憶體會先於 CPU 資源耗盡。與請求相關聯的記憶體會在請求結束時釋放。只要有可用記憶體，就能接受更多請求。

對此類應用程式，您可以使用記憶體型自動擴展。這類應用程式在擴展面向享有最大的彈性。您可以透過提供更大的 Amazon EC2 或 Fargate 記憶體資源來進行垂直擴展，也可以透過新增副本來進行水平擴展。無論是新增副本數量，還是將執行個體大小加倍，都會將相對於容量的平均記憶體使用率減半。

如果為此應用程式使用 Amazon EC2 容量，建議將其置放於記憶體最佳化的執行個體上，例如 `r5` 或 `r6g` 系列。

有些記憶體密集型應用程式不會在請求結束時釋放與之相關聯的記憶體，因此並行量減少時，已使用的記憶體也不會相應減少。對此，我們不建議使用記憶體型擴展方式。

#### 工作線程型伺服器
<a name="capacity-autoscaling-app-common-worker"></a>

工作線程型伺服器會透過個別的工作執行緒逐一處理請求。工作執行緒可以是輕量型執行緒 (例如 POSIX 執行緒)，也可以是重量型執行緒 (例如 UNIX 程序)。無論是哪種類型的執行緒，應用程式所能支援的最大並行數都存在上限。通常，並行上限會依據可用的記憶體資源按比例設定。若達到並行上限，應用程式會將額外請求放入待處理佇列。若待處理佇列溢滿，應用程式會立即拒絕後續傳入的請求。符合此模式的常見應用程式包括 Apache 網頁伺服器與 Gunicorn。

請求並行數通常是此類應用程式最適合的擴展指標。由於每個副本都有並行上限，因此在達到平均上限前進行橫向擴充至關重要。

取得請求並行數指標的最佳方式，是讓應用程式將資料回傳至 CloudWatch。應用程式的每個副本都可以高頻率發布並行請求數作為自訂指標。建議頻率至少設定為每分鐘一次。收集多次回傳資料之後，即可將平均並行數作為擴展指標。此指標的計算方式是將總並行數量除以副本數量。例如，若並行總數為 1000，而副本數量為 10，則平均並行數為 100。

如果應用程式位於 Application Load Balancer 後端，您也可以使用負載平衡器的 `ActiveConnectionCount` 指標作為擴展指標的因素。必須將 `ActiveConnectionCount` 指標除以副本數量取得平均值。再使用該平均值進行擴展，而非直接採用原始計數值。

若要使此設計達到最佳效果，回應延遲的標準偏差應該在低請求速率時保持較小。建議在低需求期間，大多數請求能在短時間內得到回應，且不會有大量請求的回應時間顯著超過平均值。平均回應時間應接近第 95 百分位數回應時間。若未達此標準，可能引發佇列溢滿，進而觸發錯誤。建議在必要時提供額外副本，降低溢滿風險。

#### 等候型伺服器
<a name="capacity-autoscaling-app-common-waiting"></a>

等候型伺服器會為每個請求執行部分處理，但高度依賴一或多個下游服務才能運作。容器應用程式通常會大量使用資料庫與其他 API 服務等下游服務。這些服務可能需要一段時間才能回應，尤其在高容量或高並行場景中更為明顯。這是因為這類應用程式往往使用較少的 CPU 資源，其並行上限主要取決於可用記憶體容量。

等候型服務可能適用於記憶體密集型伺服器模式或工作線程型伺服器模式，具體取決於應用程式的設計方式。如果應用程式的並行數僅受記憶體限制，則應使用平均記憶體使用率作為擴展指標。如果應用程式的並行數基於工作線程限制，則應使用平均並行數作為擴展指標。

#### Java 型伺服器
<a name="capacity-autoscaling-app-common-java"></a>

如果 Java 型伺服器是 CPU 密集型伺服器，且能隨 CPU 資源成比例擴展，則可能適用於高效 CPU 密集型伺服器模式。在此情況下，平均 CPU 使用率可能適合作為擴展指標。然而，許多 Java 應用程式並非 CPU 密集型，這使得擴展難以進行。

為取得最佳效能，建議將盡可能多的記憶體配置給 Java 虛擬機器 (JVM) 堆積。JVM 的最新版本 (包括 Java 8 Update 191 或更新版本)，皆會自動將堆積大小設定為容器內可容納的最大值。這表示在 Java 中，記憶體使用率鮮少與應用程式使用率成正比。隨著請求速率和並行數量增加，記憶體使用率仍保持不變。因此，我們不建議根據記憶體使用率擴展 Java 型伺服器。反之，我們通常會建議基於 CPU 使用率進行擴展。

在某些情況下，Java 型伺服器會在 CPU 耗盡前先遭遇堆積記憶體耗盡的問題。如果應用程式在高並行時容易發生堆記憶體耗盡，則平均連接數是最佳擴展指標。如果應用程式在高輸送量時容易發生堆積記憶體耗盡，則平均請求速率是最佳擴展指標。

#### 使用其他具垃圾回收執行時期的伺服器
<a name="capacity-autoscaling-app-common-garbage"></a>

許多伺服器應用程式基於具備垃圾回收功能的執行時期 (例如 .NET 和 Ruby) 開發。這些伺服器應用程式可能符合前文描述的某種模式。然而，與 Java 類似，我們不建議依據記憶體來擴展這些應用程式，因為觀測到的平均記憶體使用率通常與輸送量或並行量不相關。

對這些應用程式而言，若屬於 CPU 密集型，建議基於 CPU 使用率進行擴展。否則，建議根據負載測試結果，基於平均輸送量或平均並行數進行擴展。

#### 任務處理器
<a name="capacity-autoscaling-app-common-job"></a>

許多工作負載涉及非同步任務處理。其中包括並非即時接收請求，而是透過訂閱工作佇列接收任務的應用程式。對此類應用程式而言，合適的擴展指標幾乎都是佇列深度。佇列成長表明待處理工作超過了處理能力，而空佇列表示處理能力多於實際工作需求。

AWS 訊息服務，例如 Amazon SQS 和 Amazon Kinesis Data Streams，提供可用於擴展的 CloudWatch 指標。對於 Amazon SQS，最佳指標為 `ApproximateNumberOfMessagesVisible`。對於 Kinesis Data Streams，建議使用 Kinesis Client Library (KCL) 發布的 `MillisBehindLatest` 指標。使用此指標進行擴展前，應先計算所有消費者的平均值。