Skip to main content

Kubernetes Networking

Service — Stable Endpoint สำหรับ Pod ที่ตายเกิดใหม่ตลอดเวลา​

Pod มี IP ของตัวเองแต่ IP นั้นเปลี่ยนทุกครั้งที่ Pod ถูกสร้างใหม่ (restart, reschedule, scale) — Service คือ abstraction ที่ให้ stable IP/DNS name คงที่ ที่ route ไปยัง Pod กลุ่มหนึ่งผ่าน label selector โดยอัตโนมัติ

apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80 # port ที่ Service เปิด
targetPort: 8080 # port ที่ container ฟังจริง
type: ClusterIP

Service Types​

Typeใช้เมื่อ
ClusterIP (default)เข้าถึงได้เฉพาะภายใน cluster — ใช้สำหรับ internal communication ระหว่าง service
NodePortเปิด port คงที่ (30000-32767) บนทุก Node — ใช้ตอน dev/test หรือเมื่อไม่มี LoadBalancer
LoadBalancerขอ external load balancer จาก cloud provider (AWS ELB, GCP LB) — ใช้ expose service ออกอินเทอร์เน็ต
ExternalNameทำ DNS CNAME ไปยัง external service ภายนอก cluster (ไม่มี selector/proxy เกี่ยวข้อง)

NodePort และ LoadBalancer include พฤติกรรมของ ClusterIP ด้วยเสมอ (เป็น superset ไม่ใช่แยกจากกัน)

🧠 จำง่ายๆ

"4 ชนิด Service เรียงจากในสุดไปนอกสุด": ClusterIP (ในบ้าน) → NodePort (หน้าประตูรั้วบ้านทุก หลัง) → LoadBalancer (ป้ายถนนใหญ่ชี้เข้ามา) → ExternalName (ป้ายบอกทางไปบ้านคนอื่นเลย ไม่ใช่ บ้านตัวเอง) ยิ่งนอกยิ่งเปิดกว้างสู่ภายนอกมากขึ้น

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

Service ที่ type: ClusterIP ยังมีอีกโหมดที่คนมักไม่รู้จัก: clusterIP: None เรียกว่า Headless Service — ตอนนั้น Service จะไม่มี virtual IP เลย แต่ CoreDNS จะ return IP ของ Pod ทุกตัวที่ match selector โดยตรง (A record หลายตัว) แทนที่จะ load-balance ผ่าน kube-proxy เป็นสิ่งที่ StatefulSet ใช้เป็นมาตรฐาน เพราะต้องการให้ client resolve ไปหา Pod ตัวใดตัวหนึ่งเจาะจงได้ (เช่น db-0.db.default.svc.cluster.local ชี้ไปที่ Pod db-0 ตรงๆ) ไม่ใช่โดน load-balance ไปสุ่ม Pod เหมือน Service ปกติ — เป็นกลไกที่ทำให้ StatefulSet มี "stable network identity" ได้จริงตามที่เอกสารอ้างถึง

kube-proxy & Service Routing​

kube-proxy รันบนทุก Node มีหน้าที่เขียน network rule (ส่วนใหญ่ผ่าน iptables หรือ IPVS ในเวอร์ชันใหม่) ให้ traffic ที่ยิงไปยัง Service IP ถูก load-balance ไปยัง Pod จริงที่ backend — นี่คือกลไกเบื้องหลังที่ทำให้ Service "ดูเหมือน" มี IP เดียวแต่จริงๆ กระจายไปหลาย Pod

🧠 จำง่ายๆ

"kube-proxy ไม่ใช่ proxy จริงๆ (ตั้งแต่โหมด iptables)" ชื่อหลอกคนทั่วไปให้คิดว่า traffic วิ่งผ่านมันเหมือน reverse proxy ทั่วไป แต่จริงๆ มันแค่ "เขียนกฎจราจร" ไว้ล่วงหน้าแล้วก็เดินจาก ไป — traffic จริงวิ่งตามกฎ iptables/IPVS ที่ kernel จัดการเอง ไม่ได้วิ่งผ่านตัว process kube-proxy เลย

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

โหมด iptables (ซึ่งเคยเป็น default มานาน) มีข้อจำกัดเรื่อง performance ที่ชัดเจน: กฎ iptables rule ที่ kube-proxy สร้างเป็น linear chain ที่ kernel ต้อง traverse ทีละกฎจนกว่า จะเจอ match — เมื่อ cluster มี Service เยอะมาก (หลักพันตัว) เวลา lookup ต่อ packet จะช้าลง แบบ O(n) โหมด IPVS (ที่เป็นทางเลือกใหม่กว่า) ใช้ hash table ภายใน kernel ทำให้ lookup เป็น O(1) ไม่ว่าจำนวน Service จะเยอะแค่ไหน — cluster ขนาดใหญ่ระดับ production มักสลับไปใช้ IPVS ด้วยเหตุผลนี้โดยเฉพาะ แต่ cluster เล็กๆ ส่วนใหญ่ไม่เคยรู้สึกถึงความต่างเลยเพราะ Service มีไม่ ถึงจุดที่ performance ต่างกันชัดเจน

DNS ภายใน Cluster​

Kubernetes มี internal DNS (CoreDNS) ที่ resolve ชื่อ Service อัตโนมัติ — Pod ใน namespace เดียวกันเรียก my-app ได้เลย ส่วนข้าม namespace ต้องใช้ my-app.<namespace>.svc.cluster.local

# จาก Pod ใดๆ ใน namespace "production"
curl http://my-app # resolve ภายใน namespace เดียวกัน
curl http://my-app.staging.svc.cluster.local # ข้าม namespace
🧠 จำง่ายๆ

"ชื่อสั้นใช้ในบ้าน ชื่อเต็มใช้ข้ามบ้าน" — เหมือนเรียกชื่อเล่นได้ถ้าอยู่บ้านเดียวกัน (namespace เดียวกัน) แต่ถ้าข้ามบ้านต้องเรียกชื่อเต็มพร้อมนามสกุล (<service>.<namespace>.svc.cluster.local) ถึงจะรู้ว่าหมายถึงใคร

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

Pod ที่ curl ชื่อ service สั้นๆ แล้วทำงานช้าผิดปกติ (latency สูงกว่าที่ควร) มักมีสาเหตุมาจาก ndots:5 ซึ่งเป็นค่า default ใน /etc/resolv.conf ของทุก Pod — มันทำให้ query domain ที่ มี dot น้อยกว่า 5 ตัว (เช่น my-app มี 0 dot, หรือแม้แต่ external domain เต็มรูปแบบอย่าง api.github.com ที่มีแค่ 2 dot) ถูก ลองต่อท้ายด้วย search domain ภายใน cluster ก่อนเสมอ (เช่น my-app.default.svc.cluster.local ก่อน แล้วค่อย fallback ไปลองแบบอื่น) ทำให้ query ไป external domain หนึ่งครั้งกลายเป็นต้อง query DNS 4-5 รอบ กว่าจะ resolve สำเร็จจริง — เป็นสาเหตุ latency ที่พบบ่อยมากในระบบที่เรียก external API เยอะ วิธีแก้คือใส่ . ต่อท้าย FQDN (api.github.com.) หรือปรับ dnsConfig.options ของ Pod โดยตรง

Ingress — Layer 7 Routing เข้า Cluster​

Service ประเภท LoadBalancer เปลือง external LB ต่อ 1 service (แพงและไม่ยืดหยุ่น) — Ingress แก้ปัญหานี้ด้วยการเป็น reverse proxy ตัวเดียว ที่ route traffic เข้าหลาย Service ตาม hostname/path โดยใช้ external LB เพียงตัวเดียว

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: api.example.com
http:
paths:
- path: /users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 80
- path: /orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
ข้อควรระวัง

Ingress object เป็นแค่ "spec" — ต้องมี Ingress Controller (เช่น nginx-ingress, Traefik, หรือของ cloud provider) รันอยู่ใน cluster ถึงจะทำงานได้จริง ไม่ใช่ built-in feature ที่ทำงานเองโดยไม่ต้องติดตั้งอะไรเพิ่ม

🧠 จำง่ายๆ

"Ingress = ใบสั่งงาน, Ingress Controller = คนลงมือทำจริง" — เขียน YAML อย่างเดียวไม่มีอะไร เกิดขึ้น เหมือนเขียนใบสั่งงานทิ้งไว้บนโต๊ะ ต้องมี "พนักงาน" (controller) มาอ่านใบสั่งงานแล้ว ไปตั้งค่า reverse proxy จริงให้

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

Ingress Controller แต่ละเจ้าตีความ annotation ต่างกัน (เพราะ Ingress spec มาตรฐานของ Kubernetes เองค่อนข้าง minimal ไม่ครอบคลุม feature ขั้นสูงอย่าง rate limiting, canary deployment) ทำให้ annotation อย่าง nginx.ingress.kubernetes.io/... ใช้ได้กับ nginx-ingress เท่านั้น ย้ายไปใช้ Traefik จะต้องเขียน annotation คนละชุดใหม่ทั้งหมด — ปัญหา vendor lock-in นี้ คือเหตุผลหลักที่ Kubernetes สร้าง API ใหม่ชื่อ Gateway API (kind: Gateway, HTTPRoute) ขึ้นมาแทนที่ Ingress ในระยะยาว โดยออกแบบให้ feature ขั้นสูงเหล่านี้เป็นส่วนหนึ่งของ spec มาตรฐานแทนที่จะต้องพึ่ง annotation เฉพาะของแต่ละ vendor — รู้เรื่องนี้แล้วตอบคำถาม "Ingress กับ Gateway API ต่างกันยังไง" ได้ลึกกว่าคนทั่วไปมาก

NetworkPolicy — Firewall ระดับ Pod​

Default ของ Kubernetes คือ Pod ทุกตัวคุยกันได้หมด (flat network, allow-all) — NetworkPolicy ใช้จำกัด traffic เข้า/ออก Pod แบบ allow-list (ต้องมี CNI plugin ที่รองรับ เช่น Calico, Cilium — ไม่ใช่ทุก CNI รองรับ)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-only-frontend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080

NetworkPolicy ข้างบน: Pod ที่มี label app: backend รับ traffic ได้ เฉพาะ จาก Pod ที่มี label app: frontend บน port 8080 เท่านั้น — Pod อื่นทั้งหมดถูกบล็อก (เพราะเมื่อมี NetworkPolicy ที่ match selector หนึ่งตัว จะเปลี่ยนจาก allow-all เป็น deny-all-except-listed สำหรับ Pod นั้นทันที)

🧠 จำง่ายๆ

"ไม่มี NetworkPolicy = ประตูเปิดหมด, มี 1 ข้อ = ล็อกประตูแล้วแง้มเฉพาะที่ระบุ" — กฎข้อสำคัญคือ "additive" (บวกเพิ่ม ไม่ใช่แทนที่กัน) ถ้ามีหลาย NetworkPolicy match Pod เดียวกัน traffic จะผ่าน ได้ถ้าอย่างน้อยหนึ่งข้อ อนุญาต (union ของทุกกฎ ไม่ใช่ intersection)

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

policyTypes: [Ingress] ในตัวอย่างข้างบนควบคุมแค่ traffic ขาเข้า — traffic ขาออก (egress) จาก Pod backend ยังเปิดกว้างเหมือนเดิมทุกประการ (เพราะไม่ได้ระบุ Egress ใน policyTypes) หลายทีมเข้าใจผิดว่าตั้ง NetworkPolicy แล้ว Pod นั้น "ปลอดภัย" ทั้งสองทิศทาง ทั้งที่จริงๆ ต้องประกาศ Egress แยกต่างหากถ้าต้องการจำกัดขาออกด้วย และที่สำคัญกว่านั้น: NetworkPolicy ทำงานที่ layer 3/4 เท่านั้น (IP + port) ไม่รู้จัก HTTP path, header, หรือ mTLS identity เลย — ถ้าต้องการ fine-grained authorization ระดับ application (เช่น "endpoint นี้เท่านั้นที่เรียกได้") ต้องใช้ service mesh (Istio, Linkerd) เสริมเข้ามา NetworkPolicy อย่างเดียวให้ได้แค่ coarse-grained network segmentation เท่านั้น

English Summary

Pod IPs are ephemeral; a Service gives a stable virtual IP/DNS name that load-balances to matching Pods via kube-proxy rewriting node-level routing rules (iptables is O(n) per lookup; IPVS is O(1), which matters at high Service counts). A Headless Service (clusterIP: None) skips the virtual IP entirely and returns individual Pod IPs via DNS — the mechanism behind StatefulSet's stable per-replica addressing. ClusterIP is internal-only; NodePort/ LoadBalancer build on top of it. Watch out for ndots:5 in Pod DNS config inflating external DNS lookups into 4-5 queries. Ingress is an L7 reverse-proxy spec needing a separate Ingress Controller — vendor-specific annotations for advanced features (rate limiting, canary) are why Kubernetes is moving toward the standardized Gateway API. By default all Pods can reach each other; NetworkPolicy rules are additive (union, not intersection) across multiple matching policies, only cover L3/L4 (not HTTP-level authorization — that needs a service mesh), and ingress/egress must be declared separately in policyTypes.