Viết ra được 21 microservices bằng Go với kiến trúc chuẩn mực mới chỉ là một nửa cuộc chiến. Nếu quy trình deploy (triển khai) của bạn vẫn phụ thuộc vào một kỹ sư gõ lệnh kubectl apply từ laptop cá nhân của anh ta vào một chiều thứ Sáu, bạn chưa hề xây dựng một nền tảng enterprise — bạn vừa chế tạo ra một quả bom nổ chậm.

Khi thiết kế hệ sinh thái thương mại điện tử composable này, chúng tôi đã đặt ra một quy tắc kiến trúc thép ngay từ ngày đầu tiên: Tuyệt đối không có con người nào được phép chạm trực tiếp vào cluster production. Mọi thứ phải chảy qua Git. ArgoCD sẽ là người ép buộc điều đó.

Mô hình Tư duy GitOps: Git là Nguồn chân lý duy nhất (Source of Truth)

Nguyên tắc cốt lõi: một repository Git chính là trạng thái mong muốn mang tính khai báo (declarative desired state) của toàn bộ hạ tầng của bạn. ArgoCD là một tác tử thi hành (enforcement agent), nó liên tục đối chiếu và đồng bộ (reconcile) trạng thái của cluster sao cho khớp với trạng thái trên Git.

Điều này khác biệt về bản chất so với mô hình push của CI/CD truyền thống:

graph LR
    subgraph "CI Pipeline — Code"
        DEV["Developer"] -- "git push" --> APP["App Repository"]
        APP -- "GitHub Actions" --> DOCKER[("Container Registry")]
    end

    subgraph "CD Pipeline — GitOps"
        DOCKER -- "Cập nhật image tag vào" --> MAN["Manifest Repository"]
        ARGO["ArgoCD Controller"] -- "Polls mỗi 3 phút" --> MAN
    end

    subgraph "Kubernetes Cluster"
        ARGO -- "Áp dụng diff, rolling restart" --> K8S["K8s Nodes"]
    end
  1. CI: Kỹ sư push code Go → GitHub Actions chạy test, build binary, push một Docker image lên registry, sau đó mở một Pull Request (PR) sang một Manifest Repository riêng biệt để cập nhật image tag.
  2. CD: ArgoCD liên tục theo dõi (watch) Manifest Repo đó. Khi PR được merge, ArgoCD phát hiện ra sự sai lệch (drift) giữa trạng thái mong muốn (trên Git) và trạng thái thực tế (dưới cluster), và tự động khắc phục sự sai lệch đó.

Không ai chạm vào kubectl. Không ai cần thông tin đăng nhập của cluster (cluster credentials) trong quá trình CI. Cluster hoạt động theo cơ chế kéo (pull-based), không phải cơ chế đẩy (push-based).

Cấu trúc Repository

Chúng tôi sử dụng 2 repository riêng biệt — một pattern được gọi là split-repo GitOps:

manifest-repo/
├── apps/
│   ├── order-service/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── dev/
│   │       │   └── kustomization.yaml
│   │       ├── staging/
│   │       │   └── kustomization.yaml
│   │       └── prod/
│   │           ├── kustomization.yaml
│   │           └── hpa.yaml
│   └── checkout-service/
│       └── ...
└── argocd/
    ├── root-app.yaml          ← App-of-Apps root
    ├── order-service-prod.yaml
    └── checkout-service-prod.yaml

Việc giữ các file manifests ở một repo riêng biệt so với repo chứa code application tạo ra một dấu vết kiểm toán (audit trail) cực kỳ rõ ràng: mỗi bản deploy là một Git commit với timestamp, tác giả, và image tag rõ ràng. Việc Rollback (khôi phục bản cũ) chỉ đơn giản là gõ lệnh git revert.

Đối tượng Application CRD của ArgoCD

Mỗi service sẽ được cấp một đối tượng Application CRD để nói cho ArgoCD biết chính xác nó phải theo dõi đường dẫn nào trong manifest repo, và deploy nó vào đâu:

# argocd/order-service-prod.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-production
  namespace: argocd
  labels:
    environment: production
    team: commerce
spec:
  project: ecommerce-prod

  source:
    repoURL: https://github.com/your-org/manifest-repo.git
    targetRevision: main
    path: apps/order-service/overlays/prod  # trỏ thẳng vào Kustomize overlay của môi trường prod

  destination:
    server: https://kubernetes.default.svc
    namespace: production

  syncPolicy:
    automated:
      prune: true      # tự động xóa các resource đã bị xóa trên Git
      selfHeal: true   # tự động lật ngược lại các lệnh kubectl sửa tay của con người
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true  # tránh lỗi giới hạn độ dài annotation trên các CRD lớn
    retry:
      limit: 3
      backoff:
        duration: 30s
        factor: 2
        maxDuration: 5m

  # Health check: ArgoCD sẽ không đánh dấu là Healthy cho tới khi tất cả pod chuyển sang trạng thái Running
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas  # bỏ qua sự sai lệch về số lượng pod do HPA quản lý

selfHeal: true là một cấu hình sống còn cho production. Nếu một SRE lỡ tay gõ lệnh vá (patch) một Deployment trực tiếp trên production (vì bất kỳ lý do gì), ArgoCD sẽ ghi đè và lật ngược thao tác đó trong vòng chưa tới 3 phút. Git là nguồn chân lý duy nhất — tuyệt đối không có ngoại lệ.

Thuần phục mớ hỗn độn YAML bằng Kustomize

Việc quản lý file manifests cho 21 services trên 3 môi trường (dev, staging, prod) sẽ đẻ ra hơn 60+ file YAML. Việc copy-paste thủ công sẽ tạo ra sự trôi dạt cấu hình (configuration drift). Kustomize giải quyết triệt để chuyện này bằng mô hình base + overlay (nền tảng + các lớp phủ).

Base Manifest (Không phụ thuộc môi trường)

# apps/order-service/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 1  # sẽ được override tùy theo môi trường
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: registry.example.com/order-service:latest  # tag sẽ được override tùy theo môi trường
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 15
# apps/order-service/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml

Production Overlay (Chỉ vá những thứ khác biệt)

Một lớp phủ overlay không bao giờ được phép copy lại (duplicate) phần base — nó chỉ được quyền vá (patch) những thứ thay đổi tùy theo môi trường:

# apps/order-service/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base
  - hpa.yaml          # Chỉ ở prod mới có cấu hình Auto-scaling (HPA)

# Ghi đè image tag cho bản release cụ thể này
images:
  - name: registry.example.com/order-service
    newTag: "v1.47.2"  # được CI pipeline tự động update sau mỗi bản build

# Vá theo kiểu Strategic merge — chỉ cập nhật các field khác biệt so với thư mục base
patchesStrategicMerge:
  - |-
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: order-service
    spec:
      replicas: 4      # prod cần 4 replicas thay vì 1 như của dev
      template:
        spec:
          containers:
            - name: order-service
              resources:
                requests:
                  cpu: "500m"
                  memory: "512Mi"
                limits:
                  cpu: "2000m"
                  memory: "2Gi"
              env:
                - name: DB_HOST
                  valueFrom:
                    secretKeyRef:
                      name: order-service-prod-secrets
                      key: db_host
# apps/order-service/overlays/prod/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 4
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Tương tự, lớp phủ dev sử dụng newTag: "latest", replicas: 1, cấp ít RAM/CPU hơn, và không xài HPA. Lớp phủ staging sao chép cấu hình scale của prod nhưng trỏ vào secret của database staging. Cả 3 môi trường đều dùng chung một bộ khung logic deployment cốt lõi nằm ở thư mục base.

Gốc rễ của App-of-Apps

Với 21 services, việc tạo thủ công các đối tượng Application của ArgoCD từng cái một là bất khả thi. Chúng tôi sử dụng pattern App-of-Apps: tạo ra một Application gốc duy nhất có nhiệm vụ quản lý tất cả các đối tượng Application con lại:

# argocd/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ecommerce-platform
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/manifest-repo.git
    targetRevision: main
    path: argocd/  # ArgoCD theo dõi duy nhất thư mục này để tìm các file Application CRD
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Khi có một microservice mới được thêm vào nền tảng, quy trình chuẩn sẽ là:

  1. Thêm một file YAML Application mới vào thư mục argocd/
  2. Merge PR
  3. Đối tượng App-of-Apps gốc phát hiện ra file mới này và tự động tạo ra một Application con
  4. ArgoCD tự động cấp phát (provision) service mới lên cluster với 0% sự can thiệp thủ công

Câu chuyện Rollback: Chỉ cần git revert là đủ

Trước kỷ nguyên GitOps, việc rollback một bản deploy lỗi đồng nghĩa với việc cuống cuồng đào bới trong CI logs để tìm xem cái tag Docker cũ là gì, rồi sau đó gõ thủ công các lệnh kubectl rollout undo trong khi khách hàng đang ăn liên hoàn lỗi 500.

Với ArgoCD, quá trình cứu hộ thảm họa (disaster recovery) gói gọn trong 4 bước:

# 1. Định vị commit gây lỗi
git log --oneline manifests/apps/checkout-service/overlays/prod/kustomization.yaml

# 2. Hoàn tác (Revert) commit đó
git revert <bad-commit-hash> --no-edit

# 3. Đẩy code
git push origin main

# 4. ArgoCD phát hiện ra lệnh revert này trong vòng 3 phút và tự động rollback
# Không cần lệnh kubectl. Không cần chui vào cluster.

Lệnh revert này tạo ra một commit mới với đầy đủ tính năng kiểm toán (auditability): ai là người revert, revert lúc nào, và thay đổi nào vừa bị hoàn tác. Trạng thái của cluster quay trở về version ổn định gần nhất trong chưa tới 5 phút.

Đối với các service sinh tử (như Checkout, Payment), chúng tôi dùng thêm Argo Rollouts kết hợp với các cổng đánh giá sức khỏe dựa trên Prometheus — nếu tỷ lệ lỗi (error rate) hoặc độ trễ p99 vượt quá ngưỡng cho phép trong quá trình rollout, bản canary sẽ tự động bị hủy bỏ và luồng traffic sẽ tự động được dồn ngược lại bản ổn định cũ mà không cần đến con người can thiệp.

Tóm lược: Các Nguyên lý giúp Hệ thống hoạt động

Nguyên lýCách triển khai
Git là nguồn chân lý duy nhấtselfHeal: true trên mọi prod Applications
Không có sự trôi lệch giữa các môi trườngKustomize base + overlays — chỉ vá, không copy
Rollback là một thao tác Gitgit revert → ArgoCD tự động đồng bộ
Service mới tự động đăng kýGốc App-of-Apps theo dõi thư mục argocd/
Lỗi con người bị ngăn chặn triệt đểKhông một ai cần gõ kubectl hay thông tin đăng nhập trong CI

Chi phí đầu tư cho kiến trúc này sẽ mang lại quả ngọt ở ngay lần đầu tiên bạn gặp sự cố deploy trên production lúc 2h sáng. Thay vì cuống cuồng gõ lệnh kubectl trong hoảng loạn, bạn chỉ cần mở terminal, gõ 3 dòng lệnh git, và nhâm nhi cafe nhìn ArgoCD tự thân nó đi sửa chữa lỗi lầm trên cluster.


🤝 Kết nối với tôi

Bạn đang gặp phải những thách thức tương tự về kiến trúc hệ thống, mở rộng quy mô (scaling) hay dịch chuyển (migration)? Hãy kết nối với tôi trên LinkedIn, theo dõi GitHub của tôi, hoặc gửi một email để trao đổi nhé.