Lab 11 — GitOps CI/CD Pipeline
🎯 1. Mục tiêu
Sau lab này, bạn sẽ hiểu và thực hiện được:
- 🔄 CI/CD kết hợp với GitOps hoạt động như thế nào.
- 🐳 Tự động build Docker Image bằng GitHub Actions.
- 📦 Push Image lên Container Registry.
- 📝 Tự động cập nhật Image Tag vào GitOps Repository.
- 🚀 Để ArgoCD tự động deploy phiên bản mới lên Kubernetes.
- 🧠 Hiểu tại sao CI không nên trực tiếp
kubectl applyvào Production trong mô hình GitOps.
🤔 2. Vấn đề thực tế
Ở các lab trước, chúng ta đã có:
Git
│
▼
GitOps Repository
│
▼
ArgoCD
│
▼
Kubernetes
Khi thay đổi Kubernetes Manifest:
image: myapp:v1.0.0
thành:
image: myapp:v1.1.0
ArgoCD sẽ phát hiện Git thay đổi và deploy version mới.
Nhưng có một vấn đề:
Ai sẽ build
v1.1.0và cập nhật GitOps Repository?
Nếu làm thủ công, Developer phải:
Code
↓
Build Docker Image
↓
Push Image
↓
Sửa image tag
↓
git commit
↓
git push
↓
ArgoCD deploy
Với vài lần deploy thì không sao.
Nhưng trong môi trường thực tế, mỗi ngày có thể có hàng chục hoặc hàng trăm lần thay đổi.
Chúng ta muốn:
Developer
│
│ git push
▼
GitHub
│
▼
GitHub Actions
│
├── Test
├── Build Image
├── Push Image
│
└── Update GitOps Repo
│
▼
ArgoCD
│
▼
Kubernetes
Đây chính là GitOps CI/CD Pipeline.
🧠 3. Hiểu nhanh
3.1 🔄 CI và GitOps CD khác nhau thế nào?
Trước tiên cần phân biệt hai phần.
CI — Continuous Integration
CI chịu trách nhiệm:
Code
↓
Test
↓
Build
↓
Docker Image
↓
Container Registry
Ví dụ:
todo-backend:v1.2.0
CI trả lời câu hỏi:
"Code mới có build được và tạo ra artifact để deploy không?"
CD trong GitOps
GitOps CD không nhất thiết trực tiếp deploy bằng kubectl.
Thay vào đó:
GitOps Repository
│
│ change detected
▼
ArgoCD
│
▼
Kubernetes
ArgoCD trả lời:
"Kubernetes hiện tại có giống với trạng thái được định nghĩa trong Git không?"
Nếu không:
Git
│
│ desired state
▼
ArgoCD
│
│ reconcile
▼
Kubernetes
3.2 🧩 Vì sao CI không kubectl apply trực tiếp?
Một cách CI/CD truyền thống có thể là:
GitHub Actions
│
│ kubectl apply
▼
Kubernetes
Điều này hoạt động.
Nhưng GitOps muốn:
GitHub Actions
│
│ update Git
▼
GitOps Repository
│
▼
ArgoCD
│
▼
Kubernetes
Tại sao?
Vì Git trở thành Source of Truth — nơi lưu trạng thái mà hệ thống nên có.
Ví dụ Production đang chạy:
todo-backend:v1.2.0
GitOps Repository cũng ghi:
image: todo-backend:v1.2.0
Nếu ai đó thay đổi Kubernetes thủ công:
kubectl set image ...
thì:
Git: v1.2.0
Kubernetes: v1.3.0
ArgoCD có thể phát hiện:
OutOfSync
và đưa cluster về trạng thái được định nghĩa trong Git.
Đây chính là một trong những giá trị lớn nhất của GitOps.
🏗️ 4. Architecture
Trong lab này, chúng ta sử dụng 2 repository.
┌──────────────────────┐
│ Developer │
└──────────┬───────────┘
│
│ git push
▼
┌──────────────────────┐
│ Application Repo │
│ │
│ Source Code │
│ Dockerfile │
│ Tests │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ GitHub Actions │
│ │
│ 1. Test │
│ 2. Build Image │
│ 3. Push Image │
│ 4. Update GitOps │
└──────────┬───────────┘
│
│ git push
▼
┌──────────────────────┐
│ GitOps Repository │
│ │
│ Kubernetes YAML │
│ image: v1.2.0 │
└──────────┬───────────┘
│
│ watch
▼
┌──────────────────────┐
│ ArgoCD │
└──────────┬───────────┘
│
│ Sync
▼
┌──────────────────────┐
│ Kubernetes │
│ │
│ Todo Application │
└──────────────────────┘
💡 Điểm quan trọng
Có hai loại Git repository:
Application Repo
│
│ Source Code
▼
GitHub Actions
│
│ Docker Image
▼
Container Registry
GitOps Repo
│
│ Kubernetes desired state
▼
ArgoCD
│
▼
Kubernetes
Đây là cách tổ chức rất phổ biến trong GitOps.
🛠️ 5. Chuẩn bị môi trường
Chúng ta giả sử đã có:
- Kubernetes Cluster
- ArgoCD
- GitHub Account
- Docker
- Git
- Todo Application từ các lab trước
Chúng ta sẽ tạo:
todo-app
todo-app-gitops
Trong đó:
todo-app
là Application Repository.
todo-app-gitops
là GitOps Repository.
📁 6. Tạo Application Repository
Repository đầu tiên chứa source code.
Ví dụ:
todo-app/
├── backend/
│ ├── src/
│ ├── package.json
│ └── Dockerfile
│
├── frontend/
│ ├── src/
│ ├── package.json
│ └── Dockerfile
│
└── .github/
└── workflows/
└── ci.yml
Trong lab này, để đơn giản, chúng ta chỉ deploy Backend.
Ví dụ Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
📦 7. Chuẩn bị Container Registry
7.1 🤔 Tại sao cần Registry?
Kubernetes không lấy Docker Image trực tiếp từ GitHub source code.
Nó cần một nơi để pull image:
GitHub Actions
│
│ docker push
▼
Container Registry
│
│ docker pull
▼
Kubernetes
Ví dụ Registry:
GitHub Container Registry
Docker Hub
Amazon ECR
Google Artifact Registry
Azure Container Registry
Trong lab này sử dụng GitHub Container Registry — GHCR.
Image có thể có dạng:
ghcr.io/<github-user>/todo-backend:latest
Tốt hơn trong production:
ghcr.io/<github-user>/todo-backend:git-abc1234
vì tag gắn với Git commit giúp biết chính xác image được build từ commit nào.
🔐 8. GitHub Actions Login vào GHCR
GitHub Actions có thể sử dụng:
GITHUB_TOKEN
để authenticate với GitHub Container Registry.
Tạo file:
.github/workflows/ci.yml
Nội dung cơ bản:
name: CI
on:
push:
branches:
- main
permissions:
contents: write
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Image
run: |
docker build \
-t ghcr.io/${{ github.repository_owner }}/todo-backend:${{ github.sha }} \
./backend
- name: Push Image
run: |
docker push \
ghcr.io/${{ github.repository_owner }}/todo-backend:${{ github.sha }}
▶️ 9. Do — Push Code để chạy CI
Commit và push:
git add .
git commit -m "add ci pipeline"
git push origin main
Sau đó mở:
GitHub
→ Repository
→ Actions
Bạn sẽ thấy:
CI
├── Checkout
├── Login to GHCR
├── Build Image
└── Push Image
Nếu thành công:
✓ Checkout
✓ Login to GHCR
✓ Build Image
✓ Push Image
👀 10. See Result — Kiểm tra Docker Image
Vào:
GitHub
→ Packages
Bạn sẽ thấy:
todo-backend
với tag tương ứng với Git commit:
a8f3c91
Image:
ghcr.io/<github-user>/todo-backend:a8f3c91
🧠 11. Hiểu tại sao dùng Git SHA làm Image Tag
Một người mới thường dùng:
latest
Ví dụ:
image: todo-backend:latest
Vấn đề là:
latest
│
├── hôm nay → v1
│
└── ngày mai → v2
Nhìn vào:
latest
chúng ta không biết chính xác image nào đang chạy.
Git SHA tốt hơn:
todo-backend:a8f3c91
Ta có thể truy ngược:
Image
↓
Git SHA
↓
Commit
↓
Source Code
Đây là một nguyên tắc quan trọng trong production:
Một deployment nên có khả năng truy ngược về chính xác source code đã tạo ra nó.
📁 12. Tạo GitOps Repository
Tạo repository thứ hai:
todo-app-gitops
Cấu trúc:
todo-app-gitops/
│
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
│
└── overlays/
└── dev/
├── kustomization.yaml
└── deployment-patch.yaml
Trong lab này chúng ta có thể bắt đầu đơn giản hơn:
todo-app-gitops/
└── deployment.yaml
Ví dụ:
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-backend
namespace: todo-app
spec:
replicas: 2
selector:
matchLabels:
app: todo-backend
template:
metadata:
labels:
app: todo-backend
spec:
containers:
- name: todo-backend
image: ghcr.io/<github-user>/todo-backend:INITIAL_TAG
ports:
- containerPort: 3000
🔗 13. Kết nối GitOps Repository với ArgoCD
Nếu Application đã được tạo ở các lab trước, chỉ cần đảm bảo ArgoCD đang watch repository:
GitHub
│
│
▼
todo-app-gitops
│
▼
ArgoCD
│
▼
todo-app namespace
Ví dụ Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: todo-app
spec:
project: default
source:
repoURL: https://github.com/<github-user>/todo-app-gitops.git
targetRevision: main
path: .
destination:
server: https://kubernetes.default.svc
namespace: todo-app
syncPolicy:
automated:
prune: true
selfHeal: true
🔄 14. Bây giờ đến phần quan trọng — Update GitOps Repository
CI hiện tại đã làm được:
Code
↓
Build
↓
Docker Image
↓
GHCR
Nhưng ArgoCD vẫn đang sử dụng:
todo-backend:INITIAL_TAG
Chúng ta cần tự động thay đổi:
image: ghcr.io/user/todo-backend:INITIAL_TAG
thành:
image: ghcr.io/user/todo-backend:a8f3c91
Sau đó:
git commit
git push
vào GitOps Repository.
🔑 15. Tạo GitHub Token cho GitOps Repository
CI cần quyền push vào GitOps Repository.
Trong production, bạn nên dùng:
- GitHub App
- Fine-grained Personal Access Token
- hoặc một cơ chế authentication riêng dành cho automation.
Để học lab, có thể sử dụng GitHub Personal Access Token có quyền phù hợp.
Tạo secret trong Application Repository:
Settings
→ Secrets and variables
→ Actions
→ New repository secret
Ví dụ:
Name:
GITOPS_TOKEN
Không commit token vào source code.
❌ Không làm:
password: ghp_xxxxxxxxx
✅ Làm:
password: ${{ secrets.GITOPS_TOKEN }}
📝 16. Cập nhật GitOps bằng GitHub Actions
Thêm job:
update-gitops:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout GitOps Repository
uses: actions/checkout@v4
with:
repository: <github-user>/todo-app-gitops
token: ${{ secrets.GITOPS_TOKEN }}
- name: Update Image
run: |
sed -i "s|todo-backend:.*|todo-backend:${{ github.sha }}|" deployment.yaml
- name: Commit Changes
run: |
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add deployment.yaml
git commit -m "update todo-backend image to ${{ github.sha }}" || exit 0
git push
Sau khi chạy:
Application Repo
│
▼
GitHub Actions
│
├── Build
├── Push Image
│
└── Update GitOps
│
▼
GitOps Repository
🚀 17. Hoàn thiện CI/CD Pipeline
Bây giờ pipeline đầy đủ sẽ là:
name: CI/CD
on:
push:
branches:
- main
permissions:
contents: write
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Image
run: |
docker build \
-t ghcr.io/${{ github.repository_owner }}/todo-backend:${{ github.sha }} \
./backend
- name: Push Image
run: |
docker push \
ghcr.io/${{ github.repository_owner }}/todo-backend:${{ github.sha }}
update-gitops:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout GitOps Repository
uses: actions/checkout@v4
with:
repository: <github-user>/todo-app-gitops
token: ${{ secrets.GITOPS_TOKEN }}
- name: Update Image
run: |
sed -i \
"s|todo-backend:.*|todo-backend:${{ github.sha }}|" \
deployment.yaml
- name: Commit Changes
run: |
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add deployment.yaml
git commit \
-m "update todo-backend image to ${{ github.sha }}" \
|| exit 0
git push
💡 Lưu ý: Đoạn
sedtrên chỉ phù hợp với cấu trúc YAML đơn giản của lab. Trong project thực tế, nên dùng Kustomize, Helm hoặc một công cụ chuyên dụng để update image tag an toàn hơn.
🧪 18. Do — Thực hiện Deployment End-to-End
Bây giờ hãy thay đổi một dòng code trong Backend.
Ví dụ:
console.log("Todo Backend v2");
Commit:
git add .
git commit -m "update backend"
git push origin main
GitHub Actions bắt đầu chạy.
👀 19. See Result — Quan sát Pipeline
Bước 1 — CI
GitHub Actions:
✓ Checkout
✓ Build Image
✓ Push Image
Bước 2 — GitOps Repository
GitOps Repository xuất hiện commit mới:
update todo-backend image to a8f3c91
Manifest:
image: ghcr.io/user/todo-backend:a8f3c91
Bước 3 — ArgoCD
ArgoCD phát hiện Git thay đổi:
Git
│
│ new commit
▼
ArgoCD
│
│ Sync
▼
Kubernetes
Application chuyển:
OutOfSync
sang:
Synced
Healthy
Bước 4 — Kubernetes
Kiểm tra:
kubectl get pods -n todo-app
Ví dụ:
NAME READY STATUS
todo-backend-7c9d8f7f6d-x2abc 1/1 Running
todo-backend-7c9d8f7f6d-k9xyz 1/1 Running
Kiểm tra Image:
kubectl get deployment todo-backend \
-n todo-app \
-o jsonpath='{.spec.template.spec.containers[0].image}'
Kết quả:
ghcr.io/user/todo-backend:a8f3c91
🎉 Bạn vừa hoàn thành một GitOps CI/CD Pipeline.
🔍 20. Hiểu toàn bộ Flow
Hãy nhìn lại toàn bộ quá trình:
Developer
│
│ git push
▼
┌─────────────────┐
│ Application Repo│
└────────┬────────┘
│
▼
┌─────────────────┐
│ GitHub Actions │
│ │
│ Test │
│ Build │
│ Push Image │
└────────┬────────┘
│
│ update image tag
▼
┌─────────────────┐
│ GitOps Repo │
│ │
│ image: abc123 │
└────────┬────────┘
│
│ watch
▼
┌─────────────────┐
│ ArgoCD │
└────────┬────────┘
│
│ Sync
▼
┌─────────────────┐
│ Kubernetes │
│ │
│ Todo Backend │
└─────────────────┘
Có thể nhớ đơn giản:
CI → tạo Artifact
Git → lưu Desired State
ArgoCD → triển khai Desired State
K8s → chạy Application
🧠 21. Tại sao kiến trúc này tốt hơn?
21.1 🔐 CI không cần quyền truy cập trực tiếp Kubernetes
Trong mô hình truyền thống:
GitHub Actions
│
│ Kubernetes credentials
▼
Kubernetes
CI cần quyền deploy vào cluster.
Nếu GitHub Actions bị compromise, attacker có thể có quyền truy cập Kubernetes.
Với GitOps:
GitHub Actions
│
│ Git credentials
▼
GitOps Repository
│
▼
ArgoCD
│
▼
Kubernetes
ArgoCD là thành phần chịu trách nhiệm deploy.
CI không nhất thiết phải có:
kubectl
KUBECONFIG
Kubernetes Admin Token
Đây là một lợi ích bảo mật quan trọng.
21.2 📜 Git trở thành Audit Log
Giả sử Production đang chạy:
v1.5.0
Một developer deploy:
v1.6.0
GitOps Repository sẽ có:
commit abc123
update image to v1.6.0
Bạn có thể biết:
Ai thay đổi?
Khi nào?
Thay đổi cái gì?
Deploy version nào?
Git trở thành một phần của audit trail.
21.3 ↩️ Rollback dễ hơn
Giả sử:
v1.5.0 → OK
v1.6.0 → BUG
GitOps Repository:
Commit A → v1.5.0
Commit B → v1.6.0
Rollback về commit A:
Git
↓
ArgoCD
↓
Kubernetes
↓
v1.5.0
Thay vì phải nhớ:
kubectl set image ...
hoặc tìm lại image thủ công.
⚠️ 22. Những vấn đề thực tế bạn có thể gặp
22.1 ❌ Dùng latest
Không nên:
image: todo-backend:latest
Nên:
image: todo-backend:a8f3c91
hoặc một version immutable:
v1.4.2
Mục tiêu là:
Cùng một tag phải luôn trỏ đến cùng một artifact.
22.2 ❌ CI tự sửa Production quá nhanh
Ví dụ:
Developer push
↓
Build
↓
Deploy Production
Điều này có thể nguy hiểm.
Production thường nên có flow:
Developer
↓
CI
↓
Dev
↓
Test
↓
Staging
↓
Approval
↓
Production
GitOps không có nghĩa là:
"Push code là Production chạy ngay."
GitOps chỉ nói rằng:
Git là nơi định nghĩa trạng thái mong muốn của hệ thống.
Cách promotion giữa các environment là quyết định của team.
🌎 23. Production Pattern — Nhiều Environment
Khi project lớn hơn, GitOps Repository có thể:
todo-app-gitops/
│
├── base/
│
└── overlays/
├── dev/
│ └── kustomization.yaml
│
├── staging/
│ └── kustomization.yaml
│
└── production/
└── kustomization.yaml
Flow:
Code
│
▼
CI
│
▼
Image
│
▼
Dev
│
│ test OK
▼
Staging
│
│ approval
▼
Production
Đây là mô hình chúng ta sẽ đi sâu hơn ở các lab về Environment Management và Progressive Delivery.
🧪 24. Bài tập thực hành
Exercise 1 — Kiểm tra CI
- [ ] Push một thay đổi vào Application Repository.
- [ ] Kiểm tra GitHub Actions.
- [ ] Xác nhận Docker Image được build.
- [ ] Xác nhận Image được push lên GHCR.
Exercise 2 — Kiểm tra GitOps
- [ ] Kiểm tra GitOps Repository.
- [ ] Xác nhận GitHub Actions tạo commit mới.
- [ ] Kiểm tra Image Tag đã thay đổi.
- [ ] Xác nhận commit chứa đúng Git SHA.
Exercise 3 — Kiểm tra ArgoCD
- [ ] Mở ArgoCD.
- [ ] Kiểm tra Application.
- [ ] Quan sát trạng thái
OutOfSync. - [ ] Kiểm tra quá trình
Sync. - [ ] Xác nhận trạng thái
Synced. - [ ] Xác nhận trạng thái
Healthy.
Exercise 4 — Kiểm tra Kubernetes
Chạy:
kubectl get pods -n todo-app
Sau đó:
kubectl describe deployment todo-backend \
-n todo-app
Tìm:
Image:
và xác nhận Kubernetes đang chạy Image mới.
🧩 25. Challenge — Tự xây dựng Pipeline hoàn chỉnh
Bây giờ hãy thử tự xây dựng lại pipeline mà không copy phần YAML phía trên.
Yêu cầu:
Developer
↓
GitHub
↓
GitHub Actions
↓
Run Test
↓
Build Docker Image
↓
Push GHCR
↓
Update GitOps Repo
↓
ArgoCD
↓
Kubernetes
Pipeline phải đảm bảo:
✓ Test trước khi Build
✓ Build Image
✓ Image có immutable tag
✓ Push Image
✓ Update GitOps Repository
✓ ArgoCD tự Sync
✓ Kubernetes chạy Image mới
🧠 26. Những điều cần nhớ
Nếu chỉ nhớ 5 điều sau lab này, hãy nhớ:
1️⃣ CI tạo Artifact
Source Code
↓
CI
↓
Docker Image
2️⃣ GitOps Repository lưu Desired State
image: todo-backend:a8f3c91
3️⃣ ArgoCD theo dõi Git
Git
↓
ArgoCD
4️⃣ ArgoCD deploy Kubernetes
ArgoCD
↓
Kubernetes
5️⃣ GitHub Actions không cần kubectl apply
❌ CI → kubectl → Kubernetes
✅ CI → Git → ArgoCD → Kubernetes
🎯 27. Kết quả đạt được
Trước lab:
Developer
↓
Build thủ công
↓
Push Image
↓
Sửa YAML
↓
Git push
↓
ArgoCD
Sau lab:
Developer
│
│ git push
▼
GitHub Actions
│
├── Test
├── Build
├── Push Image
│
▼
GitOps Repository
│
▼
ArgoCD
│
▼
Kubernetes
Bạn đã ghép được CI + GitOps CD thành một pipeline hoàn chỉnh.
Ở các lab tiếp theo, chúng ta sẽ giải quyết những vấn đề thực tế hơn:
Lab 11
GitOps CI/CD
↓
Lab 12
Image Automation
↓
Lab 13
ArgoCD Notifications
↓
Lab 14
ArgoCD Troubleshooting
↓
Lab 15
ArgoCD Security & RBAC
↓
Lab 16
App of Apps
↓
Lab 17
ApplicationSet
🚀 Tư duy quan trọng nhất của Lab 11: CI chịu trách nhiệm tạo ra version mới của Application. GitOps + ArgoCD chịu trách nhiệm đưa version đó vào Kubernetes. Hai trách nhiệm này tách biệt giúp pipeline dễ kiểm soát, audit và mở rộng hơn trong môi trường Production.
All rights reserved