持續部署 – 使用 ECS Rolling Update 實現 zero downtime

POSTED BY   Chris
2021 年 7 月 10 日
持續部署 – 使用 ECS Rolling Update 實現 zero downtime

在 container 大行其道的現今,containerized application 已經很常見的應用在開發環境和線上環境之中,而這篇來介紹一下, ECS Cluster 中,使用 ECS Service 搭配 ELB,用 Rolling update 的部署方式來達成不停機的更新,而更靈活的部署方式,如 Blue / Green 、Canary 的部署方式,AWS CodeDeploy 也是有支援的,但不在本篇的範圍之中,首先來介紹一下,何謂 Rolling Update

 

Rolling Update

就字面上的義思是「滾動式的更新」,也就是透過一種機制不斷的替換更新現有的容器,而在這過程中,需要透過 Load Balance (AWS 稱為 ELB,本篇用的的是 ALB) 來做流量的切換,而舊容器替換前需要先把既有的 request 先消化完,稱之為排水(draining),來看一下示意圖

Hint: 點圖可放大

先來看一下參數的定義

  • minimumHealthyPercent: 在部署階段時,最少要保留 ECS Task 狀態為 RUNNING 的百分比數量

  • maximumPercent: 在部署階段時,最大允許 ECS Task 狀態為 RUNNING or PENDING 的百分比數量

這兩個參數的定義,詳細定義可以參考官方文件有詳細說明

desired tasks 是百分比的基準點,以示意圖為側,因為 minimumHealthyPercent 設定 100,故部署過程中 running task 不得小於 2 個,也就是在部署過程中,至少保持 2 台是可以對外服務不中斷

maximumPercent 設定 200,故部署過程中,新開出來的 ECS Task 加上原有的 ECS Task,最多不能大於 4 個

再來比較重要的一點是,Rolling Update 要做到不停機的部署,必需搭配 Load Balance 的 draining 機制,draining 是指舊容器要被刪除前,在 ALB 會先停止讓新的 request 進來,而已經進來的 request 也必需消化完,這兩個動作加起就稱為 drainin, 而 draining 具體上要設定多久,是可以在 ALB 的 Target Group 上設定的,這部分就要看對外的服務中,視實際 request 的最大時間是多久而定

再繼續看一下不同數值設定的示意圖

Hint: 點圖可放大

比較明顯的差異是 minimumHealthyPercent 設定 50,故部署過程中 running task 最少的情況只剩 1 個,而 maximumPercent 設定為 150, 所以部署階段中,最多同時 running task 的數量為 3 台,部署時分兩次階段 draining,才會把整個部署走完

 

觸發部署

一般都是 ECR 推上新 tag 版本後,在 AWS Management Console 中,先 Create New ECS Task Definition,指向新的 ECR tag 版本後,再到 ECS Service 中直接按下 Update,換成新的 Task Definition ,就會直接觸發 Rolling Update 部署,當然這是手動操作 AWS 後台的狀況

而這個手動部署的操作,可以透過 AWS CodePipeline 在做自動化的 CD 的部分,之後會寫一篇來實際說明一下,目前在我的 GitHub lab-ecs-fargate-cd-infra repo 上已經有用 CDK 實現,有興趣的朋友可以先參考一下

 

測試 zero downtime

先準備一個簡單的 API request 程式,以下用 Python3 為範例

import requests
import time
import pathlib

file_path = f"{pathlib.Path(__file__).parents[0]}/requests.txt"


def main(file_path: str):
    with open(file_path, "a+") as file:
        for i in range(240):
            r = requests.get(
                'https://restapi.9incloud.com/hello'
            )
            file.write(f"{i+1}: {r.text}\n")
            print(f"response:{i+1}: {r.text}")
            time.sleep(500/1000)


if __name__ == '__main__':
    main(file_path)

然後不論用 CodePipeline 去自動觸發或是用 AWS Management Console 後台去操作,觸發後就可以 run 這個測試程式,每個 API request 間隔 0.5 秒,持續進行多久可以自己設定,這邊設定 240,大約 2 分鐘的時間做測試,API response 就從部署更新前回傳 It's version: 1It's version: 2,很簡單的一個測試驗證,實際得到結果如下(沒全部列出,從實際發生變化的資料開始)

98: It's version:1
99: It's version:1
100: It's version:2
101: It's version:2
102: It's version:1
103: It's version:1
104: It's version:1
105: It's version:2
106: It's version:1
107: It's version:1
108: It's version:2
109: It's version:2
110: It's version:1
111: It's version:1
112: It's version:1
113: It's version:2
114: It's version:1
115: It's version:1
116: It's version:2
117: It's version:1
118: It's version:2
119: It's version:1
120: It's version:1
121: It's version:1
122: It's version:2
123: It's version:1
124: It's version:1
125: It's version:2
126: It's version:1
127: It's version:1
128: It's version:2
129: It's version:1
130: It's version:2
131: It's version:1
132: It's version:2
133: It's version:1
134: It's version:1
135: It's version:1
136: It's version:2
137: It's version:2
138: It's version:2
139: It's version:1
140: It's version:1
141: It's version:2
142: It's version:2
143: It's version:2
144: It's version:1
145: It's version:1
146: It's version:1
147: It's version:1
148: It's version:2
149: It's version:2
150: It's version:1
151: It's version:1
152: It's version:2
153: It's version:2
154: It's version:2
155: It's version:2
156: It's version:1
157: It's version:1
158: It's version:1
159: It's version:1
160: It's version:2
161: It's version:2
162: It's version:2
163: It's version:2
164: It's version:1
165: It's version:1
166: It's version:2
167: It's version:2
168: It's version:1
169: It's version:2
170: It's version:2
171: It's version:2
172: It's version:2
173: It's version:1
174: It's version:2
175: It's version:1
176: It's version:2
177: It's version:2
178: It's version:1
179: It's version:2
180: It's version:2
181: It's version:1
182: It's version:2
183: It's version:2
184: It's version:2
185: It's version:1
186: It's version:2
187: It's version:2
188: It's version:1
189: It's version:2
190: It's version:1
191: It's version:2
192: It's version:2
193: It's version:2
194: It's version:2
195: It's version:2
196: It's version:2
197: It's version:2
198: It's version:2
199: It's version:2
200: It's version:2
201: It's version:2
202: It's version:2
203: It's version:2
204: It's version:2
205: It's version:2
206: It's version:2
207: It's version:2
208: It's version:2
209: It's version:2
210: It's version:2
211: It's version:2
212: It's version:2
213: It's version:2
214: It's version:2
215: It's version:2
216: It's version:2
217: It's version:2
218: It's version:2
219: It's version:2
220: It's version:2
221: It's version:2
222: It's version:2
223: It's version:2
224: It's version:2
225: It's version:2
226: It's version:2
227: It's version:2
228: It's version:2
229: It's version:2
230: It's version:2
231: It's version:2
232: It's version:2
233: It's version:2
234: It's version:2
235: It's version:2
236: It's version:2
237: It's version:2
238: It's version:2
239: It's version:2
240: It's version:2

從這邊可以發現,在 Rolling Update 階段,新舊版本有一個短暫的時間是併存的,也就是在新增 ECS Task 後,到 Draining 前的這段時間,就會在 version1 和 version2 中互相穿插,因為 ALB 的演算法是用 Round Robin,也就是平均分散給 Target Group 中的 Target

而從 ALB 在 Draining 階段時,待移除的舊 ECS Task 就不會再接新的 request,故此時就會全都是新的 ECS Task 來接 request,所以自然全都是 version2

歡迎留言
0

您可能也想看

Workaround for AWS Grafana alerting
2023 年 8 月 3 日
AWS, DevOps
AWS VPC Endpoint 使用場景
2022 年 3 月 14 日
AWS, CDK, Network
CDK 指定 Physical names 運作方式
2021 年 12 月 4 日
AWS, CDK, Cloudformation