Deploying to the cluster
สิ่งที่จะสร้าง
หัวข้อที่มีชื่อว่า “สิ่งที่จะสร้าง”ไม่มีโค้ด application ใหม่ — บทนี้เอา chart ที่ The Helm chart → สร้างมารันจริง บน kind cluster local: load image shopmicro/* ที่ Module 13 สร้าง Service images →, เพิ่ม template ใหม่หนึ่งตัว — migration Job ที่รันเป็น Helm pre-install/pre-upgrade hook เพื่อให้ schema มีอยู่ก่อน service ใด ๆ เริ่ม — helm install, รอทุก pod ผ่าน probe, port-forward gateway, แล้วรัน curl flow เดียวกับที่ทั้งคอร์สใช้: สร้าง product, วางคำสั่งซื้อ, poll จนเป็น CONFIRMED ความต่างคือคราวนี้ saga รันข้าม pod ที่ Kubernetes schedule กู้ตัวเองและ rolling-upgrade ได้ แทนที่จะเป็นหก terminal go run
ทุกอย่างที่ chart declare ไว้ยังเฉื่อยอยู่ จนกว่าจะมีอะไรมา reconcile กับ cluster จริง นั่นคือสิ่งที่ helm install trigger และเป็นเนื้อหาทั้งหมดของบทนี้ มีสามอย่างที่กลายเป็นจริงตรงนี้ และแต่ละอันคุ้มค่าที่จะนั่งดู:
- Migration ต้องรันก่อน service ที่ต้องการ table
catalogและorderเปิด Postgres pool และคาดว่า schema มีอยู่; service ที่เริ่มกับ database ว่างเปล่าจะ crash ตอน dev local Infra & Compose → คุณรันmigrate ... upด้วยมือก่อน ใน cluster ไม่มี “ด้วยมือ” — ดังนั้น chart พกJobที่ annotate เป็น Helmpre-install,pre-upgradehook ซึ่ง Helm รัน และรอ ก่อนจะ apply Deployment ลำดับการทำงานที่ implicit ตอน shell prompt กลายเป็น dependency ที่ declare ชัด - Probe คุม readiness และ readiness คุม traffic
Serviceหน้า gateway ส่ง request เฉพาะ pod ที่ผ่านreadinessProbe; pod ที่ยังเริ่มอยู่ หรือ pod ที่/healthzfail จะถูกกันออกจาก rotation อัตโนมัติ นี่คือ cluster บังคับใช้วินัย “อย่า route ไปสิ่งที่ยังไม่พร้อม” เดียวกันที่ resilience layer Circuit breakers → บังคับใช้ที่ระดับ client — สองชั้นของแนวคิดเดียวกัน - เวอร์ชันใหม่ roll out โดยไม่ downtime
helm upgradeพร้อม image tag หรือจำนวน replica ที่เปลี่ยน trigger rolling update: Kubernetes ยก pod ใหม่ขึ้น รอให้ผ่าน readiness แล้วค่อยรื้อ pod เก่าServiceของ gateway จึงมี pod ที่พร้อมอยู่เบื้องหลังเสมอ ส่วนที่ทำให้ครึ่ง teardown ของ rollout สะอาดคือ graceful shutdown บนSIGTERMที่ทุกmainimplement ไว้ grpc-gateway → Kubernetes ส่งSIGTERMเข้ามา process ทำงานที่ค้างให้จบแล้วค่อย exit จึงไม่มี request ไหนโดนตัดกลางคัน
เราเลือก kind (Kubernetes-in-Docker) เป็น cluster ตรงนี้เพราะรันอยู่ใน Docker ที่คุณมีอยู่แล้ว ไม่ต้องมี cloud account และที่สำคัญคือ load image ที่ build ในเครื่องตรงเข้า cluster ได้ด้วย kind load จึงไม่ต้อง push ขึ้น registry แค่เพื่อทดลอง chart
ถ้าเป็น cluster จริง คุณจะ push shopmicro/* ขึ้น registry แล้ว set image.repository ตามนั้น นอกนั้นไม่มีอะไรใน chart เปลี่ยนเลย
ข้อดีข้อเสีย
หัวข้อที่มีชื่อว่า “ข้อดีข้อเสีย”kind load docker-image (cluster local อ่าน image local) เทียบกับ push image ไป registry
- Pros: ไม่ต้อง setup registry และไม่มี network round trip — build image เสร็จก็ใช้ใน cluster ได้ในไม่กี่วินาที ทำให้ loop edit-build-deploy เร็วพอจะ iterate กับ chart จริง เหมาะกับ dev บนเครื่องและ CI test cluster
- Cons: วิธีนี้ใช้ได้เพราะ node ของ
kindคือ Docker ในเครื่อง ส่วน kubelet ของ cluster หลาย node จริงอ่าน image cache บน laptop คุณไม่ได้ ดังนั้น deployment ที่ไม่ใช่ local ย่อมต้องมี registry และต้องจัดการเรื่องimagePullPolicy/imagePullSecretsที่kindให้ข้ามไป ความสะดวกนี้จริง แต่ใช้ได้เฉพาะบนเครื่อง
Migration เป็น Helm pre-install hook Job เทียบกับ initContainer บนแต่ละ service หรือ migrate ด้วยมือ
- Pros: hook รันครั้งเดียวเป๊ะต่อ release ก่อน service pod ใดจะเริ่ม และ Helm block รอจนกว่า Job จะสำเร็จ จึงมีจุดเดียวที่กำหนดลำดับและ declare ไว้ชัดว่า schema เป็นเวอร์ชันปัจจุบัน ยิ่งกว่านั้น migration ที่ fail จะทำให้
helm installfail ดัง ๆ แทนที่จะทิ้ง service ที่เริ่มมาครึ่ง ๆ ไว้ให้ crash-loop - Cons: hook Job คือ object เพิ่มอีกตัวพร้อม image และ failure mode ของตัวเอง แถมรันบน ทุก upgrade แม้ไม่มีอะไรเปลี่ยน migration จึงต้อง idempotent ซึ่ง
migrate ... upเป็นอยู่แล้ว ทางเลือกอื่นแย่กว่า:initContainerต่อ service วาง migration ไว้ติดกับ service ที่ต้องใช้ก็จริง แต่กลายเป็นรัน migration เดิม N ครั้งแข่งกันข้าม replica ส่วนการรัน “ด้วยมือ” ก็เอาไม่อยู่ทันทีที่มีมากกว่าหนึ่ง environment สำหรับ chart ที่ตั้งใจให้ install ซ้ำได้ hook จึงเป็นตัวเลือกที่แย่น้อยที่สุดในสามแบบ
ติดตั้ง
หัวข้อที่มีชื่อว่า “ติดตั้ง”1. deploy/helm/shopmicro/templates/migrate-job.yaml
หัวข้อที่มีชื่อว่า “1. deploy/helm/shopmicro/templates/migrate-job.yaml”apiVersion: batch/v1kind: Jobmetadata: name: shopmicro-migrate labels: {{- include "shopmicro.labels" . | nindent 4 }} annotations: # Run before the Deployments on every install and upgrade, and wait for # success before proceeding. delete-policy cleans up the old Job first. "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": before-hook-creationspec: backoffLimit: 3 template: metadata: labels: {{- include "shopmicro.selectorLabels" (dict "name" "migrate") | nindent 8 }} spec: restartPolicy: Never containers: - name: migrate # shopmicro/migrate bundles the migrate CLI with the migrations/ # tree — built in the next section. A cluster can't bind-mount the # host's migrations the way the Compose stack does, so they're baked # into an image instead. image: "{{ .Values.image.repository }}/migrate:{{ .Values.image.tag }}" command: ["/bin/sh", "-c"] args: - | migrate -path /migrations/catalog -database "$CATALOG_DB_URL" up && \ migrate -path /migrations/order -database "$ORDER_DB_URL" up env: - name: CATALOG_DB_URL valueFrom: secretKeyRef: { name: shopmicro-db, key: CATALOG_DB_URL } - name: ORDER_DB_URL valueFrom: secretKeyRef: { name: shopmicro-db, key: ORDER_DB_URL }บันทึกเป็น templates/migrate-job.yaml โดย hook-weight: "-5" จัดลำดับ Job นี้ไว้ก่อน hook ตัวอื่น ส่วน pre-install,pre-upgrade ทำให้ Helm รัน Job แล้ว block รอจนเสร็จก่อนจะ apply Deployment catalog และ order จึงไม่มีทางเริ่มขึ้นมาพร้อม database ที่ยังไม่ migrate
2. สร้าง cluster และ load image
หัวข้อที่มีชื่อว่า “2. สร้าง cluster และ load image”kind create cluster --name shopmicroimage ของทั้งหก service มาจาก Service images → ใน Module 13 แต่ chart ยังต้องการอีกหนึ่งตัวที่ service image จงใจไม่ bundle ไว้ — migrations image ที่จับคู่ migrate CLI เข้ากับ SQL tree build image ตัวนี้ตอนนี้เลย โดยใช้ migrations/ เป็น build context เพื่อให้ root .dockerignore ที่ exclude migrations ออกจาก service build ไม่มีผล:
docker build -t shopmicro/migrate:latest -f - migrations <<'EOF'FROM migrate/migrate:v4.17.1COPY . /migrationsEOFแล้ว load ทุก image ที่ chart อ้างถึงเข้า node ของ cluster เพื่อให้ imagePullPolicy: IfNotPresent เจอ image ในเครื่องได้เลยโดยไม่ต้องมี registry:
for svc in catalog order payment notification notification-worker gateway migrate; do kind load docker-image shopmicro/$svc:latest --name shopmicrodone3. เตรียม infrastructure
หัวข้อที่มีชื่อว่า “3. เตรียม infrastructure”chart คาดว่า Postgres, Kafka, และ RabbitMQ เข้าถึงได้ที่ชื่อใน values.yaml The Helm chart → สำหรับ cluster local ทางที่เร็วสุดคือ chart ของ Bitnami:
helm install postgres oci://registry-1.docker.io/bitnamicharts/postgresql \ --set auth.username=shopmicro,auth.password=shopmicro,auth.database=shopmicro \ --set fullnameOverride=postgreshelm install kafka oci://registry-1.docker.io/bitnamicharts/kafka \ --set fullnameOverride=kafka --set listeners.client.protocol=PLAINTEXThelm install rabbitmq oci://registry-1.docker.io/bitnamicharts/rabbitmq \ --set auth.username=shopmicro,auth.password=shopmicro --set fullnameOverride=rabbitmq(Catalog และ Order ใช้ Postgres instance เดียวกันร่วมกันด้วยสอง database, catalog และ orders — สร้าง database ที่สองเมื่อ Postgres ขึ้นแล้ว หรือชี้ infra.postgres ไปสอง host ถ้าคุณอยากให้แยกกัน)
ตรวจสอบผล
หัวข้อที่มีชื่อว่า “ตรวจสอบผล”Install chart ได้เลย Helm จะรัน migration Job ก่อน รอจนเสร็จ แล้วค่อย apply ที่เหลือ:
helm install shopmicro deploy/helm/shopmicroดู pod ทยอยขึ้นมาและผ่าน probe — READY 1/1 แปลว่า readinessProbe เขียวแล้ว:
kubectl get podsNAME READY STATUS RESTARTS AGEshopmicro-migrate-abcde 0/1 Completed 0 40scatalog-6f9c8b7d5-2xk4p 1/1 Running 0 35sorder-7d4b9c6f8-9wq2n 1/1 Running 0 35spayment-5c8d7b4f9-lm6rt 1/1 Running 0 35snotification-6b7d9f8c5-pk3wq 1/1 Running 0 35snotification-worker-8f9c7b6d4-aa11b 1/1 Running 0 35snotification-worker-8f9c7b6d4-bb22c 1/1 Running 0 35sgateway-7f8d9c6b5-zz99x 1/1 Running 0 35sgateway-7f8d9c6b5-yy88w 1/1 Running 0 35smigration Job แสดง Completed เพราะรันครั้งเดียวแล้ว exit ส่วน notification-worker สองตัวและ gateway สองตัวคือค่า replicas จาก values.yaml ต่อไป port-forward Service ของ gateway มาที่ laptop:
kubectl port-forward svc/gateway 8080:8080ทีนี้รัน flow เดียวกับที่คอร์สใช้มาตั้งแต่ REST Mapping → — สร้าง product, วางคำสั่งซื้อ, poll สถานะ — ต่างกันแค่ตอนนี้ทุก hop เป็น pod:
curl -s -X POST localhost:8080/v1/products \ -H 'Content-Type: application/json' \ -d '{"name":"Coffee Mug","description":"350ml ceramic mug","price_cents":1299,"stock":50}'curl -s -X POST localhost:8080/v1/orders \ -H 'Content-Type: application/json' \ -d '{"customer_id":"cust-1","items":[{"product_id":"8f14e45f-ceea-4c9d-b2a5-0c1e3f4a9b21","quantity":2}]}'Poll คำสั่งซื้ออีกไม่กี่วินาทีต่อมา — นานพอให้ loop outbox relay → Kafka → Payment → Kafka → saga The Saga Handler → รันข้าม pod:
curl -s localhost:8080/v1/orders/3a7c9e21-1e4d-4b8a-9c6e-2f8b1d5a7c90{ "id": "3a7c9e21-1e4d-4b8a-9c6e-2f8b1d5a7c90", "status": "ORDER_STATUS_CONFIRMED", "totalCents": "2598"}PENDING → CONFIRMED รันใน cluster ทั้งหมด — ทั้งระบบ ทุก service และทั้งสอง broker deploy ด้วย helm install เดียว ทีนี้พิสูจน์ rolling upgrade เปลี่ยนค่าแล้ว upgrade:
helm upgrade shopmicro deploy/helm/shopmicro --set services.gateway.replicas=3kubectl rollout status deployment/gatewaydeployment "gateway" successfully rolled outKubernetes ยก gateway pod ตัวที่สามขึ้น รอ readiness /healthz ของ pod นั้นผ่าน แล้ว Service ก็ balance ข้ามทั้งสามตัว ไม่มี request หล่นเลย เพราะ pod เก่าจะออกจาก rotation หลัง pod ใหม่พร้อมแล้วเท่านั้น สุดท้าย รื้อทั้งหมด:
helm uninstall shopmicrorelease "shopmicro" uninstalledแล้วยืนยันว่า chart ยัง lint สะอาดหลังเพิ่ม Job template ใหม่:
helm lint deploy/helm/shopmicroไม่มี failure แปลว่าสำเร็จ
ตรวจสอบความเข้าใจ:
- migration
Jobเป็นpre-install,pre-upgradehook อะไรจะพังถ้าเปลี่ยนเป็น template ธรรมดา (ไม่มี hook annotation) ที่ Helm apply พร้อมกับ Deployment? kubectl get podsแสดงgatewayเป็นREADY 1/1หลังผ่านไปไม่กี่วินาที ในช่วงไม่กี่วินาทีก่อนหน้านั้นServiceทำอะไรกับ gateway pod และทำไม?helm upgradeที่เปลี่ยน image tag roll out ได้โดยไม่ downtime ชิ้นไหนในmainของแต่ละ service ที่ทำให้ครึ่ง teardown ของ rollout สะอาด และ signal ไหนเป็นตัว trigger?kind load docker-imageให้คุณข้าม registry ทั้งหมด ทำไมนั่นทำงานได้กับkindแต่ไม่ได้กับ cluster หลาย node จริง?
helm install shopmicro deploy/helm/shopmicro ยกทั้งระบบขึ้นบน kind cluster โดย pre-install hook Job รัน migrate ... up ให้ทั้งสอง database และ Helm block รอ Job นั้น catalog กับ order จึงไม่มีทางเริ่มขึ้นมาพร้อม schema ว่างเปล่า จากนั้นทุก Deployment ก็ roll out และแต่ละ pod จะเข้าร่วม Service ก็ต่อเมื่อ readinessProbe ผ่าน (/healthz สำหรับ gateway, tcpSocket สำหรับ gRPC service)
image ไปถึง cluster ด้วย kind load docker-image โดยไม่ต้องมี registry และ curl flow เดียวกับที่คอร์สใช้มาตลอดก็ขับคำสั่งซื้อจาก PENDING เป็น CONFIRMED ข้าม pod ได้ทั้งหมด loop outbox-relay → Kafka → Payment → saga รันเป๊ะเหมือนตอนอยู่บนเครื่อง
helm upgrade --set services.gateway.replicas=3 พิสูจน์ zero-downtime rolling update โดยมี graceful SIGTERM shutdown เป็นตัวทำให้ teardown สะอาด และ helm uninstall ลบ release ทิ้ง ตอนนี้ ShopMicro รันใน cluster ได้แบบเดียวกับที่รันบน laptop โดยอธิบายไว้ครั้งเดียวเป็น chart ที่ version ได้ บทถัดไป Wrap-up → จะถอยกลับมามองทุกอย่างที่คุณสร้าง — service, broker สองตัว, saga และ outbox, resilience layer และ deployment นี้ — พร้อม trade-off เบื้องหลังแต่ละอัน และทิศทางต่อไปของระบบ