เจาะลึกการจัดการแอปพลิเคชัน การอัปเดตแบบไม่มีดาวน์ไทม์ การสเกลอัตโนมัติ และการจัดการคอนฟิก
ในสัปดาห์ที่แล้ว เราเรียนรู้พื้นฐานของ Kubernetes ไปแล้ว
กลุ่มของเครื่อง Server (Master & Worker Nodes) ที่ทำงานร่วมกัน
หน่วยเล็กที่สุดใน K8s ห่อหุ้ม Container (มักจะมี 1 Container ต่อ 1 Pod)
การแจก IP และ Load Balance ภายในให้ Pods เพื่อให้ติดต่อกันได้
ภาษาที่ใช้ประกาศ Desired State ให้ Kubernetes ไปทำให้เป็นจริง
Workload คือแอปพลิเคชันที่ทำงานอยู่บน Kubernetes
แม้ว่า "Pod" จะเป็นที่อยู่ของแอปพลิเคชัน แต่ในสภาพแวดล้อมจริง (Production) เราแทบจะไม่สร้าง Pod ขึ้นมาตรงๆ ด้วยตัวเองเลย เพราะถ้า Pod ตาย มันจะตายถาวร
เป้าหมายหลักของ ReplicaSet (RS) คือการรักษาสถานะและจำนวนของ Pods ให้ตรงกับที่กำหนดไว้ (Desired State) อยู่ตลอดเวลา
ReplicaSet ไม่ได้ "ผูกติด" กับ Pod ตั้งแต่เกิด แต่มันใช้ Labels ในการค้นหาและควบคุม
Selector: app=frontend
Replicas: 3
app=frontend)
app=frontend)
app=frontend)
*ถ้าเปลี่ยน Label ของ Pod A เป็น app=backend, ReplicaSet จะคิดว่า Pod หายไป 1 ตัว และจะสร้าง Pod ใหม่ขึ้นมาแทนทันที!
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend-rs
spec:
replicas: 3 # ระบุจำนวน Pod ที่ต้องการ (Desired State)
selector:
matchLabels:
app: frontend # เงื่อนไขการค้นหา Pod
template: # แม่พิมพ์สำหรับสร้าง Pod ใหม่
metadata:
labels:
app: frontend
spec:
containers:
- name: web
image: nginx:1.14
หากคุณต้องการเปลี่ยนเวอร์ชันของ Image (เช่น จาก nginx:1.14 เป็น nginx:1.15) ใน ReplicaSet คุณต้องเข้าไปแก้ไฟล์ YAML
แต่ ReplicaSet จะไม่ทำการลบ Pod เก่าและสร้างใหม่ให้! คุณต้องไปลบ Pod เก่าด้วยมือเอง เพื่อให้มันสร้าง Pod ใหม่ด้วย Image ล่าสุด
Downtime & Manual Work
Deployment คือ Workload Resource ระดับสูงที่สร้างมาครอบทับ ReplicaSet อีกที เพื่อแก้ปัญหาการอัปเดตแอปพลิเคชัน
Deployment ไม่ได้ควบคุม Pod โดยตรง แต่มันไปควบคุม ReplicaSet แทน
ควบคุมเวอร์ชันและการอัปเดต
ควบคุมจำนวน Pod ให้ครบ
ทำลาย Pod เวอร์ชั่นเก่าทั้งหมดทิ้งก่อน แล้วค่อยสร้าง Pod เวอร์ชั่นใหม่ขึ้นมา
ทยอยสร้าง Pod ใหม่ทีละตัว และทยอยลบ Pod เก่าทีละตัว สลับกันไปจนครบ
V1 ReplicaSet (เก่า)
V2 ReplicaSet (ใหม่)
Kubectl มีคำสั่งสำหรับตรวจสอบและจัดการสถานะการอัปเดต (Rollout) ของ Deployment
ดูสถานะการอัปเดตว่าเสร็จหรือยัง
$ kubectl rollout status deployment/myapp
ดูประวัติการอัปเดต (Revision History)
$ kubectl rollout history deployment/myapp
REVISION CHANGE-CAUSE
1 Initial commit
2 Update image to v2
หากอัปเดตไปแล้วแอปพลิเคชันมีปัญหา (เช่น ติด CrashLoopBackOff หรือ Error 500) เราสามารถสั่งถอยหลังกลับไปยังเวอร์ชันก่อนหน้าได้อย่างรวดเร็ว
# ย้อนกลับไปยังเวอร์ชันก่อนหน้าทันที (Revision ล่าสุด)
$ kubectl rollout undo deployment/myapp
# หรือย้อนกลับไปยัง Revision ที่ระบุเจาะจง
$ kubectl rollout undo deployment/myapp --to-revision=1
*Deployment เก็บ ReplicaSet เวอร์ชั่นเก่าไว้ (ปกติ 10 อัน) โดยตั้ง Replicas=0 ทำให้สามารถสลับกลับไปได้ทันที
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
replicas: 3
strategy: # กำหนดกลยุทธ์การ Rollout
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # ยอมให้มี Pod เกินตอนอัปเดตได้ 1 ตัว
maxUnavailable: 0 # ห้ามมี Pod ขาดเลย (ต้องรับทราฟฟิกได้เต็ม 100%)
selector:
matchLabels:
app: web
... (template ของ Pod) ...
เมื่อมีผู้ใช้งานเพิ่มขึ้นอย่างมหาศาล จำนวน Pod (Replicas) ที่กำหนดไว้คงที่อาจจะไม่พอรับโหลด K8s จึงมี HPA มาช่วย
High Traffic
CPU 85%
Metrics Server
(เก็บค่า CPU/Mem)
HPA
Target CPU: 50%
*คำเตือน: HPA จะทำงานได้ Pod ต้องมีการกำหนด resources.requests.cpu ไว้ใน YAML ด้วย
# สร้างผ่าน Command Line (Imperative)
$ kubectl autoscale deployment myapp \
--cpu-percent=50 \
--min=1 --max=10
# ตรวจสอบสถานะ
$ kubectl get hpa
NAME TARGETS MINPODS MAXPODS REPLICAS
myapp 85%/50% 1 10 1
# สร้างผ่าน YAML (Declarative)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
ในสัปดาห์ก่อน เราใช้ Service ชนิด NodePort หรือ LoadBalancer ในการเปิดให้ภายนอกเข้าถึงแอปได้ แต่มีข้อจำกัด:
/api, /web) ได้Ingress คือการจัดการ Traffic ภายนอกเข้าสู่ Cluster ในระดับ Layer 7 (HTTP/HTTPS)
app.com, api.com)/frontend, /backend)Ingress แบ่งออกเป็น 2 ส่วนที่ต้องทำงานร่วมกันเสมอ
เป็นโปรแกรมจริงๆ (มักจะรันเป็น Pod) ที่ทำหน้าที่เป็น Reverse Proxy
เป็นไฟล์ Configuration ที่เราเขียนเพื่อบอก Controller ว่าจะสร้างกฎการ Routing อย่างไร
nginx.conf) ทันทีapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
spec:
ingressClassName: nginx # ระบุว่าใช้ Controller ตัวไหน
rules:
- host: myapp.example.com # Domain-based
http:
paths:
- path: /api # Path-based
pathType: Prefix
backend:
service:
name: backend-service # ส่งไปที่ Service ของ Backend
port: { number: 8080 }
Anti-Pattern (สิ่งที่ห้ามทำ):
การใส่รหัสผ่าน Database, URL ของ API ปลายทาง, หรือค่าคอนฟิกฝังไว้ใน Source Code (Hardcode) หรือการแพ็กใส่ไว้ใน Docker Image ตรงๆ
เพราะหากต้องการเปลี่ยนค่าสำหรับ Environment อื่น (Dev -> Prod) คุณต้องทำการ Rebuild Docker Image ใหม่ทั้งหมด!
Best Practice (สิ่งที่ควรทำ):
Docker Image ตัวเดียว ควรสามารถนำไปรันได้ในทุก Environment โดยรับค่าตั้งค่าตัวแปรจากภายนอก ใน Kubernetes เราใช้ ConfigMap และ Secret ในการเก็บค่าเหล่านี้
ConfigMap คือ Object ที่ใช้เก็บข้อมูลการตั้งค่าที่ไม่ใช่ความลับ (Non-confidential data) ในรูปแบบ Key-Value
Pod สามารถนำ ConfigMap ไปใช้งานได้ 2 รูปแบบหลักๆ:
APP_ENV=production)nginx.conf หรือ settings.json ไปวาง)# 1. ไฟล์ cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
DB_HOST: "mysql-service.default.svc"
# 2. การเรียกใช้ใน Deployment YAML
...
containers:
- name: myapp
env:
- name: DATABASE_HOST # ชื่อ ENV ใน Container
valueFrom:
configMapKeyRef:
name: app-config
key: DB_HOST
Secret ทำงานคล้ายกับ ConfigMap แต่ถูกออกแบบมาเพื่อเก็บข้อมูลที่มีความอ่อนไหว (Sensitive data) เช่น รหัสผ่าน, OAuth Tokens หรือ SSH Keys
# การสร้าง Base64 ใน Linux/Mac
$ echo -n 'myPassword123' | base64
bXlQYXNzd29yZDEyMw==
# 1. ไฟล์ secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
password: bXlQYXNzd29yZDEyMw==
# 2. การเรียกใช้ใน Deployment YAML
...
containers:
- name: myapp
env:
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: password
ในส่วนของปฏิบัติการ เราจะนำทฤษฎีทั้งหมดมาประยุกต์ใช้งานจริง โดยการนำ Web Application ขึ้นบน Cluster ของคุณ
kubectl apply และการเขียน YAML ให้พร้อม แล้วเจอกันในชั่วโมง Lab ครับ!
จัดการแอปพลิเคชันอย่างมืออาชีพด้วย Deployments, ConfigMaps และเผยแพร่สู่ภายนอกด้วย Ingress
แทนที่จะ Hardcode ค่าต่างๆ ลงในโค้ด เราจะเก็บการตั้งค่าไว้ใน ConfigMap
# สร้างไฟล์ชื่อ web-configmap.yaml
$ nano web-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config
data:
APP_ENV: "production"
APP_VERSION: "1.0.0"
GREETING_MSG: "Hello from Kubernetes ConfigMap!"
เราจะสร้าง Deployment ที่ควบคุม 2 Pods และดึงค่าจาก ConfigMap มาใช้เป็น Environment Variables
# สร้างไฟล์ชื่อ web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-webapp
spec:
replicas: 2 # ต้องการ 2 Pods เสมอ
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web-container
image: nginx:latest # หรือใส่ Docker Image ที่คุณสร้างไว้
ports:
- containerPort: 80
envFrom: # ดึงข้อมูลทั้งหมดจาก ConfigMap มาเป็น ENV
- configMapRef:
name: webapp-config
ใช้ kubectl นำไฟล์ YAML ทั้งสองไปรันใน Cluster
$ kubectl apply -f web-configmap.yaml
configmap/webapp-config created
$ kubectl apply -f web-deployment.yaml
deployment.apps/my-webapp created
# ตรวจสอบ Deployment และ Pods
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
my-webapp 2/2 2 2 15s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-webapp-65c8f9b9c4-abc12 1/1 Running 0 18s
my-webapp-65c8f9b9c4-xyz89 1/1 Running 0 18s
เราจะมุดเข้าไปใน Pod ที่กำลังรันอยู่ เพื่อดูว่า Environment Variables ถูกส่งเข้าไปจริงหรือไม่
# ใช้คำสั่ง exec เข้าไปรันคำสั่ง 'env' ใน pod (เลือกชื่อ Pod ของคุณมา 1 อัน)
$ kubectl exec -it my-webapp-65c8f9b9c4-abc12 -- env | grep APP
# ผลลัพธ์ที่ควรจะเห็น:
APP_ENV=production
APP_VERSION=1.0.0
$ kubectl exec -it my-webapp-65c8f9b9c4-abc12 -- env | grep GREETING
GREETING_MSG=Hello from Kubernetes ConfigMap!
หากมีผู้ใช้งานเว็บเพิ่มขึ้น เราสามารถเพิ่มจำนวน Pod ได้ง่ายๆ ภายในเสี้ยววินาที
# สั่งเพิ่ม Replica จาก 2 เป็น 5
$ kubectl scale deployment my-webapp --replicas=5
deployment.apps/my-webapp scaled
# รีบพิมพ์คำสั่งนี้เพื่อดู K8s ทำงานแบบ Real-time
$ kubectl get pods -w
NAME READY STATUS AGE
my-webapp-65c8f9b9c4-abc12 1/1 Running 5m
my-webapp-65c8f9b9c4-xyz89 1/1 Running 5m
my-webapp-65c8f9b9c4-kpl22 0/1 ContainerCreating 2s
my-webapp-65c8f9b9c4-mnq33 0/1 ContainerCreating 2s
my-webapp-65c8f9b9c4-rst44 0/1 ContainerCreating 2s
กด Ctrl+C เพื่อออกจากโหมด Watch (-w) เมื่อทุก Pod ขึ้นสถานะ Running
ก่อนจะใช้ Ingress ได้ เราต้องมี Service ชนิด ClusterIP (IP ภายใน) เพื่อมัดรวม Pod ทั้ง 5 ตัวเข้าด้วยกันเป็นเป้าหมายเดียว
# ใช้คำสั่ง expose เพื่อสร้าง Service อย่างรวดเร็ว
$ kubectl expose deployment my-webapp --port=80 --target-port=80 --type=ClusterIP
service/my-webapp exposed
# ตรวจสอบ Service
$ kubectl get svc
ใน K8s ปกติ Ingress เป็นแค่กฎ (Rules) เราต้องมีตัว Ingress Controller (เช่น Nginx) คอยรันอยู่เพื่อทำหน้าที่เป็น Reverse Proxy จริงๆ
# สั่งให้ Minikube เปิดใช้งาน Nginx Ingress Controller
$ minikube addons enable ingress
💡 ingress is an addon maintained by Kubernetes.
... (กระบวนการดาวน์โหลดและติดตั้ง Image ของ Ingress Controller)
🌟 The 'ingress' addon is enabled
# ตรวจสอบว่า Ingress Controller รันขึ้นมาหรือยัง (จะอยู่ใน namespace: ingress-nginx)
$ kubectl get pods -n ingress-nginx
เราจะสร้างกฎ Routing ว่าถ้ามีคนเข้าเว็บด้วยโดเมน myapp.local ให้ชี้ไปที่ Service my-webapp
# สร้างไฟล์ชื่อ web-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: webapp-ingress
spec:
rules:
- host: myapp.local # โดเมนที่เราต้องการใช้งาน
http:
paths:
- path: / # ทุกๆ path ให้เข้าแอปนี้
pathType: Prefix
backend:
service:
name: my-webapp # ชื่อ Service ที่เราสร้างใน Step 6
port:
number: 80 # Port ของ Service
เนื่องจาก myapp.local เป็นโดเมนสมมติ เราต้องหลอกคอมพิวเตอร์ของเราให้ชี้โดเมนนี้ไปที่ IP ของ Minikube
# 1. Apply ไฟล์ Ingress
$ kubectl apply -f web-ingress.yaml
ingress.networking.k8s.io/webapp-ingress created
# 2. ดู IP ของ Ingress (รอจนกว่า ADDRESS จะมีค่า)
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS
webapp-ingress myapp.local 192.168.49.2 80
# 3. แก้ไขไฟล์ hosts ในคอมพิวเตอร์ของคุณ
สำหรับ Windows (เปิด Notepad แบบ Admin):
C:\Windows\System32\drivers\etc\hosts
สำหรับ Mac/Linux (ใช้ sudo nano):
/etc/hosts
# เพิ่มบรรทัดนี้ลงไปท้ายไฟล์ (ใช้ IP จากข้อ 2):
192.168.49.2 myapp.local
ความพยายามอยู่ที่ไหน ความสำเร็จอยู่ที่นั่น! ทดสอบเปิดเบราว์เซอร์ของคุณ
If you see this page, the nginx web server is successfully installed and working.
หากคุณรัน Minikube ด้วย Docker driver บน Mac หรือ Windows IP ของ Minikube จะไม่สามารถเข้าถึงได้โดยตรงจากเบราว์เซอร์โฮสต์
วิธีแก้: เปิด Terminal ใหม่แล้วรันคำสั่งนี้ทิ้งไว้
$ minikube tunnel
มันจะขอรหัสผ่าน Admin เครื่องเพื่อเจาะอุโมงค์ให้ Traffic วิ่งเข้า Ingress ได้ (ต้องเปิดค้างไว้ตลอดตอนทดสอบ)
คุณอาจจะ Save ไฟล์ไม่ได้เพราะติด Permission Denied
วิธีแก้:
sudo nano /etc/hostsweb-configmap.yamlweb-deployment.yamlweb-ingress.yamlkubectl scale และผลลัพธ์ kubectl get pods (แสดง 5 pods)kubectl exec เข้าไปดู Environment Variableshttp://myapp.local ได้สำเร็จ