ตอนแรกก็กลัวเหมือนกัน
พอได้ยินคำว่า “Kubernetes” ครั้งแรก ความรู้สึกที่ผุดขึ้นมาก่อนคือ “ซับซ้อนแน่ๆ” 😰
เพราะดูแล้วมีคำศัพท์แปลกๆ เยอะมาก:
- Pod คืออะไร
- Service กับ Ingress ต่างกันยังไง
- ConfigMap vs Secret
- Deployment vs StatefulSet
- ReplicaSet มันทำอะไร
แต่พอเวลาผ่านไป และมาถึงจุดที่ต้องจัดการ containers หลายร้อยตัว มันเริ่มเข้าใจว่า ถ้าไม่มี K8s แล้วจะทำยังไง 🤯
การเดินทางจาก Docker Compose สู่ K8s
ก่อนใช้ K8s: Docker Compose
ตอนแรกใช้ Docker Compose รันทุกอย่าง:
# docker-compose.yml
version: '3.8'
services:
web-1:
image: myapp:latest
ports:
- "3001:3000"
web-2:
image: myapp:latest
ports:
- "3002:3000"
web-3:
image: myapp:latest
ports:
- "3003:3000"
nginx:
image: nginx
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ปัญหาที่เจอ:
- Scale ด้วยมือเอง (เพิ่ม service ใน compose)
- ถ้า container ตาย ต้องไป restart เอง
- Health check พื้นฐาน
- Rolling update ทำไม่ได้
หลังใช้ K8s
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
ได้อะไรดี:
- Auto-scaling ตาม CPU/Memory
- Self-healing (container ตายแล้ว restart เอง)
- Rolling update แบบ zero-downtime
- Health checks ที่ครบครัน
ความผิดพลาดในวันแรกๆ
1. ไม่เข้าใจเรื่อง Namespace
ตอนแรกใส่ทุกอย่างใน default namespace:
kubectl get pods
# มี pod เกลื่อนกลาดเป็นร้อยตัว งงมาก 😵
เรียนรู้ว่า:
# ใช้ namespace แยกแต่ละ environment
apiVersion: v1
kind: Namespace
metadata:
name: development
---
apiVersion: v1
kind: Namespace
metadata:
name: staging
---
apiVersion: v1
kind: Namespace
metadata:
name: production
# Deploy แยก namespace
kubectl apply -f app.yaml -n development
kubectl apply -f app.yaml -n staging
2. Resource Limits ไม่ใส่
ครั้งแรกไม่ใส่ resource limits ผลคือ:
- Pod ใช้ RAM หมดเครื่อง
- CPU throttling เกิดขึ้นบ่อย
- Node หลายตัวล่ม 💀
เรียนรู้ให้ใส่ limits เสมอ:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
image: myapp:latest
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
3. Readiness vs Liveness Probes
ความเข้าใจผิดตอนแรก: คิดว่าเหมือนกัน
Liveness Probe = ตรวจว่า container ยังมีชีวิตอยู่ไหม
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
Readiness Probe = ตรวจว่าพร้อมรับ traffic ไหม
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
ผลต่างคือ:
- Liveness fail = restart container
- Readiness fail = หยุดส่ง traffic ไป pod นั้น
4. ConfigMap vs Secret สับสน
ตอนแรกใส่ password ใน ConfigMap 🤦♂️
ConfigMap สำหรับ config ธรรมดา:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
database_host: "postgres.example.com"
redis_port: "6379"
log_level: "info"
Secret สำหรับข้อมูลลับ:
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
database_password: cGFzc3dvcmQxMjM= # base64 encoded
api_key: YWJjZGVmZ2hpams=
ใช้ใน Pod:
spec:
containers:
- name: myapp
image: myapp:latest
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: database_host
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: database_password
เคสที่น่าจำ: Rolling Update ที่ผิดพลาด
วันหนึ่งต้อง deploy version ใหม่ คิดว่าง่ายๆ:
kubectl set image deployment/myapp myapp=myapp:v2.0
แล้วระเบิด! 💥
- Database schema ไม่ compatible
- New pods ไม่ startup เพราะ missing ENV
- Old pods ถูก terminate ก่อน new pods พร้อม
เรียนรู้เรื่อง Rolling Update Strategy:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # ตาย max 1 pod
maxSurge: 2 # เพิ่ม max 2 pods
template:
spec:
containers:
- name: myapp
image: myapp:v2.0
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 2
failureThreshold: 3
แล้วก็เรียนรู้เรื่อง Pre-stop hooks:
spec:
containers:
- name: myapp
image: myapp:latest
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"] # ให้เวลา graceful shutdown
Ingress: Gateway สู่ความสับสน
เอา Service ออกมาข้างนอกยังไง?
ตอนแรก: ใช้ NodePort
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
type: NodePort
ports:
- port: 80
targetPort: 3000
nodePort: 30080 # เข้าได้ผ่าน <node-ip>:30080
selector:
app: myapp
ปัญหา: URL แปลกๆ domain.com:30080
ตอนนี้: ใช้ Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
ผลได้: HTTPS cert อัตโนมัติ + custom domain
ประสบการณ์ Debugging ใน K8s
เมื่อ Pod ไม่ขึ้น
# ขั้นตอนที่ทำเป็นประจำ
kubectl get pods # ดู status
kubectl describe pod <pod-name> # ดู events
kubectl logs <pod-name> # ดู application logs
kubectl exec -it <pod-name> -- sh # เข้าไปใน container
Event ที่เจอบ่อย:
ImagePullBackOff= image ไม่เจอCrashLoopBackOff= app crash ตอน startupPending= resource ไม่พอ หรือ node selector ไม่ตรง
เมื่อ Service เข้าไม่ได้
# ตรวจสอบ Service
kubectl get svc
kubectl describe svc <service-name>
# ตรวจสอบ Endpoints
kubectl get endpoints <service-name>
กฎทองคำ: ถ้า Endpoints ว่าง = selector ไม่ตรงกับ pod labels
เมื่อ Ingress ไม่ทำงาน
# ตรวจสอบ Ingress Controller
kubectl get pods -n ingress-nginx
# ดู Ingress rules
kubectl describe ingress <ingress-name>
# ดู logs ของ ingress controller
kubectl logs -n ingress-nginx <ingress-controller-pod>
HPA: Auto Scaling ที่ต้องเรียนรู้
เริ่มแรกตั้ง HPA แบบ basic:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
ปัญหาที่เจอ:
- Scale up เร็วเกินไป
- Scale down ช้าเกินไป
- CPU metric ไม่สะท้อนความเป็นจริง
แก้โดยเพิ่ม behavior:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
เครื่องมือที่ช่วยให้ชีวิต K8s ง่ายขึ้น
1. kubectl aliases
# .bashrc หรือ .zshrc
alias k='kubectl'
alias kg='kubectl get'
alias kd='kubectl describe'
alias kl='kubectl logs'
alias ke='kubectl exec -it'
# ตัวอย่างการใช้
k get pods -n production
kl deployment/myapp -f
2. kubectx + kubens
# Switch context (cluster)
kubectx production
kubectx staging
# Switch namespace
kubens default
kubens production
3. k9s - Terminal UI
# Install k9s
brew install k9s # บน Mac
# หรือ download จาก GitHub releases
# Run
k9s
ได้ Terminal UI ที่สวยงาม เห็นทุกอย่างแบบ real-time
4. Lens - Kubernetes IDE
GUI สำหรับจัดการ K8s cluster ใช้ง่ายมาก
5. Helm - Package Manager
แทนที่จะเขียน YAML หลายไฟล์:
helm install myapp ./myapp-chart \
--set image.tag=v2.0 \
--set replicas=5 \
--namespace production
สรุปการเดินทางกับ K8s
Kubernetes มันเหมือน เครื่องบินโบอิ้ง 747
- ซับซ้อนมาก
- ต้องเรียนรู้เยอะ
- แต่พอใช้เป็นแล้ว ไปได้ไกลมาก
จากคนที่เคยกลัว K8s ตอนนี้กลับไปใช้ Docker Compose แล้วรู้สึกจำกัด
เพราะ K8s ให้อะไรที่ Docker Compose ให้ไม่ได้:
- Declarative approach - บอกว่าต้องการอะไร ไม่ต้องบอกวิธี
- Self-healing - ระบบดูแลตัวเองได้
- Horizontal scaling - Scale ออกได้ไม่จำกัด
- Rolling updates - Deploy ไม่ downtime
- Service mesh ready - พร้อมสำหรับ microservices ซับซ้อน
สุดท้าย: อย่ารีบใช้ K8s ใน production ตั้งแต่วันแรก ให้เรียนรู้ใน local/staging ก่อน เพราะพอเจอปัญหาแล้วไม่รู้จะแก้ยังไง จะเครียดมาก 😅
แต่พอเรียนรู้จนคุ้นเคยแล้ว การันตีได้เลยว่า ไม่อยากกลับไปใช้วิธีเก่าแล้ว! 🚀