background image

Sep 03 2020

AWS ECS Preparing Release 紀錄

寫在前面

我們在aws 做Production deploy的時候,都會有一個pre production的環境,這個環境主要是為了在部署流程結束後可以做概念性驗證的環境 (主要測試:db connection是否正常、網路連線、裝機腳本等等)另一個方面可以預熱application,不過我們開始有越來越多採用docker的 application並且使用ECS的部署模式,但是ECS的Service建立後就無法修改Target Group,因此沒辦法如同EC2的部署模式只在最後切換Target Group

構思&實作

一開始其實想得很簡單,就是每次部署時都要產生新的 Target group 與新的ECS Service, 將新產生的Target Group掛載到ELB的preparing規則上,然後驗證完畢後就把這組Target Group掛載到ELB的 public規則上就完成了一次的部署

但...事件總是沒想像中的美好,原先預計兩週內可以完成的項目變到了三週(其實中間也有對自己的要求增加ex: script可以重跑、自動移除舊版本等等)

Deploy Configuration

這個是為了減少使用者輸入資訊所做的一個設定,內容主要如下

Step1. 建立Target Group

在建立Target Group其實沒什麼坑主要踩到的就是Target group的名字不能超過32個字所以在命名規劃上需要思考一下該如何呈現

我的命名規則是:環境-服務名稱-版本號

這一步執行的結果需要將內容記錄下來,在修改ELB的rule時會需要用到

Step2. 連結ELB 的規則

這一步在ansible中這定較為複雜所以改用了aws cli來處理,如果你想用純的ansible的方式處理,可以參考 這篇(elb_application_lb)

ansible的回傳都是陣列的形式所以在存取result的時候需要取得第一筆資料(如果你建立了多個target group 就可以用loop來處理)

Step3. 註冊新的Task Definition

這一步驟來說應該是最複雜的一部分,我的Task Definition是由專案中的一個Configuration檔案設定 在CI建置完成後上傳到artifact server,在執行部屬時用ansible下載到Deploy server上在讀取到 ansible的變數中。

在專案中的Configuration不是一整份的設定值,他只記錄了一部分的資訊(ex: cpu, memory, family等) 主要的原因是有些資料需要在deploy時才能決定(ex: image version, env...), 所以我會在註冊task definition前先透過configuration建立一份完整的task definition, 再透過aws cli來註冊新的task definition

這邊你會有個疑問,為何不直接採用 ansible module 呢?主要原因是之前我們就有ecs deploy的CD流程,當時候有些參數我們需要但ansible無法支援 所以轉用aws cli的方式進行。

Step4. 新建ECS Service

這一步我依舊採用aws cli來建立service,主要的原因是我的service通常會由兩個target group 指向兩個不同的ELB與domain,在ansible官方網站上並沒有太多的說明與所需要的參數,因此我轉用了 aws cli,如同step3我會先將所需要的設定在一個ansible task上做好 產生了service_setting的參數 在執行cli時將參數轉換成JSON代入

切換Service Version

切換服務版本這是一個比較大的工程,剛才的部署的複雜度更高一些,主要概念就是將新產生的target group 掛載到真正線上服務的ELB上,不過呢這件事情衍生了許多細細小小的項目要處理的細節也比較多

Step1. 確認Target group health count

要切換前一定要先檢查Target group的target狀態,如果沒確認切換了一個還在做health check的target group就會發生線上可能當下沒有機器服務的窘境,所以第一步肯定就是確認health count

當如果target的health count沒有達到要求數量我就讓playbook失敗,之前在ec2的做法是要完全healthy 才能夠往下運行但這樣會遇到當下要switch會無法switch,所以這次改變一下做法讓target的health count 只要大於要求數量即可

Step2. 取得public當前的target group

為什麼要取得當前的target group?主要原因是怕瞬斷的情況發生,所以在切換的時候會有一個時間區間同時 有兩個版本在運行最後再將舊版本移除

取得ELB中的rule資料ansible並沒有太好的方式處理,所以這道題還是只能靠aws cli來協助了,aws cli 只需要輸入public rule的arn就可以取得了 詳細可以看aws cli 的官方文件

接下來這個就需要比較耐心地來處理了,因為ansible shell的回傳result會有個stdout屬性,這就是aws cli最後回傳的資料,所以我們要解析這個 資訊,然後ansible 的json_query是用這個來實作的,所以我們可以透過這個先取得我們想要的結果,當然他會有一個target group arn資料 不過我們後面需要一些壓在target group tag上的資料,所以我在這邊把他先取回來

Step3. 把Prepare的target group跟public的整合在一起

在ansible還時沒有一個簡單的設定,去修改ELB rule的target group連結,所以還是得透過aws cli來作了

下面的ansible tasks我將它設定成一個role,主要原因是我有兩個target group在處理, set_fact去跑loop的情況會比較複雜所以我設計成一個role,在playbook上採用loop 來執行這個role,如此一來邏輯比較清晰也比較好維護

如此一來在public 的規則上就有兩個target group的容器在服務了,這樣的情況我會讓他維持約1分鐘,再將原先的target group移除

Step4. 處置舊版本的服務

移轉成功後需要把舊版本的service關機或是移除,我的選擇是暫時關閉

關閉服務聽起來很簡單,但我用aws cli執行將需求值改成0,但....一點效果都沒有!!

還記得一開始設定的autoscaling嗎....沒錯就是他!當你把desired改成0,會跟autoscaling設定的發生衝突所以變得無效

在調整desired改成0前,要把autoscaling設定移除才能順利的關閉service將cluster的資源釋出!

Step5. 移除

這是整個部署流程的尾聲了(或是你可以選擇不做....)

我的做法是先將整個ecs cluster的service列出來,然後取得target group資料,再由target group資料取得tag是否含有obsolete的tag

Step6. 標記

部署的最後一步,標記已經被關閉的服務變成obsolete

這是為了下次部署可以把這些服務刪除,如果前一個步驟不做的話,這個步驟也是可以省略的。

文章標籤