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

แพ็กทั้งระบบเป็น Helm Chart

เขียน Helm chart ของตัวเองจาก manifests ที่มีอยู่ ติดตั้ง/อัปเกรด/ย้อนกลับทั้งระบบด้วยคำสั่งเดียว

พื้นฐานที่ควรรู้: ตอนนี้การ deploy ระบบของเรา = apply ไฟล์ 4 ฉบับตามลำดับ + สร้าง Secret ด้วยมือ + จำว่าต้อง build/load image ก่อน และค่าต่าง ๆ (image tag, ขนาด storage, replicas) ฝังตายอยู่ในไฟล์ — ถ้าอยากได้ระบบชุดที่สอง ต้อง copy ทั้งโฟลเดอร์มาไล่แก้ทีละจุด ใน Module 7 เราเคยใช้ Helm ฝั่ง "ผู้ใช้" (install chart สำเร็จรูปของคนอื่น) — บทนี้เราสลับมาเป็น ผู้เขียน chart เอง: ห่อ manifests ทั้งหมดเป็นแพ็กเกจเดียวที่ประกาศชัดว่า "ค่าไหนปรับได้บ้าง"

แม่พิมพ์ขนม

Chart เหมือนแม่พิมพ์ขนม — ตัว template คือแม่พิมพ์ (โครงเดิมทุกครั้ง) ส่วน values.yaml คือส่วนผสมที่เทลงไป (รสชาติ/สี/ขนาดเปลี่ยนได้) · อยากได้ขนมอีกถาดที่หวานน้อยลง ไม่ต้องแกะสลักแม่พิมพ์ใหม่ แค่เปลี่ยนส่วนผสม — นี่คือหัวใจของการ deploy หลาย environment ในบทถัดไป

brew install helm
helm version   # ต้องเป็น v3.x
💻 ติดตั้ง Helm

สร้างโครง chart จาก manifests เดิม

เราไม่ใช้ helm create (มันสร้าง boilerplate ของ nginx มาให้เพียบ ต้องลบทิ้งเกือบหมด) — สร้างเองจากของที่เรามีอยู่แล้วจะเข้าใจทุกบรรทัด:

cd ~/projects/k8s-video-pipeline
mkdir -p chart/video-pipeline/templates

# เอา manifests เดิมมาเป็นตัวตั้ง (kind-config กับ job-test ไม่เอา —
# อันแรกเป็นของระดับ cluster อันหลังเป็นไฟล์ทดสอบมือ)
cp k8s/minio.yaml k8s/api.yaml k8s/rbac.yaml k8s/ingress.yaml \
   chart/video-pipeline/templates/
💻 โครง chart + ย้าย manifests เข้าไปเป็น template
apiVersion: v2
name: video-pipeline
description: Video upload & transcoding pipeline (workshop)
type: application
version: 0.1.0      # เวอร์ชันของตัว chart (โครงสร้าง/template)
appVersion: "1.0"   # เวอร์ชันของแอปข้างใน (ไว้สื่อสาร ไม่มีผลกับการ deploy)
📄 chart/video-pipeline/Chart.yaml — บัตรประชาชนของ chart
api:
  image: video-api:v1
  replicas: 1
  resources:
    requests: { cpu: 100m, memory: 128Mi }
    limits: { cpu: 500m, memory: 256Mi }

transcoder:
  image: video-transcoder:v1

minio:
  auth:
    user: admin
    pass: password123
  storage: 5Gi
📄 chart/video-pipeline/values.yaml — "หน้าสัญญา" ว่า chart นี้ปรับอะไรได้บ้าง

แปลง manifest → template

กติกามี 2 ข้อ: (1) ลบ `namespace: video` ทิ้งทุกบรรทัด — ให้ผู้ติดตั้งเลือก namespace เองตอน helm install -n <ns> chart จะได้ลงที่ไหนก็ได้ · (2) ค่าที่อยากให้ปรับได้ แทนด้วย `{{ .Values.xxx }}` ตาม path ใน values.yaml

sed -i '' '/namespace: video/d' chart/video-pipeline/templates/*.yaml
💻 ลบบรรทัด namespace ออกจากทุก template รวดเดียว (macOS sed)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: video-api
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: video-api
spec:
  replicas: {{ .Values.api.replicas }}
  selector:
    matchLabels:
      app: video-api
  template:
    metadata:
      labels:
        app: video-api
    spec:
      serviceAccountName: video-api
      containers:
        - name: api
          image: {{ .Values.api.image }}
          imagePullPolicy: IfNotPresent
          env:
            - name: MINIO_HOST
              value: minio
            - name: MINIO_PORT   # กัน service links ฉีดทับ (บทเรียนจากบท 8.5)
              value: "9000"
            - name: MINIO_USER
              valueFrom:
                secretKeyRef: { name: minio-creds, key: user }
            - name: MINIO_PASS
              valueFrom:
                secretKeyRef: { name: minio-creds, key: pass }
            - name: ENABLE_JOBS
              value: "true"
            - name: JOB_NAMESPACE        # บอก API ว่าตัวเองอยู่ namespace ไหน
              valueFrom:
                fieldRef: { fieldPath: metadata.namespace }
            - name: TRANSCODER_IMAGE
              value: {{ .Values.transcoder.image }}
          ports:
            - containerPort: 3000
          readinessProbe:
            httpGet: { path: /healthz, port: 3000 }
            initialDelaySeconds: 3
          livenessProbe:
            httpGet: { path: /healthz, port: 3000 }
            periodSeconds: 10
          resources:
            {{- toYaml .Values.api.resources | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
  name: video-api
spec:
  selector:
    app: video-api
  ports:
    - port: 80
      targetPort: 3000
📄 templates/api.yaml — ฉบับ templatize เต็ม (เทียบกับของเดิมดูว่าจุดไหนกลายเป็นตัวแปร)
  • {{ .Values.api.replicas }} — ดึงค่าจาก values.yaml ตาม path ตรง ๆ
  • {{- toYaml .Values.api.resources | nindent 12 }} — ยก block YAML ทั้งก้อนจาก values แล้วเยื้อง 12 ช่องให้พอดีตำแหน่ง (ท่านี้ใช้บ่อยสุดและพังบ่อยสุด — เยื้องผิด YAML เพี้ยนทันที)
  • fieldRef: metadata.namespace (Downward API) — ให้ Pod อ่านค่า namespace ของตัวเองมาเป็น env var → API จะสร้าง Job ใน namespace เดียวกับตัวเองเสมอ ไม่ว่า chart ถูกติดตั้งที่ไหน

โค้ด API ต้องแก้นิดเดียวให้รับ image ของ transcoder จาก env (บรรทัด NAMESPACE เรารับจาก JOB_NAMESPACE อยู่แล้วตั้งแต่บท 8.6):

// เดิม:  image: 'video-transcoder:v1',
image: process.env.TRANSCODER_IMAGE || 'video-transcoder:v1',
📄 api/server.js — ใน transcodeJobManifest() แก้บรรทัด image

Secret ที่เคยสร้างด้วยมือ (kubectl create secret) ย้ายเข้ามาเป็น template ด้วย — จะได้ติดตั้งครบทั้งระบบในคำสั่งเดียว:

apiVersion: v1
kind: Secret
metadata:
  name: minio-creds
stringData:                # stringData = เขียน plaintext ได้ K8s encode ให้เอง
  user: {{ .Values.minio.auth.user | quote }}
  pass: {{ .Values.minio.auth.pass | quote }}
📄 templates/secret.yaml — ไฟล์ใหม่
# ... ส่วนอื่นเหมือนเดิม (แค่ไม่มีบรรทัด namespace แล้ว)
        resources:
          requests:
            storage: {{ .Values.minio.storage }}
📄 templates/minio.yaml — แก้เฉพาะขนาด storage ใน volumeClaimTemplates
subjects:
  - kind: ServiceAccount
    name: video-api
    namespace: {{ .Release.Namespace }}   # namespace ที่ release นี้ถูกติดตั้ง
📄 templates/rbac.yaml — จุดเดียวที่ "ต้อง" ระบุ namespace: subject ของ RoleBinding ใช้ตัวแปร built-in แทน

ตรวจก่อนติดตั้ง: lint + template

helm lint chart/video-pipeline
# → 1 chart(s) linted, 0 chart(s) failed

# render ออกมาดูเป็น YAML จริง ๆ ว่าตัวแปรถูกแทนค่าถูกไหม (ยังไม่แตะ cluster)
helm template video chart/video-pipeline | less
💻 นิสัยดีของคนเขียน chart — เช็คก่อน apply เสมอ

ติดตั้งด้วย Helm (ล้างของเก่าก่อน)

docker build -t video-api:v1 ./api
kind load docker-image video-api:v1 --name video

kubectl delete namespace video   # ลบระบบที่ apply มือไว้ทั้งหมด

helm install video chart/video-pipeline -n video --create-namespace
helm list -n video               # → video  deployed  video-pipeline-0.1.0
kubectl get pods -n video -w     # รอจน Running ครบ
💻 build image ที่แก้แล้ว → ล้างของเก่า → helm install
kubectl run mc -n video --rm -it --image=minio/mc --restart=Never --command -- /bin/sh
#   mc alias set store http://minio:9000 admin password123
#   mc anonymous set download store/vod
#   exit

# เปิด http://localhost:8080 อัปโหลด sample.mp4 → ต้องทำงานเหมือนเดิมทุกอย่าง
# (เบื่อไหมที่ต้องตั้ง policy ด้วยมือทุกรอบ? บทหน้าให้ chart ทำเองอัตโนมัติ)
💻 ตั้ง bucket policy อีกรอบ (namespace ใหม่ = MinIO ตัวใหม่) แล้วทดสอบ

พลังของ release: upgrade + rollback

# ขยาย API เป็น 2 ตัวด้วย --set (override ค่าใน values.yaml ชั่วคราว)
helm upgrade video chart/video-pipeline -n video --set api.replicas=2
kubectl get pods -n video        # เห็น video-api สองตัว

helm history video -n video      # ทุก upgrade = revision ใหม่
helm rollback video 1 -n video   # ย้อนกลับ revision แรก → เหลือ 1 ตัวเหมือนเดิม
💻 ปรับค่าโดยไม่แตะไฟล์ แล้วย้อนกลับได้ทั้งระบบ

สรุป Key Takeaways

  • chart = templates + values.yaml — values คือสัญญาว่าปรับอะไรได้ · ลบ namespace ออกจาก template ให้ -n ตอน install กำหนดแทน
  • toYaml + nindent ยก block จาก values · Downward API (fieldRef) ให้ Pod รู้ namespace ตัวเอง
  • helm lint + helm template ตรวจก่อนติดตั้งเสมอ
  • ทั้งระบบ = release เดียว: upgrade/history/rollback ได้ทั้งก้อน
อ่านจบแล้วอย่าลืมทำเครื่องหมาย