Kubernetes Fundamentals
สถาปัตยกรรมคร่าวๆ (Control Plane vs Node)
Kubernetes cluster ประกอบด้วย 2 ส่วนหลัก:
- Control Plane — "สมอง" ของ cluster มี
kube-apiserver(ประตูเดียวที่ทุกคำสั่งต้องผ่าน),etcd(key-value store เก็บ state ทั้งหมดของ cluster),kube-scheduler(ตัดสินใจว่า Pod ไหนควรไปรันบน Node ไหน),kube-controller-manager(รัน control loop หลายตัว เช่น ReplicaSet controller, Node controller) - Node (Worker) — เครื่องที่รัน workload จริง มี
kubelet(agent ที่คุย API server และ สั่ง container runtime),kube-proxy(จัดการ network rule ให้ Service ทำงาน), และ container runtime (เช่น containerd)
หัวใจสำคัญที่ควรเข้าใจ: Kubernetes ทำงานแบบ declarative + reconciliation loop — คุณบอก "สถานะที่ต้องการ" (desired state) ผ่าน YAML แล้ว controller จะพยายามปรับ "สถานะจริง" (actual state) ให้ตรงกันตลอดเวลา ไม่ใช่สั่งทีละคำสั่งแบบ imperative
"Control Plane = ผู้จัดการที่สั่งงาน, Node = ลูกน้องที่ลงมือทำ" — จำคู่ชื่อ-หน้าที่แบบย่อ: apiserver = พนักงานต้อนรับ (ประตูเดียวที่ทุกคนต้องผ่าน), etcd = ตู้เซฟ (เก็บ state จริง), scheduler = ฝ่ายจัดคิว (ตัดสินใจ Pod ไปไหน), kubelet = หัวหน้างานหน้างาน (สั่ง container runtime ตรงๆ บน Node)
"Declarative" ไม่ได้แปลว่า Kubernetes รู้ว่าจะ "ไปถึง" desired state ยังไงเองทั้งหมด — แต่ละ controller แก้ปัญหาแค่ ส่วนย่อยที่ตัวเองรับผิดชอบ แบบวนลูปอิสระ (control loop pattern) เช่น Deployment controller ไม่รู้จักเรื่อง networking เลย มันแค่คอยเช็คว่า ReplicaSet ตรงกับ desired count ของตัวเองไหม — ส่วน Service/Endpoint controller ก็วนลูปแยกต่างหากโดยไม่รู้จัก Deployment เลย ระบบทั้งหมดทำงานถูกเพราะ control loop หลายสิบตัวทำงานคู่ขนานกันแบบไม่รู้จักกัน โดยตรง แค่ต่างฝ่ายต่างดู state ใน etcd ผ่าน apiserver — นี่คือสถาปัตยกรรมที่เรียกว่า "eventually consistent via independent reconciliation loops" ซึ่งเป็นเหตุผลที่ Kubernetes scale ได้ดีมาก (ไม่มี single orchestrator ที่ต้องรู้ทุกอย่าง)
Pod — หน่วยที่เล็กที่สุดที่ deploy ได้
Pod คือกลุ่มของ container ตั้งแต่ 1 ตัวขึ้นไปที่ share network namespace และ storage
เดียวกัน (เข้าถึงกันผ่าน localhost ได้) ส่วนใหญ่ 1 Pod = 1 container หลัก แต่บางครั้งมี
"sidecar container" เสริม (เช่น log shipper, service mesh proxy)
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0.0
ports:
- containerPort: 8080
ในทางปฏิบัติ แทบไม่สร้าง Pod ตรงๆ เอง — Pod ที่สร้างเดี่ยวๆ เมื่อตายแล้วจะไม่ถูกสร้างใหม่ ให้อัตโนมัติ ต้องสร้างผ่าน controller เช่น Deployment หรือ Job เสมอ ในงานจริง
"Pod = ห้องพักเดียวกัน, container = รูมเมท" — container ใน Pod เดียวกันแชร์ "ที่อยู่" เดียวกัน
(network namespace/IP) คุยกันผ่าน localhost ได้เหมือนรูมเมทคุยกันในห้องเดียวกัน แต่แยก
filesystem ส่วนตัวกันคนละ container (เหมือนมีตู้เสื้อผ้าแยกคนละใบ)
ทุก Pod มี container ที่มองไม่เห็นอยู่เสมอ เรียกว่า "pause container" (หรือ infra
container) ซึ่งมีหน้าที่แค่ถือ network namespace ไว้ให้ container อื่นในกลุ่มมาเกาะ มันไม่ทำ
อะไรเลยนอกจาก sleep ตลอดชีวิต — เหตุผลที่ต้องมี: ถ้า container หลักใน Pod restart (เช่น crash
แล้ว kubelet สร้างใหม่) network namespace จะไม่หายไปด้วย เพราะ pause container ยังถือมันไว้อยู่
ทำให้ Pod ยังคง IP เดิมแม้ container ข้างในจะ restart กี่ครั้งก็ตาม docker ps บน Node จะเห็น
container ชื่อ pause เพิ่มมาเงียบๆ คู่กับทุก Pod เสมอ ซึ่งเป็นรายละเอียดที่ engineer ส่วนใหญ่
ไม่เคยสังเกต
Deployment & ReplicaSet
Deployment คือตัวจัดการ stateless workload ที่พบบ่อยที่สุด มันไม่ได้คุม Pod ตรงๆ แต่คุม
ReplicaSet อีกที ซึ่ง ReplicaSet คุมจำนวน Pod ให้ตรงตาม replicas ที่กำหนด
Deployment (จัดการ rollout/rollback, version history)
└── ReplicaSet (จัดการจำนวน Pod ให้ตรงตาม desired count)
└── Pod × N
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0.0
เมื่อ update image แล้ว apply ใหม่ Deployment จะสร้าง ReplicaSet ใหม่ แล้วค่อยๆ scale ตัวใหม่
ขึ้น + scale ตัวเก่าลง (rolling update) — นี่คือที่มาของ rollback ได้ทันที เพราะ ReplicaSet
เก่ายังถูกเก็บ history ไว้ (kubectl rollout undo deployment/my-app)
"Deployment = ผู้จัดการโครงการ, ReplicaSet = หัวหน้าทีมย่อยแต่ละเวอร์ชัน" — ทุกครั้งที่เปลี่ยน image จะได้หัวหน้าทีมคนใหม่ (ReplicaSet ใหม่) ส่วนหัวหน้าทีมคนเก่ายังไม่ถูกไล่ออก (แค่ scale down เหลือ 0 คน) เผื่อต้องเรียกกลับมาทำงานอีก (rollback)
ReplicaSet เก่าที่ scale เหลือ 0 ไม่ได้ถูกเก็บไว้ตลอดไป — ค่า default
spec.revisionHistoryLimit ของ Deployment คือ 10 (เก็บแค่ 10 revision ล่าสุด) ถ้า
deploy บ่อยมาก (เช่นวันละหลายรอบใน CI/CD) ReplicaSet เก่าสุดจะถูกลบทิ้งอัตโนมัติเงียบๆ
ทำให้ kubectl rollout undo ย้อนกลับไปได้ไกลสุดแค่ 10 เวอร์ชันเท่านั้น ทีมที่ deploy ถี่มาก
บางทีมปรับค่านี้ให้สูงขึ้น หรือพึ่ง image tag history ใน container registry แทนการพึ่ง
Kubernetes revision history เพียงอย่างเดียว
Labels & Selectors — กลไกเชื่อมทุกอย่างเข้าด้วยกัน
Kubernetes ไม่มีการอ้างอิงแบบ ID ตรงๆ ระหว่าง object ส่วนใหญ่ — ใช้ label + selector
แทนทั้งหมด (loosely-coupled by design) เช่น Deployment หา Pod ของตัวเองผ่าน
selector.matchLabels, Service หา Pod ปลายทางผ่าน spec.selector
# Pod ที่มี label app: my-app, tier: backend
metadata:
labels:
app: my-app
tier: backend
---
# Service ที่เลือก Pod ด้วย label เดียวกัน
spec:
selector:
app: my-app
ถ้า label ไม่ match กัน Service จะหา endpoint ไม่เจอ (เป็นสาเหตุ bug อันดับต้นๆ ที่เจอในงานจริง)
"Label คือป้ายชื่อติดเสื้อ, Selector คือเกณฑ์เรียกชื่อ" — Kubernetes ไม่เคยถาม "นี่ object
ID อะไร" แต่ถามว่า "ใครใส่เสื้อป้าย app: my-app บ้าง" ทุก object ที่ "แต่งตัวตรงเกณฑ์" จะถูก
นับรวมโดยอัตโนมัติ แม้จะถูกสร้างคนละเวลา คนละวิธีก็ตาม
matchLabels คือ syntax ย่อของ matchExpressions ที่ทรงพลังกว่ามาก — matchExpressions
รองรับ operator In, NotIn, Exists, DoesNotExist ทำให้เขียน selector แบบซับซ้อนได้ เช่น
"เลือก Pod ที่ env เป็น prod หรือ staging แต่ ไม่ใช่ canary":
selector:
matchExpressions:
- {key: env, operator: In, values: [prod, staging]}
- {key: track, operator: NotIn, values: [canary]}
คนส่วนใหญ่ใช้แค่ matchLabels (AND เงื่อนไขแบบ exact match เท่านั้น) ไม่รู้ว่า
matchExpressions ทำ OR-like logic ผ่าน In ได้ ทำให้บางครั้งไปออกแบบ label scheme ที่ซับซ้อน
เกินจำเป็นเพราะไม่รู้ว่า selector เองก็ยืดหยุ่นพอแล้ว
Namespace — การแบ่งขอบเขตเชิงตรรกะ
Namespace ใช้แบ่ง cluster หนึ่งออกเป็นหลาย "ห้อง" เชิงตรรกะ (เช่น dev, staging, prod)
เพื่อแยก resource quota, RBAC, และลดโอกาสชื่อชนกัน — แต่ ไม่ใช่ security boundary ที่สมบูรณ์
(Pod ข้าม namespace ยัง network กันถึงได้โดย default ถ้าไม่มี NetworkPolicy กั้น)
kubectl get pods -n production
kubectl config set-context --current --namespace=production
"Namespace คือห้องกั้นด้วยม่าน ไม่ใช่กำแพง" — แยกมองเห็น/จัดการง่าย (RBAC, quota, ชื่อไม่ชน) แต่เสียงยังลอดผ่านม่านได้ (network traffic ยังคุยข้ามห้องได้โดย default) ถ้าอยากได้กำแพงจริง ต้องสร้างเพิ่มเอง (NetworkPolicy)
ไม่ใช่ทุก object เป็น namespaced — Node, PersistentVolume (ไม่ใช่ Claim), ClusterRole,
และ Namespace เองเป็น cluster-scoped (ไม่ขึ้นกับ namespace ไหนเลย) วิธีเช็คเร็วๆ ว่า
object ชนิดไหน namespaced หรือไม่:
kubectl api-resources --namespaced=false
คนที่ไม่รู้เรื่องนี้มักงงว่าทำไม kubectl get node -n production ไม่ error แต่ก็ไม่กรองอะไร
เลย (เพราะ -n ถูกเพิกเฉยสำหรับ resource ที่ cluster-scoped) และทำไม ClusterRole ถึงใช้
ชื่อซ้ำกับ namespace ไหนก็ไม่ได้เพราะมันไม่ได้ผูกกับ namespace ใดเลยตั้งแต่ต้น
ReplicaSet ไม่ใช่ตัวเดียวที่คุมจำนวน Pod
| Controller | ใช้เมื่อ |
|---|---|
Deployment | Stateless app ทั่วไป (web server, API) |
StatefulSet | ต้องการ stable network identity + persistent storage ต่อ instance (database) |
DaemonSet | ต้องรัน Pod 1 ตัวต่อ Node ทุกตัว (log agent, monitoring agent) |
Job / CronJob | งานที่รันจบแล้วจบ (batch processing, scheduled task) |
(รายละเอียดแต่ละตัวอยู่ใน Workload Patterns)
Kubernetes is declarative: you describe desired state in YAML, and controllers continuously
reconcile actual state to match it — crucially, via many independent control loops that don't
know about each other, each watching shared state through the API server rather than one central
orchestrator. A Pod is the smallest deployable unit (one or more co-located containers sharing
network/storage via a hidden "pause container" that preserves the Pod's IP across container
restarts) but is rarely created directly — it's managed via a Deployment (which owns a
ReplicaSet, which owns Pods) for rolling updates and rollback history, though old ReplicaSets are
pruned after revisionHistoryLimit (default 10). Everything connects through labels and
selectors — matchExpressions supports In/NotIn/Exists beyond simple matchLabels AND-
matching. Namespaces provide logical separation (quota, RBAC) but not a hard network boundary,
and not every resource is even namespaced (Nodes, PVs, ClusterRoles are cluster-scoped).