ข้ามไปยังเนื้อหา

GitOps และ CI/CD สำหรับ Kubernetes

GitOps ทำให้ Git repository เป็น single source of truth ของ desired state ของ cluster โดยมี reconciler ที่รันอยู่ใน cluster คอย pull แล้ว apply state นั้น แทนที่จะให้ CI job push state เข้ามาจากภายนอก

CI/CD pipeline แบบดั้งเดิมเป็น push-based CI job build artifact เสร็จแล้วรัน kubectl apply หรือ helm upgrade ตรงเข้า cluster เลย โดยใช้ credential ของ cluster ที่ตัว CI system เก็บไว้เอง ทำงานได้ก็จริง แต่หมายความว่า CI system ซึ่งบ่อยครั้งเป็น SaaS ของบุคคลที่สามที่อยู่นอก infrastructure boundary ของเรา ต้องมี access เข้า cluster แบบถาวรและมักเป็น privileged access ด้วย

GitOps กลับทิศทางนี้ reconciler อย่าง Argo CD หรือ Flux รันอยู่ ภายใน cluster ที่จัดการ คอย watch Git repository ที่เก็บ manifest หรือ Helm values ที่ต้องการอยู่ตลอด พอ repository เปลี่ยน reconciler จะ pull desired state ใหม่แล้ว apply เอง cluster เป็นฝ่ายเอื้อมไปหา Git ไม่ใช่ Git เอื้อมเข้ามาใน cluster

Terminal window
# Push-based: CI applies directly, using credentials CI itself holds
kubectl apply -f manifests/
helm upgrade myapp ./mychart -f values-prod.yaml
# Pull-based (GitOps): CI's job stops at committing to Git;
# an in-cluster reconciler (Argo CD / Flux) takes it from there
git commit -am "bump myapp image tag to 1.5.0"
git push origin main

ย้ายขั้นตอน apply เข้าไปอยู่ใน cluster ได้ประโยชน์จริงสามอย่าง

  • ไม่มี external system ตัวไหนต้องถือ credential ระดับ cluster-admin งานของ CI system จบแค่ git push มีแค่ reconciler ที่อยู่ใน cluster ซึ่งเราคุมเองเท่านั้นที่ authenticate กับ API server ด้วยสิทธิ์เขียน
  • ตรวจจับ drift ได้ เพราะ reconciler เทียบสถานะจริงของ cluster กับสถานะที่ Git ประกาศไว้อยู่ตลอด การแก้แบบ out-of-band ด้วย kubectl edit หรือ kubectl scale จะถูกจับได้ และสามารถ revert กลับอัตโนมัติ แทนที่จะเบี่ยงเบนไปจากสิ่งที่ Git บอกไว้แบบเงียบ ๆ
  • มีประวัติที่ตรวจสอบได้ในตัว ทุกการเปลี่ยน desired state คือ Git commit หนึ่งอัน ใครเปลี่ยนอะไร เมื่อไหร่ ทำไม (ผ่าน commit message) อยู่ในประวัติเดียวกับที่เราใช้ review code อยู่แล้ว

การ build กับ deploy แบบ GitOps แยกเป็นสอง repository (หรือสอง stage ที่แยกกันชัดเจน) หนึ่งสำหรับ build แอป อีกหนึ่งสำหรับประกาศว่าควรรันอะไรอยู่

Terminal window
# Stage 1 — application repo CI: build and publish the image
docker build -t myregistry/myapp:1.5.0 .
docker push myregistry/myapp:1.5.0
# Stage 2 — update the manifests/values repo that GitOps watches
# (a small CI step, or a bot, edits the tag and commits)
sed -i 's/tag: .*/tag: "1.5.0"/' values-prod.yaml
git commit -am "myapp: bump image tag to 1.5.0"
git push origin main
# From here, the GitOps reconciler (not CI) notices the commit and applies it.
# In a push-based setup, stage 2 would instead run directly against the cluster:
# helm upgrade myapp ./mychart -f values-prod.yaml

ใน flow แบบ GitOps ตัว CI system ไม่เคยรัน kubectl หรือ helm upgrade เข้า cluster จริงเลย หน้าที่ของตัวเองจบแค่ build image แล้ว commit desired state ใหม่เข้า Git

flowchart LR
  dev["Developer"] -->|"push manifest / values change"| git["Git repository (desired state)"]
  ci["CI pipeline"] -->|"build & push image"| registry["Container registry"]
  git -->|"watched / polled"| reconciler["In-cluster GitOps reconciler (Argo CD / Flux)"]
  reconciler -->|"apply diff"| cluster["Live cluster state"]
  cluster -->|"pulls referenced image"| registry
การเปลี่ยน manifest ถูก push เข้า Git แล้วถูก pull มา apply โดย GitOps reconciler ใน cluster ส่วน CI แยก build แล้ว publish image ต่างหาก
การที่ Git เป็น single source of truth ใน GitOps workflow หมายความว่ายังไงในทางปฏิบัติ
ความต่างหลักระหว่าง push-based CI/CD pipeline กับ pull-based GitOps model คืออะไร
ประโยชน์ด้าน security ที่เป็นรูปธรรมของ pull-based GitOps model คืออะไร
ใน GitOps pipeline ที่ใช้งานจริง หลังจาก CI build แล้ว push container image เสร็จ ทำอะไรต่อ