Production Checklist และ Kubernetes API Client
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”workload ที่พร้อมขึ้น production ไม่ใช่ feature เดียว แต่เป็น checklist ของ control ที่แยกจากกัน ทั้ง resource limit, probe, disruption budget, RBAC แบบ least-privilege, network policy, secret ที่ encrypt จริง, autoscaling ที่ bound ไว้, replica ที่กระจายตัว และ namespace quota แต่ละอันปิดช่องโหว่คนละแบบที่อันอื่นปิดไม่ได้
เช็กลิสต์
หัวข้อที่มีชื่อว่า “เช็กลิสต์”แต่ละข้อด้านล่างปิดช่องว่างเฉพาะเจาะจง ไม่มีข้อไหน optional ใน production cluster จริง และไม่มีข้อไหนแทนที่ข้ออื่นได้
- Resource
requests/limitsตั้งทั้งคู่ ไม่ใช่แค่อันใดอันหนึ่งrequestsเป็นตัวขับ scheduling และกำหนด QoS class ของ Pod (Guaranteed,Burstable, หรือBestEffort) ซึ่ง kubelet ใช้ตัดสิน eviction order ตอนโหนดขาดแคลนทรัพยากรlimitsจำกัดว่า container ที่ runaway จะแย่งทรัพยากรจากเพื่อนบ้านได้มากแค่ไหน - readiness AND liveness probe readiness probe ควบคุมว่า Pod จะรับ traffic จาก Service หรือไม่ (แก้ปัญหา “ส่ง traffic ไปหา Pod ที่ยังไม่พร้อม”) ส่วน liveness probe ควบคุมว่า kubelet จะ restart container หรือไม่ (แก้ปัญหา “process ยังมีชีวิตอยู่แต่ hang แล้ว traffic ก็ fail ต่อเนื่อง”) ทั้งสองตัวแก้คนละปัญหา workload เลยต้องมีทั้งคู่
- PodDisruptionBudget (PDB) สำหรับ service ที่มีหลาย replica จำกัดว่า disruption แบบ voluntary (node drain, cluster upgrade) จะเอา replica ออกพร้อมกันได้กี่ตัว ทำให้ rolling node upgrade ไม่พา service ทั้งตัวเหลือ replica เป็นศูนย์โดยไม่ตั้งใจ
- RBAC scope แบบ least-privilege Role กับ RoleBinding (หรือ ClusterRole/ClusterRoleBinding) ให้สิทธิ์เฉพาะ verb กับ resource ที่ ServiceAccount ของ workload ต้องใช้จริง ไม่ใช่
cluster-adminโดย default - default-deny NetworkPolicy ตรงจุดที่เหมาะสม ถ้าไม่มี Pod ทุกตัวจะเข้าถึง Pod อื่นได้หมดโดย default policy แบบ default-deny บวก allow rule ที่ระบุชัดทำให้การเคลื่อนที่แบบ lateral จาก Pod ที่ถูก compromise ยากขึ้นมาก
- การป้องกัน secret แบบจริงจัง encryption at rest สำหรับ etcd (Secret ถูก encode ด้วย base64 ไม่ใช่ encrypt เว้นแต่จะตั้ง
EncryptionConfiguration) หรือใช้ external secret manager ไม่ใช่พึ่ง base64 อย่างเดียวเป็น security boundary - HorizontalPodAutoscaler ที่ bound ไว้อย่างเหมาะสม
minReplicasป้องกันไม่ให้ scale ลงเหลือศูนย์สำหรับ service ที่ต้อง available ตลอดเวลาmaxReplicasจำกัด cost และ load ที่ไปกระทบ dependency ปลายทาง (database และอื่น ๆ) ตอน traffic พุ่ง - replica หลายตัวกระจายข้ามโหนด/zone Pod anti-affinity หรือ topology spread constraint ทำให้โหนดเดียวหรือ availability zone เดียวล่มไม่พา replica ทุกตัวหายไปพร้อมกัน จำนวน replica เฉยๆ ไม่ได้การันตีเรื่องนี้
- ResourceQuota/LimitRange ต่อ namespace
ResourceQuotaจำกัด resource รวมของ namespace ส่วนLimitRangeให้ค่า default ต่อ container ที่สมเหตุสมผล เพื่อไม่ให้ Pod ที่ deploy โดยไม่ตั้งrequests/limitsกลายเป็นBestEffortแบบเงียบ ๆ
apiVersion: apps/v1kind: Deploymentmetadata: name: orders-apispec: replicas: 3 template: spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - topologyKey: 'kubernetes.io/hostname' labelSelector: matchLabels: app: orders-api containers: - name: orders-api image: myregistry/orders-api:2.3.0 resources: requests: cpu: '250m' memory: '256Mi' limits: cpu: '500m' memory: '512Mi' readinessProbe: httpGet: path: /healthz/ready port: 8080 periodSeconds: 5 livenessProbe: httpGet: path: /healthz/live port: 8080 periodSeconds: 10---apiVersion: policy/v1kind: PodDisruptionBudgetmetadata: name: orders-api-pdbspec: minAvailable: 2 selector: matchLabels: app: orders-apiปิดวงจรด้วย programmatic verification script
หัวข้อที่มีชื่อว่า “ปิดวงจรด้วย programmatic verification script”kubectl rollout status ครอบคลุมกรณีที่ใช้แบบ interactive ได้ แต่ deployment pipeline บ่อยครั้งต้องการเช็คแบบเดียวกันในรูป code เช่น gate ขั้นตอน promotion ด้วยการเช็คว่า Deployment ถึง replica count ที่ต้องการจริง ๆ ไม่ใช่แค่ kubectl apply คืนค่าสำเร็จ KubeConfig.loadFromDefault() ของ @kubernetes/client-node resolve credential แบบเดียวกันไม่ว่าจะรัน script บนเครื่อง laptop ของวิศวกร (ผ่าน KUBECONFIG หรือ ~/.kube/config) หรือรันอยู่ใน cluster เองในฐานะ CI job หรือ Job Pod (ผ่าน ServiceAccount token ที่ mount ไว้) ไม่ต้องเขียน logic แยกกรณีเลย
import * as k8s from '@kubernetes/client-node';
const kc = new k8s.KubeConfig();// Resolves KUBECONFIG env var, then ~/.kube/config, then in-cluster ServiceAccountkc.loadFromDefault();
const appsApi = kc.makeApiClient(k8s.AppsV1Api);
async function verifyRollout(name: string, namespace: string, timeoutMs = 120_000): Promise<void> { const start = Date.now();
while (Date.now() - start < timeoutMs) { const dep = await appsApi.readNamespacedDeployment({ name, namespace });
const desired = dep.spec?.replicas ?? 0; const ready = dep.status?.readyReplicas ?? 0; const updated = dep.status?.updatedReplicas ?? 0;
console.log(`${namespace}/${name}: ${ready}/${desired} ready, ${updated}/${desired} updated`);
if (ready === desired && updated === desired) { console.log('rollout complete'); return; }
await new Promise((resolve) => setTimeout(resolve, 3000)); }
throw new Error(`rollout of ${namespace}/${name} did not complete within ${timeoutMs}ms`);}
await verifyRollout('orders-api', 'default');นี่คือกรณีที่ programmatic client ชนะ shell script ที่ห่อ kubectl อยู่ตรง ๆ ผลลัพธ์ป้อนตรงเข้าการตัดสิน pass/fail ของ pipeline ได้เลย เข้าถึง status.readyReplicas กับ status.updatedReplicas แบบมี type แทนที่จะต้อง parse text output จาก CLI
flowchart TB dep["Deployment: orders-api (3 replicas, spread across nodes)"] dep --> probes["Readiness + liveness probes"] dep --> resources["requests/limits (QoS)"] dep --> pdb["PodDisruptionBudget (minAvailable: 2)"] dep --> rbac["RBAC: least-privilege ServiceAccount"] dep --> netpol["Default-deny NetworkPolicy + explicit allows"] dep --> hpa["HorizontalPodAutoscaler (bounded min/max)"] ns["Namespace"] --> quota["ResourceQuota / LimitRange"] quota --> dep