Kubernetes for beginnersKubernetes สำหรับผู้เริ่มต้น
Kubernetes Beginner Guide
Tell it what you want running; it keeps it that wayบอกว่าต้องการให้อะไรทำงาน แล้วมันจะรักษาสภาพนั้นไว้
Docker runs containers on one machine. Kubernetes runs them across many machines, and keeps them the way you described: if a container crashes it starts a new one, if a machine dies it moves the work, and when you ship a new version it swaps containers one by one without downtime. In this guide a small web app, hello-k8s, runs on a real three-node cluster on a laptop and goes through everything a beginner meets: pods, deployments, services, configuration, health checks, limits, updates, rollbacks, storage and debugging. Docker รัน container บนเครื่องเดียว Kubernetes รัน container บนหลายเครื่อง และรักษาให้เป็นไปตามที่เราอธิบายไว้ ถ้า container พังก็เริ่มตัวใหม่ ถ้าเครื่องตายก็ย้ายงานไปที่อื่น และเมื่อออกเวอร์ชันใหม่ก็สลับ container ทีละตัวโดยไม่มีช่วงหยุดให้บริการ ในคู่มือนี้เว็บแอปเล็กๆ ชื่อ hello-k8s จะรันบน cluster จริงสามโหนดบนโน้ตบุ๊ก และผ่านทุกเรื่องที่ผู้เริ่มต้นต้องเจอ ได้แก่ pod, deployment, service การตั้งค่า การตรวจสุขภาพ การจำกัดทรัพยากร การอัปเดต การย้อนกลับ พื้นที่เก็บข้อมูล และการดีบัก
- Run withรันด้วย Kubernetes 1.37
- kind 0.33
- kubectl 1.36
- Nodesโหนด 3
Running appsรันแอป
Pods, deployments that heal themselves, and services that find them.pod, deployment ที่ซ่อมตัวเองได้ และ service ที่หา pod เจอ
Lessons 1–4 →บทที่ 1–4 →Running them wellรันให้ดี
Configuration, health checks, limits, safe updates and storage.การตั้งค่า การตรวจสุขภาพ ขีดจำกัด การอัปเดตอย่างปลอดภัย และพื้นที่เก็บข้อมูล
Lessons 5–8 →บทที่ 5–8 →Operatingดูแลระบบ
Labels, namespaces, and reading what went wrong.label, namespace และการอ่านว่าเกิดอะไรผิดพลาด
Lessons 9–10 →บทที่ 9–10 →Lesson 1 · Running appsบทที่ 1 · รันแอป
What Kubernetes isKubernetes คืออะไร
With Kubernetes you don't say "start a container". You write down the desired state ("three copies of hello-k8s version 1.0, reachable on port 80") and hand it to the cluster. Programs called controllers compare the desired state with what is actually running, all the time, and fix any difference. That loop is why Kubernetes heals itself. กับ Kubernetes เราไม่ได้สั่งว่า "เริ่ม container" แต่เขียน สถานะที่ต้องการ (desired state) เช่น "hello-k8s เวอร์ชัน 1.0 สามชุด เข้าถึงได้ที่พอร์ต 80" แล้วส่งให้ cluster โปรแกรมที่เรียกว่า controller จะเทียบสถานะที่ต้องการกับสิ่งที่ทำงานอยู่จริงตลอดเวลา และแก้ทุกความต่าง วงจรนี้คือเหตุที่ Kubernetes ซ่อมตัวเองได้
kind builds a cluster out of Docker containers, one per node, which is perfect for learning. kubectl, the command-line tool, comes with Docker Desktop; kind is brew install kind or a single download from its releases page. This config asks for one control-plane node and two workers, and connects port 30080 of the cluster to localhost:8095.
kind สร้าง cluster จาก container ของ Docker โหนดละหนึ่ง container ซึ่งเหมาะกับการเรียนรู้ kubectl ซึ่งเป็นเครื่องมือ command line มาพร้อม Docker Desktop ส่วน kind ติดตั้งด้วย brew install kind หรือดาวน์โหลดไฟล์เดียวจากหน้า releases การตั้งค่านี้ขอ control-plane หนึ่งโหนดและ worker สองโหนด และเชื่อมพอร์ต 30080 ของ cluster กับ localhost:8095
$ kind create cluster --name guide --config kind.yaml Creating cluster "guide" ... • Ensuring node image (kindest/node:v1.37.0) 🖼️ ... ✓ Ensuring node image (kindest/node:v1.37.0) 🖼️ • Preparing nodes 📦 📦 📦 ... ✓ Preparing nodes 📦 📦 📦 • Writing configuration 📜 ... ✓ Writing configuration 📜 • Starting control-plane 🕹️ ... ✓ Starting control-plane 🕹️ • Installing CNI 🔌 ... ✓ Installing CNI 🔌 • Installing StorageClass 💾 ... ✓ Installing StorageClass 💾 • Joining worker nodes 🚜 ... ✓ Joining worker nodes 🚜 Set kubectl context to "kind-guide" You can now use your cluster with: kubectl cluster-info --context kind-guide Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/ $ kubectl get nodes NAME STATUS ROLES AGE VERSION guide-control-plane Ready control-plane 28s v1.37.0 guide-worker Ready <none> 14s v1.37.0 guide-worker2 Ready <none> 14s v1.37.0 $ kubectl get pods -n kube-system NAME READY STATUS RESTARTS AGE coredns-559f6c778d-4crsr 1/1 Running 0 19s coredns-559f6c778d-jpt62 1/1 Running 0 19s etcd-guide-control-plane 1/1 Running 0 27s kindnet-lnk7l 1/1 Running 0 14s kindnet-lrjd9 1/1 Running 0 19s kindnet-zzrvm 1/1 Running 0 14s kube-apiserver-guide-control-plane 1/1 Running 0 27s kube-controller-manager-guide-control-plane 1/1 Running 0 26s kube-proxy-fqgt9 1/1 Running 0 19s kube-proxy-kkgzg 1/1 Running 0 14s kube-proxy-x9c4d 1/1 Running 0 14s kube-scheduler-guide-control-plane 1/1 Running 0 26s
Kubernetes runs its own parts as pods in the kube-system namespace: the API server, etcd, the scheduler and the controller manager on the control plane; kube-proxy and kindnet (the pod network) on every node; and CoreDNS, the cluster's DNS.Kubernetes รันส่วนประกอบของตัวเองเป็น pod ใน namespace kube-system ได้แก่ API server, etcd, scheduler และ controller manager บน control plane, kube-proxy และ kindnet (เครือข่ายของ pod) บนทุกโหนด และ CoreDNS ซึ่งเป็น DNS ของ cluster
Lesson 2 · Running appsบทที่ 2 · รันแอป
PodsPod
A pod is the smallest thing Kubernetes runs: one container (sometimes a few that work closely together) with its own IP address. kubectl run starts a single pod, good for a quick experiment.
pod คือหน่วยที่เล็กที่สุดที่ Kubernetes รัน ประกอบด้วย container หนึ่งตัว (บางครั้งหลายตัวที่ทำงานใกล้ชิดกัน) และมี IP address ของตัวเอง kubectl run เริ่ม pod เดี่ยวหนึ่งตัว เหมาะกับการทดลองเร็วๆ
$ kubectl run web --image=nginx:alpine pod/web created $ kubectl wait --for=condition=Ready pod/web --timeout=120s pod/web condition met $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web 1/1 Running 0 31s 10.244.1.2 guide-worker <none> <none> $ kubectl exec web -- nginx -v nginx version: nginx/1.31.6 $ kubectl describe pod web | sed -n '/Events:/,$p' Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 31s default-scheduler Successfully assigned default/web to guide-worker Normal Pulling 31s kubelet spec.containers{web}: Pulling image "nginx:alpine" Normal Pulled 0s kubelet spec.containers{web}: Successfully pulled image "nginx:alpine" in 30.906s (30.906s including waiting). Image size: 26260874 bytes. Normal Created 0s kubelet spec.containers{web}: Container created Normal Started 0s kubelet spec.containers{web}: Container started
-o wide adds the pod's IP and the node it was scheduled on. The events at the end of describe tell the pod's story: the scheduler assigned it to a node, that node's kubelet pulled the image and started the container. Read them first whenever something is stuck.-o wide เพิ่ม IP ของ pod และโหนดที่มันถูกจัดให้ไปอยู่ events ท้าย describe เล่าเรื่องของ pod ว่า scheduler จัดให้อยู่โหนดไหน แล้ว kubelet ของโหนดนั้นดึง image และเริ่ม container เมื่อมีอะไรติดขัด ให้อ่านส่วนนี้ก่อนเสมอ
$ kubectl delete pod web pod "web" deleted from default namespace $ kubectl get pods No resources found in default namespace.
A bare pod is gone for good once deleted: nothing was told to keep it running. In real use you almost never create pods yourself; you create a Deployment, which creates and replaces them.pod เดี่ยวหายไปถาวรเมื่อถูกลบ เพราะไม่มีใครถูกสั่งให้รักษามันไว้ ในการใช้งานจริงเราแทบไม่สร้าง pod เอง แต่สร้าง Deployment ซึ่งสร้างและแทนที่ pod ให้
Lesson 3 · Running appsบทที่ 3 · รันแอป
DeploymentsDeployment
Our app is a small Node.js server that replies with a greeting, its version and the name of the pod that answered. Version 1.2 has a bug on purpose, for Lesson 7. It is packed into an image as in the Docker guide. แอปของเราคือเซิร์ฟเวอร์ Node.js ขนาดเล็กที่ตอบกลับด้วยคำทักทาย เวอร์ชัน และชื่อของ pod ที่ตอบ เวอร์ชัน 1.2 ถูกใส่บั๊กไว้โดยตั้งใจสำหรับบทที่ 7 แอปถูกแพ็กเป็น image แบบเดียวกับในคู่มือ Docker
A Deployment keeps a number of identical pods running. Every Kubernetes object is written the same way in YAML: apiVersion and kind say what it is, metadata names it, and spec is the desired state. Here: 3 replicas of a pod template. The selector tells the Deployment which pods are its own: the ones with the label app: hello-k8s.
Deployment รักษาให้มี pod ที่เหมือนกันจำนวนหนึ่งทำงานอยู่เสมอ ทุกออบเจกต์ของ Kubernetes เขียนด้วย YAML แบบเดียวกัน apiVersion และ kind บอกว่ามันคืออะไร metadata ตั้งชื่อให้ และ spec คือสถานะที่ต้องการ ในที่นี้คือ pod ตาม template จำนวน 3 replicas ส่วน selector บอก Deployment ว่า pod ไหนเป็นของมัน คือ pod ที่มี label app: hello-k8s
$ docker build -q --build-arg VERSION=1.0 -t hello-k8s:1.0 . sha256:30ecb51495482e04aa4d68bdf0618b7d61aee444ab9ab3384a2e1caa9b391661 $ kind load docker-image hello-k8s:1.0 --name guide Image: "hello-k8s:1.0" with ID "sha256:30ecb51495482e04aa4d68bdf0618b7d61aee444ab9ab3384a2e1caa9b391661" not yet present on node "guide-worker2", loading... Image: "hello-k8s:1.0" with ID "sha256:30ecb51495482e04aa4d68bdf0618b7d61aee444ab9ab3384a2e1caa9b391661" not yet present on node "guide-control-plane", loading... Image: "hello-k8s:1.0" with ID "sha256:30ecb51495482e04aa4d68bdf0618b7d61aee444ab9ab3384a2e1caa9b391661" not yet present on node "guide-worker", loading... $ kubectl apply -f k8s/deployment.yaml deployment.apps/hello-k8s created $ kubectl rollout status deployment/hello-k8s Waiting for deployment "hello-k8s" rollout to finish: 0 of 3 updated replicas are available... Waiting for deployment "hello-k8s" rollout to finish: 1 of 3 updated replicas are available... Waiting for deployment "hello-k8s" rollout to finish: 2 of 3 updated replicas are available... deployment "hello-k8s" successfully rolled out $ kubectl get deployments,replicasets,pods NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/hello-k8s 3/3 3 3 0s NAME DESIRED CURRENT READY AGE replicaset.apps/hello-k8s-79ff7998f 3 3 3 0s NAME READY STATUS RESTARTS AGE pod/hello-k8s-79ff7998f-2mmsb 1/1 Running 0 0s pod/hello-k8s-79ff7998f-7hg4t 1/1 Running 0 0s pod/hello-k8s-79ff7998f-vdvbq 1/1 Running 0 0s
The nodes keep their own copies of images, so kind load copies the image from Docker into them; a real cluster pulls from a registry instead. kubectl apply sends the desired state. The Deployment made a ReplicaSet, and the ReplicaSet made the pods; that's why pod names are deployment-replicaset-random.โหนดต่างๆ เก็บ image ของตัวเอง kind load จึงคัดลอก image จาก Docker เข้าไปให้ ส่วน cluster จริงจะดึงจาก registry แทน kubectl apply ส่งสถานะที่ต้องการไป Deployment สร้าง ReplicaSet และ ReplicaSet สร้าง pod นี่คือเหตุที่ชื่อ pod อยู่ในรูป deployment-replicaset-สุ่ม
Now break something on purpose. Delete one pod, and scale up: ลองทำให้พังโดยตั้งใจ ลบ pod หนึ่งตัว แล้วขยายจำนวน
$ kubectl delete $(kubectl get pods -l app=hello-k8s -o name | head -n 1) pod "hello-k8s-79ff7998f-2mmsb" deleted from default namespace $ kubectl get pods -l app=hello-k8s NAME READY STATUS RESTARTS AGE hello-k8s-79ff7998f-7hg4t 1/1 Running 0 7s hello-k8s-79ff7998f-vdvbq 1/1 Running 0 7s hello-k8s-79ff7998f-zjbfq 1/1 Running 0 7s $ kubectl scale deployment hello-k8s --replicas=5 deployment.apps/hello-k8s scaled $ kubectl get pods -l app=hello-k8s -o custom-columns=POD:.metadata.name,NODE:.spec.nodeName,IP:.status.podIP POD NODE IP hello-k8s-79ff7998f-25lq6 guide-worker 10.244.1.5 hello-k8s-79ff7998f-7hg4t guide-worker2 10.244.2.2 hello-k8s-79ff7998f-ffqlm guide-worker 10.244.1.6 hello-k8s-79ff7998f-vdvbq guide-worker 10.244.1.4 hello-k8s-79ff7998f-zjbfq guide-worker2 10.244.2.3 $ kubectl scale deployment hello-k8s --replicas=3 deployment.apps/hello-k8s scaled
A few seconds after the delete there were three pods again, one of them brand new. The five pods were spread over both workers, each with its own IP. -l app=hello-k8s selects by label.ไม่กี่วินาทีหลังการลบ ก็กลับมามีสาม pod อีกครั้ง โดยมีตัวหนึ่งเป็นตัวใหม่ pod ทั้งห้าตัวกระจายอยู่บน worker ทั้งสองโหนด และแต่ละตัวมี IP ของตัวเอง -l app=hello-k8s เลือกด้วย label
Lesson 4 · Running appsบทที่ 4 · รันแอป
ServicesService
Pods are replaced all the time, and every new pod gets a new IP. A Service gives a group of pods one stable name and address, and spreads connections across whichever pods currently match its selector and are ready.
pod ถูกแทนที่อยู่ตลอดเวลา และ pod ใหม่ทุกตัวได้ IP ใหม่ Service ให้ชื่อและ address ที่คงที่แก่กลุ่มของ pod และกระจายการเชื่อมต่อไปยัง pod ที่ตรงกับ selector และพร้อมทำงานอยู่ในขณะนั้น
nodePort is opened on every node; port is the Service's own port inside the cluster; targetPort is where the container listens.nodePort ถูกเปิดบนทุกโหนด port คือพอร์ตของ Service เองภายใน cluster และ targetPort คือพอร์ตที่ container listen อยู่$ kubectl apply -f k8s/service.yaml service/hello-k8s created $ kubectl get service hello-k8s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-k8s NodePort 10.96.156.186 <none> 80:30080/TCP 0s $ kubectl get endpointslices -l kubernetes.io/service-name=hello-k8s NAME ADDRESSTYPE PORTS ENDPOINTS AGE hello-k8s-xk4lh IPv4 3000 10.244.2.2,10.244.2.3,10.244.1.4 0s $ for i in 1 2 3 4 5 6; do curl -s localhost:8095; done {"greeting":"Hello","version":"1.0","pod":"hello-k8s-79ff7998f-zjbfq"} {"greeting":"Hello","version":"1.0","pod":"hello-k8s-79ff7998f-zjbfq"} {"greeting":"Hello","version":"1.0","pod":"hello-k8s-79ff7998f-vdvbq"} {"greeting":"Hello","version":"1.0","pod":"hello-k8s-79ff7998f-zjbfq"} {"greeting":"Hello","version":"1.0","pod":"hello-k8s-79ff7998f-7hg4t"} {"greeting":"Hello","version":"1.0","pod":"hello-k8s-79ff7998f-7hg4t"}
The Service got a stable cluster IP, and its EndpointSlice lists the three pod IPs behind it. Six requests from my computer were answered by different pods: that's the load balancing.Service ได้ cluster IP ที่คงที่ และ EndpointSlice ของมันแสดง IP ของ pod สามตัวที่อยู่ด้านหลัง คำขอหกครั้งจากเครื่องของผมถูกตอบโดย pod ต่างกัน นี่คือการกระจายโหลด
Inside the cluster, other pods use the Service's name. Its full DNS name is service.namespace.svc.cluster.local. The app image includes busybox's wget and nslookup, so I ran them in one of its pods:
ภายใน cluster pod อื่นใช้ชื่อของ Service ชื่อ DNS เต็มคือ service.namespace.svc.cluster.local image ของแอปมี wget และ nslookup ของ busybox อยู่แล้ว ผมจึงรันมันใน pod หนึ่งของแอป
$ kubectl exec deploy/hello-k8s -- wget -qO- http://hello-k8s/ {"greeting":"Hello","version":"1.0","pod":"hello-k8s-79ff7998f-7hg4t"} $ kubectl exec deploy/hello-k8s -- nslookup hello-k8s.default.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10:53 Name: hello-k8s.default.svc.cluster.local Address: 10.96.156.186
| Service typeชนิดของ Service | Reachable fromเข้าถึงได้จาก |
|---|---|
ClusterIP | inside the cluster only (the default): databases, internal APIsภายใน cluster เท่านั้น (ค่าเริ่มต้น) เช่น ฐานข้อมูล API ภายใน |
NodePort | a port (30000–32767) on every node: handy for labs like this oneพอร์ต (30000–32767) บนทุกโหนด สะดวกสำหรับแล็บแบบนี้ |
LoadBalancer | a cloud load balancer with a public address (AWS, Azure, Google Cloud)load balancer ของคลาวด์ที่มี address สาธารณะ (AWS, Azure, Google Cloud) |
| Ingress / GatewayIngress / Gateway | not a Service type: HTTP routing by host name and path to many Services behind one addressไม่ใช่ชนิดของ Service แต่เป็นการจัดเส้นทาง HTTP ตามชื่อโฮสต์และพาธ ไปยังหลาย Service ผ่าน address เดียว |
Lesson 5 · Running them wellบทที่ 5 · รันให้ดี
ConfigMaps and SecretsConfigMap และ Secret
The same image should run in testing and in production; only the settings differ. A ConfigMap holds settings as key-value pairs, and a pod can receive them as environment variables. envFrom turns every key into a variable.
image เดียวกันควรรันได้ทั้งตอนทดสอบและบน production ที่ต่างกันมีเพียงการตั้งค่า ConfigMap เก็บค่าตั้งเป็นคู่ key กับ value และ pod รับค่าเหล่านั้นเป็นตัวแปรสภาพแวดล้อมได้ envFrom เปลี่ยนทุก key ให้เป็นตัวแปร
$ kubectl apply -f k8s/ configmap/hello-config created deployment.apps/hello-k8s configured service/hello-k8s unchanged $ kubectl rollout status deployment/hello-k8s Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... deployment "hello-k8s" successfully rolled out $ curl -s localhost:8095 {"greeting":"Sawasdee","version":"1.0","pod":"hello-k8s-5cbd59d585-h5br5"}
apply -f k8s/ applies every file in the folder; Kubernetes only changes what's different. Changing the pod template replaced the pods one at a time, and the new ones say Sawasdee.apply -f k8s/ apply ทุกไฟล์ในโฟลเดอร์ และ Kubernetes เปลี่ยนเฉพาะสิ่งที่ต่างไป การเปลี่ยน template ของ pod ทำให้ pod ถูกแทนที่ทีละตัว และตัวใหม่ทักทายว่า Sawasdee
Passwords and keys go in a Secret, which works like a ConfigMap but is kept separately, so access to it can be limited. The database in Lesson 8 will read this one: รหัสผ่านและกุญแจเก็บไว้ใน Secret ซึ่งทำงานคล้าย ConfigMap แต่ถูกเก็บแยก จึงจำกัดสิทธิ์การเข้าถึงได้ ฐานข้อมูลในบทที่ 8 จะอ่าน Secret ตัวนี้
$ kubectl create secret generic db-secret --from-literal=password=s3cret-pass secret/db-secret created $ kubectl get secret db-secret -o jsonpath='{.data.password}'; echo czNjcmV0LXBhc3M= $ kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d; echo s3cret-pass
Secret values are only base64-encoded, and anyone allowed to read the Secret can decode them, as above. Protect them with access rules (RBAC), turn on encryption at rest, and keep them out of Git; teams often use a secrets manager (Vault, a cloud secrets service) that feeds Kubernetes.ค่าใน Secret แค่ เข้ารหัสแบบ base64 และใครก็ตามที่มีสิทธิ์อ่าน Secret ก็ถอดรหัสได้แบบข้างบน ให้ป้องกันด้วยกฎการเข้าถึง (RBAC) เปิดการเข้ารหัสขณะจัดเก็บ และเก็บไว้นอก Git หลายทีมใช้ตัวจัดการข้อมูลลับ (Vault หรือบริการของคลาวด์) ที่ส่งค่าเข้า Kubernetes
Lesson 6 · Running them wellบทที่ 6 · รันให้ดี
Health checks and resourcesการตรวจสุขภาพและทรัพยากร
A running process isn't necessarily a working one. A readiness probe asks "can you take traffic yet?": until it passes, the Service sends the pod nothing. A liveness probe asks "are you still OK?": if it fails, the kubelet restarts the container. Our app takes 3 seconds to get ready, and answers /ready and /healthz.
process ที่ทำงานอยู่ไม่ได้แปลว่าทำงานได้ดี readiness probe ถามว่า "รับงานได้หรือยัง" ก่อนจะผ่าน Service จะไม่ส่งอะไรให้ pod นั้น liveness probe ถามว่า "ยังปกติดีไหม" ถ้าไม่ผ่าน kubelet จะเริ่ม container ใหม่ แอปของเราใช้เวลา 3 วินาทีกว่าจะพร้อม และตอบที่ /ready และ /healthz
Requests are what the scheduler reserves on a node for the pod; limits are the most it may use. 50m is 50 millicores, a twentieth of one CPU; Mi is mebibytes.
requests คือสิ่งที่ scheduler จองไว้ให้ pod บนโหนด ส่วน limits คือปริมาณสูงสุดที่ใช้ได้ 50m คือ 50 มิลลิคอร์ หรือหนึ่งในยี่สิบของ CPU หนึ่งตัว และ Mi คือเมบิไบต์
$ kubectl apply -f k8s/deployment.yaml deployment.apps/hello-k8s configured $ kubectl get pods -l app=hello-k8s NAME READY STATUS RESTARTS AGE hello-k8s-5cbd59d585-h5br5 1/1 Running 0 3s hello-k8s-5cbd59d585-p4jq2 1/1 Running 0 4s hello-k8s-5cbd59d585-tmwld 1/1 Running 0 4s hello-k8s-84697db984-n9v7d 0/1 Running 0 1s $ kubectl rollout status deployment/hello-k8s Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... deployment "hello-k8s" successfully rolled out $ kubectl describe deployment hello-k8s | sed -n '/Containers:/,/Volumes:/p' Containers: hello: Image: hello-k8s:1.0 Port: 3000/TCP Host Port: 0/TCP Limits: memory: 128Mi Requests: cpu: 50m memory: 64Mi Liveness: http-get http://:3000/healthz delay=0s timeout=1s period=10s #success=1 #failure=3 Readiness: http-get http://:3000/ready delay=0s timeout=1s period=2s #success=1 #failure=3 Environment Variables from: hello-config ConfigMap Optional: false Environment: <none> Mounts: <none> Volumes: <none>
One second after the apply, the first new pod was Running but 0/1 ready: it was still starting, so it got no traffic, and the old pods kept serving. Kubernetes only removed an old pod once a new one was ready.หนึ่งวินาทีหลัง apply pod ใหม่ตัวแรกมีสถานะ Running แต่พร้อมเพียง 0/1 เพราะยังเริ่มทำงานไม่เสร็จ จึงยังไม่ได้รับคำขอ และ pod เก่ายังให้บริการต่อ Kubernetes ลบ pod เก่าก็ต่อเมื่อ pod ใหม่พร้อมแล้วเท่านั้น
What happens at a limit? This pod keeps allocating memory with a 64 MiB limit, and this one asks for 64 CPUs: เกิดอะไรขึ้นเมื่อถึงขีดจำกัด pod นี้จองหน่วยความจำไปเรื่อยๆ โดยมีขีดจำกัด 64 MiB และ pod นี้ขอ CPU 64 ตัว
$ kubectl apply -f k8s/memhog.yaml pod/memhog created $ kubectl get pod memhog NAME READY STATUS RESTARTS AGE memhog 0/1 OOMKilled 0 8s $ kubectl describe pod memhog | grep -A 2 ' State:' State: Terminated Reason: OOMKilled Exit Code: 137 $ kubectl apply -f k8s/greedy.yaml pod/greedy created $ kubectl get pod greedy NAME READY STATUS RESTARTS AGE greedy 0/1 Pending 0 4s $ kubectl describe pod greedy | sed -n '/Events:/,$p' Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 4s (x2 over 4s) default-scheduler 0/3 nodes are available: 1 node(s) had untolerated taint(s), 2 Insufficient cpu. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
Going over a memory limit kills the container: OOMKilled, exit code 137 (going over a CPU limit only slows it down). A pod whose requests fit on no node stays Pending, and the scheduler says why: the control plane is reserved (a "taint"), and the two workers have too little CPU.การใช้หน่วยความจำเกินขีดจำกัดทำให้ container ถูกฆ่า: OOMKilled exit code 137 (ส่วนการใช้ CPU เกินขีดจำกัดแค่ทำให้ช้าลง) pod ที่ requests ไม่พอดีกับโหนดใดเลยจะค้างอยู่ที่ Pending และ scheduler บอกเหตุผลว่า control plane ถูกสงวนไว้ (มี "taint") และ worker ทั้งสองมี CPU ไม่พอ
Lesson 7 · Running them wellบทที่ 7 · รันให้ดี
Rolling updates and rollbacksrolling update และการย้อนกลับ
To ship version 1.1, change the image in the YAML and apply it. The Deployment makes a new ReplicaSet and does a rolling update: start a new pod, wait until it's ready, remove an old one, repeat. By default at most a quarter extra pods are started (maxSurge) and a quarter may be unavailable (maxUnavailable), rounded so that with 3 replicas all 3 keep serving.
การออกเวอร์ชัน 1.1 ทำได้โดยเปลี่ยน image ใน YAML แล้ว apply Deployment จะสร้าง ReplicaSet ใหม่และทำ rolling update คือเริ่ม pod ใหม่ รอจนพร้อม ลบ pod เก่าหนึ่งตัว แล้วทำซ้ำ โดยค่าเริ่มต้นจะเริ่ม pod เพิ่มได้ไม่เกินหนึ่งในสี่ (maxSurge) และไม่พร้อมใช้งานได้ไม่เกินหนึ่งในสี่ (maxUnavailable) ซึ่งปัดเศษจนเมื่อมี 3 replicas ทั้ง 3 ตัวยังให้บริการอยู่
$ docker build -q --build-arg VERSION=1.1 -t hello-k8s:1.1 . && docker build -q --build-arg VERSION=1.2 -t hello-k8s:1.2 . sha256:a9217a10073affc5404234eab18b613524460b9d529aa2ae941ab53cbbea4ba5 sha256:35d1fdd215c3592579274a35e0be5e83cb3a021d7431b999f9d13c0d9c19b079 $ kind load docker-image hello-k8s:1.1 hello-k8s:1.2 --name guide Image: "hello-k8s:1.1" with ID "sha256:a9217a10073affc5404234eab18b613524460b9d529aa2ae941ab53cbbea4ba5" not yet present on node "guide-worker2", loading... Image: "hello-k8s:1.1" with ID "sha256:a9217a10073affc5404234eab18b613524460b9d529aa2ae941ab53cbbea4ba5" not yet present on node "guide-control-plane", loading... Image: "hello-k8s:1.1" with ID "sha256:a9217a10073affc5404234eab18b613524460b9d529aa2ae941ab53cbbea4ba5" not yet present on node "guide-worker", loading... Image: "hello-k8s:1.2" with ID "sha256:35d1fdd215c3592579274a35e0be5e83cb3a021d7431b999f9d13c0d9c19b079" not yet present on node "guide-worker2", loading... Image: "hello-k8s:1.2" with ID "sha256:35d1fdd215c3592579274a35e0be5e83cb3a021d7431b999f9d13c0d9c19b079" not yet present on node "guide-control-plane", loading... Image: "hello-k8s:1.2" with ID "sha256:35d1fdd215c3592579274a35e0be5e83cb3a021d7431b999f9d13c0d9c19b079" not yet present on node "guide-worker", loading... $ kubectl apply -f k8s/deployment.yaml deployment.apps/hello-k8s configured $ kubectl rollout status deployment/hello-k8s Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... deployment "hello-k8s" successfully rolled out $ for i in 1 2 3; do curl -s localhost:8095; done {"greeting":"Sawasdee","version":"1.1","pod":"hello-k8s-69fbc5644c-v7qkn"} {"greeting":"Sawasdee","version":"1.1","pod":"hello-k8s-69fbc5644c-4xn2h"} {"greeting":"Sawasdee","version":"1.1","pod":"hello-k8s-69fbc5644c-v7qkn"}
Now the broken version 1.2, set straight from the command line: ต่อไปคือเวอร์ชัน 1.2 ที่เสีย ตั้งค่าตรงจาก command line
$ kubectl set image deployment/hello-k8s hello=hello-k8s:1.2 deployment.apps/hello-k8s image updated $ kubectl rollout status deployment/hello-k8s --timeout=40s Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... error: timed out waiting for the condition $ kubectl get pods -l app=hello-k8s NAME READY STATUS RESTARTS AGE hello-k8s-5d6c95bd9d-xv9fz 0/1 CrashLoopBackOff 3 (6s ago) 40s hello-k8s-69fbc5644c-4xn2h 1/1 Running 0 49s hello-k8s-69fbc5644c-v7qkn 1/1 Running 0 59s hello-k8s-69fbc5644c-xkswd 1/1 Running 0 54s $ kubectl logs -l app=hello-k8s --prefix --tail=1 [pod/hello-k8s-5d6c95bd9d-xv9fz/hello] fatal: cannot read /etc/hello/config.json [pod/hello-k8s-69fbc5644c-4xn2h/hello] hello-k8s 1.1 listening on port 3000 [pod/hello-k8s-69fbc5644c-v7qkn/hello] hello-k8s 1.1 listening on port 3000 [pod/hello-k8s-69fbc5644c-xkswd/hello] hello-k8s 1.1 listening on port 3000 $ curl -s localhost:8095 {"greeting":"Sawasdee","version":"1.1","pod":"hello-k8s-69fbc5644c-4xn2h"}
The first 1.2 pod crashed on start, again and again: CrashLoopBackOff (Kubernetes waits longer between each restart). Because it never became ready, the rollout stopped there. The three 1.1 pods kept serving, so users saw nothing wrong.pod 1.2 ตัวแรกพังตอนเริ่มทำงานซ้ำแล้วซ้ำเล่า เกิดสถานะ CrashLoopBackOff (Kubernetes รอนานขึ้นเรื่อยๆ ระหว่างการเริ่มใหม่แต่ละครั้ง) เพราะมันไม่เคยพร้อม การ rollout จึงหยุดอยู่ตรงนั้น pod 1.1 ทั้งสามตัวยังให้บริการต่อ ผู้ใช้จึงไม่เห็นความผิดปกติ
$ kubectl rollout undo deployment/hello-k8s Warning: resource deployments/hello-k8s was previously managed with 'kubectl apply'. Rolling back will not update the kubectl.kubernetes.io/last-applied-configuration annotation, which may cause unexpected behavior on future 'kubectl apply' operations. Consider using 'kubectl apply' with your previous configuration file instead. deployment.apps/hello-k8s rolled back $ kubectl rollout status deployment/hello-k8s Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... deployment "hello-k8s" successfully rolled out $ kubectl rollout history deployment/hello-k8s deployment.apps/hello-k8s REVISION CHANGE-CAUSE 1 <none> 2 <none> 3 <none> 5 <none> 6 <none> $ kubectl get pods -l app=hello-k8s NAME READY STATUS RESTARTS AGE hello-k8s-69fbc5644c-4xn2h 1/1 Running 0 50s hello-k8s-69fbc5644c-v7qkn 1/1 Running 0 60s hello-k8s-69fbc5644c-xkswd 1/1 Running 0 55s
rollout undo went back to the previous ReplicaSet: the crashing pod was removed and the three 1.1 pods stayed. Each change of the pod template is a revision; the undo re-used revision 4 under the new number 6. Read the warning too: the YAML file and the cluster now disagree (Mistakes).rollout undo กลับไปใช้ ReplicaSet ก่อนหน้า pod ที่พังถูกลบออก และ pod 1.1 ทั้งสามตัวยังอยู่ ทุกการเปลี่ยน template ของ pod คือหนึ่ง revision การ undo นำ revision 4 กลับมาใช้ใหม่ในชื่อหมายเลข 6 ให้อ่านคำเตือนด้วย เพราะตอนนี้ไฟล์ YAML กับ cluster ไม่ตรงกันแล้ว (ข้อผิดพลาด)
Lesson 8 · Running them wellบทที่ 8 · รันให้ดี
Storageพื้นที่เก็บข้อมูล
A pod's files vanish with the pod. For data, a pod claims storage with a PersistentVolumeClaim (PVC): "I need 1 GiB". The cluster's StorageClass creates a matching PersistentVolume: in kind a folder on the node, in the cloud a real disk. Here is Postgres with a PVC, its password from the Secret, and a ClusterIP Service so other pods can reach it as postgres.
ไฟล์ของ pod หายไปพร้อมกับ pod ถ้าต้องการเก็บข้อมูล pod จะขอพื้นที่ด้วย PersistentVolumeClaim (PVC) เช่น "ต้องการ 1 GiB" แล้ว StorageClass ของ cluster จะสร้าง PersistentVolume ที่ตรงกันให้ ใน kind คือโฟลเดอร์บนโหนด บนคลาวด์คือดิสก์จริง นี่คือ Postgres ที่มี PVC ใช้รหัสผ่านจาก Secret และมี Service แบบ ClusterIP ให้ pod อื่นเข้าถึงด้วยชื่อ postgres
$ kubectl apply -f k8s/postgres.yaml persistentvolumeclaim/postgres-data created deployment.apps/postgres created service/postgres created $ kubectl rollout status deployment/postgres --timeout=180s Waiting for deployment "postgres" rollout to finish: 0 of 1 updated replicas are available... deployment "postgres" successfully rolled out $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE postgres-data Bound pvc-ac5ceb84-5d23-48a2-8b6f-06e27f24ab23 1Gi RWO standard <unset> 18s $ kubectl exec deploy/postgres -- psql -U app -c "CREATE TABLE notes (body text); INSERT INTO notes VALUES ('kept on a volume');" CREATE TABLE INSERT 0 1 $ kubectl delete pod -l app=postgres pod "postgres-9495f6676-tm6cr" deleted from default namespace $ kubectl rollout status deployment/postgres --timeout=180s deployment "postgres" successfully rolled out $ kubectl exec deploy/postgres -- psql -U app -c "SELECT * FROM notes;" body ------------------ kept on a volume (1 row) $ kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage,CLAIM:.spec.claimRef.name,CLASS:.spec.storageClassName NAME CAPACITY CLAIM CLASS pvc-ac5ceb84-5d23-48a2-8b6f-06e27f24ab23 1Gi postgres-data standard
The claim is Bound to a volume the standard class created. The Deployment replaced the deleted pod, the new pod mounted the same volume, and the row was still there.claim มีสถานะ Bound กับ volume ที่ class standard สร้างให้ Deployment แทนที่ pod ที่ถูกลบ pod ใหม่ mount volume เดิม และแถวข้อมูลยังอยู่
Real databases on Kubernetes usually use a StatefulSet (stable names and one volume per replica) or an operator that handles backups and failover, and many teams simply use a managed database from their cloud. strategy: Recreate above makes sure two Postgres pods never share the volume during an update.ฐานข้อมูลจริงบน Kubernetes มักใช้ StatefulSet (ชื่อคงที่และ volume ของแต่ละ replica) หรือ operator ที่จัดการการสำรองข้อมูลและการสลับเมื่อเกิดเหตุ และหลายทีมก็เลือกใช้ฐานข้อมูลแบบ managed ของคลาวด์ไปเลย strategy: Recreate ข้างบนทำให้ไม่มี pod ของ Postgres สองตัวใช้ volume ร่วมกันระหว่างการอัปเดต
Lesson 9 · Operatingบทที่ 9 · ดูแลระบบ
Labels and namespacesLabel และ namespace
Labels are key-value tags on objects, and they are how Kubernetes connects things: Deployments find their pods and Services find their endpoints by label selector. Namespaces divide one cluster into separate spaces, for example per team or per environment; names only have to be unique inside a namespace. label คือแท็กแบบ key-value บนออบเจกต์ และเป็นวิธีที่ Kubernetes เชื่อมสิ่งต่างๆ เข้าด้วยกัน Deployment หา pod ของมัน และ Service หา endpoint ของมันด้วย label selector ส่วน namespace แบ่ง cluster เดียวออกเป็นหลายพื้นที่แยกกัน เช่น ตามทีมหรือตามสภาพแวดล้อม ชื่อต่างๆ ต้องไม่ซ้ำกันเฉพาะภายใน namespace เดียวกัน
$ kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS hello-k8s-69fbc5644c-4xn2h 1/1 Running 0 72s app=hello-k8s,pod-template-hash=69fbc5644c hello-k8s-69fbc5644c-v7qkn 1/1 Running 0 82s app=hello-k8s,pod-template-hash=69fbc5644c hello-k8s-69fbc5644c-xkswd 1/1 Running 0 77s app=hello-k8s,pod-template-hash=69fbc5644c postgres-9495f6676-lr2hg 1/1 Running 0 4s app=postgres,pod-template-hash=9495f6676 $ kubectl create namespace staging namespace/staging created $ kubectl apply -n staging -f k8s/configmap.yaml -f k8s/deployment.yaml configmap/hello-config created deployment.apps/hello-k8s created $ kubectl get pods -n staging NAME READY STATUS RESTARTS AGE hello-k8s-69fbc5644c-4tnfn 1/1 Running 0 7s hello-k8s-69fbc5644c-f92dk 1/1 Running 0 7s hello-k8s-69fbc5644c-jgtnf 1/1 Running 0 7s $ kubectl get deployments --all-namespaces NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE default hello-k8s 3/3 3 3 2m17s default postgres 1/1 1 1 29s kube-system coredns 2/2 2 2 3m23s local-path-storage local-path-provisioner 1/1 1 1 3m22s staging hello-k8s 3/3 3 3 7s $ kubectl delete namespace staging namespace "staging" deleted
The same files made a second, separate copy of the app in staging. pod-template-hash is a label the Deployment adds itself to tell its ReplicaSets apart. Deleting a namespace deletes everything in it.ไฟล์ชุดเดียวกันสร้างแอปชุดที่สองแยกออกมาใน staging ส่วน pod-template-hash คือ label ที่ Deployment เพิ่มเองเพื่อแยก ReplicaSet ของมัน การลบ namespace จะลบทุกอย่างในนั้น
Lesson 10 · Operatingบทที่ 10 · ดูแลระบบ
Debuggingการดีบัก
Most problems show up in the STATUS column. This pod's image tag has a letter O instead of a zero:
ปัญหาส่วนใหญ่ปรากฏในคอลัมน์ STATUS image ของ pod นี้มี tag ที่ใช้ตัวอักษร O แทนเลขศูนย์
$ kubectl run typo --image=hello-k8s:1.O pod/typo created $ kubectl get pod typo NAME READY STATUS RESTARTS AGE typo 0/1 ImagePullBackOff 0 15s $ kubectl describe pod typo | sed -n '/Events:/,$p' Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 15s default-scheduler Successfully assigned default/typo to guide-worker Warning Failed 13s kubelet spec.containers{typo}: Failed to pull image "hello-k8s:1.O": failed to pull and unpack image "docker.io/library/hello-k8s:1.O": failed to resolve reference "docker.io/library/hello-k8s:1.O": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed Warning Failed 13s kubelet spec.containers{typo}: Error: ErrImagePull Normal BackOff 12s kubelet spec.containers{typo}: Back-off pulling image "hello-k8s:1.O" Warning Failed 12s kubelet spec.containers{typo}: Error: ImagePullBackOff Normal Pulling 1s (x2 over 15s) kubelet spec.containers{typo}: Pulling image "hello-k8s:1.O"
| Statusสถานะ | Usually meansมักหมายถึง | Look atดูที่ |
|---|---|---|
Pending | no node can take it: not enough CPU or memory, or no volume yetไม่มีโหนดรับได้ CPU หรือหน่วยความจำไม่พอ หรือยังไม่มี volume | describe pod |
ImagePullBackOff | wrong image name or tag, or no access to the registryชื่อหรือ tag ของ image ผิด หรือไม่มีสิทธิ์เข้าถึง registry | describe pod |
CrashLoopBackOff | the program starts and exits, again and againโปรแกรมเริ่มแล้วจบซ้ำแล้วซ้ำเล่า | logs --previous |
OOMKilled | used more memory than its limitใช้หน่วยความจำเกินขีดจำกัด | describe pod |
Running, 0/1 | running, but the readiness probe doesn't passกำลังทำงาน แต่ readiness probe ไม่ผ่าน | describe pod, logs |
| Commandคำสั่ง | Showsแสดง |
|---|---|
kubectl get pods -o wide | status, restarts, node, IPสถานะ จำนวนการเริ่มใหม่ โหนด IP |
kubectl describe pod NAME | settings, state and the eventsการตั้งค่า สถานะ และ events |
kubectl logs NAME --previous | output of the container before its last crashผลลัพธ์ของ container ก่อนพังครั้งล่าสุด |
kubectl get events --sort-by=.lastTimestamp | everything that happened recently, in orderทุกอย่างที่เพิ่งเกิดขึ้นตามลำดับเวลา |
kubectl exec -it NAME -- sh | a shell inside a running containershell ภายใน container ที่กำลังทำงาน |
kubectl port-forward svc/NAME 8080:80 | reach a ClusterIP service from your computerเข้าถึง service แบบ ClusterIP จากเครื่องของเรา |
Lesson 11 · Your turnบทที่ 11 · ถึงตาคุณแล้ว
Practiceแบบฝึกหัด
Three tasks on the same cluster. Each solution was run; the output is under it. งานสามข้อบน cluster เดิม ทุกเฉลยรันจริงแล้ว ผลลัพธ์อยู่ด้านล่าง
1. Four replicas1. สี่ replicas
Run four copies of hello-k8s and check which nodes they landed on.รัน hello-k8s สี่ชุด แล้วตรวจว่าแต่ละตัวไปอยู่บนโหนดไหน
Show solutionดูเฉลย
$ kubectl scale deployment hello-k8s --replicas=4 deployment.apps/hello-k8s scaled $ kubectl get pods -l app=hello-k8s -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES hello-k8s-69fbc5644c-4xn2h 1/1 Running 0 111s 10.244.1.12 guide-worker <none> <none> hello-k8s-69fbc5644c-nk245 1/1 Running 0 5s 10.244.2.13 guide-worker2 <none> <none> hello-k8s-69fbc5644c-v7qkn 1/1 Running 0 2m1s 10.244.1.11 guide-worker <none> <none> hello-k8s-69fbc5644c-xkswd 1/1 Running 0 116s 10.244.2.7 guide-worker2 <none> <none>
In a GitOps workflow you would change replicas in the YAML instead, so the file stays the truth.ถ้าทำงานแบบ GitOps จะเปลี่ยน replicas ในไฟล์ YAML แทน เพื่อให้ไฟล์ยังเป็นความจริงเสมอ
2. A new greeting2. คำทักทายใหม่
Change the greeting to Mingalaba in the ConfigMap and make the app use it.เปลี่ยนคำทักทายเป็น Mingalaba ใน ConfigMap แล้วทำให้แอปใช้ค่าใหม่
Show solutionดูเฉลย
$ sed -i '' 's/GREETING: Sawasdee/GREETING: Mingalaba/' k8s/configmap.yaml && kubectl apply -f k8s/configmap.yaml configmap/hello-config configured $ curl -s localhost:8095 {"greeting":"Sawasdee","version":"1.1","pod":"hello-k8s-69fbc5644c-4xn2h"} $ kubectl rollout restart deployment/hello-k8s && kubectl rollout status deployment/hello-k8s deployment.apps/hello-k8s restarted Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "hello-k8s" rollout to finish: 1 old replicas are pending termination... deployment "hello-k8s" successfully rolled out $ curl -s localhost:8095 {"greeting":"Mingalaba","version":"1.1","pod":"hello-k8s-68c9954c49-78k4h"}
Environment variables are read when a container starts, so the running pods kept Sawasdee until rollout restart replaced them. (sed -i '' is the macOS form; on Linux it's sed -i.)ตัวแปรสภาพแวดล้อมถูกอ่านตอน container เริ่มทำงาน pod ที่ทำงานอยู่จึงยังใช้ Sawasdee จนกว่า rollout restart จะแทนที่มัน (sed -i '' เป็นรูปแบบของ macOS บน Linux ใช้ sed -i)
3. A one-off Job3. Job ที่รันครั้งเดียว
Deployments run forever. Run a task that should finish (a backup, a migration) as a Job: make one that prints a line, wait for it, read its output.Deployment ทำงานไปตลอด งานที่ควรจบ (การสำรองข้อมูล การ migrate) ให้รันเป็น Job ลองสร้าง Job ที่พิมพ์หนึ่งบรรทัด รอให้เสร็จ แล้วอ่านผลลัพธ์
Show solutionดูเฉลย
$ kubectl create job hello-job --image=busybox -- echo "hello from a job" job.batch/hello-job created $ kubectl wait --for=condition=complete job/hello-job --timeout=120s job.batch/hello-job condition met $ kubectl logs job/hello-job hello from a job $ kubectl get jobs NAME STATUS COMPLETIONS DURATION AGE hello-job Complete 1/1 10s 10s
A CronJob creates a Job on a schedule, like cron.CronJob สร้าง Job ตามตารางเวลา เหมือน cron
Lesson 12 · Debuggingบทที่ 12 · การดีบัก
Common mistakesข้อผิดพลาดที่พบบ่อย
Each of these was made on purpose on the kind cluster; the output lines are copied from it. ทุกข้อทำขึ้นโดยตั้งใจบน cluster ของ kind และบรรทัดผลลัพธ์คัดลอกมาจากที่นั่น
Using :latest for your own imageใช้ :latest กับ image ของเราเอง
For a :latest tag Kubernetes sets imagePullPolicy: Always and tries the registry every time, even though the image was loaded onto the node. Use a version tag; it also tells you exactly what is running.สำหรับ tag :latest Kubernetes ตั้ง imagePullPolicy: Always และพยายามดึงจาก registry ทุกครั้ง แม้ image จะถูกโหลดลงโหนดแล้ว ให้ใช้ tag ที่เป็นเวอร์ชัน ซึ่งยังบอกได้ชัดเจนด้วยว่ากำลังรันอะไรอยู่
A Service selector that matches nothingselector ของ Service ที่ไม่ตรงกับอะไรเลย
A typo in the selector (hello-k8) gives a Service with no endpoints. Nothing warns you; connections are just refused. Check the EndpointSlice.พิมพ์ selector ผิด (hello-k8) ทำให้ได้ Service ที่ไม่มี endpoint ไม่มีอะไรเตือน การเชื่อมต่อแค่ถูกปฏิเสธ ให้ตรวจ EndpointSlice
The wrong targetPorttargetPort ผิด
The Service finds the pods, but sends traffic to a port where nothing listens. targetPort must be the container's port.Service หา pod เจอ แต่ส่งคำขอไปที่พอร์ตที่ไม่มีอะไร listen อยู่ targetPort ต้องเป็นพอร์ตของ container
Expecting a ConfigMap change to reach running podsคิดว่าการแก้ ConfigMap จะไปถึง pod ที่ทำงานอยู่
Environment variables are fixed when the container starts. After changing a ConfigMap, restart the Deployment (Practice 2).ตัวแปรสภาพแวดล้อมถูกกำหนดตอน container เริ่มทำงาน หลังแก้ ConfigMap ให้ restart Deployment (แบบฝึกหัดข้อ 2)
Fixing the cluster but not the YAMLแก้ที่ cluster แต่ไม่แก้ YAML
kubectl set image, scale and rollout undo change the cluster but not your files, and the next apply brings the file's values back. Kubernetes even warns about it. In real projects, change the YAML in Git and apply that.kubectl set image, scale และ rollout undo เปลี่ยน cluster แต่ไม่เปลี่ยนไฟล์ของเรา และ apply ครั้งถัดไปจะนำค่าในไฟล์กลับมา Kubernetes ก็เตือนเรื่องนี้ ในโปรเจกต์จริงให้แก้ YAML ใน Git แล้ว apply ไฟล์นั้น
Lesson 13 · Referenceบทที่ 13 · อ้างอิง
Glossaryคำศัพท์
| English | ไทย | Meaningความหมาย |
|---|---|---|
| cluster / node | คลัสเตอร์ / โหนด | A group of machines run as one / one of those machinesกลุ่มเครื่องที่ทำงานเหมือนเป็นระบบเดียว / เครื่องหนึ่งในกลุ่มนั้น |
| control plane | control plane | The parts that store the desired state and make decisionsส่วนที่เก็บสถานะที่ต้องการและตัดสินใจ |
| desired state | สถานะที่ต้องการ | What you declared should be runningสิ่งที่เราประกาศว่าควรทำงานอยู่ |
| pod | พ็อด | One or more containers scheduled together, with one IPcontainer หนึ่งตัวหรือมากกว่าที่ถูกจัดวางด้วยกันและมี IP เดียว |
| Deployment / ReplicaSet | Deployment / ReplicaSet | Keeps N identical pods running and updates them / the set of pods for one versionรักษา pod ที่เหมือนกัน N ตัวและอัปเดตให้ / ชุดของ pod สำหรับเวอร์ชันหนึ่ง |
| Service | เซอร์วิส | A stable name and address in front of matching podsชื่อและ address ที่คงที่ซึ่งอยู่หน้า pod ที่ตรงเงื่อนไข |
| label / selector | ป้าย / ตัวเลือก | A key-value tag / a query that picks objects by labelsแท็กแบบ key-value / เงื่อนไขที่เลือกออบเจกต์ด้วย label |
| ConfigMap / Secret | ConfigMap / Secret | Settings / sensitive settings, given to podsค่าตั้ง / ค่าตั้งที่เป็นความลับ ที่ส่งให้ pod |
| probe | probe | A health check: readiness (traffic?) and liveness (restart?)การตรวจสุขภาพ: readiness (รับงานได้ไหม) และ liveness (ต้องเริ่มใหม่ไหม) |
| requests / limits | requests / limits | Resources reserved for scheduling / the most a container may useทรัพยากรที่จองไว้ตอนจัดวาง / ปริมาณสูงสุดที่ container ใช้ได้ |
| rolling update | rolling update | Replacing pods a few at a time, without downtimeการแทนที่ pod ทีละไม่กี่ตัวโดยไม่มีช่วงหยุดให้บริการ |
| PVC / PV | PVC / PV | A request for storage / the storage that satisfies itคำขอพื้นที่เก็บข้อมูล / พื้นที่ที่ตอบคำขอนั้น |
| namespace | เนมสเปซ | A separate space for names inside one clusterพื้นที่แยกสำหรับชื่อภายใน cluster เดียว |
| Job / CronJob | Job / CronJob | A task that runs to completion / on a scheduleงานที่รันจนจบ / ตามตารางเวลา |
| kubectl | kubectl | The command-line tool that talks to the API serverเครื่องมือ command line ที่คุยกับ API server |