Chuyển đến nội dung chính

BÀI 31: CI/CD PIPELINE — BUILD, TEST, DEPLOY VỚI GITOPS

Xây dựng complete CI/CD pipeline: GitHub Actions build & test, container image build, vulnerability scanning, GitOps trigger, ArgoCD auto-deploy, và promotion workflow.

🔒 DevSecOps — Bài 31 BÀI 31: CI/CD PIPELINE — BUILD, TEST, DEPLOY VỚI GITOPS

Deploy Microservices On-Premises với Kubernetes HA

Phần 7: GitOps với ArgoCD, Helm & Vault

xdev.asia

🎯 MỤC TIÊU BÀI HỌC

  • ✅ Thiết kế CI/CD pipeline cho microservices + GitOps
  • ✅ GitHub Actions: build, test, lint, scan
  • ✅ Container image build với multi-stage Dockerfile
  • ✅ Image vulnerability scanning (Trivy)
  • ✅ GitOps trigger: auto-update image tag in Git
  • ✅ Environment promotion workflow (dev → staging → prod)

PHẦN 1: CI/CD + GITOPS PIPELINE DESIGN


Complete CI/CD + GitOps Pipeline:

┌──────────────────────────────────────────────────────────┐
│                    CI PIPELINE (GitHub Actions)           │
│                                                           │
│  Developer ──► Git Push ──► Build ──► Test ──► Scan      │
│                                                  │        │
│                                            ┌─────▼─────┐ │
│                                            │ Build &    │ │
│                                            │ Push Image │ │
│                                            └─────┬─────┘ │
└──────────────────────────────────────────────────┼────────┘
                                                   │
                                          ┌────────▼────────┐
                                          │ Update image    │
                                          │ tag in GitOps   │
                                          │ repo (PR/commit)│
                                          └────────┬────────┘
                                                   │
┌──────────────────────────────────────────────────┼────────┐
│                    CD PIPELINE (ArgoCD)           │        │
│                                                  ▼        │
│  GitOps Repo ◄── ArgoCD watches ──► K8s Cluster          │
│  (manifests)     (auto-sync)        (deploy)              │
└───────────────────────────────────────────────────────────┘

Two Repositories:
1. App Repo: source code, Dockerfile, CI pipeline
2. GitOps Repo: K8s manifests, Helm values, ArgoCD config

PHẦN 2: MULTI-STAGE DOCKERFILE

# Dockerfile (Go microservice example):

# Stage 1: Build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# Stage 2: Runtime
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
COPY --from=builder /app/configs /configs

USER nonroot:nonroot
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s CMD ["/server", "healthcheck"]
ENTRYPOINT ["/server"]
# Dockerfile (Node.js microservice):

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2: Runtime
FROM node:20-alpine
RUN addgroup -g 1001 -S nodejs && adduser -S appuser -u 1001
WORKDIR /app
COPY --from=builder --chown=appuser:nodejs /app/dist ./dist
COPY --from=builder --chown=appuser:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:nodejs /app/package.json ./

USER appuser
EXPOSE 8080
CMD ["node", "dist/index.js"]

PHẦN 3: GITHUB ACTIONS CI PIPELINE

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

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  REGISTRY: registry.myapp.com
  IMAGE: order-service

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Run Tests
        run: |
          go test -v -race -coverprofile=coverage.out ./...
          go tool cover -func=coverage.out

      - name: Lint
        uses: golangci/golangci-lint-action@v6
        with:
          version: latest

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    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.REGISTRY }}/${{ env.IMAGE }}
          tags: |
            type=sha,prefix=
            type=semver,pattern={{version}}

      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  scan:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Trivy Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ needs.build-and-push.outputs.image-tag }}"
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

  update-gitops:
    needs: [build-and-push, scan]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout GitOps repo
        uses: actions/checkout@v4
        with:
          repository: myorg/k8s-manifests
          token: ${{ secrets.GITOPS_TOKEN }}
          path: gitops

      - name: Update image tag
        run: |
          cd gitops
          NEW_TAG="${{ needs.build-and-push.outputs.image-tag }}"
          
          # Update Helm values:
          yq eval ".image.tag = \"$NEW_TAG\"" -i \
            apps/order-service/values-staging.yaml
          
          # Commit and push:
          git config user.name "github-actions"
          git config user.email "[email protected]"
          git add .
          git commit -m "chore: update order-service to $NEW_TAG"
          git push

PHẦN 4: ENVIRONMENT PROMOTION


Promotion Workflow:

  main branch push
       │
       ▼
  ┌────────────┐
  │ CI: Build  │
  │ Test, Scan │
  └─────┬──────┘
        │ Auto-update staging values
        ▼
  ┌────────────┐     ArgoCD
  │  STAGING   │ ◄── auto-sync
  │  (auto)    │
  └─────┬──────┘
        │ Manual PR: promote to production
        ▼
  ┌────────────┐     ArgoCD
  │ PRODUCTION │ ◄── auto-sync (after PR merge)
  │ (approval) │
  └────────────┘
# Promotion PR workflow:
# .github/workflows/promote.yaml:
name: Promote to Production

on:
  workflow_dispatch:
    inputs:
      service:
        description: 'Service to promote'
        required: true
        type: choice
        options:
          - order-service
          - payment-service
          - user-service
      tag:
        description: 'Image tag to promote'
        required: true

jobs:
  promote:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Update production values
        run: |
          yq eval ".image.tag = \"${{ inputs.tag }}\"" -i \
            apps/${{ inputs.service }}/values-production.yaml

      - name: Create PR
        uses: peter-evans/create-pull-request@v6
        with:
          title: "🚀 Promote ${{ inputs.service }} ${{ inputs.tag }} to production"
          body: |
            Promoting **${{ inputs.service }}** version `${{ inputs.tag }}` to production.
            
            ## Checklist
            - [ ] Staging tests passed
            - [ ] Performance acceptable
            - [ ] Rollback plan ready
          branch: "promote/${{ inputs.service }}-${{ inputs.tag }}"
          reviewers: "platform-team"

PHẦN 5: PRIVATE REGISTRY (HARBOR)

# Install Harbor:
helm repo add harbor https://helm.goharbor.io
helm repo update

helm install harbor harbor/harbor \
  --namespace harbor \
  --create-namespace \
  --set expose.type=ingress \
  --set expose.ingress.hosts.core=registry.myapp.com \
  --set externalURL=https://registry.myapp.com \
  --set persistence.persistentVolumeClaim.registry.storageClass=ceph-block \
  --set persistence.persistentVolumeClaim.registry.size=100Gi \
  --set trivy.enabled=true

💡 KEY TAKEAWAYS

  1. Two repos: App repo (CI) + GitOps repo (CD) separation
  2. CI pipeline: Test → Build → Scan → Push image
  3. GitOps trigger: CI updates image tag in GitOps repo
  4. ArgoCD auto-sync: Detects tag change → deploys to K8s
  5. Promotion: Auto to staging, PR-based to production
  6. Harbor: Private registry with built-in vulnerability scanning

🎯 BÀI TẬP

Bài tập 1: Complete Pipeline

  • Setup GitHub Actions CI for sample Go service
  • Build + push to Harbor, scan with Trivy
  • Auto-update GitOps repo, ArgoCD deploys

Bài tập 2: Promotion Workflow

  • Implement staging → production promotion PR
  • Add required reviewers and checks
  • Test full cycle: code change → production deploy

📚 BÀI TIẾP THEO

Trong Bài 32: Prometheus Stack — Monitoring Infrastructure, chúng ta sẽ setup observability stack với Prometheus, Grafana, và Alertmanager.