0

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 apply và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.0 và 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

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 sed trê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 ManagementProgressive 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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí