
POV:你把 Day 24 那份 agent-api.yaml 原封不動 kubectl apply 到另一朵雲,Pod 起來了,但 image 拉不下來、Ingress 一直沒有 IP,你開始懷疑「K8s 可攜」是不是一句行銷話。
昨天用對照表講 AKS 跟 GKE 的名詞怎麼對應。今天動手:同一個 agent-api,Day 23 上過 Cloud Run、Day 24 上過 GKE Autopilot,現在搬到 AKS。目的不是比誰好,而是驗證昨天那句話:Deployment 與 Service 的 YAML 一樣,主要會卡的是 registry、ingress、identity 三件事。
先講清楚:我在 AKS 上的實戰時數比 GKE 少很多,這篇的指令是照官方文件整理的最小路徑,寫的時候還沒重新實跑一遍(verified: false),旗標請以文中連結的官方頁為準。
AKS 上有三個 GCP 那邊不用特別做的前置動作:先有一個 Resource Group(昨天講過,Azure 的資源都掛在它底下)、把 image 推到 Azure Container Registry(ACR)、讓叢集拿得到 ACR 的拉取權限。推 image 我用 az acr build,它會在雲端把目前目錄的 Dockerfile 建成 image 並直接推進 ACR,本機不用裝 Docker;沒有這一步,後面 Deployment 引用的 tag 不存在,Pod 一定卡 ImagePullBackOff。拉取權限官方 quickstart 用 --attach-acr 在建叢集時一次綁定,之後也可以用 az aks update --attach-acr 補綁;Ingress 要先開 application routing add-on(下一節解釋):
az group create --name RG_NAME --location eastasia
az acr create --resource-group RG_NAME --name REGISTRY --sku Basic
az acr build --registry REGISTRY --image agents/agent-api:v1 .
az aks create --resource-group RG_NAME --name AKS_NAME \
--node-count 1 --attach-acr REGISTRY --generate-ssh-keys
az aks approuting enable --resource-group RG_NAME --name AKS_NAME
az aks get-credentials --resource-group RG_NAME --name AKS_NAME
kubectl create namespace agents
kubectl -n agents create secret generic llm-api-key \
--from-literal=LLM_API_KEY="$LLM_API_KEY"
kubectl -n agents apply -f agent-api.yaml -f agent-api-ingress.yaml
kubectl -n agents get pods,svc,ingress
注意空行之後那五行:除了多套一份 Ingress 檔,跟 Day 24 一樣。kubectl 不在乎底下是 GKE 還是 AKS,這是 K8s 可攜性最具體的一面。--node-count 1 是因為 quickstart 提到不指定時預設會開三個節點,對一隻 replicas 1 的 agent 來說太多;--attach-acr 則是把「拉 image 的權限」問題在建叢集時就解決掉,不然就算 image 推上去了,Pod 一樣會卡在 ImagePullBackOff,那是 Day 14 五種錯裡的第一種。
apiVersion: apps/v1
kind: Deployment
metadata: {name: agent-api, namespace: agents}
spec:
replicas: 1
selector: {matchLabels: {app: agent-api}}
template:
metadata: {labels: {app: agent-api}}
spec:
containers:
- name: agent-api
image: REGISTRY.azurecr.io/agents/agent-api:v1 # 唯一的差異
ports: [{containerPort: 8080}]
envFrom: [{secretRef: {name: llm-api-key}}]
resources:
requests: {cpu: 250m, memory: 512Mi}
limits: {memory: 1Gi}
readinessProbe: {httpGet: {path: /healthz, port: 8080}}
---
apiVersion: v1
kind: Service
metadata: {name: agent-api, namespace: agents}
spec: {selector: {app: agent-api}, ports: [{port: 80, targetPort: 8080}]}
Deployment 與 Service 跟 Day 24 逐行相同,只有 image 換成 ACR 路徑。requests / limits、readinessProbe、Service 的 port 對應,全都不用動。
順帶一提,這裡的 containerPort 跟 readinessProbe,對應的是 compose 裡的 ports 與 healthcheck,跟我 dev-box 上那隻 LINE bot 的 compose(Day 03 貼過的 "127.0.0.1:8092:8080" 與 pgrep -x openab)是同一類東西。Day 13 從 compose 翻譯到 k3s 時寫過一次,之後不管是 GKE 還是 AKS,這幾行都不用再改。真正跟著雲走的,從頭到尾只有 image 路徑。
Ingress 物件的規格也一樣,差別在「誰來實作」。GKE 有內建的 Ingress 控制器;AKS 官方的對應做法是前面那行 az aks approuting enable,它會替你裝一個代管的 NGINX ingress controller,然後 Ingress 要指名這個 class。這是 Day 24 那份 YAML 之外,要多改的就這一行:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: {name: agent-api, namespace: agents}
spec:
ingressClassName: webapprouting.kubernetes.azure.com # AKS 才有的那一行
rules:
- http:
paths:
- {path: /, pathType: Prefix, backend: {service: {name: agent-api, port: {number: 80}}}}
這行 class 名稱就是「YAML 一樣,行為不一樣」的那一行:物件是標準 K8s,但沒有人實作它,它就只是一筆靜靜躺在 etcd 裡的資料,kubectl get ingress 也等不到 IP。
跑完上面兩塊,昨天的預測可以逐條核對:
az acr build),叢集還要有拉取權限(--attach-acr)。後者是 identity 問題,不是 YAML 問題,所以解法在 az aks 那一層而不是 kubectl 那一層。ingressClassName。以最小集來說,我目前看到的就這三件;真的上了生產環境,網路、儲存、監控各家還會有自己的細節。但這已經是昨天說「換雲要重學的是方言,不是語言」的具體證據:要重做的主要是三件事,不是三十件。
看到這裡你可能會問:Azure 上有沒有像 Cloud Run 那種「不想管叢集,跑容器就好」的東西?有,就是 Azure Container Apps(ACA)。它的位置對應 Cloud Run,AKS 對應 GKE;官方自己也有一頁在講 Container Apps 跟其他 Azure 容器選項怎麼比。
同一個 image 上 ACA,官方最短路徑是先 az extension add --name containerapp --upgrade,再一行 az containerapp up --name agent-api --image REGISTRY.azurecr.io/agents/agent-api:v1 --ingress external --target-port 8080,它會順手建 Resource Group、environment 跟 Log Analytics workspace。有一個坑要先講:ACR 預設是私有的,前面 --attach-acr 給的拉取權限只有 AKS 拿得到,ACA 不會繼承;所以這行還要補 --registry-server REGISTRY.azurecr.io 加憑證,或改用 --registry-identity 讓 ACA 用 managed identity 拉 image(正式環境官方建議後者,做法見延伸閱讀)。scale 用 --min-replicas 0 --max-replicas N 加 scale rule 設定,minReplicas: 0 就是縮到零。
分界線跟 Day 21 問的是同一個問題:一段時間沒人碰,它該不該活著? 可以接受縮到零跟冷啟動的 request-driven API,走 ACA;必須常駐、或者你要自己控制排程與資源的,走 AKS。今天這份 YAML 是給 AKS 的,上 ACA 不需要它,就像 Cloud Run 不吃 Deployment 一樣。
明天把 GKE、Cloud Run、AKS、ACA 四個放進同一張表,用同一個 agent workload 的五個面向對照。
Deployment 與 Service 從 GKE 搬到 AKS,只改了 image 那一行;Ingress 多一行 class 名稱;剩下要重做的主要是 registry 授權、ingress 實作、identity,三件事,不是三十件。
az aks create、get-credentials):Deploy an Azure Kubernetes Service (AKS) Cluster using Azure CLI
--attach-acr):Integrate Azure Container Registry with Azure Kubernetes Service (AKS)
--registry-identity):Azure Container Apps image pull from Azure Container Registry with managed identity