Skip to content

Designing the Ultimate CI/CD Pipeline for Kubernetes

🏗️ Góc nhìn DevOps Architect - HPN

Module này được viết với tư duy kiến trúc hệ thống và tích hợp. Mục tiêu: Thiết kế pipeline CI/CD hoàn chỉnh với sự phân tách rõ ràng giữa CI và CD.

🎯 Triết Lý: "Separation of Concerns"

Nguyên tắc vàng

text
╔═══════════════════════════════════════════════════════════════════════╗
║   CI (Continuous Integration)     ≠     CD (Continuous Delivery)      ║
╠═══════════════════════════════════════════════════════════════════════╣
║   Jenkins / GitHub Actions               ArgoCD / Flux                ║
║   ─────────────────────────              ─────────────────            ║
║   ✅ Run Tests                           ✅ Watch Git Manifests       ║
║   ✅ Build Docker Image                  ✅ Sync to Cluster           ║
║   ✅ Push to Registry                    ✅ Self-Healing              ║
║   ─────────────────────────              ─────────────────            ║
║   ❌ KHÔNG CHẠM VÀO CLUSTER              ❌ KHÔNG BUILD CODE          ║
╚═══════════════════════════════════════════════════════════════════════╝

🚫 ANTI-PATTERN: "CI làm tất cả"

bash
# ❌ SAI: CI pipeline trực tiếp deploy vào cluster
stages:
  - build
  - test
  - deploy  # <- NGUY HIỂM!

deploy:
  script:
    - kubectl apply -f deployment.yaml  # CI cần cluster credentials!

Vấn đề:

  • CI server cần lưu kubeconfigSecurity risk
  • Nếu CI bị hack → Attacker có toàn quyền cluster
  • Không có self-healing khi drift xảy ra

🔄 The Complete Flow

End-to-End CI/CD Pipeline

Giải thích từng bước

BướcComponentHành độngOutput
1Developergit push code mớiCode in App Repo
2CI (Test)Run unit tests, linting✅ Pass / ❌ Fail
3CI (Build)docker build -t myapp:v2Docker Image
4CI (Push)docker push registry/myapp:v2Image in Registry
5BridgeUpdate imageTag in Manifest RepoGit Commit
6ArgoCDDetect Git changeOutOfSync status
7ArgoCDkubectl apply (internal)Pod recreated

🌉 The Bridge: Kết nối CI và CD

Vấn đề: Làm sao CI "nói chuyện" với ArgoCD?

ArgoCD chỉ watch Git Repository. CI không push trực tiếp vào cluster. Vậy làm sao để trigger deployment?

Giải pháp: CI cập nhật Git Manifest Repository

Cấu trúc 2 Git Repositories

📂 Separation of Repositories

text
📁 github.com/hpn/my-app          # Application Code
├── src/
├── tests/
├── Dockerfile
└── .github/workflows/ci.yaml     # CI Pipeline

📁 github.com/hpn/k8s-manifests   # Infrastructure as Code
├── apps/
│   └── my-app/
│       ├── base/
│       │   └── deployment.yaml
│       └── overlays/
│           ├── dev/values.yaml
│           ├── staging/values.yaml
│           └── production/values.yaml
└── argocd-apps/
    └── my-app.yaml               # ArgoCD Application CRD

GitHub Actions: CI Pipeline mẫu

yaml
# .github/workflows/ci.yaml
name: CI Pipeline

on:
  push:
    branches: [main, develop]

env:
  IMAGE_NAME: ghcr.io/hpn/my-app
  MANIFEST_REPO: hpn/k8s-manifests

jobs:
  # ============================================
  # STAGE 1: TEST
  # ============================================
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run Unit Tests
        run: |
          npm install
          npm run test:coverage
      
      - name: Run Linting
        run: npm run lint

  # ============================================
  # STAGE 2: BUILD & PUSH
  # ============================================
  build:
    needs: test
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      
      - name: Docker Meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch
      
      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ steps.meta.outputs.tags }}

  # ============================================
  # STAGE 3: UPDATE MANIFESTS (The Bridge!)
  # ============================================
  update-manifests:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Manifest Repo
        uses: actions/checkout@v4
        with:
          repository: ${{ env.MANIFEST_REPO }}
          token: ${{ secrets.MANIFEST_PAT }}  # Personal Access Token
          path: manifests
      
      - name: Update Image Tag
        run: |
          cd manifests
          # Xác định environment dựa trên branch
          if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
            ENV_PATH="apps/my-app/overlays/production"
          else
            ENV_PATH="apps/my-app/overlays/dev"
          fi
          
          # ⚡ THE MAGIC COMMAND ⚡
          sed -i "s|tag: .*|tag: ${{ needs.build.outputs.image_tag }}|g" \
            "${ENV_PATH}/values.yaml"
          
          # Commit và push
          git config user.name "CI Bot"
          git config user.email "ci@hpn.dev"
          git add .
          git commit -m "🚀 Deploy: my-app:${{ needs.build.outputs.image_tag }}"
          git push

      - name: Notify Success
        run: |
          echo "✅ Manifest updated! ArgoCD will sync automatically."
          echo "📦 Image: ${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}"

Kỹ thuật cập nhật Manifest

Phương phápCommandUse Case
sedsed -i 's/tag: v1/tag: v2/g' values.yamlSimple, quick
yqyq -i '.image.tag = "v2"' values.yamlYAML-aware, safer
Kustomizekustomize edit set image myapp=myapp:v2Kustomize projects
HelmUpdate Chart.yaml version + values.yamlHelm charts

💡 Best Practice: Sử dụng yq

bash
# yq giữ nguyên cấu trúc YAML, không phá format
yq -i '.image.tag = "sha-abc123"' apps/my-app/overlays/dev/values.yaml

🌿 Environment Promotion Strategy

Branch-based Promotion

Quy trình Promotion

text
┌─────────────────────────────────────────────────────────────────────┐
│                    ENVIRONMENT PROMOTION FLOW                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   1. Developer pushes to `develop`                                  │
│      └── CI builds → Updates dev/values.yaml                        │
│      └── ArgoCD syncs to DEV cluster                               │
│                                                                      │
│   2. QA approves → PR from `develop` to `staging`                  │
│      └── Merge triggers CI → Updates staging/values.yaml           │
│      └── ArgoCD syncs to STAGING cluster                           │
│                                                                      │
│   3. Release Manager approves → PR from `staging` to `main`        │
│      └── Merge triggers CI → Updates production/values.yaml        │
│      └── ArgoCD syncs to PRODUCTION cluster                        │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

ArgoCD ApplicationSet cho Multi-Environment

yaml
# argocd-apps/my-app-appset.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - env: dev
            branch: develop
            cluster: https://dev-cluster.hpn.io
          - env: staging
            branch: staging
            cluster: https://staging-cluster.hpn.io
          - env: production
            branch: main
            cluster: https://prod-cluster.hpn.io
  template:
    metadata:
      name: 'my-app-{{env}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/hpn/k8s-manifests.git
        targetRevision: '{{branch}}'
        path: 'apps/my-app/overlays/{{env}}'
      destination:
        server: '{{cluster}}'
        namespace: 'my-app-{{env}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Promotion với Git Tags

🏷️ Tag-based Promotion (Alternative)

bash
# Promote từ dev lên staging
git tag staging-v1.2.3
git push origin staging-v1.2.3

# Promote từ staging lên production
git tag release-v1.2.3
git push origin release-v1.2.3

ArgoCD Application config:

yaml
source:
  targetRevision: "release-*"  # Chỉ sync production releases

🛡️ Security Considerations

Secrets Management

CI Secrets Best Practices

SecretStorageAccess
Docker RegistryGitHub SecretsCI only
Manifest Repo PATGitHub SecretsCI only
Cluster CredentialsKHÔNG CÓ TRONG CIArgoCD only

📊 Tổng Kết

The Golden Architecture

PrincipleImplementation
Separation of ConcernsCI ≠ CD, each has its domain
Git as Single Source of TruthAll state in Git repositories
No Direct Cluster Access from CICI only touches Git + Registry
Pull-based DeploymentArgoCD pulls from Git
Environment Promotion via GitBranch/Tag based promotion

⚠️ KEY TAKEAWAYS

  1. CI xây dựng, CD triển khai - Không nhầm lẫn trách nhiệm
  2. Git Manifest Repo là cầu nối - CI cập nhật, ArgoCD watch
  3. Không bao giờ lưu cluster credentials trong CI - ArgoCD xử lý việc đó
  4. Environment promotion qua Git branches - Review, approve, merge
  5. Self-healing đảm bảo consistency - Drift được tự động fix

🔗 Liên kết