กลับไปยังบทความ

DevOps

Kubernetes Architecture: เข้าใจ Control Plane และ Worker Node ทีละส่วน

อธิบาย Kubernetes architecture ทีละส่วน ทั้ง API server, etcd, scheduler, controller, kubelet และ kube-proxy ไล่ดูว่าเกิดอะไรขึ้นเมื่อสั่ง kubectl apply และ networking กับ namespace เข้ามาเกี่ยวอย่างไร

24 กันยายน 256910 นาทีในการอ่าน

kubernetes architecture control plane and nodes

Kubernetes architecture แบ่งคลัสเตอร์ออกเป็นสองฝั่ง ฝั่งแรกคือ control plane ที่เก็บ desired state และเป็นผู้ตัดสินใจ อีกฝั่งคือ worker node ที่รัน container ของคุณจริง ๆ ทุก component คุยกันผ่าน API server เพียงตัวเดียว และทุกการเปลี่ยนแปลงเดินตาม loop เดียวกัน: มีคนบันทึกว่าต้องการอะไร, controller เห็นว่าของจริงยังไม่ตรงกับที่ต้องการ, แล้ว agent บน node ก็ลงมือทำให้ตรง

พอเข้าใจว่าแต่ละ component ทำหน้าที่อะไร คุณจะตอบได้ทันทีว่าทำไมคลัสเตอร์ยังรับ traffic ได้ตอน control plane ล่ม, ทำไมการเสีย etcd ถึงเป็นหายนะ และต้องไปดูตรงไหนเมื่อ Pod ค้างอยู่ที่ Pending

สองฝั่งของ Kubernetes architecture

text
CONTROL PLANE
  kubectl --> kube-apiserver <--> etcd
              ^  scheduler
              ^  controller-manager
              ^  cloud-controller-manager (cloud only)
              |
              | watch assigned Pods, report status
              |
WORKER NODES (one set per node)
  kubelet --> container runtime (CRI) --> Pods
  kube-proxy --> Service routing rules

ถ้าใช้ managed service อย่าง EKS, GKE หรือ AKS ผู้ให้บริการจะดูแล control plane ให้ และคุณจะเห็นแค่ API endpoint ส่วนคลัสเตอร์ที่ตั้งเองด้วย kubeadm, component ของ control plane จะรันเป็น static Pod อยู่ใน namespace kube-system

Component ฝั่ง control plane

kube-apiserver: ประตูเดียวของคลัสเตอร์

API server เป็น REST API ที่ทุกอย่างต้องผ่าน ไม่ว่าจะเป็น kubectl, controller, scheduler หรือ kubelet ทุกตัว ทุก request เดินผ่าน pipeline เดียวกัน:

  1. Authentication: ใครเป็นคนเรียก (certificate, token, OIDC)
  2. Authorization: คนนี้มีสิทธิ์ทำสิ่งนี้ไหม ส่วนใหญ่ตัดสินด้วย RBAC
  3. Admission: mutating webhook และ plugin แก้ไข object ได้ จากนั้น validating webhook มีสิทธิ์ปฏิเสธ
  4. Persistence: เขียน object ลง etcd

API server ยังให้บริการ watch ด้วย คือ component ต่าง ๆ subscribe รอรับการเปลี่ยนแปลงแทนการ poll ซ้ำ ๆ ตัว API server เองเป็น stateless จึง scale ได้ด้วยการรันหลาย replica หลัง load balancer ถ้ามันล่ม คุณจะแก้ไขอะไรในคลัสเตอร์ไม่ได้เลย แต่ Pod ที่รันอยู่แล้วยังทำงานต่อตามปกติ

etcd: source of truth ของคลัสเตอร์

etcd เป็น key-value store แบบกระจายที่เก็บทุก object ในคลัสเตอร์ ตั้งแต่ Deployment, Pod, Service, ConfigMap, Secret ไปจนถึงข้อมูล node มีแค่ API server เท่านั้นที่คุยกับ etcd ตรง ๆ ถ้า etcd หายไปโดยไม่มี backup คลัสเตอร์ก็เท่ากับความจำเสื่อม ไม่รู้แล้วว่าอะไรควรรันอยู่บ้าง

etcd ใช้ Raft consensus และต้องมีสมาชิกเกินครึ่ง (quorum) จึงจะเขียนข้อมูลได้ คลัสเตอร์ production จึงรัน 3 หรือ 5 member โดย 3 ตัวทนเสียได้ 1 ตัว และ 5 ตัวทนเสียได้ 2 ตัว การเพิ่มเป็น 4 ตัวไม่ได้ช่วยให้ทนขึ้น เพราะ quorum ขยับจาก 2 เป็น 3 ตาม

ถ้าดูแลคลัสเตอร์เอง ให้ตั้ง snapshot ตามรอบ และซ้อม restore จริงด้วย:

bash
etcdctl snapshot save /backup/etcd-$(date +%F).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

Secret ถูกเก็บแบบ base64 ไม่ได้เข้ารหัส เว้นแต่คุณเปิด encryption at rest ที่ API server ใครที่อ่านไฟล์ backup ของ etcd ได้ ก็อ่าน Secret ของคุณได้ทั้งหมด

kube-scheduler: ตัดสินว่า Pod ควรไปอยู่ node ไหน

Scheduler คอย watch หา Pod ที่ยังไม่มี node แล้วทำสองขั้น ขั้นแรก filter ตัด node ที่รับไม่ได้ออก เช่น CPU หรือ memory ที่ request ไว้ไม่พอ, มี taint ที่ Pod ไม่ tolerate หรือ node selector และ affinity ไม่ตรง ขั้นที่สอง score node ที่เหลือแล้วเลือกตัวที่ดีที่สุด จากนั้นเขียนผลเป็น binding กลับไปที่ API server มันแค่ตัดสินใจ ไม่ได้ไปสั่งรัน container เอง เรื่อง requests, limits และ QoS ที่มีผลต่อการตัดสินใจนี้มีรายละเอียดอยู่ในบทความ Kubernetes requests, limits และ scheduling

kube-controller-manager: รวม reconcile loop ไว้ในที่เดียว

Binary ตัวนี้รัน controller หลายตัวรวมกัน ทั้ง Deployment, ReplicaSet, StatefulSet, DaemonSet, Job, Node lifecycle, EndpointSlice, ServiceAccount และอื่น ๆ ทุกตัววน loop แบบเดียวกัน:

  1. ดู current state ผ่าน watch
  2. เทียบกับ desired state ใน spec ของ object
  3. ลงมือแก้ให้ตรง แล้วเขียนผลลง status

Loop นี้คือที่มาของ self-healing ลองลบ Pod ที่เป็นของ ReplicaSet ดู แล้ว ReplicaSet controller จะสร้างตัวใหม่มาแทนทันที

cloud-controller-manager: ตัวเชื่อมกับ cloud

ส่วนนี้เก็บ logic เฉพาะของแต่ละ cloud เช่น ลบ node object เมื่อ VM หายไป, ตั้งค่า route และสร้าง cloud load balancer ให้ Service ประเภท LoadBalancer มีเฉพาะเมื่อรันบน cloud เท่านั้น บน bare metal, Service แบบ LoadBalancer จะค้างอยู่ที่ <pending> จนกว่าจะติดตั้งตัวทำหน้าที่แทนอย่าง MetalLB ดูรายละเอียดได้ใน Kubernetes Service types

Component ฝั่ง worker node

kubelet: agent ที่ลงมือทำงานจริง

kubelet รันอยู่บนทุก node คอย watch หา Pod ที่ถูกจัดมาให้ node ของตัวเอง แล้วทำให้เกิดขึ้นจริง: สั่ง container runtime ดึง image, mount volume, start container, รัน probe และรายงานสถานะกลับ ถ้า node เริ่มขาด memory หรือ disk, kubelet จะ evict Pod ออกเพื่อปกป้อง node

Container runtime และ CRI

kubelet ไม่ได้รัน container เอง แต่สั่งผ่าน container runtime ด้วยมาตรฐาน Container Runtime Interface (CRI) ปัจจุบันแทบทั้งหมดใช้ containerd หรือ CRI-O ส่วน dockershim ที่เคยให้ Kubernetes คุยกับ Docker Engine ตรง ๆ ถูกถอดออกตั้งแต่เวอร์ชัน 1.24 แต่ image ที่ build ด้วย Docker ยังใช้ได้เหมือนเดิม เพราะเป็น OCI image มาตรฐาน

ตอน Pod เริ่มทำงาน runtime จะเรียก CNI plugin ของคลัสเตอร์ (Calico, Cilium, Flannel ฯลฯ) เพื่อสร้าง network interface และแจก IP ให้ Pod

kube-proxy: routing ของ Service บนแต่ละ node

kube-proxy คอย watch Service และ EndpointSlice แล้วเขียนกฎบน node ด้วย iptables, IPVS หรือโหมด nftables ที่ใหม่กว่า เพื่อให้ traffic ที่ยิงไปยัง virtual IP ของ Service ไปถึง Pod ที่พร้อมรับงาน ในโหมดเหล่านี้ kernel เป็นคนส่งต่อ packet ส่วน kube-proxy แค่ดูแลกฎให้เป็นปัจจุบัน CNI บางตัวอย่าง Cilium สามารถแทนที่ kube-proxy ทั้งหมดด้วย eBPF ได้

เกิดอะไรขึ้นเมื่อสั่ง kubectl apply

ลองตาม Deployment ที่มี 3 replica ตั้งแต่ต้นจนจบ:

  1. kubectl ส่ง manifest ไปที่ API server
  2. API server ทำ authentication, authorization, admission, validate แล้วเก็บลง etcd จากนั้น kubectl แสดง deployment.apps/web created ซึ่งแปลว่า object ถูกบันทึกแล้ว แต่ยังไม่มีอะไรรันเลย
  3. Deployment controller เห็น Deployment ใหม่ จึงสร้าง ReplicaSet
  4. ReplicaSet controller เห็นว่า ReplicaSet ต้องการ 3 Pod แต่ยังไม่มีสักตัว จึงสร้าง Pod object 3 ตัวที่ยังไม่มี node
  5. Scheduler เห็น Pod ที่ยังไม่ถูกจัดวาง เลือก node ให้แต่ละตัว แล้วเขียน binding
  6. kubelet บน node ที่ถูกเลือกเห็นว่ามี Pod ของตัวเอง runtime ดึง image, CNI แจก IP แล้ว container ก็เริ่มทำงาน
  7. เมื่อ readiness probe ผ่าน kubelet จะรายงานว่า Pod Ready แล้ว EndpointSlice controller เพิ่ม IP ของ Pod เข้า Service ที่ตรงกัน และ kube-proxy บนทุก node ก็อัปเดตกฎตาม

ไม่มี component ไหนเรียกหากันตรง ๆ ทุกตัวแค่ watch API server แล้วตอบสนอง:

bash
kubectl apply -f deployment.yaml
kubectl get events --watch
kubectl get pods -o wide --watch

ลำดับนี้ยังบอกด้วยว่าเมื่อมีอะไรค้าง ควรไปดูที่ไหน:

อาการลำดับหยุดอยู่ตรงไหน
Pod ค้าง PendingScheduler: ไม่มี node ที่เหมาะ (resource, taint, affinity, PVC ที่ยังไม่ bind)
ค้าง ContainerCreating นานkubelet, runtime, CNI หรือการ mount volume บน node
ImagePullBackOffruntime ดึง image ไม่ได้: ชื่อ image, tag หรือ credential ของ registry ผิด
Running แต่ไม่ Readyreadiness probe ไม่ผ่าน
Service ไม่ตอบไม่มี Pod ที่ ready อยู่ใน EndpointSlice หรือ selector ไม่ตรง

ส่วน Pod ที่ restart วนไม่จบเป็นอีกเรื่องหนึ่ง อ่านต่อได้ที่ การ debug CrashLoopBackOff และ probe

Control traffic กับ data traffic

Kubernetes architecture แยก traffic สองแบบออกจากกันชัดเจน:

  • Control traffic คือการบริหารคลัสเตอร์ เช่น kubectl apply, kubelet รายงานสถานะ, scheduler เขียน binding ทั้งหมดนี้วิ่งผ่าน API server
  • Data traffic คือ request จริงของผู้ใช้ วิ่งจาก load balancer เข้า node แล้วกฎของ kube-proxy ส่งต่อไปยัง Pod เส้นทางนี้ไม่แตะ API server หรือ etcd เลย

การแยกแบบนี้สำคัญทั้งเรื่อง scale และความทนทาน ถ้าทุก request ของผู้ใช้ต้องผ่าน API server มันจะกลายเป็นคอขวดทันที และเมื่อ control plane ล่ม แอปของคุณก็ยังให้บริการผู้ใช้ได้

แต่ "ยังให้บริการได้" ไม่ได้แปลว่า "ปกติดี" ระหว่างที่ control plane ล่ม ถ้ามี node ตายจะไม่มีการย้าย Pod ไปที่อื่น, deploy หรือ scale ไม่ได้ และ endpoint ของ Service จะไม่อัปเดต Pod ที่กลายเป็น unready จึงยังได้รับ traffic ต่อไป สิ่งเดียวที่ยังทำได้คือ kubelet restart container ที่ crash ภายใน node ของตัวเอง

Networking model ของ Kubernetes

โมเดลเครือข่ายมีกฎพื้นฐานสามข้อ:

  • Pod ทุกตัวได้ IP ของตัวเอง
  • Pod คุยกับ Pod อื่นได้ทุกตัว ไม่ว่าจะอยู่ node ไหน โดยไม่ต้องผ่าน NAT
  • Agent บน node เช่น kubelet เข้าถึง Pod ทุกตัวบน node นั้นได้

CNI plugin เป็นตัวทำให้กฎเหล่านี้เป็นจริง ต่อยอดจาก Pod networking ก็จะมี:

  • Service: virtual IP ที่นิ่ง วางไว้หน้า Pod ที่ IP เปลี่ยนได้ตลอด
  • CoreDNS: ชื่อแบบ orders.shop.svc.cluster.local
  • NetworkPolicy: firewall ระหว่าง Pod ซึ่งจะมีผลก็ต่อเมื่อ CNI plugin รองรับ
  • Ingress: routing ระดับ HTTP ตาม host และ path เข้าสู่คลัสเตอร์

Namespace: จัดระเบียบคลัสเตอร์ที่ใช้ร่วมกัน

Namespace คือขอบเขตของชื่อและ policy สองทีมมี Service ชื่อ api ได้พร้อมกันถ้าอยู่คนละ namespace และ namespace ยังเป็นที่ผูก RBAC, ResourceQuota, LimitRange และ NetworkPolicy:

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: team-payments
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute
  namespace: team-payments
spec:
  hard:
    requests.cpu: "8"
    requests.memory: 16Gi
    limits.memory: 32Gi
    pods: "50"

ทุกคลัสเตอร์เริ่มต้นด้วย default, kube-system (control plane และ add-on), kube-public และ kube-node-lease (heartbeat ของ node) resource บางชนิดเป็น cluster-scoped ไม่อยู่ใน namespace ไหนเลย เช่น Node, PersistentVolume, StorageClass, ClusterRole, CustomResourceDefinition และตัว Namespace เอง ดูรายการทั้งหมดได้ด้วย kubectl api-resources --namespaced=false

Namespace อย่างเดียวไม่ใช่ security boundary โดยค่าเริ่มต้น Pod ต่าง namespace ยังคุยกันได้ ถ้าต้องการแยกกันจริงต้องใช้ร่วมกับ RBAC, NetworkPolicy และ Pod Security Admission

คำสั่งสำหรับสำรวจ architecture ของคลัสเตอร์

bash
kubectl get nodes -o wide                  # nodes, versions, runtime
kubectl get pods -n kube-system            # control plane and add-ons
kubectl get --raw='/readyz?verbose'        # API server health checks
kubectl describe node <node-name>          # capacity, allocations, conditions

Pattern watch-แล้ว-reconcile แบบเดียวกันนี้คือวิธีขยายความสามารถของ Kubernetes ด้วย: Operator เพิ่ม custom resource ชนิดใหม่พร้อม controller ที่ดูแลมัน อ่านต่อที่ Kubernetes Operator และ CRD

คำถามที่พบบ่อย

Control plane กับ worker node ต่างกันอย่างไร

Control plane เก็บ state ของคลัสเตอร์และตัดสินใจว่าอะไรควรรันที่ไหน ส่วน worker node คือที่ที่ Pod รันจริง โดย kubelet บนแต่ละ node เป็นคนทำตามสิ่งที่ control plane บันทึกไว้

ถ้า control plane ของ Kubernetes ล่ม จะเกิดอะไรขึ้น

Pod ที่รันอยู่ยังทำงานและรับ traffic ต่อได้ แต่ deploy, scale หรือแก้ไขอะไรไม่ได้, Pod บน node ที่ตายจะไม่ถูกย้าย และ endpoint ของ Service จะไม่อัปเดตจนกว่า control plane จะกลับมา

ทำไม etcd ต้องมีจำนวน member เป็นเลขคี่

etcd ต้องได้เสียงข้างมากจึงจะเขียนข้อมูลได้ 3 member ทนเสียได้ 1 ตัว 5 member ทนเสียได้ 2 ตัว การเพิ่มเป็นเลขคู่ทำให้ quorum สูงขึ้นโดยไม่ได้ทนความเสียหายได้มากขึ้น

Kubernetes ยังใช้ Docker อยู่ไหม

ไม่ได้ใช้เป็น runtime แล้ว ตั้งแต่ 1.24 Kubernetes คุยกับ containerd หรือ CRI-O ผ่าน CRI แต่ image ที่ build ด้วย Docker เป็น OCI image จึงรันได้เหมือนเดิม

Namespace เป็น security boundary หรือเปล่า

ไม่ใช่ถ้าใช้อย่างเดียว มันแค่แบ่งขอบเขตชื่อ, RBAC และ quota ส่วน traffic ข้าม namespace ยังวิ่งได้จนกว่าจะเพิ่ม NetworkPolicy

สรุปสิ่งที่ควรจำ

  • API server คือทางเข้าเดียว และ etcd คือที่เก็บข้อมูลเดียว ต้องปกป้องและ backup etcd เสมอ
  • Controller และ scheduler แค่เขียนลง API server ส่วนงานจริงบน node เป็นหน้าที่ของ kubelet กับ runtime
  • kubectl apply สำเร็จแปลว่า object ถูกบันทึกแล้ว ไม่ได้แปลว่ารันแล้ว ให้ไล่ตามลำดับ controller, scheduler, kubelet, endpoint
  • Traffic ของผู้ใช้ไม่ผ่าน control plane ดังนั้นเมื่อ control plane ล่ม สิ่งที่หยุดคือการเปลี่ยนแปลง ไม่ใช่การให้บริการ
  • Namespace ช่วยจัดระเบียบ แต่การแยกกันจริงต้องมี RBAC และ NetworkPolicy ด้วย

ถ้าอยากลองฝึกแนวคิดเหล่านี้บนคลัสเตอร์จริง คอร์ส DevOps ฟรีของ Vectorkub พาทำทีละขั้นตอน

พร้อมสร้างสิ่งที่ยิ่งใหญ่หรือยัง?

เล่าโปรเจคของคุณให้เราฟัง — เราจะพาคุณไปถึง

เลือกคุกกี้ที่เราใช้ได้ คุกกี้ที่จำเป็นช่วยให้เว็บไซต์ทำงานได้และเปิดใช้งานเสมอ