Kubernetes ตั้งแต่ศูนย์ถึงระดับมืออาชีพ: 100 ตัวอย่างด้วย kubectl, Manifest, Helm และ GitOps

Kubernetes ตั้งแต่ศูนย์ถึงระดับมืออาชีพ: 100 ตัวอย่างด้วย kubectl, Manifest, Helm และ GitOps

Kubernetes รันคอนเทนเนอร์บนกลุ่มเครื่องที่เรียกว่าคลัสเตอร์ มันรีสตาร์ตคอนเทนเนอร์ที่ล้มเหลว กระจายคอนเทนเนอร์ไปยังโหนดต่างๆ ปล่อยเวอร์ชันใหม่โดยไม่หยุดให้บริการ และปรับขนาดตามโหลด บทความนี้พาคุณจาก Pod แรกไปจนถึงการตั้งค่าที่คำนึงถึงโปรดักชันใน 100 ตัวอย่าง คุณจะได้เรียน kubectl และ context, Pod, Deployment และการอัปเดตแบบค่อยเป็นค่อยไป, Service และ Ingress, ConfigMap และ Secret, ที่เก็บข้อมูลถาวร, Job และ CronJob, StatefulSet และ DaemonSet, การปรับขนาดอัตโนมัติ, การขอทรัพยากรและ probe, การจัดตาราง, RBAC และความปลอดภัยของ Pod, Helm และ Kustomize, การสังเกตการณ์และการดีบัก, GitOps และการสำรองข้อมูล หมายเหตุตามจริง: เครื่องที่ใช้เตรียมบทความนี้ไม่ได้ติดตั้งคลัสเตอร์ Kubernetes หรือ kubectl จึงไม่ได้รันตัวอย่างทั้ง 100 ข้อที่นี่ manifest เขียนตาม Kubernetes API ปัจจุบัน แต่คุณควรนำไปใช้กับคลัสเตอร์ทดสอบด้วยตัวเองก่อนพึ่งพา

สิ่งที่ต้องเตรียม

Docker Desktop ที่มี Kubernetes ในตัว หรือ kind หรือ minikube สำหรับคลัสเตอร์ฝึกบนเครื่อง พร้อม kubectl และ Helm ความรู้พื้นฐานเรื่องคอนเทนเนอร์จะช่วยได้ บทความ Docker ในชุดนี้เป็นจุดเริ่มต้นที่ดี บางตัวอย่างใช้ผู้ให้บริการคลาวด์อย่าง Azure Kubernetes Service และ ingress controller ให้ใช้โฮสต์ตัวอย่างเช่น shop.example.com และรหัสผ่านตัวอย่างเช่น <strong-password> ห้ามใส่ข้อมูลรับรองจริงลงใน manifest หรือคำสั่ง

วิธีใช้ตัวอย่าง

ตัวอย่าง bash ใช้กับ kubectl, Helm และ Kustomize ตัวอย่าง YAML ให้บันทึกเป็นไฟล์ .yaml แล้วใช้ kubectl apply -f ตามชื่อไฟล์ที่ระบุ ตัวอย่าง Python ใช้ไลบรารี kubernetes ของ Python

หัวข้อที่ 1: เริ่มต้นกับ Kubernetes

Kubernetes คือลูปควบคุม คุณอธิบายสถานะที่ต้องการด้วย YAML แล้วคลัสเตอร์จะทำงานต่อจนสภาพจริงตรงกับที่ต้องการ เรียนรู้องค์ประกอบของคลัสเตอร์ ไคลเอนต์ kubectl context และ namespace

control plane และโหนด

control plane เก็บสถานะที่ต้องการและตัดสินใจ ส่วนโหนดของ worker รันคอนเทนเนอร์ของคุณ คุณคุยกับคลัสเตอร์ผ่าน API server และ kubectl คือไคลเอนต์มาตรฐานที่ส่งคำขอของคุณ

context และ namespace

context เก็บว่าคำสั่งของคุณใช้คลัสเตอร์ ผู้ใช้ และ namespace ใด ตรวจด้วย kubectl config current-context ก่อนเปลี่ยนแปลงอะไร เพื่อไม่ให้ลบทรัพยากรผิดคลัสเตอร์

manifest และการเปลี่ยนแปลงแบบ declarative

manifest มี apiVersion, kind, metadata และ spec คำสั่ง kubectl apply เทียบไฟล์ของคุณกับคลัสเตอร์และเปลี่ยนเฉพาะส่วนที่ต่างกัน ทำให้การเปลี่ยนแปลงทำซ้ำได้และตรวจสอบใน Git ได้ง่าย

ภาพประกอบ

คำขอจากผู้ใช้ไปถึงแอปใน Kubernetes อย่างไร: ผู้ใช้เข้าถึง Ingress, Ingress ส่งต่อไปยัง Service และ Service ส่งทราฟฟิกไปยัง Pod ที่ปกติ ซึ่ง Deployment คอยรักษาให้ทำงานอยู่
คำขอจากผู้ใช้ไปถึงแอปใน Kubernetes อย่างไร: ผู้ใช้เข้าถึง Ingress, Ingress ส่งต่อไปยัง Service และ Service ส่งทราฟฟิกไปยัง Pod ที่ปกติ ซึ่ง Deployment คอยรักษาให้ทำงานอยู่

ตัวอย่างที่ 1: ตรวจสอบ client และคลัสเตอร์

ยืนยันว่า kubectl คุยกับคลัสเตอร์ได้

kubectl version --client
kubectl cluster-info
kubectl get nodes -o wide

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 2: สร้างคลัสเตอร์ฝึกด้วย kind

คลัสเตอร์บนเครื่องของคุณสำหรับการเรียนรู้

kind create cluster --name learn
kubectl config current-context
kubectl get nodes

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 3: สลับ context

เลือกว่าคำสั่งจะไปที่คลัสเตอร์ไหน

kubectl config get-contexts
kubectl config use-context kind-learn
kubectl config view --minify --output "jsonpath={.current-context}"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 4: สร้าง namespace

แยกทีมและแอปภายในคลัสเตอร์เดียว

kubectl create namespace shop
kubectl config set-context --current --namespace=shop
kubectl get pods

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 5: อ่าน manifest

ฟิลด์ระดับบนสุดสี่ตัวที่ทุกออบเจกต์มี

apiVersion: v1
kind: Namespace
metadata:
  name: shop
  labels:
    team: platform

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 6: apply manifest

ทำการเปลี่ยนแปลงแบบ declarative ด้วย kubectl apply

kubectl apply -f namespace.yaml
kubectl get namespace shop --show-labels
kubectl describe namespace shop

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 2: Pod: หน่วยที่เล็กที่สุด

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

Pod คืออะไร

คอนเทนเนอร์ทั้งหมดใน Pod ใช้ IP เดียวกันและเข้าถึงกันผ่าน localhost ได้ Pod เป็นของชั่วคราว เมื่อตัวใดตาย คลัสเตอร์จะสร้างตัวใหม่ที่มีชื่อและที่อยู่ใหม่ ดังนั้นอย่าพึ่งที่อยู่ของ Pod

init และ sidecar container

init container ทำงานจนเสร็จก่อนคอนเทนเนอร์หลักจะเริ่ม เหมาะกับการรอฐานข้อมูล sidecar ทำงานคู่กับคอนเทนเนอร์หลักเพื่องานบันทึก พร็อกซี หรือซิงก์ไฟล์ และใช้วอลุ่มร่วมกัน

log, event และ exec

kubectl logs แสดงผลลัพธ์ kubectl describe แสดง event เช่นข้อผิดพลาดการดึงอิมเมจและการจัดตารางที่ล้มเหลว kubectl exec เปิด shell อ่าน describe ก่อนเสมอ เพราะอธิบายความล้มเหลวตอนเริ่มส่วนใหญ่ได้

ตัวอย่างที่ 7: รัน Pod อย่างรวดเร็ว

คำสั่งแบบ imperative ใช้ทดสอบเร็วเท่านั้น

kubectl run hello --image=nginx:1.27 --port=80
kubectl get pod hello -o wide

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 8: Pod แบบ declarative

หน่วยที่เล็กที่สุดที่คุณ deploy

apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: web
spec:
  containers:
    - name: web
      image: nginx:1.27
      ports:
        - containerPort: 80

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 9: อ่าน log และ event

หาว่าเกิดอะไรขึ้นกับ Pod

kubectl logs web
kubectl logs web --previous
kubectl get events --sort-by=.lastTimestamp

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 10: รันคำสั่งภายใน Pod

ดีบักจากข้างใน

kubectl exec -it web -- sh
kubectl exec web -- nginx -v

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 11: port-forward ไปยัง Pod

เข้าถึง Pod จากคอมพิวเตอร์ของคุณเพื่อทดสอบ

kubectl port-forward pod/web 8080:80
curl http://localhost:8080

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 12: คอนเทนเนอร์สองตัวใช้วอลุ่มร่วมกัน

รูปแบบ sidecar

apiVersion: v1
kind: Pod
metadata:
  name: sidecar-demo
spec:
  volumes:
    - name: logs
      emptyDir: {}
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "while true; do date >> /logs/app.log; sleep 5; done"]
      volumeMounts:
        - name: logs
          mountPath: /logs
    - name: log-reader
      image: busybox:1.36
      command: ["sh", "-c", "tail -F /logs/app.log"]
      volumeMounts:
        - name: logs
          mountPath: /logs

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 13: init container รอให้พร้อม

รันขั้นตอนเตรียมก่อนที่แอปจะเริ่ม

apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command: ["sh", "-c", "until nslookup db; do sleep 2; done"]
  containers:
    - name: app
      image: nginx:1.27

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 14: ลบ Pod สำหรับทดสอบ

ล้างออบเจกต์ที่ไม่ต้องใช้แล้ว

kubectl delete pod hello web sidecar-demo init-demo --ignore-not-found
kubectl get pods

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 3: Deployment และการอัปเดตแบบค่อยเป็นค่อยไป

คุณแทบไม่เคยสร้าง Pod โดยตรง Deployment จัดการ ReplicaSet ซึ่งรักษาจำนวน Pod ที่ต้องการให้ทำงานอยู่ และทำการอัปเดตแบบค่อยเป็นค่อยไปเพื่อให้เวอร์ชันใหม่แทนที่เวอร์ชันเก่าทีละน้อย

จำนวนเรพลิกาและ selector

ฟิลด์ replicas กำหนดจำนวน Pod ที่ต้องการ selector ต้องตรงกับ label ของ Pod เพื่อให้ Deployment รู้ว่าเป็นเจ้าของ Pod ใด หากไม่ตรงจะถูกปฏิเสธตอนสร้าง

กลยุทธ์ RollingUpdate

maxSurge กำหนดจำนวน Pod ส่วนเกินที่อนุญาตระหว่างอัปเดต และ maxUnavailable กำหนดจำนวนที่อาจขาดหายไป การตั้ง maxUnavailable เป็น 0 จะรักษาความจุเต็มระหว่างการปล่อยเวอร์ชัน

ประวัติ rollout และการย้อนกลับ

การเปลี่ยนแปลงแต่ละครั้งสร้าง ReplicaSet ใหม่และหมายเลขรีวิชัน kubectl rollout undo กลับไปเวอร์ชันก่อนหน้า และ kubectl rollout history แสดงรายการรีวิชันให้คุณเลือกได้

ตัวอย่างที่ 15: Deployment รักษาจำนวนเรพลิกาให้ทำงาน

การอัปเดตแบบค่อยเป็นค่อยไปที่จัดการให้

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 16: สร้างและติดตาม Deployment

ตรวจสถานะการ rollout

kubectl apply -f deployment.yaml
kubectl rollout status deployment/web
kubectl get deploy,rs,pods -l app=web

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 17: ปรับจำนวนด้วยมือ

เปลี่ยนจำนวนเรพลิกา

kubectl scale deployment web --replicas=5
kubectl get pods -l app=web -o wide

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 18: rollout อิมเมจใหม่

อัปเดตเวอร์ชันของแอป

kubectl set image deployment/web web=nginx:1.27.2
kubectl rollout status deployment/web
kubectl rollout history deployment/web

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 19: ย้อนการปล่อยที่มีปัญหา

ยกเลิก rollout ที่ผิดพลาด

kubectl rollout undo deployment/web
kubectl rollout undo deployment/web --to-revision=1

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 20: ควบคุมการอัปเดตแบบค่อยเป็นค่อยไป

maxSurge และ maxUnavailable

spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 21: หยุดชั่วคราวและกลับมาทำต่อ

รวมการเปลี่ยนแปลงหลายอย่างเข้าด้วยกัน

kubectl rollout pause deployment/web
kubectl set resources deployment/web -c web --limits=memory=256Mi
kubectl rollout resume deployment/web

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 22: readiness probe

ทราฟฟิกรอจนกว่าแอปจะพร้อม

containers:
  - name: web
    image: nginx:1.27
    readinessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 3
      periodSeconds: 5

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 4: Service และเครือข่าย

Pod มาๆ ไปๆ ดังนั้นไคลเอนต์ต้องการชื่อและที่อยู่ที่คงที่ Service เลือก Pod ด้วย label และให้ IP เสมือนหนึ่งตัวกับชื่อ DNS ที่ไม่เปลี่ยน

ชนิดของ Service

ClusterIP เข้าถึงได้เฉพาะภายในคลัสเตอร์ NodePort เปิดพอร์ตบนทุกโหนด LoadBalancer ขอโหลดบาลานเซอร์ภายนอกจากผู้ให้บริการคลาวด์ เลือกชนิดที่เล็กที่สุดที่ตอบโจทย์

DNS และ endpoint

ทุก Service มีชื่อ DNS เช่น web-svc.shop.svc.cluster.local และภายใน namespace เดียวกันใช้ชื่อสั้นได้ kubectl get endpoints แสดงที่อยู่ของ Pod ที่อยู่หลัง Service ซึ่งเป็นสิ่งแรกที่ควรตรวจเมื่อ Service ไม่ตอบสนอง

Service แบบ headless

เมื่อกำหนด clusterIP เป็น None DNS จะคืนที่อยู่ของ Pod แต่ละตัวแทนที่จะเป็น IP เสมือนตัวเดียว ฐานข้อมูลและระบบแบบคลัสเตอร์ใช้วิธีนี้เพื่อให้ไคลเอนต์คุยกับสมาชิกที่ระบุได้

ตัวอย่างที่ 23: Service แบบ ClusterIP

ที่อยู่คงที่ภายในคลัสเตอร์

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 24: expose ด้วย kubectl

วิธีรวดเร็วในการสร้าง Service

kubectl expose deployment web --port=80 --target-port=80 --name=web-svc
kubectl get svc web-svc
kubectl get endpoints web-svc

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 25: เรียก Service จาก Pod ชั่วคราว

ทดสอบ DNS ภายในคลัสเตอร์

kubectl run tmp-client --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://web-svc

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 26: Service แบบ NodePort

เปิดพอร์ตบนทุกโหนด

apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 27: Service แบบ LoadBalancer

โหลดบาลานเซอร์ของคลาวด์สำหรับเข้าถึงสาธารณะ

apiVersion: v1
kind: Service
metadata:
  name: web-public
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "false"
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 28: หาที่อยู่ภายนอก

อ่านที่อยู่ที่คลาวด์กำหนดให้

kubectl get svc web-public -o jsonpath="{.status.loadBalancer.ingress[0].ip}"
kubectl get svc web-public -w

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 29: พอร์ตที่มีชื่อ

ใช้ชื่อพอร์ตเพื่อเปลี่ยนหมายเลขได้อย่างปลอดภัย

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - name: http
      port: 8000
      targetPort: http

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 30: Service แบบ headless

รับที่อยู่ของ Pod แต่ละตัว

apiVersion: v1
kind: Service
metadata:
  name: db-headless
spec:
  clusterIP: None
  selector:
    app: db
  ports:
    - port: 5432

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 5: การตั้งค่าด้วย ConfigMap และ Secret

เก็บการตั้งค่าไว้นอกอิมเมจ ConfigMap เก็บการตั้งค่าทั่วไป ส่วน Secret เก็บค่าที่ละเอียดอ่อน ทั้งสองอย่างกลายเป็นตัวแปรสภาพแวดล้อมหรือไฟล์ภายใน Pod ได้ จึงเปลี่ยนการตั้งค่าได้โดยไม่ต้อง build อิมเมจใหม่

ConfigMap

ConfigMap เก็บคู่คีย์-ค่าหรือทั้งไฟล์ ใช้ envFrom เพื่อโหลดทุกคีย์เป็นตัวแปรสภาพแวดล้อม หรือเมานต์ ConfigMap เป็นไดเรกทอรีเมื่อแอปอ่านจากไฟล์

Secret

Secret เก็บค่าที่เข้ารหัส base64 base64 เป็นการเข้ารหัสเท่านั้น ไม่ใช่การเข้ารหัสที่ปกป้องข้อมูล จึงควรเปิดการเข้ารหัสขณะจัดเก็บในคลัสเตอร์ จำกัดผู้ที่อ่าน Secret ได้ด้วย RBAC และเมานต์เป็นไฟล์เมื่อทำได้

การโหลดการเปลี่ยนแปลงใหม่

ตัวแปรสภาพแวดล้อมถูกอ่านเพียงตอนเริ่ม ดังนั้น ConfigMap ที่เปลี่ยนต้องมีการ rollout ใหม่ ไฟล์ที่เมานต์จะอัปเดตหลังดีเลย์ แต่แอปต้องอ่านไฟล์นั้นใหม่ วางแผนการโหลดใหม่ทุกครั้งที่เปลี่ยนการตั้งค่า

ตัวอย่างที่ 31: ConfigMap จากค่าโดยตรง

เก็บการตั้งค่าที่ไม่ใช่ความลับ

kubectl create configmap app-config --from-literal=LOG_LEVEL=info --from-literal=FEATURE_X=on
kubectl get configmap app-config -o yaml

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 32: manifest ของ ConfigMap

การตั้งค่าเขียนเป็น YAML

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: info
  FEATURE_X: "on"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 33: ConfigMap เป็นตัวแปรสภาพแวดล้อม

โหลดทุกคีย์ด้วย envFrom

containers:
  - name: api
    image: sample-api:1.4.2
    envFrom:
      - configMapRef:
          name: app-config

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 34: เมานต์ ConfigMap เป็นไฟล์

ไฟล์การตั้งค่าภายในคอนเทนเนอร์

containers:
  - name: web
    image: nginx:1.27
    volumeMounts:
      - name: site-config
        mountPath: /etc/nginx/conf.d
        readOnly: true
volumes:
  - name: site-config
    configMap:
      name: nginx-conf

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 35: สร้าง Secret

เก็บรหัสผ่านโดยไม่เขียนลงไฟล์

kubectl create secret generic db-secret --from-literal=DB_PASSWORD='<strong-password>'
kubectl get secret db-secret -o jsonpath="{.type}"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 36: manifest ของ Secret

stringData ให้เขียนข้อความธรรมดาได้

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData:
  DB_PASSWORD: <strong-password>

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 37: อ่านค่าใน Secret

ตรวจค่าที่ถอดรหัสแล้วและเก็บเป็นความลับ

kubectl get secret db-secret -o jsonpath="{.data.DB_PASSWORD}" | base64 --decode; echo
# Do not print secrets in shared logs or screenshots.

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 38: เมานต์ Secret เป็นไฟล์

ไฟล์ข้อมูลรับรองแบบอ่านอย่างเดียว

containers:
  - name: api
    image: sample-api:1.4.2
    volumeMounts:
      - name: db-creds
        mountPath: /var/run/secrets/db
        readOnly: true
volumes:
  - name: db-creds
    secret:
      secretName: db-secret

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 6: ที่เก็บข้อมูลถาวร

คอนเทนเนอร์จะสูญเสียไฟล์เมื่อรีสตาร์ต PersistentVolumeClaim ขอที่เก็บข้อมูลที่อยู่รอดกว่าอายุของ Pod และ StorageClass บอกคลัสเตอร์ว่าจะสร้างที่เก็บนั้นอย่างไร

PV, PVC และ StorageClass

PersistentVolume คือที่เก็บข้อมูลจริง PersistentVolumeClaim คือคำขอใช้ที่เก็บนั้น และ StorageClass กำหนดวิธีสร้างวอลุ่มตามต้องการ คลัสเตอร์ส่วนใหญ่มี StorageClass เริ่มต้น ดังนั้น claim มักต้องระบุเพียงขนาด

โหมดการเข้าถึง

ReadWriteOnce เมานต์วอลุ่มบนโหนดเดียวในแต่ละครั้ง เหมาะกับฐานข้อมูลส่วนใหญ่ ReadWriteMany ให้หลายโหนดเขียนได้ แต่มีที่เก็บเพียงบางชนิดที่รองรับ เลือกโหมดที่ที่เก็บของคุณรองรับ

นโยบายการเรียกคืน

Delete ลบที่เก็บเมื่อลบ claim ส่วน Retain เก็บข้อมูลไว้ให้กู้คืนด้วยมือ ใช้ Retain กับข้อมูลที่ต้องรอดจากการลบโดยไม่ตั้งใจ และสำรองข้อมูลสำคัญไว้เสมอ

ตัวอย่างที่ 39: PersistentVolumeClaim

ขอพื้นที่จัดเก็บจากคลัสเตอร์

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 40: Pod ที่ใช้ claim

ข้อมูลอยู่รอดแม้ Pod จะรีสตาร์ต

apiVersion: v1
kind: Pod
metadata:
  name: writer
spec:
  containers:
    - name: writer
      image: busybox:1.36
      command: ["sh", "-c", "echo hello >> /data/out.txt; sleep 3600"]
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: data-pvc

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 41: ตรวจพื้นที่ที่ผูกแล้ว

อ่านสถานะของ claim และวอลุ่ม

kubectl get pvc data-pvc
kubectl get pv
kubectl describe pvc data-pvc

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 42: เลือก StorageClass

เลือกชนิดของดิสก์

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: fast-pvc
spec:
  storageClassName: managed-premium
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 43: ดูรายการ StorageClass

หาคลาสเริ่มต้น

kubectl get storageclass
kubectl get storageclass -o custom-columns=NAME:.metadata.name,DEFAULT:.metadata.annotations."storageclass\.kubernetes\.io/is-default-class"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 44: ขยาย claim

เพิ่มพื้นที่ในคลาสที่รองรับ

kubectl patch pvc data-pvc -p '{"spec":{"resources":{"requests":{"storage":"2Gi"}}}}'
kubectl get pvc data-pvc

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 45: นโยบาย reclaim

เก็บข้อมูลไว้หลังลบ claim

apiVersion: v1
kind: PersistentVolume
metadata:
  name: archive-pv
spec:
  capacity:
    storage: 5Gi
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /srv/archive

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 46: จำกัด ephemeral storage

หยุดไฟล์ชั่วคราวที่โตไม่หยุด

containers:
  - name: worker
    image: busybox:1.36
    resources:
      limits:
        ephemeral-storage: 1Gi

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 7: Ingress และการกำหนดเส้นทางทราฟฟิก

Ingress เปิดเส้นทาง HTTP และ HTTPS จากภายนอกคลัสเตอร์ไปยัง Service มันกำหนดเส้นทางตามชื่อโฮสต์และ path และจบการเข้ารหัส TLS ทำให้ที่อยู่สาธารณะเดียวให้บริการแอปได้หลายตัว

Ingress และ controller

ทรัพยากร Ingress ทำงานไม่ได้เพียงลำพัง ingress controller เช่น ingress-nginx อ่านออบเจกต์ Ingress และตั้งค่าพร็อกซี ติดตั้ง controller หนึ่งตัวต่อคลัสเตอร์ และกำหนด ingressClassName ในแต่ละ Ingress

ใบรับรอง TLS

ส่วน tls ชี้ไปยัง Secret ที่เก็บใบรับรองและคีย์ cert-manager ขอและต่ออายุใบรับรองให้อัตโนมัติ คุณจึงไม่ต้องจัดการวันหมดอายุด้วยมือ

การกำหนดเส้นทางตาม path และโฮสต์

กฎจับคู่ชื่อโฮสต์และ path prefix path ที่เจาะจงที่สุดชนะ เช่น /api ไป API ขณะที่ / ไปเว็บไซต์ ควรให้เส้นทางเรียบง่ายเพื่อให้ตารางการกำหนดเส้นทางตรวจได้ง่าย

ตัวอย่างที่ 47: Ingress ตามชื่อโฮสต์

กำหนดเส้นทาง HTTP ไปยัง Service

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 48: Ingress กับ TLS

HTTPS ด้วยใบรับรองที่เก็บใน Secret

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-tls
spec:
  ingressClassName: nginx
  tls:
    - hosts: [shop.example.com]
      secretName: shop-tls
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 49: ติดตั้ง ingress controller

ติดตั้งตัวควบคุมด้วย Helm

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress ingress-nginx/ingress-nginx --namespace ingress --create-namespace

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 50: กำหนดเส้นทางตาม path

ส่ง /api และ / ไปยัง Service คนละตัว

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-routes
spec:
  ingressClassName: nginx
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 8000
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 51: ความเป็นไปได้ของ annotation สำหรับการอัปโหลด

เพิ่มขีดจำกัดขนาดการอัปโหลดและเวลาหมดอายุสำหรับเว็บไซต์หนึ่ง

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: upload-ingress
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "20m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
  ingressClassName: nginx
  rules:
    - host: upload.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: uploader
                port:
                  number: 80

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 52: ClusterIssuer ของ cert-manager

ขอใบรับรองจาก Let's Encrypt โดยอัตโนมัติ

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            ingressClassName: nginx

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 53: ขอใบรับรองจาก Ingress

ใช้ annotation เพื่อให้ระบบจัดการ TLS ให้

metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: [shop.example.com]
      secretName: shop-tls

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 54: NetworkPolicy สำหรับ API

อนุญาตเฉพาะชั้นเว็บให้เข้าถึง API ได้

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-allow-web
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - port: 8000

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 8: Job และ CronJob

ไม่ใช่ทุกงานต้องทำงานตลอดเวลา Job รันงานจนสำเร็จ ส่วน CronJob เริ่ม Job ตามตารางเวลา เหมือนตาราง cron ที่ทำงานภายในคลัสเตอร์

Job และจำนวนที่สำเร็จ

Job สร้าง Pod จนกว่าจำนวน completions ที่ต้องการจะสำเร็จ backoffLimit จำกัดจำนวนครั้งที่ลองใหม่ ใช้ restartPolicy เป็น Never หรือ OnFailure เพราะ Job ไม่อนุญาต Always

งานขนาน

parallelism กำหนดว่ามี Pod กี่ตัวทำงานพร้อมกัน และ completions กำหนดจำนวนการรันที่สำเร็จที่ต้องการ รูปแบบนี้ประมวลผลชุดของรายการด้วย worker หลายตัว

CronJob และประวัติ

CronJob ใช้ไวยากรณ์ cron ในตารางเวลา concurrencyPolicy เป็น Forbid ป้องกันการรันซ้อนทับกัน และ history limits ลบ Job เก่าเพื่อให้คลัสเตอร์เป็นระเบียบ

ตัวอย่างที่ 55: Job ทำงานจนเสร็จ

งานครั้งเดียว เช่น การย้ายฐานข้อมูล

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: sample-api:1.4.2
          command: ["python", "manage.py", "migrate"]

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 56: รัน Job และรอผล

รอจนงานเสร็จ แล้วอ่าน log

kubectl apply -f job.yaml
kubectl wait --for=condition=complete job/db-migrate --timeout=120s
kubectl logs job/db-migrate

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 57: Job แบบขนาน

ประมวลผลหลายรายการด้วยผู้ทำงานหลายตัว

apiVersion: batch/v1
kind: Job
metadata:
  name: thumbnails
spec:
  completions: 10
  parallelism: 3
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: worker
          image: thumbnailer:2.0

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 58: CronJob ตามตารางเวลา

รันรายงานทุกคืนเวลา 02:00

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-report
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: report
              image: reporter:1.1

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 59: ตรวจและสั่ง CronJob

ดูการรันที่ผ่านมาและเริ่มเองด้วยมือ

kubectl get cronjob nightly-report
kubectl get jobs --sort-by=.metadata.creationTimestamp
kubectl create job --from=cronjob/nightly-report manual-report-1

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 60: จำกัดประวัติของ Job

ไม่ให้ Job ที่เสร็จแล้วสะสมมากเกินไป

spec:
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  startingDeadlineSeconds: 300

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 9: StatefulSet และ DaemonSet

บางงานต้องการตัวตนที่คงที่หรือหนึ่ง Pod ต่อหนึ่งโหนด StatefulSet ให้ Pod มีชื่อเรียงลำดับและที่เก็บข้อมูลของตัวเอง ส่วน DaemonSet รันสำเนาของ Pod หนึ่งตัวบนทุกโหนดที่ตรงเงื่อนไข

ตัวตนของ StatefulSet

Pod ถูกตั้งชื่อ name-0, name-1 และต่อไป แต่ละตัวเก็บ claim ของตัวเองจาก volumeClaimTemplates ชื่อยังคงเดิมเมื่อ Pod รีสตาร์ต เหมาะกับฐานข้อมูลและ message broker

การทำงานตามลำดับ

โดยค่าเริ่มต้น StatefulSet เริ่มและหยุด Pod ตามลำดับ การอัปเดตแบบ partition ให้เปลี่ยนเฉพาะ Pod ที่หมายเลขสูงสุดก่อน ซึ่งเป็นการทดสอบแบบ canary ที่ควบคุมได้สำหรับระบบที่มีสถานะ

DaemonSet

DaemonSet รักษา Pod หนึ่งตัวบนแต่ละโหนด เหมาะกับตัวเก็บ log เอเจนต์ตรวจสอบ และปลั๊กอินเครือข่าย nodeSelector หรือ tolerations จำกัดให้ทำงานเฉพาะบางโหนด

ตัวอย่างที่ 61: StatefulSet

ชื่อและที่เก็บข้อมูลที่คงที่สำหรับฐานข้อมูล

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: redis:7-alpine
          volumeMounts:
            - name: data
              mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 62: ตรวจ StatefulSet

ชื่อตามลำดับและ claim หนึ่งตัวต่อ Pod

kubectl get statefulset redis
kubectl get pods -l app=redis
kubectl get pvc

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 63: DaemonSet

Pod หนึ่งตัวบนทุกโหนด สำหรับ agent

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
spec:
  selector:
    matchLabels:
      app: node-agent
  template:
    metadata:
      labels:
        app: node-agent
    spec:
      containers:
        - name: agent
          image: busybox:1.36
          command: ["sh", "-c", "sleep 3600"]

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 64: DaemonSet บนโหนดที่มีป้ายกำกับ

ใช้ nodeSelector เพื่อเลือกโหนด

spec:
  template:
    spec:
      nodeSelector:
        workload: edge

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 65: partition ของ StatefulSet

อัปเดต Pod บางส่วนก่อน

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 66: ปรับขนาด StatefulSet อย่างระมัดระวัง

ตรวจ claim หลังลดจำนวน

kubectl get statefulset redis -o jsonpath="{.spec.replicas}"; echo
kubectl scale statefulset redis --replicas=2
kubectl get pvc -l app=redis

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 10: การปรับขนาดอัตโนมัติ

โหลดเปลี่ยนไปตลอดทั้งวัน การปรับขนาดอัตโนมัติเพิ่ม Pod เมื่อความต้องการสูง และลดเมื่อต่ำลง คุณจึงจ่ายเฉพาะสิ่งที่ใช้ และผู้ใช้ยังได้การตอบสนองที่รวดเร็ว

Horizontal Pod Autoscaler

HPA เทียบตัวชี้วัด เช่น การใช้ CPU เฉลี่ย กับเป้าหมาย แล้วปรับจำนวนเรพลิกา ต้องใช้ metrics server และการขอ CPU ใน Pod เพราะการใช้งานวัดเทียบกับค่าที่ขอ

พฤติกรรมการปรับขนาด

ฟิลด์ behavior ควบคุมความเร็วในการเพิ่มและลดขนาด stabilization window ตอนลดขนาดช่วยป้องกันการแกว่งเร็วหลังมีการพุ่งขึ้นชั่วคราว

การปรับขนาดตามเหตุการณ์

KEDA ปรับขนาดตามสัญญาณภายนอก เช่น ความยาวของคิวหรือความล่าช้าของข้อความ และลดเหลือศูนย์ได้เมื่อไม่มีงาน ใช้กับ worker ที่อ่านคิว

ตัวอย่างที่ 67: การใช้ทรัพยากรแบบเรียลไทม์

kubectl top ต้องใช้ metrics server

kubectl top nodes
kubectl top pods -l app=web

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 68: ปรับขนาดตาม CPU อัตโนมัติ

เพิ่ม Pod เมื่อโหลดเฉลี่ยสูง

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 69: สร้าง HPA จากบรรทัดคำสั่ง

ปรับขนาดอัตโนมัติและดูการตอบสนอง

kubectl autoscale deployment web --min=2 --max=10 --cpu-percent=70
kubectl get hpa web -w

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 70: ปรับขนาดตามหน่วยความจำ

ใช้มากกว่าหนึ่งตัวชี้วัด

spec:
  metrics:
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 75

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 71: จำกัดความเร็วในการลดขนาดของ HPA

หลีกเลี่ยงการแกว่งด้วยช่วงรอเสถียรภาพ

spec:
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 1
          periodSeconds: 60

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 72: ปรับขนาดตามเหตุการณ์

ปรับขนาด worker ตามความยาวคิวด้วย KEDA

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: worker-scaler
spec:
  scaleTargetRef:
    name: worker
  minReplicaCount: 0
  maxReplicaCount: 20
  triggers:
    - type: rabbitmq
      metadata:
        queueName: orders
        mode: QueueLength
        value: "50"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 11: ทรัพยากร probe และการจัดตาราง

บอก Kubernetes ว่าแต่ละคอนเทนเนอร์ต้องการ CPU และหน่วยความจำเท่าไร และจะรู้ได้อย่างไรว่าคอนเทนเนอร์สุขภาพดี ตัวจัดตารางใช้ requests เพื่อวาง Pod ส่วน probe ตัดสินว่าเมื่อใดควรรีสตาร์ตหรือส่งทราฟฟิก

requests และ limits

request คือปริมาณที่ตัวจัดตารางสงวนให้คอนเทนเนอร์ limit คือปริมาณสูงสุดที่ใช้ได้ หน่วยความจำที่เกิน limit ทำให้คอนเทนเนอร์ถูกฆ่า ส่วน CPU ที่เกินจะถูกจำกัดความเร็ว ตั้งค่าทั้งสองจากการใช้งานที่วัดได้

probe

liveness probe รีสตาร์ตคอนเทนเนอร์ที่ค้าง readiness probe เอาคอนเทนเนอร์ออกจาก endpoint ของ Service ขณะที่ยังไม่พร้อม startup probe ระงับ probe อื่นสำหรับการเริ่มที่ช้า

การกระจายและการหยุดชะงัก

topology spread constraints วางเรพลิกาไว้คนละโหนดหรือโซน PodDisruptionBudget จำกัดจำนวน Pod ที่การกระทำโดยสมัครใจ เช่น drain จะลบพร้อมกันได้

ตัวอย่างที่ 73: requests และ limits

ตัวจัดตารางและเคอร์เนลใช้ค่าเหล่านี้อย่างไร

containers:
  - name: api
    image: sample-api:1.4.2
    resources:
      requests:
        cpu: 250m
        memory: 256Mi
      limits:
        cpu: "1"
        memory: 512Mi

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 74: liveness probe

รีสตาร์ตคอนเทนเนอร์ที่ค้าง

livenessProbe:
  httpGet:
    path: /healthz
    port: 8000
  initialDelaySeconds: 10
  periodSeconds: 15
  failureThreshold: 3

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 75: startup probe

ให้แอปที่เริ่มช้ามีเวลาเริ่มทำงาน

startupProbe:
  httpGet:
    path: /healthz
    port: 8000
  failureThreshold: 30
  periodSeconds: 10

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 76: PodDisruptionBudget

รักษาจำนวนเรพลิกาให้พอระหว่างการบำรุงรักษา

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 77: กระจายเรพลิกาไปยังหลายโหนด

ไม่วางทุกเรพลิกาไว้บนเครื่องเดียว

spec:
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: kubernetes.io/hostname
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          app: web

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 78: PriorityClass

ให้ Pod สำคัญได้รับการจัดตารางแม้คลัสเตอร์เต็ม

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: business-critical
value: 100000
globalDefault: false
description: "Keep these Pods running when the cluster is full."

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 12: ความปลอดภัย: RBAC และความปลอดภัยของ Pod

หลักสิทธิ์น้อยที่สุดใช้กับทั้งคนและแอปพลิเคชัน RBAC กำหนดว่าใครทำอะไรได้ ส่วนการตั้งค่าความปลอดภัยของ Pod จำกัดสิ่งที่คอนเทนเนอร์ทำได้บนโหนด

ServiceAccount, Role และ binding

ServiceAccount คือตัวตนของ Pod Role ระบุ verb ที่อนุญาตบนทรัพยากรภายใน namespace หนึ่ง และ RoleBinding ผูกเข้ากับบัญชี ให้สิทธิ์เฉพาะ verb ที่แอปต้องใช้ และหลีกเลี่ยง cluster-admin สำหรับแอป

การตรวจสิทธิ์

kubectl auth can-i ทดสอบสิทธิ์ โดยระบุผู้ใช้หรือ ServiceAccount อื่นได้ ควรรันในขั้นตอนตรวจทานเพื่อให้พบข้อผิดพลาดก่อนการ deploy ไม่ใช่หลังเหตุการณ์

Pod Security

label ของ namespace บังคับใช้ Pod Security Standards เช่น restricted ซึ่งบล็อกคอนเทนเนอร์ที่รันเป็น root และการยกระดับสิทธิ์ ใช้ร่วมกับ securityContext ที่ตัด capability และตั้งระบบไฟล์รากเป็นอ่านอย่างเดียว

ตัวอย่างที่ 79: ServiceAccount

ตัวตนของ Pod โดยปิดการใช้โทเค็น

apiVersion: v1
kind: ServiceAccount
metadata:
  name: api-sa
automountServiceAccountToken: false

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 80: Role ที่มีสิทธิ์น้อยที่สุด

อ่าน ConfigMap ได้ในเนมสเปซเดียวเท่านั้น

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: read-config
  namespace: shop
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list"]

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 81: RoleBinding

ผูก Role เข้ากับ ServiceAccount

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: api-read-config
  namespace: shop
subjects:
  - kind: ServiceAccount
    name: api-sa
    namespace: shop
roleRef:
  kind: Role
  name: read-config
  apiGroup: rbac.authorization.k8s.io

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 82: ตรวจสิทธิ์ด้วย auth can-i

ยืนยัน RBAC ก่อนนำไป deploy

kubectl auth can-i list configmaps --as=system:serviceaccount:shop:api-sa -n shop
kubectl auth can-i delete pods --as=system:serviceaccount:shop:api-sa -n shop

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 83: Pod Security Standards

บังคับใช้โปรไฟล์ restricted กับเนมสเปซ

kubectl label namespace shop pod-security.kubernetes.io/enforce=restricted
kubectl label namespace shop pod-security.kubernetes.io/warn=restricted

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 84: security context ที่เสริมความแข็งแรง

ไม่ใช่ root อ่านอย่างเดียว และไม่มี capability เพิ่ม

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 13: Helm และ Kustomize

แอปจริงมี manifest จำนวนมากและหลายสภาพแวดล้อม Helm รวมเป็น chart พร้อมค่า values ส่วน Kustomize เก็บ base ชุดเดียวและใช้ overlay เล็กๆ ของแต่ละสภาพแวดล้อม

chart และ release ของ Helm

chart คือแพ็กเกจเทมเพลตที่มีค่าเริ่มต้น release คือสำเนาที่ติดตั้งหนึ่งชุด ติดตั้ง อัปเกรด และย้อนกลับด้วย helm และใช้ --atomic เพื่อให้การอัปเกรดที่ล้มเหลวย้อนกลับเองโดยอัตโนมัติ

values ตามสภาพแวดล้อม

เก็บไฟล์ values หนึ่งไฟล์ต่อสภาพแวดล้อมและส่งด้วย -f ตรวจผลลัพธ์ด้วย helm template ก่อนติดตั้ง เพื่อรู้แน่ชัดว่าจะมีอะไรไปถึงคลัสเตอร์

overlay ของ Kustomize

Kustomize รวม base กับ overlay ที่เปลี่ยนจำนวนเรพลิกา อิมเมจ หรือ label โดยไม่ใช้เทมเพลต kubectl apply -k นำผลไปใช้ และ kubectl diff แสดงการเปลี่ยนแปลงก่อน

ตัวอย่างที่ 85: ติดตั้งแอปด้วย Helm

ใช้ chart จากรีโพซิทอรี

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install shop-cache bitnami/redis --namespace shop --set architecture=standalone

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 86: แสดงผลและตรวจ chart

ดูตัวอย่าง YAML และตรวจกับเซิร์ฟเวอร์

helm template shop-web ./chart --values values-prod.yaml > rendered.yaml
kubectl apply --dry-run=server -f rendered.yaml

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 87: ไฟล์ค่า values ของ Helm

การตั้งค่าสำหรับสภาพแวดล้อมหนึ่ง

replicaCount: 3
image:
  repository: registry.example.com/team/web
  tag: "1.4.2"
resources:
  limits:
    memory: 512Mi
ingress:
  enabled: true
  host: shop.example.com

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 88: อัปเกรดและย้อนกลับด้วย Helm

อัปเกรดแบบ atomic ที่ย้อนกลับเองเมื่อล้มเหลว

helm upgrade shop-web ./chart -n shop -f values-prod.yaml --atomic --wait
helm history shop-web -n shop
helm rollback shop-web 1 -n shop

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 89: overlay ของ Kustomize

เปลี่ยนค่าตามสภาพแวดล้อมโดยไม่ใช้เทมเพลต

# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
replicas:
  - name: web
    count: 5
images:
  - name: web
    newTag: "1.4.2"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 90: apply overlay ของ Kustomize

ดู diff ก่อน แล้วค่อย apply

kubectl kustomize overlays/prod | kubectl diff -f -
kubectl apply -k overlays/prod

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 14: การสังเกตการณ์และการดีบัก

เมื่อ Pod ทำงานผิดปกติ คำตอบอยู่ในสถานะ event log และ metric ของมัน กิจวัตรที่ใจเย็นและเป็นลำดับจะหาข้อผิดพลาดส่วนใหญ่ได้เร็ว และสัญญาณเดียวกันนี้ป้อนให้แดชบอร์ดและการแจ้งเตือน

สถานะ event และ describe

kubectl get pod แสดงเฟสและจำนวนการรีสตาร์ต kubectl describe แสดง event เช่น ImagePullBackOff, CrashLoopBackOff หรือ FailedScheduling และอธิบายว่าทำไม Pod ติดอยู่

log ข้าม Pod

เลือก Pod ด้วย label ด้วย kubectl logs -l และใช้ --prefix เพื่อดูว่า Pod ใดเขียนแต่ละบรรทัด ใช้ --previous เพื่ออ่าน log ของคอนเทนเนอร์ที่ล่มและรีสตาร์ต

คอนเทนเนอร์ดีบักชั่วคราว

kubectl debug แนบคอนเทนเนอร์ชั่วคราวเข้ากับ Pod ที่กำลังทำงาน ทำให้ใช้เครื่องมือที่ไม่มีในอิมเมจของแอปได้ โดยไม่ต้องรีสตาร์ตแอป

ตัวอย่างที่ 91: log จากหลาย Pod

เลือก Pod ด้วยป้ายกำกับ

kubectl logs -l app=web --all-containers --prefix --tail=20
kubectl logs -l app=web -f --max-log-requests=10

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 92: ทำไม Pod จึงไม่เริ่ม

อ่านผล describe และสถานะของคอนเทนเนอร์

kubectl describe pod web-6d4cf56db6-abcde
kubectl get pod web-6d4cf56db6-abcde -o jsonpath="{.status.containerStatuses[0].state}"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 93: คอนเทนเนอร์ดีบักชั่วคราว

ดีบัก Pod ที่กำลังทำงานโดยไม่รีสตาร์ต

kubectl debug -it web-6d4cf56db6-abcde --image=busybox:1.36 --target=web -- sh

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 94: event และแรงกดดันของโหนด

หาปัญหาความจุและการขับไล่ Pod

kubectl get events -A --sort-by=.lastTimestamp | tail -20
kubectl describe node worker-1 | grep -A5 "Conditions"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

หัวข้อที่ 15: การดำเนินงานโปรดักชันและ GitOps

โปรดักชันหมายถึงการปล่อยที่ทำซ้ำได้ การบำรุงรักษาที่ปลอดภัย และการกู้คืน GitOps ทำให้คลัสเตอร์ตรงกับรีโพซิทอรี Git และการสำรองข้อมูลปกป้องออบเจกต์และข้อมูลที่สร้างใหม่ไม่ได้

GitOps

เครื่องมือ GitOps เช่น Argo CD เฝ้าดูรีโพซิทอรีและนำ manifest ไปใช้กับคลัสเตอร์ ทุกการเปลี่ยนแปลงผ่าน pull request และ selfHeal ย้อนการแก้ด้วยมือที่เบี่ยงจาก Git

การบำรุงรักษาโดยไม่หยุดให้บริการ

kubectl drain ย้าย Pod ออกจากโหนดก่อนอัปเกรด และ PodDisruptionBudget รักษาเรพลิกาให้ทำงานต่อ เมื่อโหนดพร้อมอีกครั้ง รัน kubectl uncordon

การสำรองและการกู้คืน

Velero สำรองออบเจกต์ของคลัสเตอร์และวอลุ่มถาวรไปยัง object storage ทดสอบการกู้คืนในคลัสเตอร์แยก เพราะการสำรองที่ไม่เคยกู้คืนได้ยังไม่ได้รับการพิสูจน์

ตัวอย่างที่ 95: GitOps ด้วย Argo CD

ทำให้คลัสเตอร์ตรงกับรีโพซิทอรี Git เสมอ

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: shop
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example/shop-deploy
    targetRevision: main
    path: overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: shop
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 96: Deployment สำหรับโปรดักชัน

probe, limits และการ rollout ที่ปลอดภัยรวมกัน

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app: api
spec:
  replicas: 3
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: api
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/team/api:1.4.2
          ports:
            - containerPort: 8000
          resources:
            requests: {cpu: 250m, memory: 256Mi}
            limits: {memory: 512Mi}
          readinessProbe:
            httpGet: {path: /ready, port: 8000}
          livenessProbe:
            httpGet: {path: /healthz, port: 8000}

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 97: สำรองข้อมูลด้วย Velero

บันทึกออบเจกต์และวอลุ่มของคลัสเตอร์ลงที่เก็บข้อมูล

velero install --provider azure --plugins velero/velero-plugin-for-microsoft-azure:v1.10.0 --bucket k8s-backups
velero backup create shop-daily --include-namespaces shop
velero backup describe shop-daily

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 98: drain และ uncordon โหนด

บำรุงรักษาโดย Pod ไม่หยุดทำงาน

kubectl cordon worker-1
kubectl drain worker-1 --ignore-daemonsets --delete-emptydir-data
kubectl uncordon worker-1

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 99: ตรวจจำนวนเรพลิกาจาก Python

ใช้ไลบรารี Kubernetes อย่างเป็นทางการ

from kubernetes import client, config

config.load_kube_config()
apps = client.AppsV1Api()
for d in apps.list_namespaced_deployment("shop").items:
    ready = d.status.ready_replicas or 0
    print(d.metadata.name, f"{ready}/{d.spec.replicas}")

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

ตัวอย่างที่ 100: สคริปต์เช็กลิสต์ก่อนปล่อยเวอร์ชัน

หยุดการปล่อยเมื่อการตรวจสอบใดล้มเหลว

#!/usr/bin/env bash
set -euo pipefail
NS="$1"
kubectl -n "$NS" rollout status deployment --timeout=180s
kubectl -n "$NS" get pdb
kubectl -n "$NS" auth can-i --list --as=system:serviceaccount:"$NS":default | grep -q "\*" && { echo "default account has wildcard rights"; exit 1; }
kubectl -n "$NS" get pods -o jsonpath="{range .items[*]}{.metadata.name}{' runAsRoot='}{.spec.securityContext.runAsNonRoot}{'\n'}{end}"
echo "release checks passed for namespace $NS"

*ตัวอย่างนี้ยังไม่ได้รันที่นี่ เพราะเครื่องที่เตรียมบทความไม่มีคลัสเตอร์ Kubernetes หรือ kubectl*

แล็บ: deploy, เปิดเผย และปล่อยแอปเว็บขนาดเล็ก

รวมบทความเข้าด้วยกันในคลัสเตอร์ฝึกเดียว: Deployment พร้อม probe และ limits, Service, Ingress, ConfigMap และ Secret, การอัปเดตแบบค่อยเป็นค่อยไปพร้อมการย้อนกลับ และการตรวจก่อนปล่อย

1. สร้างคลัสเตอร์ฝึกด้วย kind จากนั้นสร้าง namespace ชื่อ shop และตั้งเป็นค่าเริ่มต้น

2. apply Deployment ที่มีสามเรพลิกา ค่า requests และ limits ของทรัพยากร readiness probe และ liveness probe

3. เพิ่ม Service แบบ ClusterIP และตรวจ endpoint จากนั้นเรียกใช้จาก Pod ชั่วคราว

4. สร้าง ConfigMap สำหรับการตั้งค่า และ Secret สำหรับรหัสผ่านตัวอย่าง แล้วเมานต์ทั้งคู่เข้ากับ Pod

5. rollout อิมเมจเวอร์ชันใหม่ ดูการ rollout แล้วย้อนด้วย kubectl rollout undo

6. เพิ่ม Ingress สำหรับ shop.example.com จากนั้นจำกัด API ด้วย NetworkPolicy

7. รันเช็กลิสต์ก่อนปล่อยจากตัวอย่างที่ 100 กับ namespace และหยุดหากมีการตรวจข้อใดล้มเหลว

ขั้นต่อไป

หลังแล็บ ให้ฝึกบนคลัสเตอร์ที่ผู้ให้บริการจัดการให้ในบัญชีทดลอง เรียน Helm chart สำหรับแอปของคุณเอง และตั้งค่า GitOps เพื่อให้ทุกการเปลี่ยนแปลงถูกตรวจทานใน Git เก็บตัวอย่างไว้ในรีโพซิทอรีและรันซ้ำเมื่ออัปเกรดคลัสเตอร์ เพราะ API ของ Kubernetes เปลี่ยนระหว่างเวอร์ชัน

สรุป

Kubernetes ทำให้คอนเทนเนอร์ของคุณทำงานต่อเนื่อง กระจายไปตามโหนด อัปเดตโดยไม่หยุดให้บริการ และปรับขนาดตามความต้องการ คุณได้เรียน kubectl และ context, Pod, Deployment และการย้อนกลับ, Service และ Ingress, การตั้งค่าและความลับ, ที่เก็บข้อมูล, Job และ CronJob, StatefulSet และ DaemonSet, การปรับขนาดอัตโนมัติ, การขอทรัพยากรและ probe, RBAC และความปลอดภัยของ Pod, Helm และ Kustomize, การดีบัก และ GitOps พร้อมการสำรองข้อมูลสำหรับโปรดักชัน