คอนเทนเนอร์· ~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: TCPcd ~/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=password123MinIO: 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 }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# 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 เพื่อออก)# 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 ต้องมีไฟล์สรุป Key Takeaways
- MinIO (มี state) = StatefulSet + volumeClaimTemplates · API (stateless) = Deployment
- Service ทำให้เรียกกันด้วยชื่อ DNS ได้ (minio:9000) — โค้ดเดิมจาก compose ใช้ได้เลย
- image ที่ build เองต้อง kind load + imagePullPolicy: IfNotPresent
อ่านจบแล้วอย่าลืมทำเครื่องหมาย

