Metadata Server & Token Exchange Path — Trái Tim Của Cơ Chế
Why this matters in production
Đây là file trung tâm của cả chương. Mọi thứ trước nó (kiến trúc niềm tin, token gốc) là điều kiện chuẩn bị; mọi thứ sau nó (IAM binding, ADC pattern) là hệ quả. File này trả lời câu hỏi cơ học cốt lõi: khi code trong Pod gọi một Google API, chính xác điều gì xảy ra giữa lời gọi đó và lúc một access token Google hợp lệ xuất hiện? Câu trả lời là một chuỗi năm bước đi qua một thành phần ít người để ý nhưng quyết định tất cả — gke-metadata-server, một DaemonSet chạy trên từng node, chặn (intercept) mọi request tới địa chỉ metadata và thực hiện token exchange thay cho workload.
Tầm quan trọng production của việc hiểu thành phần này nằm ở chỗ nó là single point of failure cho xác thực trên mỗi node, và nó có những giới hạn scale rất thật mà khi vượt qua sẽ gây outage xác thực toàn node:
Khi metadata server bị OOM-killed, mọi Pod trên node mất khả năng xác thực với Google. Tài liệu nêu rõ metadata server dùng bộ nhớ tỉ lệ với tổng số ServiceAccount, và cluster vượt 3.000 ServiceAccount có nguy cơ kubelet giết Pod metadata server. Một cluster lớn vô tình vượt ngưỡng này sẽ thấy lỗi xác thực rải rác không rõ nguyên nhân — trừ khi bạn biết bottleneck này tồn tại.
Giới hạn 500 concurrent connection/node tạo ra hàng đợi và độ trễ. Workload Identity giới hạn 500 kết nối đồng thời tới metadata server trên mỗi node; vượt ngưỡng, request xếp hàng. Một node chạy nhiều Pod cùng burst gọi API (ví dụ khởi động đồng loạt sau scale-up) có thể chạm trần này, gây độ trễ xác thực mà profiler ứng dụng không giải thích được.
hostNetwork: trueâm thầm bypass toàn bộ cơ chế. Pod dùng host network không đi quagke-metadata-servermà chạm thẳng Compute Engine metadata server của node — nghĩa là không có Workload Identity, và tệ hơn, có thể lấy được token của node service account. Đây là một trong những lỗ hổng cấu hình kín đáo và nguy hiểm nhất.Network policy quá chặt chặn nhầm metadata traffic. Vì token exchange đi qua một địa chỉ IP nội bộ cụ thể, một egress policy default-deny không whitelist đúng địa chỉ và port sẽ phá vỡ xác thực — một tương tác giữa hai tính năng bảo mật (Network Policy và Workload Identity) mà nếu không biết sẽ debug rất lâu.
Hiểu trái tim cơ chế này là điều kiện để vận hành Workload Identity ở quy mô mà không biến nó thành nguồn outage.
Internal model: gke-metadata-server là gì
Một DaemonSet, một Pod mỗi node
Theo tài liệu Workload Identity Federation for GKE, khi bật Workload Identity, GKE triển khai một metadata server "as a DaemonSet, with one Pod on each Linux node, or a native Windows service on each Windows node". Đặc tính kiến trúc:
- Chạy trong namespace
kube-systemnhư một thành phần hạ tầng cluster, không phải workload của bạn. - Một instance trên mỗi node, phục vụ mọi Pod trên node đó. Đây là lý do nó là điểm hội tụ — và điểm thất bại — cấp node.
- Trên Linux, mỗi Pod metadata server xử lý xác thực cho toàn bộ Pod (không hostNetwork) của node; trên Windows là một native service.
Interception: chặn request tới 169.254.169.254
Đây là cơ chế cốt lõi. Trên Compute Engine, mọi VM có một metadata server tại địa chỉ link-local 169.254.169.254 (cũng có tên DNS metadata.google.internal), nơi VM lấy thông tin instance và — quan trọng nhất — access token của service account gắn với VM. Client library Google trên bất kỳ môi trường GCP nào đều biết hỏi địa chỉ này.
GKE Workload Identity lợi dụng chính cơ chế đó: gke-metadata-server chặn các request mà Pod gửi tới http://metadata.google.internal (169.254.169.254:80) và xử lý chúng theo logic Workload Identity, thay vì để chúng chạm tới Compute Engine metadata server của node. Tài liệu mô tả: "your requests to the instance metadata server are routed to the GKE metadata server".
Hệ quả thiết kế xuất sắc: ứng dụng không cần biết gì về Workload Identity. Code dùng client library Google gọi metadata endpoint y như trên một VM Compute Engine bình thường; gke-metadata-server chặn ở giữa và biến nó thành luồng Workload Identity. Tài liệu xác nhận code dùng client library "should work without modification". Đây là lý do migration sang Workload Identity thường không đụng tới code ứng dụng — chỉ đụng cấu hình.
Trust boundary cấp node và rủi ro hostNetwork
Việc interception hoạt động ở tầng mạng của node: traffic tới 169.254.169.254 từ Pod (trong network namespace của Pod, qua CNI) bị định tuyến tới gke-metadata-server. Đây tạo ra một trust boundary cấp node — metadata server quyết định Pod nào nhận token nào.
Nhưng boundary này có một lỗ hổng theo thiết kế: Pod với hostNetwork: true chia sẻ network namespace của node, nên request 169.254.169.254 của nó không đi qua đường định tuyến của Pod mà chạm thẳng metadata server thật của Compute Engine. Tài liệu nêu rõ: Pod hostNetwork "bypass the GKE metadata server and access the Compute Engine metadata server directly—they do not use Workload Identity Federation". Hai hệ quả:
- Mất Workload Identity. Workload trong Pod hostNetwork không có danh tính KSA-mapped; client library lấy token của node service account thay vì danh tính workload.
- Rủi ro leo thang. Nếu node service account có quyền rộng (ví dụ default Compute Engine SA), một Pod hostNetwork bị chiếm lấy được token đó — đúng vector mà Workload Identity sinh ra để cắt. Đây là lý do hostNetwork nên bị hạn chế nghiêm ngặt qua Pod Security / admission policy (xem Chương 12, file 06, 08).
Interception được hiện thực ở đâu trong stack mạng
Việc "chặn" 169.254.169.254 không phải phép thuật ở tầng ứng dụng mà là định tuyến ở tầng node: traffic từ network namespace của Pod tới IP link-local metadata được chuyển hướng (qua quy tắc kiểu iptables/redirect do GKE cài trên node) tới gke-metadata-server lắng nghe cục bộ, thay vì ra Compute Engine metadata server thật của VM. Chính vì cơ chế dựa trên network namespace của Pod, một Pod chia sẻ network namespace của host (hostNetwork: true) nằm ngoài phạm vi chuyển hướng đó — đây là lý do kỹ thuật (không chỉ "quy ước") khiến hostNetwork bypass được. Hiểu điều này cũng giải thích vì sao bản thân gke-metadata-server cần chạm Compute Engine metadata thật (nó dùng danh tính node để hoạt động) — nó không tự loại mình khỏi quyền truy cập metadata, chỉ chèn mình vào đường đi của Pod thường.
So sánh: cùng bài toán trên cloud khác
Mô hình "thành phần cấp node/cluster đổi danh tính Kubernetes lấy credential cloud" không độc quyền của GKE — đối chiếu giúp củng cố mental model. AWS EKS dùng IRSA (IAM Roles for Service Accounts): cluster làm OIDC provider, Pod nhận projected SA token (aud=sts.amazonaws.com), và workload gọi AWS STS AssumeRoleWithWebIdentity để đổi lấy credential AWS tạm thời — về bản chất là cùng khung RFC-style token exchange, chỉ khác là client library gọi STS trực tiếp thay vì qua một metadata server cấp node, và đích là một IAM Role thay vì principal. (EKS Pod Identity sau này thêm một agent cấp node gần hơn với mô hình GKE.) Điểm chung xuyên các cloud: cluster Kubernetes làm IdP, projected token làm subject token, một STS đổi lấy credential cloud short-lived — nhận ra khung chung này khiến federation đa đám mây (file 05) trở nên trực giác thay vì lạ lẫm.
Token exchange path: năm bước chi tiết
Đây là phần phải nắm vững nhất của toàn chương. Khi code trong Pod gọi một Google API qua client library (dùng ADC), chuỗi sau diễn ra tự động, trong suốt với ứng dụng:
┌─────────────────────────────────────────────────────────────────────┐
│ POD (workload container) │
│ │
│ [1] App gọi Google API qua client library (ADC) │
│ client library hỏi token từ metadata endpoint: │
│ GET http://metadata.google.internal/computeMetadata/v1/ │
│ instance/service-accounts/default/token │
│ Header: Metadata-Flavor: Google │
└───────────────────────────────┬─────────────────────────────────────┘
│ (request bị chặn ở tầng node)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ gke-metadata-server (DaemonSet, trên cùng node) │
│ │
│ [2] Chặn request. Xác định Pod gọi là Pod nào, KSA nào. │
│ │
│ [3] Lấy projected KSA JWT cho Pod này từ Kubernetes API server │
│ (token: iss=cluster issuer, aud=sts.googleapis.com, exp≤1h) │
│ │
│ [4] Gọi Security Token Service (STS): │
│ POST https://sts.googleapis.com/v1/token │
│ grant_type=token-exchange (RFC 8693) │
│ subject_token = projected KSA JWT │
│ → STS verify JWT bằng JWKS của cluster (offline) │
│ → STS trả "federated access token" short-lived │
└───────────────────────────────┬─────────────────────────────────────┘
│ [5] trả access token về Pod
▼
┌─────────────────────────────────────────────────────────────────────┐
│ POD: client library nhận access token, gọi Google API thật │
│ Authorization: Bearer <federated access token> │
│ → Google API backend verify token, áp quyền IAM của danh tính │
└─────────────────────────────────────────────────────────────────────┘Diễn giải từng bước:
Bước 1 — App yêu cầu token qua ADC. Client library Google, khi chạy trên GKE, mặc định thử lấy token từ metadata endpoint …/instance/service-accounts/default/token với header bắt buộc Metadata-Flavor: Google (header này chống tấn công SSRF — request không có nó bị từ chối). App không biết nó đang ở luồng Workload Identity; nó nghĩ mình đang trên một VM Compute Engine.
Bước 2 — Metadata server chặn và định danh Pod. gke-metadata-server nhận request (vì traffic 169.254.169.254 được route tới nó), xác định request đến từ Pod nào qua thông tin kết nối, và tra ra KSA của Pod đó.
Bước 3 — Lấy projected KSA JWT. Metadata server lấy một bound ServiceAccount token (JWT) cho KSA của Pod từ Kubernetes API server — chính token mà file 02 mổ xẻ, với aud=sts.googleapis.com. Tài liệu: metadata server "queries the Kubernetes API server for a JWT ServiceAccount token identifying the workload".
Bước 4 — Đổi JWT lấy federated token qua STS. Metadata server gọi Security Token Service tại sts.googleapis.com, dùng grant token-exchange theo OAuth 2.0 Token Exchange (RFC 8693): gửi JWT làm subject_token. STS verify chữ ký JWT bằng JWKS của cluster (offline, như file 02 mô tả), kiểm aud/exp, rồi — theo tài liệu — "exchange the JWT for a short-lived federated access token that references the identity of the Kubernetes workload". Token trả về đại diện cho danh tính KSA trong không gian IAM.
Bước 5 — Trả token, gọi API. Federated access token được trả về workload qua metadata response. Client library đặt nó vào header Authorization: Bearer ... và gọi Google API thật. Backend Google verify token và áp quyền IAM của danh tính (các role cấp cho principal://...sa/KSA — file 04).
Điểm cốt lõi cần ghim: không một credential dài hạn nào tham gia chuỗi này, và toàn bộ ẩn sau ADC. Workload chỉ thấy "tôi gọi API và nó hoạt động". Mọi sự phức tạp — projected JWT, STS exchange, verify JWKS — nằm trong metadata server và STS.
Trường hợp impersonation: thêm một bước
Khi dùng mô hình impersonation (KSA annotate trỏ tới một GSA — file 04), có thêm một bước sau bước 4: federated token được dùng để mạo danh GSA qua IAM Credentials API (generateAccessToken), và token cuối cùng app nhận là access token của GSA. Mô hình trực tiếp (principal binding) bỏ qua bước này — federated token gọi API luôn. Phân biệt hai luồng này là chìa khóa debug ở file 04 và 07.
Caching, refresh, và lifetime
Token exchange không xảy ra mỗi lần gọi API — đó sẽ là thảm họa hiệu năng và chạm quota ngay. Có nhiều tầng cache:
- Lifetime token: 1 giờ. Tài liệu: "the access token returned has a lifetime of 1 hour (3,600 seconds)". Trong một giờ đó, cùng token được tái dùng.
- Metadata server cache token. Server giữ token đã đổi để phục vụ các request tiếp theo của cùng KSA mà không gọi lại STS.
- Client library refresh sớm. Cloud Client Libraries kiểm tra nếu token sẽ hết hạn trong 3 phút 45 giây tới và chủ động refresh trước, để không bao giờ dùng token sát hạn. Điều này tránh lỗi race "token hết hạn giữa lúc gọi".
- Quota STS: 6.000 request/phút. Exchange Token API của STS có quota "6,000 requests per minute". Nhờ caching, số lần exchange thực tế thấp hơn nhiều số lần gọi API — nhưng cluster cực lớn với hàng nghìn KSA cùng refresh có thể chạm quota này, gây lỗi xác thực rải rác.
Hiểu các con số này giúp chẩn đoán đúng các lỗi xác thực mang tính thời gian hoặc tải: lỗi lúc burst khởi động (chạm 500 connection hoặc quota STS), lỗi sau đúng ~1 giờ (refresh thất bại), lỗi rải rác trên cluster lớn (metadata server gần OOM).
Scale bottlenecks và yêu cầu mạng
| Giới hạn | Giá trị | Hệ quả khi vượt | Cách xử lý |
|---|---|---|---|
| Concurrent connection/node | 500 | Request xếp hàng, độ trễ xác thực | Giảm Pod/node, tránh burst đồng loạt |
| ServiceAccount/cluster | ~3.000 | Metadata server OOM, kubelet giết Pod | Giảm số KSA, tách cluster |
| Quota STS Exchange Token | 6.000 req/phút | Lỗi xác thực rải rác | Caching (mặc định), xin tăng quota |
| Token lifetime | 3.600s | (thiết kế) refresh tự động | Không cần can thiệp |
Yêu cầu network policy
Vì token exchange đi qua địa chỉ metadata nội bộ, egress network policy phải whitelist đúng đích. Theo tài liệu:
- GKE Dataplane V2: cho phép egress tới
169.254.169.254/32trên port80. - Cluster với policy chặt (non-Dataplane V2): cho phép egress tới
169.254.169.252/32trên port988.
Một default-deny egress policy (xem Chương 12, file 07) phải chừa các đích này, nếu không mọi Pod trong namespace bị áp policy sẽ mất khả năng xác thực với Google. Đây là tương tác kinh điển giữa microsegmentation và Workload Identity — và là một mục bắt buộc trong checklist khi triển khai default-deny.
Common mistakes / anti-patterns
1. Dùng hostNetwork cho workload cần Workload Identity. Đặt hostNetwork: true (thường vì lý do mạng/hiệu năng) rồi ngạc nhiên workload lấy token sai. Vì sao xảy ra: không biết hostNetwork bypass metadata server. Hệ quả: mất WI, có thể lấy token node SA quyền rộng. Phòng tránh: tránh hostNetwork cho workload cần WI; nếu buộc dùng, xử lý xác thực riêng và thu hẹp node SA tối đa; chặn hostNetwork qua admission policy.
2. Default-deny egress quên whitelist metadata. Áp Network Policy default-deny, mọi Pod mất xác thực Google. Vì sao xảy ra: không biết token exchange đi qua đích mạng cụ thể. Hệ quả: outage xác thực toàn namespace, dễ chẩn đoán nhầm thành lỗi IAM. Phòng tránh: luôn whitelist 169.254.169.254/32:80 (Dataplane V2) hoặc 169.254.169.252/32:988 trong mọi default-deny egress; thêm vào template policy chuẩn.
3. Vượt 3.000 ServiceAccount mà không biết bottleneck. Cluster đa tenant sinh hàng nghìn KSA, metadata server bắt đầu OOM. Vì sao xảy ra: không biết bộ nhớ metadata server tỉ lệ số KSA. Hệ quả: lỗi xác thực rải rác, khó tái hiện, khó chẩn đoán. Phòng tránh: giám sát số KSA, giữ dưới ngưỡng; tách workload sang nhiều cluster khi cần.
4. Burst khởi động đồng loạt chạm 500 connection. Scale-up lớn, hàng trăm Pod cùng khởi động và cùng gọi API. Vì sao xảy ra: không lường giới hạn connection/node. Hệ quả: độ trễ xác thực lúc khởi động, timeout giả. Phòng tránh: stagger khởi động, giảm mật độ Pod/node cho workload nặng-xác-thực, dựa vào caching token.
5. Nghĩ metadata server là endpoint stateless không cần quan tâm. Bỏ qua sức khỏe gke-metadata-server khi vận hành. Vì sao xảy ra: nó là thành phần hệ thống "Google lo". Hệ quả: khi nó OOM/crash, toàn bộ xác thực node chết mà không có cảnh báo nếu không giám sát. Phòng tránh: đưa health của metadata server DaemonSet vào dashboard vận hành; cảnh báo khi Pod restart bất thường.
GCP-native implementation guidance
# Kiểm tra metadata server DaemonSet đang khỏe
kubectl get pods -n kube-system -l k8s-app=gke-metadata-server -o wide
# Đếm tổng ServiceAccount toàn cluster (cảnh giác khi gần 3.000)
kubectl get serviceaccounts -A --no-headers | wc -l
# Xác nhận MỌI node pool ở chế độ GKE_METADATA (cờ đỏ nếu thấy GCE_METADATA)
gcloud container node-pools list --cluster CLUSTER --region REGION \
--format="table(name, config.workloadMetadataConfig.mode)"
# Bật GKE_METADATA cho một node pool còn ở chế độ cũ
gcloud container node-pools update POOL \
--cluster CLUSTER --region REGION \
--workload-metadata=GKE_METADATAKiểm chứng token exchange thực tế từ bên trong một Pod — gọi đúng endpoint mà client library gọi ở bước 1:
kubectl exec -it POD -n NS -- \
curl -s -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"
# -> phải trả về danh tính KSA-mapped, KHÔNG phải node SANếu lệnh trên trả về email của node service account (thường là ...-compute@developer.gserviceaccount.com), đó là dấu hiệu chắc chắn node pool chưa ở GKE_METADATA hoặc Pod dùng hostNetwork — token exchange không diễn ra. Quy trình debug đầy đủ ở file 07.
Operational implications
Metadata server biến Workload Identity từ một khái niệm bảo mật thành một thành phần hạ tầng cần vận hành như mọi thành phần hạ tầng khác: nó có sức khỏe, có giới hạn tài nguyên, có bottleneck, có failure mode. Đội ngũ vận hành GKE ở quy mô lớn phải coi gke-metadata-server ngang hàng với CoreDNS hay kube-proxy về mức độ quan trọng — khi nó suy giảm, không phải một workload mà mọi workload trên node mất khả năng gọi Google API. Đáng tiếc nó thường vô hình cho đến lúc hỏng.
Hệ quả kiến trúc sâu hơn: vì token exchange là cấp node và có bottleneck cấp node, mật độ Pod và phân bố KSA trên node trở thành yếu tố thiết kế bảo mật-hiệu năng, không chỉ là chuyện scheduling. Một node nhồi quá nhiều Pod nặng-xác-thực không chỉ tốn CPU/memory mà còn ép một metadata server duy nhất qua giới hạn connection của nó. Đây là một ví dụ điển hình cho luận điểm rằng ở quy mô lớn, các tầng tưởng độc lập (scheduling, networking, security, identity) thực ra ràng buộc lẫn nhau, và thiết kế tốt đòi hỏi nhìn chúng cùng lúc.
File 04 tiếp nối bằng câu hỏi authorization: federated token mà metadata server vừa lấy về có quyền gì — và đó là nơi hai mô hình IAM binding (principal trực tiếp vs impersonation GSA) phân nhánh.