Skip to content

Container-native Load Balancing & NEG Internals — Traffic Thẳng Đến Pod

Tại sao container-native LB là bước ngoặt về kiến trúc

Trước khi container-native load balancing (và NEGs) ra đời, GKE LB hoạt động theo mô hình Instance Group: GCP LB forward traffic đến một node (VM), sau đó kube-proxy trên node đó DNAT packet đến một Pod bất kỳ. Mô hình này có một vấn đề kiến trúc cơ bản: GCP LB không biết gì về Pods.

Hậu quả:

  • Health check của GCP LB check node (VM), không phải Pod. Node healthy → GCP LB gửi traffic, dù Pod trên node đó đang crash
  • Traffic phải đi qua thêm một hop (node → kube-proxy DNAT → Pod) — tăng latency
  • Load balancing không cân bằng thực sự: GCP LB phân phối đều đến nodes, nhưng số Pods trên mỗi node có thể khác nhau

Container-native LB với NEGs giải quyết bằng cách cho GCP LB "nhìn thấy" từng Pod endpoint trực tiếp.

Network Endpoint Groups (NEGs) — Mô hình nền tảng

NEG là gì về mặt kỹ thuật

NEG (Network Endpoint Group) là một GCP resource đại diện cho một nhóm network endpoints. Khác với Instance Group (nhóm VMs), NEG chứa các endpoints ở mức granular hơn: IP:port pairs.

Trong context GKE container-native LB, mỗi endpoint trong NEG là một Pod IP:containerPort pair. Đây là điểm then chốt: Pod IP là routable trong VPC-native cluster (nhờ alias IP ranges — xem Chương 7), nên GCP LB có thể kết nối trực tiếp mà không cần đi qua node.

GCP Backend Service

    ├── NEG (zone us-central1-a)
    │     ├── Endpoint: 10.0.1.5:8080  (Pod A trên node 1)
    │     ├── Endpoint: 10.0.1.6:8080  (Pod B trên node 1)
    │     └── Endpoint: 10.0.1.7:8080  (Pod C trên node 2)

    └── NEG (zone us-central1-b)
          ├── Endpoint: 10.0.2.3:8080  (Pod D trên node 3)
          └── Endpoint: 10.0.2.4:8080  (Pod E trên node 4)

Mỗi zone có một NEG riêng biệt. GCP LB nhận traffic và distribute across zones đến các endpoints — không phải đến nodes.

Loại NEGs trong GKE

GKE sử dụng Zonal NEG với endpoint type GCE_VM_IP_PORT. Nhưng endpoint không nhất thiết phải là VM IP — vì GKE dùng VPC-native (alias IP), Pod IPs là valid routable IPs trong VPC, nên NEG endpoint có thể là Pod IP trực tiếp.

Đây là điều mà docs ít giải thích rõ: khi GKE Ingress hay Gateway tạo NEG cho container-native LB, các endpoints trong NEG là Pod IPs (không phải node IPs). GCP LB forward packet đến Pod IP:containerPort — packet đi thẳng qua VPC routing đến Pod, không qua kube-proxy.

NEG Controller — Vòng đời và cơ chế

Controller chạy ở đâu

NEG controller là một component trong GKE control plane (Google-managed). Nó watch:

  • Service objects với annotation cloud.google.com/neg
  • Endpoints (hoặc EndpointSlice) objects tương ứng với các Services đó
  • Pod objects để track readiness state

Tự động enable container-native LB

Khi một GKE cluster đáp ứng các điều kiện sau, container-native LB được tự động enable (GKE 1.17+):

  • VPC-native cluster (alias IP) — bắt buộc vì Pod IPs phải routable
  • Không phải Shared VPC (có hạn chế riêng)
  • GKE Network Policy không được enable (có conflict với một số NEG behaviors trong versions cũ)
  • HttpLoadBalancing add-on enabled (mặc định)

Khi auto-enable, NEG controller tự động thêm annotation vào Services:

yaml
cloud.google.com/neg: '{"ingress": true}'

"ingress": true nghĩa là NEG sẽ được tạo và quản lý khi Service được tham chiếu bởi Ingress.

NEG lifecycle — Từ Service đến Endpoint

Bước 1: Service được tạo với annotation NEG.

Bước 2: NEG controller đọc annotation, tạo NEG resources trong GCP — một per zone mà cluster có nodes. Tên NEG được generated automatically, format: k8s1-{cluster-hash}-{namespace}-{service-name}-{port}-{hash}.

Bước 3: NEG controller bắt đầu watch EndpointSlice objects cho Service đó. Mỗi khi có Pod mới được schedule (và trở thành endpoint của Service), NEG controller gọi GCP API để add endpoint vào NEG tương ứng.

Bước 4: Khi Pod bị terminate hoặc remove khỏi Service (readiness failure, deletion), NEG controller gọi GCP API để remove endpoint khỏi NEG.

Pod created + passes readiness probe


Kubernetes Endpoints/EndpointSlice updated


NEG Controller detects change (Watch event)


GCP NEG API: attach endpoint (Pod IP:port)


GCP Backend Service: new endpoint available for routing


GCP Health Check: begin probing new endpoint

        ▼ (health check passes)
GCP LB: start routing traffic to this endpoint

Pod Readiness Gates — Synchronization quan trọng

Có một race condition tiềm ẩn: Pod trở thành Ready theo Kubernetes (readinessProbe pass), nhưng GCP health check chưa pass. Nếu traffic được route đến endpoint trong khoảng thời gian này, request sẽ fail.

GKE giải quyết bằng Pod Readiness Gates: NEG controller có thể inject một readiness gate vào Pod spec. Pod sẽ chỉ được mark Ready: true sau khi cả hai điều kiện thỏa mãn:

  1. Readiness probe của ứng dụng pass
  2. GCP health check confirm endpoint healthy
yaml
# Pod spec được inject readiness gate bởi NEG controller
readinessGates:
  - conditionType: "cloud.google.com/load-balancer-neg-ready"

Điều này đảm bảo Pod không nhận traffic từ GCP LB cho đến khi GCP health check thực sự xác nhận endpoint healthy — loại bỏ race condition.

Health checking ở Pod level

Sự khác biệt so với node-level health check

Với Instance Group backends, health check của GCP LB probe vào node:nodePort. Nếu node trả lời, backend được coi là healthy — dù Pod đang crash hay không tồn tại.

Với NEG-based backends, health check probe trực tiếp vào Pod IP:containerPort. Nếu Pod không trả lời (crash, OOMKilled, slow), chỉ endpoint cụ thể đó bị mark unhealthy — không ảnh hưởng các Pods khác trên cùng node.

Điều này tạo ra Pod-level granularity cho health: một Pod unhealthy trên node X không ảnh hưởng traffic đến Pods healthy trên cùng node X.

Health check configuration và defaults

Khi Ingress tạo Backend Service, GCP tạo health check tự động với defaults:

  • Protocol: HTTP
  • Path: /
  • Interval: 5 giây
  • Timeout: 5 giây
  • Healthy threshold: 1 success
  • Unhealthy threshold: 2 failures

Nếu ứng dụng không serve HTTP / hoặc cần authentication để trả 200, health check sẽ fail liên tục và endpoint sẽ không bao giờ được coi là healthy.

Customize health check qua BackendConfig (cho Ingress) hoặc HealthCheckPolicy (cho Gateway API):

yaml
# Gateway API: HealthCheckPolicy
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
  name: api-healthcheck
spec:
  targetRef:
    group: ""
    kind: Service
    name: api-service
  default:
    checkIntervalSec: 10
    timeoutSec: 5
    healthyThreshold: 1
    unhealthyThreshold: 2
    logConfig:
      enabled: true
    config:
      type: HTTP
      httpHealthCheck:
        port: 8080
        requestPath: /healthz

Lưu ý quan trọng về Gateway API: Theo tài liệu GKE, "Gateway does not infer health check parameters." Nếu ứng dụng không trả 200 cho GET /, phải explicitly cấu hình HealthCheckPolicy. Không như Ingress (cố gắng infer từ readinessProbe), Gateway API yêu cầu cấu hình tường minh.

Data path so sánh — Instance Group vs NEG

Instance Group path (legacy)

GCP LB (biết về nodes)

    ├── Node 1 (nodePort 31234) → kube-proxy iptables DNAT → Pod A hoặc Pod B hoặc Pod C
    ├── Node 2 (nodePort 31234) → kube-proxy iptables DNAT → Pod D
    └── Node 3 (nodePort 31234) → kube-proxy iptables DNAT → Pod E

Problems:

  • Double-hop: GCP LB → node → kube-proxy → Pod
  • GCP LB không biết Pod distribution: có thể gửi 50% traffic đến Node 1 có 1 Pod và 50% đến Node 2 có 5 Pods → uneven load
  • Health check tại node level: node up nhưng Pods down → traffic vẫn được gửi → 502/503

NEG path (container-native)

GCP LB (biết về Pod IPs)

    ├── Pod A (10.0.1.5:8080) - direct
    ├── Pod B (10.0.1.6:8080) - direct
    ├── Pod C (10.0.1.7:8080) - direct
    ├── Pod D (10.0.2.3:8080) - direct
    └── Pod E (10.0.2.4:8080) - direct

Benefits:

  • Single hop: GCP LB → Pod IP (packet đi qua VPC routing)
  • Load balancing dựa trên số Pods thực sự, không phải số nodes
  • Health check tại Pod level: Pod unhealthy → ngay lập tức không nhận traffic

Standalone NEGs — Manual management

Khi nào cần standalone NEGs

Ingress-managed NEGs được tạo và managed tự động bởi controller — bạn không can thiệp trực tiếp. Standalone NEGs cho phép bạn tự tạo và quản lý NEG, rồi attach thủ công vào Backend Service được tạo bên ngoài GKE.

Use cases:

  1. Multi-cluster backends: Tạo một GCP LB external, attach Pods từ nhiều GKE clusters khác nhau (mỗi cluster contribute NEGs)
  2. Custom load balancer configuration: Cần cấu hình LB nâng cao không được Ingress/Gateway expose
  3. Hybrid backends: Mix GKE Pods và VM endpoints trong cùng Backend Service
  4. Non-Ingress LBs: Dùng NEGs với Cloud CDN hay Cloud Armor độc lập với Ingress

Tạo standalone NEG

yaml
# Annotation trên Service: tạo standalone NEG với custom name
apiVersion: v1
kind: Service
metadata:
  name: api-service
  annotations:
    cloud.google.com/neg: |
      {
        "exposed_ports": {
          "8080": {
            "name": "my-custom-neg"
          }
        }
      }
spec:
  ports:
    - port: 8080
      targetPort: 8080
  selector:
    app: api

Với annotation này, NEG controller tạo một NEG tên my-custom-neg và sync Pod endpoints vào đó. NEG này không tự động được attach vào bất kỳ Backend Service nào — bạn phải manually attach:

bash
gcloud compute backend-services add-backend my-backend-service \
  --network-endpoint-group=my-custom-neg \
  --network-endpoint-group-zone=us-central1-a \
  --balancing-mode=RATE \
  --max-rate-per-endpoint=100 \
  --global

Lifecycle management của standalone NEGs

Khi Service bị xóa, NEG controller xóa NEG — nhưng nếu NEG đang được attach vào Backend Service, GCP API sẽ từ chối xóa. Cần detach khỏi Backend Service trước.

Pattern an toàn khi teardown:

  1. Remove NEG khỏi Backend Service (GCP CLI/Terraform)
  2. Đợi inflight connections drain (connectionDraining.drainingTimeoutSec)
  3. Xóa Service annotation → controller sẽ xóa NEG

Constraints và failure modes

Chỉ hoạt động với VPC-native clusters

Container-native LB yêu cầu Pod IPs phải là routable trong VPC — điều này chỉ đúng với VPC-native (alias IP) clusters. Routes-based clusters (legacy) không support NEG endpoint với Pod IPs.

Cố gắng enable container-native LB trên routes-based cluster sẽ không fail ngay mà tạo NEGs với node IPs thay vì Pod IPs — behavior âm thầm sai mà khó detect.

NEG sync delay

NEG controller không sync ngay lập tức. Sau khi Pod trở thành Ready, cần thêm thời gian để:

  1. EndpointSlice được update bởi Kubernetes control plane
  2. NEG controller nhận Watch event
  3. GCP NEG API call hoàn thành

Tổng delay có thể từ vài giây đến vài chục giây. Trong thời gian này, Pod đang chạy nhưng chưa nhận traffic từ GCP LB. Với Pod Readiness Gates enabled, Pod sẽ không được mark Ready cho đến khi sync hoàn thành — đây là behavior đúng đắn.

Maximum endpoints per NEG

GCP NEG có giới hạn số endpoints. Nếu một Service có quá nhiều Pods trong một zone, có thể hit giới hạn. Kiểm tra quota GCP cho network-endpoint-groups/max-endpoints-per-neg.

Firewall rules cho health checks

NEG-based health checks probe đến Pod IPs, không đến node IPs. Firewall rules phải allow traffic từ GCP health check probers (130.211.0.0/2235.191.0.0/16) đến Pod CIDRs, không chỉ đến node CIDRs.

GKE Ingress controller tự động tạo firewall rules này. Nhưng với standalone NEGs và manual Backend Services, phải tự tạo:

bash
gcloud compute firewall-rules create allow-health-check-to-pods \
  --network=my-vpc \
  --action=allow \
  --direction=ingress \
  --source-ranges=130.211.0.0/22,35.191.0.0/16 \
  --rules=tcp:8080 \
  --target-tags=gke-nodes

References