ข้ามไปเนื้อหาหลัก
คอนเทนเนอร์· ~35 นาที

ยกทั้งระบบขึ้น kind cluster

สร้าง cluster, Secret, MinIO StatefulSet + PVC และ API Deployment + Service

ถึงเวลาของจริง — เอาสิ่งที่รันบน compose ขึ้น Kubernetes โดยแปลงทีละส่วน: MinIO (มี state ต้องเก็บไฟล์) เป็น StatefulSet + PVC ส่วน API (stateless) เป็น Deployment และสร้าง Secret เก็บรหัส MinIO แทนการฝัง plaintext ใน manifest

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            node-labels: "ingress-ready=true"
    extraPortMappings:
      - containerPort: 80
        hostPort: 8080   # เข้า Ingress ผ่าน http://localhost:8080
        protocol: TCP
📄 k8s/kind-config.yaml — เตรียมพอร์ตให้ Ingress ตั้งแต่ตอนสร้าง cluster (ใช้บท 8.7)
cd ~/projects/k8s-video-pipeline
docker compose down   # จากบทนี้ไปทุกอย่างอยู่บน kind — ไม่ปิดจะแย่งพอร์ต 3000 ตอน port-forward

kind create cluster --name video --config k8s/kind-config.yaml
kubectl cluster-info

kubectl create namespace video
kubectl config set-context --current --namespace=video  # ไม่ต้องพิมพ์ -n video ทุกคำสั่ง

kubectl create secret generic minio-creds \
  --from-literal=user=admin \
  --from-literal=pass=password123
💻 ปิด compose จากบทที่แล้วก่อน (เลิกใช้แล้ว + กันพอร์ตชนกัน) แล้วสร้าง cluster + namespace + Secret

MinIO: StatefulSet + PVC

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: minio
  namespace: video
spec:
  serviceName: minio
  replicas: 1
  selector:
    matchLabels:
      app: minio
  template:
    metadata:
      labels:
        app: minio
    spec:
      containers:
        - name: minio
          image: minio/minio
          args: ["server", "/data", "--console-address", ":9001"]
          env:
            - name: MINIO_ROOT_USER
              valueFrom:
                secretKeyRef: { name: minio-creds, key: user }
            - name: MINIO_ROOT_PASSWORD
              valueFrom:
                secretKeyRef: { name: minio-creds, key: pass }
          ports:
            - { containerPort: 9000, name: api }
            - { containerPort: 9001, name: console }
          volumeMounts:
            - name: data
              mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
  name: minio
  namespace: video
spec:
  selector:
    app: minio
  ports:
    - { name: api, port: 9000 }
    - { name: console, port: 9001 }
📄 k8s/minio.yaml

API: Deployment + Service

apiVersion: v1
kind: ServiceAccount
metadata:
  name: video-api
  namespace: video
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: video-api
  namespace: video
spec:
  replicas: 1
  selector:
    matchLabels:
      app: video-api
  template:
    metadata:
      labels:
        app: video-api
    spec:
      serviceAccountName: video-api   # ตัวตนของ Pod — ใช้ผูกสิทธิ์สร้าง Job ในบทถัดไป
      containers:
        - name: api
          image: video-api:v1
          imagePullPolicy: IfNotPresent   # สำคัญมากสำหรับ image ที่ load เข้า kind เอง!
          env:
            - name: MINIO_HOST
              value: minio
            - name: MINIO_PORT   # ต้องประกาศเอง! ไม่งั้นโดน service links ของ K8s ฉีดค่าขยะทับ (ดู callout ข้างล่าง)
              value: "9000"
            - name: MINIO_USER
              valueFrom:
                secretKeyRef: { name: minio-creds, key: user }
            - name: MINIO_PASS
              valueFrom:
                secretKeyRef: { name: minio-creds, key: pass }
          ports:
            - containerPort: 3000
          readinessProbe:
            httpGet: { path: /healthz, port: 3000 }
            initialDelaySeconds: 3
          livenessProbe:
            httpGet: { path: /healthz, port: 3000 }
            periodSeconds: 10
          resources:
            requests: { cpu: 100m, memory: 128Mi }
            limits: { cpu: 500m, memory: 256Mi }
---
apiVersion: v1
kind: Service
metadata:
  name: video-api
  namespace: video
spec:
  selector:
    app: video-api
  ports:
    - port: 80
      targetPort: 3000
📄 k8s/api.yaml
# kind ไม่เห็น image ในเครื่องเราอัตโนมัติ — ต้อง load เข้าไป
docker build -t video-api:v1 ./api
kind load docker-image video-api:v1 --name video

kubectl apply -f k8s/minio.yaml -f k8s/api.yaml
kubectl get pods -w   # รอจน minio-0 กับ video-api-xxx เป็น Running (Ctrl+C เพื่อออก)
💻 โหลด image เข้า kind แล้ว deploy ทั้งหมด
# terminal 1 — เปิดค้างไว้ (ถ้า forward มีปัญหา error จะโชว์ที่จอนี้)
kubectl port-forward svc/video-api 3000:80

# terminal 2
curl -F "video=@sample.mp4" http://localhost:3000/api/upload
# → {"videoId":"...","status":"uploaded"}  = API บน K8s คุยกับ MinIO บน K8s สำเร็จ!

# ส่องใน MinIO console ก็ได้ (Ctrl+C ตัว forward เดิมก่อน แล้วรัน):
kubectl port-forward svc/minio 9001:9001
# เปิด http://localhost:9001 → bucket uploads ต้องมีไฟล์
💻 ทดสอบผ่าน port-forward (ยังไม่มี Ingress) — รัน forward ค้างหน้าจอไว้ อย่าใส่ & เพราะจะซ่อน error

สรุป Key Takeaways

  • MinIO (มี state) = StatefulSet + volumeClaimTemplates · API (stateless) = Deployment
  • Service ทำให้เรียกกันด้วยชื่อ DNS ได้ (minio:9000) — โค้ดเดิมจาก compose ใช้ได้เลย
  • image ที่ build เองต้อง kind load + imagePullPolicy: IfNotPresent
อ่านจบแล้วอย่าลืมทำเครื่องหมาย