Kubernetes Beginner Guide

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 การตั้งค่า การตรวจสุขภาพ การจำกัดทรัพยากร การอัปเดต การย้อนกลับ พื้นที่เก็บข้อมูล และการดีบัก

Every command on this page was run for real on a kind cluster (Kubernetes in Docker), and the output under it is what it printed. Manifests were saved after each lesson, so each file shown is the one that was applied. You should know containers first: see the Docker Beginner Guide. ทุกคำสั่งในหน้านี้รันจริงบน cluster ที่สร้างด้วย kind (Kubernetes in Docker) และผลลัพธ์ด้านล่างคือสิ่งที่มันพิมพ์ออกมา ไฟล์ manifest ถูกบันทึกหลังแต่ละบท ไฟล์ที่เห็นจึงเป็นไฟล์ที่ถูก apply จริง ควรรู้เรื่อง container มาก่อน ดู Docker Beginner Guide

  • Run withรันด้วย Kubernetes 1.37
  • kind 0.33
  • kubectl 1.36
  • Nodesโหนด 3

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 ซ่อมตัวเองได้

kubectl talks to the API server on the control plane. The control plane also has etcd, the scheduler and the controller manager. Each worker node runs a kubelet, kube-proxy and pods. kubectl control plane API server etcd (the state) scheduler controller manager worker node 1 worker node 2 kubelet · kube-proxy kubelet · kube-proxy pod pod pod
Everything goes through the API server, which stores the desired state in etcd. The scheduler picks a node for each new pod; each node's kubelet starts the containers.ทุกอย่างผ่าน API server ซึ่งเก็บสถานะที่ต้องการไว้ใน etcd scheduler เลือกโหนดให้ pod ใหม่แต่ละตัว และ kubelet ของแต่ละโหนดเป็นผู้เริ่ม container

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

Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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 เดี่ยวหนึ่งตัว เหมาะกับการทดลองเร็วๆ

Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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 เมื่อมีอะไรติดขัด ให้อ่านส่วนนี้ก่อนเสมอ

Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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

Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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 หนึ่งตัว แล้วขยายจำนวน

Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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 และพร้อมทำงานอยู่ในขณะนั้น

A request to localhost:8095 reaches node port 30080, then the Service hello-k8s on port 80, which forwards to port 3000 of one of the three pods. localhost :8095 node :30080 hello-k8s Service :80 pod :3000 pod :3000 pod :3000
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 อยู่
Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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 หนึ่งของแอป

Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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ชนิดของ ServiceReachable fromเข้าถึงได้จาก
ClusterIPinside the cluster only (the default): databases, internal APIsภายใน cluster เท่านั้น (ค่าเริ่มต้น) เช่น ฐานข้อมูล API ภายใน
NodePorta port (30000–32767) on every node: handy for labs like this oneพอร์ต (30000–32767) บนทุกโหนด สะดวกสำหรับแล็บแบบนี้
LoadBalancera cloud load balancer with a public address (AWS, Azure, Google Cloud)load balancer ของคลาวด์ที่มี address สาธารณะ (AWS, Azure, Google Cloud)
Ingress / GatewayIngress / Gatewaynot 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 ให้เป็นตัวแปร

Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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 ตัวนี้

Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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
Not encryptedไม่ได้เข้ารหัส

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 คือเมบิไบต์

Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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 ตัว

Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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 ตัวยังให้บริการอยู่

Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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

Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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 ทั้งสามตัวยังให้บริการต่อ ผู้ใช้จึงไม่เห็นความผิดปกติ

Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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

Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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 เดิม และแถวข้อมูลยังอยู่

In productionบน production

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 เดียวกัน

Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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 แทนเลขศูนย์

Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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ดูที่
Pendingno node can take it: not enough CPU or memory, or no volume yetไม่มีโหนดรับได้ CPU หรือหน่วยความจำไม่พอ หรือยังไม่มี volumedescribe pod
ImagePullBackOffwrong image name or tag, or no access to the registryชื่อหรือ tag ของ image ผิด หรือไม่มีสิทธิ์เข้าถึง registrydescribe pod
CrashLoopBackOffthe program starts and exits, again and againโปรแกรมเริ่มแล้วจบซ้ำแล้วซ้ำเล่าlogs --previous
OOMKilledused more memory than its limitใช้หน่วยความจำเกินขีดจำกัดdescribe pod
Running, 0/1running, but the readiness probe doesn't passกำลังทำงาน แต่ readiness probe ไม่ผ่านdescribe pod, logs
Commandคำสั่งShowsแสดง
kubectl get pods -o widestatus, restarts, node, IPสถานะ จำนวนการเริ่มใหม่ โหนด IP
kubectl describe pod NAMEsettings, state and the eventsการตั้งค่า สถานะ และ events
kubectl logs NAME --previousoutput of the container before its last crashผลลัพธ์ของ container ก่อนพังครั้งล่าสุด
kubectl get events --sort-by=.lastTimestampeverything that happened recently, in orderทุกอย่างที่เพิ่งเกิดขึ้นตามลำดับเวลา
kubectl exec -it NAME -- sha shell inside a running containershell ภายใน container ที่กำลังทำงาน
kubectl port-forward svc/NAME 8080:80reach 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ดูเฉลย
Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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ดูเฉลย
Terminal, in ~/hello-k8sเทอร์มินัล ใน ~/hello-k8skind 0.33 · Kubernetes 1.37
$ 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ดูเฉลย
Terminalเทอร์มินัลkind 0.33 · Kubernetes 1.37
$ 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 ที่เป็นเวอร์ชัน ซึ่งยังบอกได้ชัดเจนด้วยว่ากำลังรันอะไรอยู่

✗ Wrong✗ ผิด
✓ Right✓ ถูก

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

✗ Wrong✗ ผิด
✓ Right✓ ถูก

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

✗ Wrong✗ ผิด
✓ Right✓ ถูก

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)

✗ Wrong✗ ผิด
✓ Right✓ ถูก

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 ไฟล์นั้น

✗ Wrong✗ ผิด
✓ Right✓ ถูก

Lesson 13 · Referenceบทที่ 13 · อ้างอิง

Glossaryคำศัพท์

EnglishไทยMeaningความหมาย
cluster / nodeคลัสเตอร์ / โหนดA group of machines run as one / one of those machinesกลุ่มเครื่องที่ทำงานเหมือนเป็นระบบเดียว / เครื่องหนึ่งในกลุ่มนั้น
control planecontrol planeThe 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 / ReplicaSetDeployment / ReplicaSetKeeps 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 / SecretConfigMap / SecretSettings / sensitive settings, given to podsค่าตั้ง / ค่าตั้งที่เป็นความลับ ที่ส่งให้ pod
probeprobeA health check: readiness (traffic?) and liveness (restart?)การตรวจสุขภาพ: readiness (รับงานได้ไหม) และ liveness (ต้องเริ่มใหม่ไหม)
requests / limitsrequests / limitsResources reserved for scheduling / the most a container may useทรัพยากรที่จองไว้ตอนจัดวาง / ปริมาณสูงสุดที่ container ใช้ได้
rolling updaterolling updateReplacing pods a few at a time, without downtimeการแทนที่ pod ทีละไม่กี่ตัวโดยไม่มีช่วงหยุดให้บริการ
PVC / PVPVC / PVA request for storage / the storage that satisfies itคำขอพื้นที่เก็บข้อมูล / พื้นที่ที่ตอบคำขอนั้น
namespaceเนมสเปซA separate space for names inside one clusterพื้นที่แยกสำหรับชื่อภายใน cluster เดียว
Job / CronJobJob / CronJobA task that runs to completion / on a scheduleงานที่รันจนจบ / ตามตารางเวลา
kubectlkubectlThe command-line tool that talks to the API serverเครื่องมือ command line ที่คุยกับ API server