Giao diện
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
kubeconfig→ Security 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ước | Component | Hành động | Output |
|---|---|---|---|
| 1 | Developer | git push code mới | Code in App Repo |
| 2 | CI (Test) | Run unit tests, linting | ✅ Pass / ❌ Fail |
| 3 | CI (Build) | docker build -t myapp:v2 | Docker Image |
| 4 | CI (Push) | docker push registry/myapp:v2 | Image in Registry |
| 5 | Bridge | Update imageTag in Manifest Repo | Git Commit |
| 6 | ArgoCD | Detect Git change | OutOfSync status |
| 7 | ArgoCD | kubectl 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 CRDGitHub 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áp | Command | Use Case |
|---|---|---|
| sed | sed -i 's/tag: v1/tag: v2/g' values.yaml | Simple, quick |
| yq | yq -i '.image.tag = "v2"' values.yaml | YAML-aware, safer |
| Kustomize | kustomize edit set image myapp=myapp:v2 | Kustomize projects |
| Helm | Update Chart.yaml version + values.yaml | Helm 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: truePromotion 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.3ArgoCD Application config:
yaml
source:
targetRevision: "release-*" # Chỉ sync production releases🛡️ Security Considerations
Secrets Management
CI Secrets Best Practices
| Secret | Storage | Access |
|---|---|---|
| Docker Registry | GitHub Secrets | CI only |
| Manifest Repo PAT | GitHub Secrets | CI only |
| Cluster Credentials | KHÔNG CÓ TRONG CI | ArgoCD only |
📊 Tổng Kết
The Golden Architecture
| Principle | Implementation |
|---|---|
| Separation of Concerns | CI ≠ CD, each has its domain |
| Git as Single Source of Truth | All state in Git repositories |
| No Direct Cluster Access from CI | CI only touches Git + Registry |
| Pull-based Deployment | ArgoCD pulls from Git |
| Environment Promotion via Git | Branch/Tag based promotion |
⚠️ KEY TAKEAWAYS
- CI xây dựng, CD triển khai - Không nhầm lẫn trách nhiệm
- Git Manifest Repo là cầu nối - CI cập nhật, ArgoCD watch
- Không bao giờ lưu cluster credentials trong CI - ArgoCD xử lý việc đó
- Environment promotion qua Git branches - Review, approve, merge
- Self-healing đảm bảo consistency - Drift được tự động fix
🔗 Liên kết
- Trước đó: Module 13: GitOps với ArgoCD
- Tiếp theo: Challenge: GitOps Drift Scenario