Multi-layer load balancing คือการที่ request หนึ่งตัวถูกกระจายโหลดหลายครั้งระหว่างทางไปถึง pod เริ่มจาก DNS หรือ global load balancer เลือก region, network load balancer เลือก node, ingress controller เลือก Service และ Service เลือก pod แต่ละชั้นแก้ปัญหาคนละเรื่อง และคำถามแนว "ตรงนี้เป็นคอขวดไหม" ส่วนใหญ่จะตอบได้เองเมื่อรู้ว่าชั้นไหนทำหน้าที่อะไร
บทความนี้เริ่มจากความกังวลที่เจอบ่อย ว่า NGINX ingress ตัวเดียวที่อยู่หน้าทุก service ต้องเป็นคอขวดแน่ ๆ แล้วไล่ออกไปทีละชั้น ได้แก่การ scale ingress, DNS round-robin, GSLB, Anycast, Cloudflare Load Balancing และ externalTrafficPolicy
Ingress controller ตัวเดียวเป็นคอขวดไหม
เป็นได้ แต่ไม่ใช่แบบที่เห็นตอนแรก
Ingress controller เป็นส่วนประกอบเดียวในเชิง logical ไม่ได้แปลว่ามี pod เดียว มันรันเป็น Deployment ที่ scale ได้หลาย replica หรือเป็น DaemonSet ที่มี pod บนทุก node และ external load balancer ที่อยู่ข้างหน้าก็กระจาย connection ไปยัง pod เหล่านั้นอยู่แล้ว ก่อน request จะถึงกฎ routing จึงผ่านการกระจายโหลดมาแล้วสองชั้น
Ingress ยังรับ traffic น้อยกว่าที่หลายคนคิด มันดูแลเฉพาะ traffic แบบ north-south คือจากนอก cluster เข้ามา ส่วน traffic แบบ east-west เช่น service หนึ่งเรียกอีก service หรือ backend คุยกับ database จะวิ่งผ่าน ClusterIP Service โดยตรง ไม่ผ่าน ingress เลย ในระบบ microservice traffic กลุ่มนี้มักเป็นส่วนใหญ่
ถ้า ingress รับโหลดหนักจริง มีสามทางให้เลือก:
- Scale ตัว controller เพิ่ม replica หรือใส่ HPA ตาม CPU pod ของ ingress ถือ connection ที่อยู่ยาว จึงควร scale down ช้า ๆ
- รันหลาย controller แยกด้วย IngressClass เช่น controller หนึ่งสำหรับ traffic สาธารณะ อีกตัวสำหรับ traffic ภายใน หรือแยก controller ให้ domain ที่โหลดหนักโดยเฉพาะ แต่ละชุดมี load balancer ของตัวเอง
- อย่าให้การเรียกภายในวิ่งผ่าน ingress service ควรเรียกกันผ่าน DNS ภายใน cluster ไม่ใช่ผ่าน hostname สาธารณะ
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ingress-controller
namespace: ingress
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ingress-controller
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
behavior:
scaleDown:
stabilizationWindowSeconds: 600รายละเอียดพฤติกรรมของ HPA อยู่ใน Kubernetes autoscaling ข้อสังเกตสำหรับคนที่ใช้ controller ingress-nginx ของ community: โปรเจกต์นี้หยุดการดูแลไปตั้งแต่มีนาคม 2026 ทุกอย่างในบทความนี้ใช้ได้เหมือนกันกับ ingress controller ตัวอื่นและ implementation ของ Gateway API
ทำไม DNS round-robin อย่างเดียวถึงไม่พอ
ไอเดียถัดมาที่มักคิดกันคือรัน ingress หลายชุด แต่ละชุดมี public IP ของตัวเอง แล้วผูก IP ทั้งหมดไว้ใต้ hostname เดียว ระบบใหญ่ใช้ DNS เป็นชั้นแรกจริง แต่ถ้าใช้ DNS อย่างเดียวจะมีจุดอ่อนสามข้อ:
- ไม่มี health check ถ้าชุดที่อยู่หลัง IP หนึ่งล่ม DNS ก็ยังแจก IP นั้นต่อไป client ที่ได้ IP นั้นจะเข้าไม่ได้จนกว่าจะมีคนแก้ record และ cache หมดอายุ
- Cache ทำให้กระจายไม่สม่ำเสมอ resolver และ client cache คำตอบไว้ตาม TTL และบางตัวไม่เคารพ TTL ที่สั้นมาก ผู้ใช้จำนวนมากที่อยู่หลัง resolver เดียวกันอาจไปลง IP เดียวกันหมด
- ไม่รู้โหลดจริง round-robin วน record ไปเรื่อย ๆ และยังส่งคนไปยังจุดที่แน่นอยู่แล้ว
เทคนิคในหัวข้อถัดไปแต่ละตัวแก้จุดอ่อนเหล่านี้ได้อย่างน้อยหนึ่งข้อ
GSLB และ geo-based routing
Global Server Load Balancing (GSLB) คือ DNS ที่ตัดสินใจได้ ผู้ให้บริการจะ health-check endpoint ของคุณอยู่ตลอด แล้วตอบแต่ละ query ตามผลที่ได้:
- Endpoint ที่ health check ไม่ผ่านจะถูกเอาออกจากคำตอบอัตโนมัติ
- คำตอบขึ้นกับตำแหน่งของผู้ใช้ได้ ผู้ใช้ในไทยได้ region เอเชีย ผู้ใช้ในยุโรปได้ region ยุโรป traffic จึงไม่ต้องวิ่งข้ามโลกโดยไม่จำเป็น
- ใช้ weight หรือค่า latency ที่วัดได้ในการย้าย traffic ระหว่าง region ได้
Amazon Route 53, Cloudflare และ NS1 มีความสามารถนี้ ซึ่งแก้จุดอ่อนข้อแรกและข้อสามได้ แต่ข้อสองยังอยู่ เพราะ failover ยังต้องรอ cache หมดอายุ record ที่อาจต้อง failover จึงควรตั้ง TTL สั้น
Anycast และ BGP
Anycast ใช้แนวทางต่างออกไป แทนที่จะแจก IP หลายตัว คุณประกาศ IP เดียวกัน จากหลายที่ผ่าน BGP แล้ว routing ของอินเทอร์เน็ตจะพา client แต่ละคนไปยังจุดที่ใกล้ที่สุดในเชิง network ถ้าจุดหนึ่งล่ม มันจะถอน route ออก และ traffic จะไหลไปจุดที่ใกล้รองลงมา client ยังใช้ IP เดิม จึงไม่ต้องรอ DNS cache
CDN และผู้ให้บริการ DNS รายใหญ่ทำงานแบบนี้ ต้องมี IP space และ BGP peering ของตัวเอง หรือใช้ผู้ให้บริการที่มี anycast network ให้ แนวคิดเดียวกันนี้ยังโผล่ใน data center ด้วย MetalLB ในโหมด BGP จะประกาศ Service IP จาก node ใน cluster ไปยัง router แล้ว router กระจาย traffic ไปหลาย node ด้วย ECMP
ข้อควรระวังคือ ถ้า routing เปลี่ยนระหว่างที่ TCP connection ยาว ๆ ยังเปิดอยู่ packet อาจไปโผล่อีกจุดและ connection จะหลุด Anycast จึงเหมาะกับ connection สั้น หรือวางไว้หน้าชั้น proxy ที่ terminate connection
| เทคนิค | รู้สถานะ health | ความเร็ว failover | รู้ตำแหน่งผู้ใช้ | สิ่งที่ต้องมี |
|---|---|---|---|---|
| DNS round-robin | ไม่ | แก้มือ บวกรอ TTL | ไม่ | DNS ทั่วไป |
| GSLB / smart DNS | ใช่ | ขึ้นกับ TTL | ใช่ | Managed DNS ที่มี health check |
| Anycast + BGP | ใช่ ผ่านการถอน route | ตาม routing convergence ไม่ต้องรอ DNS | ใช่ ตามระยะทาง network | IP space และ BGP หรือผู้ให้บริการ |
ตั้งค่า Cloudflare Load Balancing
Cloudflare Load Balancing รวมแนวคิดข้างต้นไว้ในสามส่วน:
- Monitor กำหนดวิธีเช็ค health เช่น HTTPS
GET /healthzที่ต้องได้200 - Pool กลุ่มของ endpoint มักจัดหนึ่ง pool ต่อ region และถูกตรวจด้วย monitor endpoint ที่ไม่ผ่านจะถูกเอาออกจาก pool อัตโนมัติ
- Load balancer ผูกกับ hostname ตัดสินว่าแต่ละ request จะไป pool ไหน และ failover ตามลำดับอย่างไร
Monitor (POST /accounts/{account_id}/load_balancers/monitors) ที่ต้อง retry พลาดสองครั้งก่อนจะถือว่า endpoint ไม่ healthy:
{
"type": "https",
"method": "GET",
"path": "/healthz",
"expected_codes": "200",
"interval": 60,
"timeout": 5,
"retries": 2
}Pool ต่อ region (POST /accounts/{account_id}/load_balancers/pools) โดย endpoint แต่ละตัวคือ public IP ของ ingress หนึ่งชุด:
{
"name": "asia-pool",
"monitor": "<monitor_id>",
"origins": [
{ "name": "sg-ingress-1", "address": "203.0.113.10", "enabled": true },
{ "name": "sg-ingress-2", "address": "203.0.113.11", "enabled": true }
]
}Load balancer (POST /zones/{zone_id}/load_balancers) ที่ steer ตาม region และมี fallback pool:
{
"name": "www.example.com",
"proxied": true,
"steering_policy": "geo",
"region_pools": {
"SEAS": ["<asia_pool_id>"],
"WEU": ["<europe_pool_id>"]
},
"default_pools": ["<asia_pool_id>", "<europe_pool_id>"],
"fallback_pool": "<asia_pool_id>"
}เมื่อตั้ง proxied: true edge แบบ anycast ของ Cloudflare จะ terminate connection ของ client แล้วส่ง request ต่อไปยัง pool ที่เลือก failover จึงไม่ขึ้นกับ DNS cache ถ้าใช้โหมด DNS-only Cloudflare จะตอบ DNS ด้วย IP ของ endpoint ที่ healthy ทำงานแบบ GSLB รวมถึงมีข้อจำกัดเรื่อง TTL ด้วย
Steering policy กำหนดวิธีเลือก pool:
| Steering | วิธีเลือก pool |
|---|---|
| Off (failover) | ใช้ pool ตามลำดับที่ระบุ ย้ายไปตัวถัดไปเมื่อตัวก่อนหน้าไม่ healthy |
| Random | สุ่มแบบถ่วงน้ำหนัก เช่น 70% กับ 30% |
| Geo | map region ของผู้ใช้ไปยัง pool |
| Dynamic | เลือก round-trip time ต่ำสุด ซึ่งวัดจาก health check ที่ยิงจาก data center ของ Cloudflare |
| Proximity | pool ที่ใกล้ที่สุดตามพิกัดที่กำหนดไว้ |
| Least outstanding requests | pool ที่มี request ค้างน้อยที่สุด |
ภายใน pool เดียวกัน ยังเลือก endpoint steering ได้อีก เช่น random, แบบ hash, least outstanding requests หรือ least connections
Load balancing สี่ชั้น
| ชั้น | ตัวอย่าง | ระดับ | กระจายตาม | หน้าที่ |
|---|---|---|---|---|
| 1. Global | Cloudflare LB, Route 53 | DNS หรือ L7 | ตำแหน่งผู้ใช้, health ของ pool, latency | เลือก region หรือ data center |
| 2. External LB | Network LB ของ cloud, MetalLB | L4 | health ของ node, connection | พา traffic เข้า cluster กระจายไปยัง node ที่รัน ingress |
| 3. Ingress | NGINX, Traefik, Envoy | L7 | host, path, header | route ไปยัง Service ที่ถูกต้อง, terminate TLS |
| 4. Service | kube-proxy หรือ eBPF dataplane | L4 | endpoint ที่ Ready | เลือก pod |
ชั้นที่ 4 คือชั้นที่คนมักลืม Service ทุกตัวเลือกเฉพาะ pod ที่ readiness probe ผ่าน pod ที่ readiness probe ล้มจึงถูกถอดออกจากการรับ traffic ที่ชั้นนี้ อ่านกลไกเต็ม ๆ ได้ใน Kubernetes Service แต่ละประเภท
externalTrafficPolicy: Cluster vs Local
ระหว่างชั้นที่ 2 กับชั้นที่ 4 มีค่าหนึ่งของ Service ที่กำหนดว่า traffic จะเดินทางอย่างไรเมื่อถึง node
Cluster (ค่าเริ่มต้น) node ไหนก็รับ traffic ของ Service ได้ ถ้า node นั้นไม่มี pod ที่ตรง kube-proxy จะส่งต่อไปยัง pod บน node อื่น
- โหลดกระจายสม่ำเสมอ และทุก node เป็นปลายทางได้
- บาง request ต้องเสีย network hop เพิ่มหนึ่งทอด
- เสีย source IP ของ client ไป เพราะ traffic ถูก SNAT เป็น address ของ node เพื่อให้คำตอบย้อนกลับทางเดิม pod จึงเห็น IP ของ node
Local node ส่ง traffic ให้เฉพาะ pod ที่รันอยู่บน node เดียวกันเท่านั้น
- ไม่มี hop เพิ่ม
- เก็บ source IP ของ client ไว้ได้
- Node ที่ไม่มี pod ในเครื่องจะไม่มี endpoint สำหรับ Service แบบ
LoadBalancerKubernetes จะจัดสรรhealthCheckNodePortให้ cloud load balancer ใช้ตรวจ แล้วข้าม node เหล่านั้นไป - โหลดอาจไม่สม่ำเสมอถ้า pod กระจุกอยู่ไม่กี่ node เพราะ external LB กระจายตาม node ไม่ใช่ตาม pod
สำหรับ ingress controller นิยมใช้ Local เพราะมักต้องการ IP จริงของ client ไว้ใช้กับ log, rate limiting และ allow-list ควรใช้คู่กับ DaemonSet หรือ topology spread constraint เพื่อให้ pod ของ ingress กระจายทั่วทุก node
apiVersion: v1
kind: Service
metadata:
name: ingress-controller
namespace: ingress
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: ingress-controller
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443ถ้า Local ไม่เหมาะกับระบบของคุณ load balancer หลายตัวรองรับ PROXY protocol ซึ่งส่ง IP ของ client ไปให้ ingress controller ภายในตัว connection
คำถามที่พบบ่อย
NGINX ingress controller เป็น single point of failure ไหม
ไม่เป็น ถ้ารันหลาย replica กระจายหลาย node หลัง load balancer จะเป็นก็ต่อเมื่อรัน pod เดียว หรือทุก replica ไปกองอยู่บน node เดียวกัน
GSLB ต่างจาก load balancer ทั่วไปอย่างไร
GSLB ตัดสินว่าผู้ใช้จะไปลง region หรือ data center ไหน ส่วนใหญ่ทำผ่านคำตอบ DNS ส่วน load balancer ทั่วไปกระจาย connection ไปยัง server ภายใน location เดียว
Anycast ใช้แทน DNS load balancing ได้ไหม
ใช้แทนได้ในเรื่อง failover และการพาไปจุดที่ใกล้ที่สุด เพราะ client ใช้ IP เดียวตลอด แต่หลายระบบยังใช้ DNS ครอบอีกชั้นเพื่อถ่วงน้ำหนักหรือย้าย traffic ด้วยมือ
ควรใช้ externalTrafficPolicy แบบ Local เมื่อไหร่
เมื่อ pod ต้องการ IP จริงของ client หรืออยากตัด hop ที่เพิ่มมา ต้องแน่ใจว่า load balancer health-check node ได้ และกระจาย pod ให้ทั่ว
Checklist สำหรับ multi-layer load balancing
- ชั้น global health-check ทุก region และมี fallback pool
- Record ที่อาจต้อง failover ตั้ง TTL สั้น
- Ingress controller รันหลาย replica กระจายหลาย node และมี HPA
- Traffic ที่หนักหรือ traffic ภายในมี IngressClass และ controller ของตัวเอง
- การเรียกระหว่าง service ใช้ DNS ภายใน cluster ไม่ผ่าน ingress
- เลือก
externalTrafficPolicyอย่างตั้งใจ และ IP ของ client ไปถึง log จริง - Readiness probe สะท้อนว่า pod พร้อมรับ traffic จริงหรือไม่
แต่ละชั้นควรทำงานของตัวเองให้ดีเพียงอย่างเดียว ชั้น global เลือก region, network LB พา traffic เข้า cluster, ingress ทำ routing และ Service เลือก pod ที่ healthy ถ้าอยากลงมือฝึกเรื่อง Service, Ingress และ autoscaling คอร์ส DevOps ฟรีของ Vectorkub สอนเรื่องเหล่านี้ทีละขั้น
