article

Kubernetes ชื่อยากแต่ทำให้ชีวิตง่ายขึ้น (จริงๆ นะ)

5 min read

ตอนแรกก็กลัวเหมือนกัน

พอได้ยินคำว่า “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 ตอน startup
  • Pending = 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 ก่อน เพราะพอเจอปัญหาแล้วไม่รู้จะแก้ยังไง จะเครียดมาก 😅

แต่พอเรียนรู้จนคุ้นเคยแล้ว การันตีได้เลยว่า ไม่อยากกลับไปใช้วิธีเก่าแล้ว! 🚀