我們已經蓋好了標準化的廚房 (IaC),也設計好了標準化的「半成品料理包」 (Docker Image)。現在,我們需要一個智能、高效的「總廚系統」來管理整個後場的運作。這個系統需要知道:
這個「總廚系統」,就是 Amazon ECS (Elastic Container Service)
。
要理解 ECS,我們要先認識它的 4 個核心組件: ECS Cluster (叢集) - 「廚房本身」
、 Task Definition (任務定義) - 「標準作業程序 (SOP) 卡」
、 Task (任務) - 「大火快炒的爐位」
、 Service (服務) - 「不知疲倦的總廚」
。
核心概念:一個邏輯上的分組,用來容納我們的所有容器化應用。我們可以把它想像成 Dev Kitchen Worktop
, Staging Kitchen Worktop
, Prod Kitchen Worktop
,就像是跟其他人說我的廚房工作區域有幾坪一樣,尚未涉及到實際裡面的布置,它本身不花錢,只是一個管理的範圍邊界。
重點: 叢集需要有「算力」來運作,就像廚房需要有爐子。算力來源有兩種模式:
概念: 這是 ECS 最核心的藍圖。它是一份 JSON 格式的說明書,詳細定義了「如何烹飪一道菜」。
重點:就像是一張給廚師看的傻瓜式 SOP 卡,上面精確地寫著:
desiredCount: { N }
:「我希望永遠有 { N }
份『法式紅酒燉牛肉』在火上烹飪,不多也不少。」Health Check
,總廚會不斷巡視所有爐子是不是正常的。如果他發現其中一份因為爐子故障了(HC fail
),他會立刻把這份丟掉,並馬上開一個新的爐子上,確保開火中的爐口的數量永遠是 3。實作範例
{
"family": "app-service",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::account:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::account:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "app",
"image": "your-registry/app:${ENVIRONMENT}",
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{
"name": "NODE_ENV",
"value": "${ENVIRONMENT}"
}
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:${ENVIRONMENT}/database-url"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/${ENVIRONMENT}/app",
"awslogs-region": "us-west-2",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
簡單說明了 ECS 的意涵之後,那我們的 Dev Kitchen Worktop
, Staging Kitchen Worktop
, Prod Kitchen Worktop
策略在 ECS 層級如何體現?
叢集隔離: 遵循多帳號策略,我們會在 Account-Dev
裡建立 dev-cluster
,在 Account-Prod
裡建立 prod-cluster
。這是最徹底的隔離。
SOP 卡 (Task Definition) 的管理: 我們依然使用 IaC (Terraform) 來管理任務定義。我們會有一份共用的 task-definition.tf
模板,然後為不同環境傳入不同的參數。
dev.tfvars
: cpu = 256 (1/4 vCPU)
, memory = 512 (MB)
, API_KEY_SECRET_ARN = "arn:aws:secretsmanager:ap-northeast-1:DEV_ACCOUNT_ID..."
prod.tfvars
: cpu = 1024 (1 vCPU)
, memory = 2048 (MB)
, API_KEY_SECRET_ARN = "arn:aws:secretsmanager:ap-northeast-1:PROD_ACCOUNT_ID..."
注意囉,我們 image
的參數會指向 同一個 Docker Image ,但注入的 CPU
、 記憶體
和 Secrets
依舊來自不同的環境配置,維持了我們的 一致性
與 不變性
。
總廚指令 (Service) 的管理: 同樣用 IaC 定義。
dev
環境的 Service,desiredCount 可能只是 1
,而且可能不設定自動擴縮,以節省成本。prod
環境的 Service,desiredCount 可能是 3
起跳,並且會配置接下來要講的自動擴縮策略。我們的餐廳不能只靠固定 3 位廚師。有時候是有規律的,像是週二下午可能一位就夠了,但週五晚上可能需要 10 位才能應付源源不絕的訂單這種固定的規律模型,有時又會突然人潮爆量到需要 20 位廚師來 handle。
這時候手動去增減廚師數量太慢且不切實際,所以我們需要設置一個 「客流-爐口監控連動系統」( ECS Service Auto Scaling )
,讓我們能根據「客流量」自動增減人手從而最佳化我們的成本考量。
ALBRequestCountPerTarget
指標維持在 500 左右。運作流程大致如下:
desiredCount
(例如從 2->3, 3->5...),ECS 就會啟動新的 Task。新的 Task 自動註冊到 ALB,分攤流量,直到平均請求數降回 500 左右。desiredCount
,ECS 就會優雅地終止掉多餘的 Task,將資源釋放,為我們節省成本。19:00-23:00
這個區建置固定的 Task { N }
數來面對流量,這又能更進一步地減少成本花費 - 監聽也是要錢的。# ecs-auto-scaling.yml
Resources:
ServiceScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MaxCapacity: !Ref MaxCapacity
MinCapacity: !Ref MinCapacity
ResourceId: !Sub "service/${ClusterName}/${ServiceName}"
RoleARN: !Sub "arn:aws:iam::${AWS::AccountId}:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService"
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
ServiceScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub "${ServiceName}-scaling-policy"
PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref ServiceScalingTarget
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
TargetValue: 70.0
人力有時而窮,味蕾也會出現疲乏。有時候要吸引更多的人而開更多的 米其林餐廳 不一定是更好的選擇。或許是口味考量,或許是預算的考量,當我們的服務進行業務邏輯的擴張時,一直在同一家餐廳裡不斷增長新的菜色會讓管理成本與複雜度逐漸增加,同時,也有可能開了一新店後,大家都指點某一個菜品(例如米其林主廚做的起司漢堡與吃完後自動爆炸的餐廳),其他服務根本沒用上 - 這不是廠商的疏失,這無異是成本上的浪費。
如果我們接下來的目標是開一個根據不同客群擁有數十個不同品牌(微服務)、未來還可能把分店開到 Google 或 Microsoft 商場(多雲)的餐飲集團,那麼我們就需要一個業界通用的、標準化的「集團營運系統」。這個系統,就是 Kubernetes (常簡稱為 K8s)。
Amazon EKS (Elastic Kubernetes Service),就是 AWS 提供的、幫我們把這個複雜營運系統中最核心、最燒腦的「集團總部管理室」(控制平面)給託管起來的服務。=
如果說 ECS Cluster 是一間獨立餐廳的後廚,那麼 EKS Cluster 就是整個美食廣場的場地和基礎設施,其中最重要的就是 「美食廣場管理辦公室」(控制平面 - Control Plane) 與 「美食攤位的鋪位」(數據平面 - Data Plane / Worker Nodes)。
控制平面 (Control Plane) - 「美食廣場管理辦公室」
數據平面 (Data Plane) / Worker Nodes - 「美食攤位的鋪位」
m5.large
),但鋪位的電力、網路、消防等都由 AWS 協助管理,例如自動化的節點升級和替換。這是最常見的模式。 - Fargate Profiles: 和 ECS Fargate 模式一樣。我們連鋪位都不用租。我們直接跟管理處說:「我要開一個臨時的章魚燒攤位,需要 1vCPU 和 2GB 記憶體的空間。」EKS 會自動在廣場的某個角落給我們變出一個符合要求的空間,用完就收走。非常適合無狀態應用或需要應對突發流量的場景。多環境策略: 原則不變。我們會在 Account-Dev
裡建立一個 dev-eks-cluster
,在 Account-Prod
裡建立 prod-eks-cluster
。IaC (Terraform) 依然是我們用來標準化定義這些叢集配置的工具。
# eks-cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: app-cluster-${ENVIRONMENT}
region: us-west-2
version: "1.28"
nodeGroups:
- name: worker-nodes
instanceType: ${INSTANCE_TYPE}
minSize: ${MIN_SIZE}
maxSize: ${MAX_SIZE}
desiredCapacity: ${DESIRED_SIZE}
privateNetworking: true
labels:
environment: ${ENVIRONMENT}
tags:
Environment: ${ENVIRONMENT}
Project: "multi-env-demo"
addons:
- name: vpc-cni
- name: coredns
- name: kube-proxy
- name: aws-load-balancer-controller
Kubernetes 的世界裡,描述「如何經營一個攤位」有一套標準的詞彙和文件(YAML 檔)。這比 ECS 的 Task Definition 更為細緻和強大。
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
namespace: ${ENVIRONMENT}
labels:
app: demo-app
environment: ${ENVIRONMENT}
spec:
replicas: ${REPLICA_COUNT}
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
environment: ${ENVIRONMENT}
spec:
containers:
- name: app
image: your-registry/app:${IMAGE_TAG}
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: ${ENVIRONMENT}
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: database-secret
key: url
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: app-service
namespace: ${ENVIRONMENT}
spec:
selector:
app: demo-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
現在,攤位都開好了,內部員工也可以互相點餐了。但最重要的問題:外面的顧客要怎麼走進來,並找到我們的攤位?
一個簡單粗暴的方法是給每個攤位都開一個直通外面的大門 (Service type LoadBalancer)。這會為每個服務都建立一個獨立的 AWS 負載平衡器,在有數十個服務時,成本會非常驚人。
更優雅、更經濟的做法是使用 Ingress
。 Ingress
是管理外部流量進入叢集的 API 物件,它提供基於 HTTP/HTTPS 的路由規則,這就像是整個美食廣場只有一個總入口,而在入口處有一塊巨大的電子導覽看板,上面寫著:
host: food.com, path: /chicken
) -> 請走 B 區 52 號 (導向 chicken-service
)host: food.com, path: /pizza
) -> 請走 C 區 03 號 (導向 pizza-service
)構成我們的大門( Ingress )體系的兩個關鍵組件:
Ingress 的巨大優勢就是我們可以用一個 ALB,來作為數十甚至數百個後端服務的流量入口,透過不同的域名或 URL 路徑,將流量精準分發給對應的 Service。這極大地節省了成本,並簡化了外部流量的管理,基德再也不會找上門了
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: ${ENVIRONMENT}
annotations:
kubernetes.io/ingress.class: "alb"
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/certificate-arn: ${CERTIFICATE_ARN}
alb.ingress.kubernetes.io/ssl-redirect: "443"
spec:
rules:
- host: ${ENVIRONMENT}.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
我們今天(?)從宏觀的叢集搭建,到微觀的應用部署,再到流量的入口管理,完整地走了一遍 Kubernetes 的核心路徑。這套體系雖然複雜,但它正是支撐起當今大規模微服務應用的骨架。
最後的最後我們來簡單比較一下兩者的差別:
ECS 是條理分明的精緻餐廳:
AWS 幫我們定義好了很多規則,我們只需要專注於菜色(我們的應用),整合度高,學習曲線平緩。
EKS/Kubernetes 是生機勃勃的國際美食廣場:
它提供了一套功能極其強大、靈活、且廠商中立的「營運標準」。我們需要學習它的一整套術語和概念(Pod, Deployment, Service, Ingress...),但回報是無與倫比的彈性、擴展性和活躍的生態圈。