Skip to content

Network Endpoint Groups — Trỏ thẳng tới endpoint, bỏ qua VM

Why this matters in production

Network Endpoint Group (NEG) là khái niệm thay đổi đơn vị mà load balancer nhắm tới. Trước NEG, backend của một LB là một instance group — LB gửi traffic tới VM, rồi VM (qua kube-proxy/iptables trong GKE) lại chuyển tiếp tới container/Pod đích. Với NEG, backend là một tập endpoint trực tiếp(IP, port) của chính Pod, hoặc một dịch vụ serverless, hoặc một endpoint on-prem. LB nói chuyện thẳng với endpoint thật.

Sự thay đổi đơn vị này có hệ quả production rất cụ thể:

  • Bỏ một network hop. Container-native LB (NEG trỏ Pod IP) loại bỏ chặng VM→kube-proxy→Pod, giảm latency và tăng throughput. Đây không phải tối ưu vi mô — ở scale lớn, hop thừa này là nguồn của tail latency và phân phối lệch.
  • Health check chính xác tới Pod, không phải Node. Với instance group, LB health-check VM; nó không biết Pod cụ thể nào sống. Với NEG, health check (và readiness gate) tới đúng Pod — LB biết chính xác Pod nào sẵn sàng nhận traffic.
  • NEG là nền tảng của Ingress/Gateway hiện đại, serverless LB, PSC, và hybrid LB. Hầu hết kiến trúc LB hiện đại trên GCP đều xây trên NEG. Không hiểu NEG thì không hiểu được Ingress, Gateway API ([Chương 21]), hay vì sao Cloud Run đứng sau một ALB được.

Internal model — NEG là gì và các loại

Định nghĩa cơ chế

Theo tài liệu NEG: "Một network endpoint group (NEG) là một configuration object chỉ định một nhóm backend endpoint hoặc service." NEG không phải là một tài nguyên compute (không phải VM, không phải Pod) — nó là một object metadata liệt kê các endpoint mà backend service nên gửi traffic tới. Mỗi endpoint trong NEG mô tả một đích cụ thể, thường là (IP, port).

Điểm cơ chế quan trọng: NEG tách danh sách đích ra khỏi hạ tầng chứa đích. Một instance group ràng buộc backend vào vòng đời VM; một NEG chỉ là một danh sách (IP, port)thứ gì đó (NEG controller, bạn, hoặc Google) cập nhật. Điều này cho phép đích là Pod (vòng đời nhanh hơn VM nhiều), là dịch vụ serverless (không có VM nào cả), hay là endpoint ngoài Google Cloud.

Các loại NEG và endpoint của chúng

Loại NEG                Endpoint chứa gì                            LB dùng
──────────────────────────────────────────────────────────────────────────────────────
Zonal NEG               (IP, port) nội bộ → VM hoặc Pod GKE         Hầu hết mọi LB:
  • GCE_VM_IP_PORT      IP nội bộ hoặc alias IP + port              passthrough/proxy NLB,
                        (container-native: Pod IP)                  Application LB,
  • GCE_VM_IP           IP chính của NIC VM (không port)            Cloud Service Mesh
                        (dùng cho L4 backend-service NLB)

Serverless NEG          1 endpoint trỏ Cloud Run / App Engine /     External ALB,
                        Cloud Run functions / API Gateway           Regional Internal ALB

Internet NEG            1 endpoint internet ngoài Google Cloud      Cloud CDN, External/
  • INTERNET_IP_PORT    (IP:port hoặc FQDN:port)                    Internal ALB,
  • INTERNET_FQDN_PORT  (global NEG: 1 endpoint; regional: 256)     Cloud Service Mesh

Private Service         Trỏ tới Google-managed API hoặc managed     Application LB,
Connect (PSC) NEG       service qua PSC                             Proxy NLB

Hybrid Connectivity     Endpoint on-prem / cloud khác qua           Application & Proxy NLB
NEG (NON_GCP_..._PORT)  Interconnect/VPN                            (với routing hybrid)

Mỗi loại NEG mở ra một lớp kiến trúc:

  • Zonal NEG GCE_VM_IP_PORT là loại quan trọng nhất — nó là cơ sở của container-native load balancing: endpoint là (Pod IP, container port), nên LB trỏ thẳng tới Pod. Đây là "NEG" mà mọi kỹ sư GKE gặp.
  • Zonal NEG GCE_VM_IP chứa IP chính của VM (không port), dùng cho backend-service-based passthrough NLB (L4) — ví dụ GKE subsetting cho internal LB (file 07).
  • Serverless NEG cho phép đặt Cloud Run/App Engine sau một Application LB — để gắn custom domain, Cloud Armor, CDN, hay multi-region routing lên một dịch vụ serverless vốn không có các thứ đó.
  • Internet NEG cho phép một GCP LB trỏ tới một origin ngoài Google Cloud — ví dụ dùng Cloud CDN/Cloud Armor trước một origin chạy ở data center khác.
  • PSC NEGHybrid NEG mở rộng LB tới Google-managed services và tới on-prem/multi-cloud.

Container-native load balancing — bỏ hop kube-proxy

Đây là phần đắt giá nhất của file. Hiểu nó là hiểu vì sao NEG tồn tại trong thế giới GKE.

Không có container-native LB (instance group backend):

   LB ──► Node VM (NodePort) ──► kube-proxy/iptables ──► Pod
          (chọn 1 node bất kỳ)   (DNAT + có thể nhảy        (có thể ở node KHÁC)
                                  sang node khác)

Datapath này có hai vấn đề cơ chế. Thứ nhất, hop thừa: LB gửi tới một node, nhưng Pod đích có thể ở node khác, nên kube-proxy phải DNAT và forward lần nữa qua mạng — thêm một network hop, thêm latency, và che giấu client IP (SNAT). Thứ hai, health check mù: LB health-check node, không phải Pod, nên nó không biết Pod nào thực sự sẵn sàng; nó chỉ biết node còn sống.

Có container-native LB (NEG GCE_VM_IP_PORT):

   LB ──────────────────────► Pod (Pod IP : container port)
          (trỏ THẲNG Pod IP, không qua NodePort/kube-proxy)

Theo tài liệu container-native LB: "traffic được load balanced thẳng tới Pod, bỏ qua việc đi qua VM IP và mạng kube-proxy trên node. Luồng này loại bỏ network hop thừa, và cải thiện latency lẫn throughput." Vì LB nói thẳng với Pod IP (khả thi nhờ VPC-native cluster — Pod IP là địa chỉ thật trong VPC, xem Chương 7), không còn hop kube-proxy, không còn SNAT che client, và health check tới đúng Pod.

NEG controller — ai cập nhật danh sách endpoint

Vì NEG chỉ là một danh sách (IP, port), phải có thứ gì đó giữ danh sách đó đồng bộ với thực tế. Trong GKE, đó là NEG controller chạy trong control plane. Cơ chế:

  1. Bạn (hoặc GKE tự động) gắn annotation cloud.google.com/neg: '{"ingress": true}' (hoặc qua Gateway/Service) lên một Service.
  2. NEG controller watch các Pod khớp selector của Service đó, qua Endpoints/EndpointSlice của Kubernetes.
  3. Khi một Pod trở nên Ready (hoặc bị xóa, hoặc đổi IP), controller gọi Compute API để thêm/bớt endpoint (Pod IP, port) tương ứng trong NEG zonal của zone chứa Pod đó.
  4. Backend service trỏ tới các NEG này; LB datapath cập nhật theo.

Đây là một reconciliation loop điển hình: trạng thái mong muốn (Pod nào Ready) được phản ánh vào NEG qua API, eventual consistency. Khi Pod scale, controller cập nhật NEG; có một độ trễ ngắn giữa "Pod Ready trong K8s" và "endpoint xuất hiện trong NEG và được LB datapath nhận". Hiểu độ trễ này quan trọng cho rolling update (xem readiness gate bên dưới).

Readiness gate — đồng bộ vòng đời Pod với LB

Đây là cơ chế tinh tế giải quyết một race condition kinh điển. Vấn đề: Kubernetes coi một Pod "Ready" (theo readiness probe) trước khi endpoint của nó thực sự được program vào LB datapath. Nếu Service rolling update dựa vào "Pod Ready" của K8s để tắt Pod cũ, có thể xảy ra cửa sổ mà Pod mới đã Ready trong K8s nhưng LB chưa biết về nó — trong khi Pod cũ đã bị tắt → traffic rơi vào khoảng trống → 502.

Container-native LB giải quyết bằng readiness gate: GKE tiêm một readiness gate vào Pod, và Pod chỉ được coi là thật sự Ready khi load balancer đã xác nhận endpoint của Pod là healthy — không chỉ dựa vào in-cluster probe. Theo tài liệu, cơ chế này xác định Pod health "từ góc nhìn của load balancer, thay vì chỉ dựa vào in-cluster health probe". Hệ quả: rolling update chờ LB thật sự sẵn sàng nhận traffic trên Pod mới trước khi rút Pod cũ → không có khoảng trống → không 502 khi deploy. Đây là một ví dụ đẹp về việc đồng bộ hai control plane (Kubernetes và LB) qua một tín hiệu chung.

Constraints, trade-offs & failure modes

Điều kiện để container-native LB tự bật

Theo tài liệu, GKE tự bật container-native LB và gắn annotation NEG khi: cluster là VPC-native (Pod IP là alias IP thật, routable), không phải Shared VPC (trong một số phiên bản/cấu hình), HTTP load balancing add-on bật. Yêu cầu VPC-native là cốt lõi: container-native LB cần Pod IP routable trong VPC để LB trỏ thẳng tới — routes-based cluster (Pod IP không phải alias IP thật) không làm được. Đây là một lý do nữa để luôn dùng VPC-native cluster.

Failure mode: NEG sync lag trong scale nhanh

Khi HPA scale Pod rất nhanh (ví dụ traffic spike), có độ trễ giữa "Pod Ready" và "endpoint vào NEG + LB nhận". Trong cửa sổ này, Pod mới chưa nhận traffic dù đã chạy. Ngược lại, khi scale-in nhanh, endpoint cần được rút khỏi NEG kịp thời nếu không traffic tới Pod đã xóa → lỗi (đây là lý do connection draining + đợi NEG sync khi terminate Pod). Đây là eventual consistency của reconciliation loop, không tránh được hoàn toàn; thiết kế client retry và dùng readiness gate + draining để giảm thiểu.

Trade-off: NEG granular hơn nhưng nhiều object hơn

NEG cho phép trỏ Pod-level, nhưng đổi lại số lượng object và lời gọi API tăng (mỗi Pod là một endpoint, mỗi zone một NEG). Ở scale rất lớn (hàng nghìn Pod), điều này tạo áp lực lên NEG controller và Compute API quota. Đây là lý do GKE có các giới hạn về số endpoint mỗi NEG và vì sao subsetting (gom node thành nhóm) tồn tại cho L4. Với hầu hết workload, đây không phải vấn đề, nhưng ở scale rất lớn cần để ý quota Compute API.

Real-world scenario: Cloud Run sau Global ALB qua serverless NEG

Một dịch vụ chạy trên Cloud Run cần: custom domain với managed cert, Cloud Armor chống abuse, và CDN cho static path. Cloud Run "trần" không có sẵn các thứ này. Lời giải dùng serverless NEG:

  1. Tạo serverless NEG trỏ tới Cloud Run service.
  2. Đặt nó làm backend của một Global External ALB.
  3. Gắn managed SSL cert + SSL policy lên target proxy (file 09).
  4. Gắn Cloud Armor policy lên backend service (file 10).
  5. URL map route static path tới một backend bucket có CDN.

Cơ chế then chốt: serverless NEG là cầu nối đưa một tài nguyên không-có-VM (Cloud Run) vào mô hình backend service của ALB, qua đó kế thừa toàn bộ năng lực edge của GFE (TLS, WAF, CDN, anycast). Đây là minh họa rõ nhất rằng NEG tách "đích" khỏi "hạ tầng chứa đích".

Common mistakes / anti-patterns

Anti-pattern 1 — Dùng routes-based cluster rồi mong container-native LB. Hiểu lầm: nghĩ NEG hoạt động trên mọi cluster. Hậu quả: container-native LB không bật được vì Pod IP không routable trong VPC. Sửa: luôn dùng VPC-native cluster (alias IP) — yêu cầu bắt buộc cho NEG Pod-level.

Anti-pattern 2 — Tắt Pod cũ dựa vào K8s Ready, bỏ qua readiness gate. Hiểu lầm: tin "Pod Ready trong K8s = LB đã sẵn sàng". Hậu quả: rolling update tạo khoảng trống giữa lúc Pod mới chưa vào LB và Pod cũ đã tắt → 502. Sửa: để readiness gate của container-native LB điều phối; đừng bypass nó bằng probe tự chế.

Anti-pattern 3 — Quên rằng NEG sync là eventual. Hiểu lầm: kỳ vọng endpoint xuất hiện/biến mất tức thì khi Pod thay đổi. Hậu quả: trong scale nhanh, traffic tới Pod đã xóa hoặc Pod mới bị bỏ đói. Sửa: phối hợp connection draining + readiness gate + client retry; chấp nhận độ trễ reconciliation nhỏ.

References