綜合前兩天的基本 ECS service 跟 load balancing,我們要來幫 ECS service 加上 load balancing 了!(本日程式碼)
第一件要做的事情是調整 task definition 的 container port mapping。前面有說到 task definition 不能修改、只能建立新版本,所以我們從右上角 create new revision:
記得前面 Day 8 我們建立 task definition 的時候是讓 container 的 port 80 對應到 host 的 port 80 嗎?(不記得的話回去翻一下)
這樣的 mapping 表示一台 host(即 container instance)上只能開一個 container,因為 host 的 port 80 被佔走啦~第二台 container 要再做 port mapping 的時候就會失敗,也就開不起來。欸不是,一個 container instance 只能開一個 container 是在搞笑嗎?這樣即使用大台機器也沒用啊~也不需要 container 了吧…… (開始思考人生意義)
ECS 當然沒那麼難用,除了原本的 static port mapping 外,它還有另一種 port mapping 稱為 dynamic port mapping。我們現在就是要調整 task definition 裡的 container port mapping,從 static port mapping 改成 dynamic port mapping。
static port mapping 就是原本直接指定 host 的某個 port,讓 container port 對應到那個 host port。dynamic port mapping 則是不指定 host port(或者給 0),container 會自動得到一個 ephemeral port 範圍 內的 host port 來做 mapping。
其他設定不變,Create revision。
要使用 ALB 得重新建立一個 ECS service,無法沿用前面的 service(筆者原本以為可以…QQ)。先前建立的 service 可以到 CloudFormation 將對應的 stack 砍掉就能刪除了。
小提示:如果想避免建立 ECS service 因為 deploy 失敗而整個 rollback 或 deploy 到天荒地老 cloudformation stack 一直卡在 creating 都不知道是怎樣,可以先把 desired tasks 設成 0 讓 service 順利建立,之後再改 desired task 來進行 deployment。最常遇到的狀況是我們 mysql server 開在另一台 EC2 instance 上,為了省錢(O)會把它關掉,重新打開後 public IP address 改變,這時候需要修改 Laravel env 的設定並且再 deploy 一次 docker image。
建立 ECS service 的設定大部分跟前面一樣,差別在 Load balancing 這塊,只要選擇昨天建立的 ALB、target group 跟要做 load balancing 的 container 即可~
如果 create ECS service 把 desired task count 設成 0,可以從右上角 update service 進去將 desired task count 調成 1。
接著到 Deployments tab 觀察 deploy 的狀況:
task 啟動後,ecs service 會向 ALB 的 target group 註冊一個 target,可以點進 target group 看看它各方面的狀態。現在就能看到底下 Registered target 有東西了,可以看到 instance 的 port 是 32781、不是 port 80:
各位可以試試重新 deploy 一次,看看 target 的 port 有什麼改變(它應該要是另一個亂數 port number)。要重新 deploy 可以從 ECS service 的 update service 裡勾選 Force new deployment 後 update 來 trigger:
ECS service 的 deployment 只要出現 reached steady state 就表示穩定了。先檢查一個 task 的狀況下,application 是否有正常執行。進到剛建立的 ALB:
複製它的 DNS name,貼到瀏覽器上看看:
太好了又是這個熟悉的畫面!
再來我們要讓 ECS service 可以跑多個 task,這很簡單,只要更新 service 的 desired tasks 數量即可,我們更新到… 3 好了。
deploy 中…
欸?怎麼停了?原來是 container instance 的 resource 不夠,那…回到 2 好了,先確認多個 task 的時候能夠正常運作。
OK 正常!一樣用瀏覽器打開 ALB DNS name 來驗證~
網頁應該要能正常開啟~(一模一樣就不貼圖了…)
既然有兩個 container,當然要確認一下 request 是不是真的會給不同的 container 處理再回傳給 client,我們利用 ECS container metadata file 內的資訊確認!
依照 文件 說明,要打開 ECS container metadata file 功能,要設定 ECS_ENABLE_CONTAINER_METADATA
container agent variable 為 true
,我們可以把 container instance 的 launch template 的 user data 改成如下:
#!/bin/bash
cat <<'EOF' >> /etc/ecs/ecs.config
ECS_CLUSTER=my-app
ECS_ENABLE_CONTAINER_METADATA=true
EOF
這個 script 跑完就是在 container instance 的 /etc/ecs/ecs.config
多加一行 ECS_ENABLE_CONTAINER_METADATA=true
。
跟 task definition 一樣,修改 launch template 是要建立新版本、不能直接更新原有的 template:
到最底下 Advanced details 的 user data 把新的 script 貼上去後按右邊的 Create template version。
筆者通常習慣把最新的 launch template version 設成 default,並且讓 auto scaling group 使用 default version 的 launch template。或者直接讓 auto scaling group 使用最新(latest)版本的 launch template。
auto scaling group 設定以 default version 的 launch template 啟動 EC2 instance:
設定完會看到 auto scaling group 的 launch template version 長這樣:
container 內的 metadata file 位置在 /opt/ecs/metadata/random_ID/ecs-container-metadata.json
,其中 random_ID
是個亂數,為了方便使用,metadata file 的位置會記錄在環境變數 ECS_CONTAINER_METADATA_FILE
。所以我們在 Laravel 加個 HelloController
,它會讀取環境變數 ECS_CONTAINER_METADATA_FILE
所指的檔案,parse 其中的 josn 後回傳 container id。
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\File;
class HelloController extends Controller
{
public function showContainer()
{
$content = json_decode(File::get($_ENV['ECS_CONTAINER_METADATA_FILE']), true);
return $content['ContainerID'];
}
}
這是個簡單的 API controller,我們到 routes/api.php
加入它的 route:
Route::get('/container', [HelloController::class, 'showContainer']);
最後用 ALB DNS name 連到 /api/container
,重新載入頁面幾次應該可以看到兩個 container id 輪番出現:
container id 就是 docker 的 container id,從 ECS task 的詳細頁面也可以看到:
現在的架構長這樣:
ALB 可以把流量導到兩個 public subnet,在 ap-northeast-1a
的 subnet 裡有一台 container instance,它身上分別用不同 port 跑著兩個 container。request 從 internet 進到 ALB,經過 Listener 的判斷後導向 target group,target group 會再將 request 丟給 EC2 instance 的某個 port(也就是 container)來處理。