Debugging Workload Identity — Khi Token Exchange Thất Bại
Why this matters in production
Mọi file trước dạy cơ chế khi nó hoạt động. File này dạy điều quan trọng nhất khi nó không hoạt động: định vị lỗi ở tầng nào trong chuỗi, một cách có hệ thống, thay vì thử-và-sai. Workload Identity thất bại theo những cách trông giống nhau từ phía ứng dụng (403 Permission Denied, could not find default credentials, hoặc API call treo) nhưng có nguyên nhân ở các tầng hoàn toàn khác — projected token, metadata server, STS exchange, hay IAM binding. Không có một quy trình điều tra có cấu trúc, một sự cố Workload Identity lúc 3 giờ sáng dễ kéo dài hàng giờ vì kỹ sư đoán mò ở sai tầng.
Lý do production cần kỷ luật debug ở đây đặc biệt cao:
Triệu chứng không chỉ ra tầng lỗi. Cùng một
403có thể là: KSA không có IAM binding, node pool còn ởGCE_METADATA, principal string sai namespace, hoặc network policy chặn metadata. Bốn nguyên nhân, bốn cách sửa, một triệu chứng. Quy trình phải tách bạch chúng.Lỗi có thể là authentication HOẶC authorization — và hai cái khác nhau căn bản. "Không lấy được token" (authentication, lỗi ở token/metadata) khác hẳn "lấy được token nhưng bị từ chối" (authorization, lỗi ở IAM). Bước đầu tiên của mọi debug là xác định bạn đang ở loại nào — và nhiều kỹ sư bỏ qua bước này, lao thẳng vào sửa IAM khi lỗi thực ra ở metadata.
Lỗi scale ẩn dưới lỗi cấu hình. Một lỗi rải rác, không tái hiện ổn định, trên cluster lớn thường là bottleneck scale (metadata server gần OOM, vượt connection) chứ không phải cấu hình sai. Phân biệt "sai cấu hình" (luôn lỗi) với "vượt giới hạn" (lỗi tải) định hướng điều tra hoàn toàn khác.
File này cung cấp một cây quyết định và bộ lệnh tương ứng để đi từ triệu chứng tới nguyên nhân theo đường ngắn nhất.
Internal model: bốn tầng lỗi
Trước khi vào lệnh, ghim mô hình bốn tầng — mọi lỗi Workload Identity nằm ở đúng một trong số này, và mục tiêu debug là xác định tầng nào:
| Tầng | Câu hỏi | Lỗi điển hình | File tham chiếu |
|---|---|---|---|
| A. Token gốc | Projected token có đúng iss/aud/exp không? | Token sai audience, hết hạn, clock skew | 02 |
| B. Metadata server | gke-metadata-server có chặn và đổi token không? | Node pool GCE_METADATA, hostNetwork, server OOM, network policy chặn | 03 |
| C. STS exchange | STS có verify và đổi token không? | Pool sai, JWKS không tải được, vượt quota STS | 03, 05 |
| D. IAM binding | Danh tính có quyền gọi API không? | Thiếu binding, principal sai, role không đủ | 01, 04 |
Nguyên tắc: debug từ A đến D theo thứ tự, hoặc dùng một phép thử nhanh để nhảy thẳng tới tầng nghi ngờ. Lỗi tầng A–C là authentication (không có/sai token); lỗi tầng D là authorization (token đúng nhưng không đủ quyền).
Bước 0: phân loại authentication vs authorization
Phép thử đầu tiên, rẻ nhất, định hướng tất cả: từ trong Pod, hỏi metadata server xem nó trả về danh tính nào.
kubectl exec -it POD -n NS -- \
curl -s -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"Diễn giải kết quả:
- Trả về email/danh tính KSA-mapped đúng (ví dụ GSA đúng trong mô hình impersonation, hoặc principal của KSA): authentication OK → lỗi nằm ở tầng D (IAM). Nhảy tới phần verify IAM binding.
- Trả về node service account (thường
...-compute@developer.gserviceaccount.com): metadata server không đổi token Workload Identity → lỗi tầng B. Node pool cònGCE_METADATA, hoặc Pod hostNetwork. Nhảy tới verify GKE_METADATA. - 404 / Not Found /
unable to detect environment: metadata server không phục vụ được → lỗi tầng B (server không chạy/không reachable) hoặc network policy chặn (tầng B). Kiểm DaemonSet và network. - Treo / timeout: thường network policy chặn đích metadata (tầng B), hoặc metadata server quá tải (tầng B/C scale). Kiểm egress policy và sức khỏe server.
- Token rỗng hoặc lỗi khi hỏi
/token: token exchange thất bại ở metadata/STS (tầng B/C).
Bước 0 này tách ngay hai nửa thế giới — authentication (B/C) vs authorization (D) — và tiết kiệm phần lớn thời gian điều tra.
Bước 1 (tầng B): verify GKE_METADATA trên mọi node pool
Nếu bước 0 trả về node SA, nguyên nhân số một là node pool chưa ở GKE_METADATA. Kiểm mọi pool — chỉ một pool sót cũng đủ gây lỗi cho Pod chạy trên nó:
gcloud container node-pools list --cluster CLUSTER --region REGION \
--format="table(name, config.workloadMetadataConfig.mode)"Bất kỳ pool nào hiển thị GCE_METADATA (hoặc trống) là cờ đỏ. Sửa:
gcloud container node-pools update POOL \
--cluster CLUSTER --region REGION \
--workload-metadata=GKE_METADATAĐồng thời xác nhận cluster có workloadPool được đặt (Workload Identity bật ở cấp cluster):
gcloud container clusters describe CLUSTER --region REGION \
--format="value(workloadIdentityConfig.workloadPool)"
# -> phải là PROJECT_ID.svc.id.goog; rỗng = WI chưa bật ở clusterNếu Pod dùng hostNetwork: true, đó là nguyên nhân (bypass metadata server — file 03). Kiểm:
kubectl get pod POD -n NS -o jsonpath='{.spec.hostNetwork}'
# -> "true" nghĩa là Pod bypass WI; đây là lỗi cấu hình workloadBước 2 (tầng B): kiểm sức khỏe metadata server và network
Nếu node pool đúng GKE_METADATA nhưng vẫn lỗi, kiểm thành phần và mạng:
# Metadata server DaemonSet — có Pod khỏe trên node của workload không?
kubectl get pods -n kube-system -l k8s-app=gke-metadata-server -o wide
# Pod metadata server restart nhiều lần? -> dấu hiệu OOM (tầng C scale)
kubectl get pods -n kube-system -l k8s-app=gke-metadata-server \
-o custom-columns=NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount
# Tổng số ServiceAccount — gần 3.000 là cảnh báo OOM
kubectl get serviceaccounts -A --no-headers | wc -lNếu nghi network policy chặn metadata (Pod treo khi gọi metadata), xác nhận egress policy chừa đích metadata (file 03):
- Dataplane V2: cho phép egress
169.254.169.254/32port80. - Non-Dataplane V2: cho phép egress
169.254.169.252/32port988.
Kiểm các NetworkPolicy áp lên namespace của Pod và xác nhận chúng whitelist các đích này.
Bước 3 (tầng A/C): token validity check
Nếu metadata trả token nhưng STS hoặc API vẫn từ chối, kiểm chính token. Lấy projected token và decode để xem claim:
# Lấy token KSA và decode payload
kubectl exec -it POD -n NS -- \
cat /var/run/secrets/tokens/token 2>/dev/null | cut -d. -f2 | base64 -d 2>/dev/null | jq
# Hoặc tạo token mới cho KSA và decode (so sánh)
kubectl create token KSA_NAME -n NS --audience=sts.googleapis.com \
| cut -d. -f2 | base64 -d 2>/dev/null | jqKiểm ba claim then chốt (file 02):
iss: phải là issuer URL của đúng cluster đang chạy Pod. Sai issuer = STS không verify được.aud: phải chứasts.googleapis.com. Sai audience = STS từ chối.exp: phải còn hiệu lực. Hết hạn = 401; nếu lỗi mang tính thời gian/ngắt quãng, nghi clock skew (kiểm NTP trên node).
Lỗi iss/aud chỉ tới cấu hình token/pool; lỗi exp/thời gian chỉ tới clock skew hoặc refresh thất bại.
Bước 4 (tầng D): verify IAM binding
Nếu bước 0 cho thấy authentication OK (metadata trả danh tính đúng) nhưng API vẫn 403, lỗi ở IAM. Quy trình phụ thuộc mô hình binding (file 04):
Mô hình trực tiếp — kiểm role cấp cho principal KSA:
# Lấy policy của project/resource và lọc principal của KSA
gcloud projects get-iam-policy PROJECT_ID --format=json \
| jq '.bindings[] | select(.members[] | contains("svc.id.goog/subject/ns/NS/sa/KSA_NAME"))'Kiểm: principal string có đúng namespace và đúng tên KSA không (lỗi phổ biến nhất là sai một trong hai); role có đủ quyền cho API đang gọi không; binding ở đúng resource (project vs bucket vs secret) không.
Mô hình impersonation — kiểm cả hai phần:
# 1. KSA có annotate trỏ đúng GSA?
kubectl get serviceaccount KSA_NAME -n NS \
-o jsonpath='{.metadata.annotations.iam\.gke\.io/gcp-service-account}'
# 2. GSA có cho KSA mạo danh (workloadIdentityUser)?
gcloud iam service-accounts get-iam-policy GSA@PROJECT_ID.iam.gserviceaccount.com \
--format=json | jq '.bindings[] | select(.role=="roles/iam.workloadIdentityUser")'
# 3. GSA có quyền resource thật?
gcloud projects get-iam-policy PROJECT_ID --format=json \
| jq '.bindings[] | select(.members[] | contains("GSA@PROJECT_ID.iam.gserviceaccount.com"))'Thiếu bất kỳ một trong ba = 403. Lỗi phổ biến: annotate đúng nhưng quên binding workloadIdentityUser, hoặc cấp role resource cho KSA principal thay vì cho GSA (file 04, anti-pattern 5).
Một công cụ chéo hữu ích — kiểm quyền hiệu lực từ góc IAM:
# Mô phỏng: principal này có quyền gọi API X không?
gcloud projects get-iam-policy PROJECT_ID # rồi đối chiếu role ↔ permission cầnCây quyết định lỗi (tổng hợp)
| Triệu chứng | Tầng | Nguyên nhân thường gặp | Lệnh kiểm |
|---|---|---|---|
| metadata trả node SA | B | node pool GCE_METADATA / hostNetwork | node-pools list ... workloadMetadataConfig.mode |
unable to detect environment / could not find default credentials | B | metadata không reachable, WI chưa bật | DaemonSet status, workloadPool |
| metadata 404 | B | metadata server không chạy/sai route | kubectl get pods -n kube-system -l k8s-app=gke-metadata-server |
| gọi metadata treo/timeout | B/C | network policy chặn / server quá tải | egress policy, restart count |
| token có nhưng STS từ chối | A/C | aud/iss sai, pool sai | decode token claim |
403 nhưng metadata trả danh tính đúng | D | thiếu/sai IAM binding | get-iam-policy lọc principal |
| lỗi rải rác trên cluster lớn | C (scale) | metadata OOM / vượt 500 conn / quota STS | restart count, số KSA, quota STS |
| lỗi sau đúng ~1 giờ | A | refresh token thất bại / clock skew | NTP, decode exp |
Quan sát từ phía Google: logs và metrics
Phần lớn bước trên debug từ trong cluster. Nhưng nhiều tín hiệu quý nằm ở phía Google Cloud, và một đội ngũ trưởng thành nên biết tra chúng:
- Cloud Audit Logs cho STS và IAM Credentials. Mỗi token exchange thành công/thất bại ở STS, và mỗi
generateAccessToken(impersonation), để lại dấu vết trong audit log. Tra nhanh các từ chối:
# Trong Logs Explorer — các lỗi từ STS / IAM Credentials
protoPayload.serviceName="sts.googleapis.com" OR protoPayload.serviceName="iamcredentials.googleapis.com"
severity>=ERRORMột PERMISSION_DENIED ở iamcredentials.googleapis.com với method GenerateAccessToken chỉ thẳng tới mô hình impersonation thiếu workloadIdentityUser — bằng chứng phía server cho lỗi mà bước 4 tìm từ phía client.
Thông điệp lỗi của API đích là dữ liệu, không phải nhiễu.
403trả về thường kèm chuỗi rất cụ thể; đọc kỹ thay vì lướt qua: "The caller does not have permission" (thiếu role), "... is not authorized to ... iam.serviceAccounts.getAccessToken ..." (thiếuworkloadIdentityUsertrên GSA), "Unable to acquire impersonated credentials" (chuỗi impersonation gãy), "compute.googleapis.com metadata ... 404" (tầng B), "requires a quota project" (thiếu quota project — file 06). Mỗi chuỗi ánh xạ gần như 1:1 tới một tầng.Metric cho metadata server. Để bắt bottleneck scale trước khi nó thành outage, giám sát các tín hiệu của
gke-metadata-server: số lần restart của Pod DaemonSet (proxy cho OOM), độ trễ/tỉ lệ lỗi của request token, và tổng số ServiceAccount toàn cluster so với ngưỡng ~3.000. Đặt alert ở các ngưỡng cảnh báo sớm (ví dụ 2.400 KSA, ~80% của 3.000) thay vì chờ chạm trần.
Worked case study: "deploy mới, 403 ngắt quãng"
Một sự cố thực tế minh họa giá trị của quy trình tầng. Triệu chứng: sau khi scale một service từ 1 lên 3 node pool, ~1/3 Pod báo 403 khi gọi Cloud Storage, 2/3 còn lại bình thường — ngắt quãng theo Pod, không theo thời gian.
- Bước 0 trên một Pod lỗi:
curlmetadata trả về...-compute@developer.gserviceaccount.com(node SA), trong khi Pod khỏe trả về principal KSA đúng. → Phân loại ngay: đây là tầng B (authentication), không phải IAM. Nếu bỏ qua bước 0 và lao vào cấp thêm role Storage, sẽ tốn hàng giờ vô ích. - Bước 1: liệt kê mọi node pool → pool thứ ba (vừa tạo) hiển thị
GCE_METADATA, hai pool cũ ởGKE_METADATA. Nguyên nhân lộ diện: pool mới tạo thiếu cờ--workload-metadata=GKE_METADATA, nên mọi Pod được lập lịch lên pool đó lấy token node SA →403. "Ngắt quãng theo Pod" thực ra là "tất định theo node". - Sửa:
node-pools updateđặtGKE_METADATAcho pool thứ ba; rollout lại Pod trên pool đó. - Phòng ngừa: thêm một check CI/policy xác nhận mọi pool ở
GKE_METADATA(anti-pattern 2), để pool mới không bao giờ vào production thiếu cấu hình này.
Bài học: triệu chứng "ngắt quãng" gần như luôn là không đồng nhất ẩn (node pool, namespace, version) chứ không phải ngẫu nhiên thật — và bước 0 phân loại authn/authz cắt thẳng tới đúng tầng trong chưa đầy một phút.
Common mistakes / anti-patterns trong debug
1. Lao thẳng vào sửa IAM khi lỗi ở metadata. Thấy 403, đổ ngay vào IAM binding, thử cấp thêm quyền. Vì sao xảy ra: bỏ qua bước 0 phân loại. Hệ quả: cấp quyền dư thừa mà vẫn không sửa được (vì lỗi thực ở tầng B). Phòng tránh: luôn chạy bước 0 (metadata trả danh tính nào) trước khi đụng IAM.
2. Chỉ kiểm một node pool. Verify GKE_METADATA trên pool mặc định, bỏ qua pool khác. Vì sao xảy ra: nghĩ cluster đồng nhất. Hệ quả: Pod trên pool sót vẫn lỗi, debug tưởng "lúc được lúc không". Phòng tránh: liệt kê mọi pool; lỗi ngắt quãng theo node là dấu hiệu pool không đồng nhất.
3. Bỏ qua hostNetwork. Không nghĩ tới hostNetwork khi Pod lấy token sai. Vì sao xảy ra: hostNetwork hiếm, dễ quên. Hệ quả: debug metadata/IAM vô ích vì Pod bypass cả hệ thống. Phòng tránh: kiểm spec.hostNetwork sớm khi metadata trả node SA.
4. Không phân biệt lỗi cấu hình vs lỗi tải. Coi lỗi rải rác như lỗi cấu hình, tìm mãi không ra. Vì sao xảy ra: không biết các bottleneck scale tồn tại. Hệ quả: bỏ lỡ nguyên nhân thật (OOM, connection, quota). Phòng tránh: lỗi luôn xảy ra → cấu hình; lỗi rải rác/theo tải → kiểm scale (restart count, số KSA, quota STS).
5. Quên clock skew cho lỗi theo thời gian. Lỗi 401 đúng chu kỳ ~1 giờ, nghi token logic. Vì sao xảy ra: không nghĩ tới đồng hồ. Hệ quả: điều tra sai. Phòng tránh: lỗi token mang tính chu kỳ/thời gian → kiểm NTP và exp/iat.
GCP-native implementation guidance
Một script kiểm tra nhanh toàn diện, chạy theo thứ tự tầng:
# === BƯỚC 0: danh tính metadata trả về (phân loại authn vs authz) ===
kubectl exec -it POD -n NS -- curl -s -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"; echo
# === TẦNG B: cấu hình cluster + node pool ===
gcloud container clusters describe CLUSTER --region REGION \
--format="value(workloadIdentityConfig.workloadPool)"
gcloud container node-pools list --cluster CLUSTER --region REGION \
--format="table(name, config.workloadMetadataConfig.mode)"
kubectl get pod POD -n NS -o jsonpath='{.spec.hostNetwork}{"\n"}'
# === TẦNG B/C: sức khỏe metadata server + scale ===
kubectl get pods -n kube-system -l k8s-app=gke-metadata-server -o wide
kubectl get serviceaccounts -A --no-headers | wc -l
# === TẦNG A: token claims ===
kubectl create token KSA_NAME -n NS --audience=sts.googleapis.com \
| cut -d. -f2 | base64 -d 2>/dev/null | jq '{iss, aud, exp, sub}'
# === TẦNG D: IAM binding của principal KSA ===
gcloud projects get-iam-policy PROJECT_ID --format=json \
| jq '.bindings[] | select(.members[] | contains("subject/ns/NS/sa/KSA_NAME"))'Chạy tuần tự, mỗi tầng loại trừ một nhóm nguyên nhân, dẫn tới gốc rễ theo đường ngắn nhất thay vì đoán mò.
Operational implications
Khả năng debug Workload Identity một cách có hệ thống là ranh giới giữa một đội ngũ dùng Workload Identity và một đội ngũ làm chủ nó. Vì cơ chế trải nhiều tầng (Kubernetes token, metadata server cấp node, STS, IAM) thuộc nhiều "sở hữu" khác nhau — kubelet, DaemonSet hệ thống, dịch vụ Google, chính sách IAM — một sự cố có thể trông như lỗi của bất kỳ nhóm nào. Quy trình bốn tầng trong file này biến một bài toán mơ hồ liên-nhóm thành một chuỗi kiểm tra xác định, mỗi bước trả lời rõ "tầng này ổn hay không". Đây chính là giá trị của việc hiểu sâu cơ chế ở các file trước: mỗi tầng debug ở đây tương ứng trực tiếp một file cơ chế (A↔file 02, B↔file 03, C↔file 03/05, D↔file 01/04).
Về lâu dài, đầu tư đáng giá nhất là phòng ngừa bằng enforcement và quan sát, không chỉ debug phản ứng. Đưa các kiểm tra của file này vào tự động hóa: một check định kỳ xác nhận mọi node pool ở GKE_METADATA; một alert khi gke-metadata-server restart bất thường hoặc số ServiceAccount tiến gần 3.000; một policy admission chặn hostNetwork cho workload thường và chặn GOOGLE_APPLICATION_CREDENTIALS cùng việc mount SA key (file 06). Khi các bất biến này được enforce tự động, phần lớn sự cố Workload Identity không bao giờ xảy ra — và những sự cố còn lại được quy trình bốn tầng giải quyết nhanh chóng. Đó là trạng thái trưởng thành mà chương này hướng tới: Workload Identity không phải một hộp đen đáng sợ, mà một hệ thống minh bạch, có thể suy luận, enforce, và debug một cách kỷ luật.