+9

🧩🧠 GitOps: Git không chỉ để lưu code - Microservice Architecture P12

GitOps: Git không chỉ để lưu code

1. Buổi chiều mà cả cluster trở nên xa lạ

Hãy tưởng tượng cảnh này: một kỹ sư backend nhận được ticket báo lỗi production lúc 3 giờ chiều. Service payment-gateway đang timeout không xác định được nguyên nhân. Để xử lý nhanh, anh ta SSH vào bastion host, kết nối vào Kubernetes cluster, rồi thực hiện thủ công vài lệnh để tăng replica và thêm một environment variable mà anh ta tin là sẽ vá tạm thời vấn đề:

kubectl set env deployment/payment-gateway PAYMENT_TIMEOUT=30s -n production
kubectl scale deployment/payment-gateway --replicas=5 -n production

Timeout biến mất. Mọi người thở phào. Ticket được đóng lại.

Ba tuần sau, team deploy một phiên bản mới của payment-gateway. Helm chart trong Git vẫn là cấu hình cũ: PAYMENT_TIMEOUT=10s, replicas: 3. Pipeline CI/CD chạy, apply cấu hình từ Git lên cluster, đè lên toàn bộ những gì đã được sửa thủ công. Timeout quay trở lại. Hệ thống crash vào lúc 2 giờ sáng.

Điều tệ nhất không phải là hệ thống bị lỗi. Điều tệ nhất là không ai biết tại sao nó lại lỗi, bởi vì nhìn vào Git, mọi thứ trông hoàn toàn đúng. Nhìn vào cluster production, nó đang chạy một cấu hình không còn được ghi lại ở bất cứ đâu. Hai thực tế đã tách đôi ra — âm thầm, trong suốt ba tuần, mà không ai nhận ra.

Đây chính là Configuration Drift — và đó là lý do GitOps tồn tại.


2. Niềm tin phổ biến: "Thêm bước kubectl apply vào pipeline là xong GitOps"

Khi nghe đến GitOps, phản xạ đầu tiên của nhiều team là: "Ừ, chúng tôi đã có rồi. Workflow của chúng tôi là: merge vào main → GitHub Actions chạy → kubectl apply -f k8s/ → xong."

Lý luận này hợp lý hoàn toàn bởi vì nó đạt được một điều quan trọng: Git là nguồn kiểm soát thay đổi. Mọi thay đổi phải qua Pull Request, qua review, qua merge. Đó là tiến bộ rõ ràng so với tình trạng mỗi người SSH vào server và tự làm theo ý mình.

Và hầu hết các tài liệu kỹ thuật cũng mô tả GitOps theo hướng đó: "Dùng Git để kiểm soát deployment." Vậy đâu là vấn đề?


3. Tại sao mô hình Push-based không phải là GitOps thực sự

Sự khác biệt nằm ở một câu hỏi đơn giản: Ai là người chịu trách nhiệm đảm bảo cluster luôn khớp với Git?

Trong mô hình push-based — tức là GitHub Actions chạy kubectl apply — câu trả lời là: pipeline CI/CD, nhưng chỉ khi có trigger. Khi một commit được merge, pipeline chạy và đẩy thay đổi ra cluster. Nhưng sau đó pipeline không còn quan tâm nữa. Nếu có ai đó vào cluster sửa thủ công, nếu một pod crashloop khiến cấu hình bị reset một phần, nếu một admission webhook thay đổi spec — pipeline không biết gì về điều đó. Nó sẽ chỉ đẩy lại vào lần commit tiếp theo.

Push-based (GitHub Actions / Jenkins):

  Git → [Pipeline CI/CD] → kubectl apply → Cluster
         (chỉ chạy khi có trigger)

  Khoảng trống: Cluster có thể drift bất cứ lúc nào giữa hai lần trigger.

Đây là vấn đề cốt lõi. Mô hình Push-based tạo ra trạng thái "đúng tại thời điểm deploy" nhưng không có cơ chế nào liên tục giám sát và tự phục hồi khi có sai lệch. Cluster trở thành một thứ có thể bị can thiệp từ bên ngoài mà không ai hay biết.

GitOps thực sự giải quyết vấn đề này bằng một hướng tiếp cận hoàn toàn khác về mặt kiến trúc.


4. Góc nhìn mới: Git là nguồn chân lý duy nhất — không phải nguồn tham chiếu

Điểm khác biệt căn bản của GitOps so với "CI/CD có dùng Git" nằm ở nguyên lý Declarative Infrastructure kết hợp với cơ chế Pull-based Reconciliation.

Thay vì pipeline push cấu hình vào cluster, GitOps đặt một Operator (một process) chạy bên trong cluster. Operator này liên tục làm một việc duy nhất: so sánh trạng thái mong muốn trong Git với trạng thái thực tế trên cluster, và tự động điều chỉnh nếu có sai lệch.

Pull-based (ArgoCD / Flux):

  Git (Desired State)
      ↑  [Operator trong cluster tự kéo về, liên tục]
      │
  Cluster (Actual State)
      │
      └─ Nếu Actual ≠ Desired → Operator tự sync lại

  Không có khoảng trống. Sai lệch bị phát hiện và vá trong vài phút.

Đây là sự khác biệt kiến trúc quan trọng, không phải khác biệt về công cụ. Với mô hình này:

  • Nếu ai đó chạy kubectl set env thủ công, Operator sẽ phát hiện sai lệch và revert lại trong vòng vài phút.
  • Nếu một node gặp sự cố và cấu hình của pod bị thay đổi do rescheduling, Operator sẽ đảm bảo nó được restore về đúng desired state.
  • Không cần cấp credentials cluster cho pipeline CI/CD bên ngoài — Operator ở trong cluster, nó tự kéo từ Git về, không ai push vào nó từ bên ngoài.

5. Declarative Infrastructure: Mô tả "tôi muốn gì" thay vì "hãy làm gì"

Để hiểu tại sao Pull-based Reconciliation hoạt động được, cần hiểu nguyên lý Declarative Infrastructure trước.

Hầu hết script bash deploy truyền thống là imperative — nó mô tả một chuỗi lệnh cần chạy theo thứ tự:

# Imperative: "Hãy làm những việc này theo thứ tự"
docker pull my-app:v2.1
docker stop my-app-container
docker rm my-app-container
docker run -d --name my-app-container my-app:v2.1

Vấn đề của imperative là nếu trạng thái ban đầu không như kỳ vọng — ví dụ container đã bị stop từ trước — script có thể fail hoặc cho ra kết quả sai.

Kubernetes YAML là declarative — nó mô tả trạng thái mong muốn, không phải các bước cần thực hiện:

# Declarative: "Tôi muốn hệ thống trông như thế này"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-gateway
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: payment-gateway
          image: my-app:v2.1
          env:
            - name: PAYMENT_TIMEOUT
              value: "30s"

Kubernetes nhận file này và tự làm mọi thứ cần thiết để đạt được trạng thái đó — tạo container mới, xóa container cũ, scale, restart. Nó không quan tâm trạng thái hiện tại là gì.

GitOps đưa nguyên lý này lên một tầng cao hơn: toàn bộ tập hợp các file YAML mô tả trạng thái của cluster được lưu trong Git, và Operator đảm bảo cluster luôn phản ánh đúng những gì Git đang nói.


6. Quy trình GitOps hoàn chỉnh: Từ Pull Request đến Production

Một quy trình GitOps điển hình với ArgoCD hoạt động như sau:

Developer                Git Repo               ArgoCD Operator           Cluster
    │                        │                        │                      │
    │─── Push code ──────────▶                        │                      │
    │                        │                        │                      │
    │                   CI Pipeline                   │                      │
    │                   (build, test,                 │                      │
    │                    push image)                  │                      │
    │                        │                        │                      │
    │─── Update manifest ────▶                        │                      │
    │    (image tag mới                               │                      │
    │     trong YAML)         │                       │                      │
    │                        │                        │                      │
    │                        │──── Poll mỗi 3 phút ──▶                      │
    │                        │◀─── Detect: diff! ─────                      │
    │                        │                        │                      │
    │                        │                        │─── Apply changes ───▶
    │                        │                        │                      │
    │                        │                        │◀── Sync complete ────

Điểm quan trọng cần chú ý: CI và CD đã được tách biệt hoàn toàn. Pipeline CI làm nhiệm vụ của nó: build image, chạy test, push lên registry, rồi cập nhật image tag trong file manifest YAML trên Git repo cấu hình. Nhưng nó không deploy trực tiếp lên cluster. ArgoCD là người duy nhất có quyền làm điều đó — và nó làm điều đó theo schedule polling của chính nó.

Đây là lý do GitOps cho phép tách repo ứng dụng (application repo) và repo cấu hình hạ tầng (config repo) ra làm hai thực thể riêng biệt. Nhiều team có một repo riêng chỉ chứa Helm charts và Kustomize manifests, với cấu trúc thư mục theo từng environment:

config-repo/
├── apps/
│   ├── payment-gateway/
│   │   ├── base/
│   │   │   └── deployment.yaml
│   │   └── overlays/
│   │       ├── staging/
│   │       │   └── kustomization.yaml  # replicas: 2, image: v2.1-rc1
│   │       └── production/
│   │           └── kustomization.yaml  # replicas: 5, image: v2.0-stable
│   └── order-service/
│       └── ...

7. Rollback trong 30 giây: Khi Git trở thành time machine

Đây là moment mà GitOps thực sự tỏa sáng so với bất kỳ quy trình deploy nào khác.

Trong mô hình push-based truyền thống, rollback thường có nghĩa là: tìm image tag cũ, tạo commit mới, chờ pipeline chạy lại, hoặc tệ hơn là chạy lệnh rollback thủ công và hy vọng mọi thứ không bị lệch. Toàn bộ quá trình có thể mất 10-20 phút và tiềm ẩn nhiều rủi ro.

Với GitOps, rollback là một hành động Git thuần túy:

# Xem lịch sử commit của config repo
git log --oneline

# abc1234  Update payment-gateway image to v2.1 (BROKEN)
# def5678  Update payment-gateway image to v2.0 (STABLE)

# Revert commit gây lỗi
git revert abc1234
git push origin main

# ArgoCD phát hiện commit mới, sync lại → cluster quay về v2.0

Toàn bộ thao tác mất chưa đến 30 giây — và quan trọng hơn, nó để lại dấu vết rõ ràng trong Git history: ai revert, lúc nào, lý do gì. Không có hành động thủ công nào xảy ra ngoài tầm kiểm soát của Git.

Trong tình huống khẩn cấp production, sự khác biệt giữa "rollback trong 30 giây với audit trail đầy đủ" và "chạy script tay rồi không nhớ mình đã làm gì" là không thể đánh giá thấp.


8. Trade-offs: GitOps không phải là viên đạn bạc

Đây là phần mà nhiều bài viết về GitOps thường bỏ qua. Mô hình này có những chi phí và hạn chế thực sự mà bạn cần nhận thức rõ trước khi áp dụng.

Bạn được:

  • Audit trail hoàn hảo. Mọi thay đổi cấu hình đều đi qua Git commit, có tác giả, timestamp, và lý do. Điều này cực kỳ có giá trị cho compliance và post-mortem.
  • Self-healing cluster. Operator liên tục giám sát và tự động phục hồi drift, giảm gánh nặng vận hành đáng kể.
  • Tăng tốc onboarding. Developer mới muốn biết service X đang chạy với cấu hình gì? Đọc Git, không cần SSH vào cluster.
  • Bảo mật tốt hơn. Pipeline CI/CD bên ngoài không cần credentials cluster. Bề mặt tấn công thu hẹp lại đáng kể.

Bạn mất và những phức tạp mới xuất hiện:

  • Độ trễ sync. Với ArgoCD, mặc định poll mỗi 3 phút. Nếu cần deploy khẩn cấp, bạn phải trigger sync thủ công hoặc cấu hình webhook để ArgoCD nhận thông báo ngay.
  • Quản lý Secrets là bài toán riêng. GitOps lưu YAML lên Git, nhưng bạn tuyệt đối không được push secrets, passwords, hay API keys lên Git dưới dạng plaintext. Cần giải pháp riêng như Sealed Secrets, External Secrets Operator, hay HashiCorp Vault. Đây là điểm phức tạp đầu tiên mà mọi team mới áp dụng GitOps đều phải giải quyết.
  • Learning curve và operational complexity. ArgoCD hay Flux là thêm một hệ thống bạn phải vận hành, monitor, và debug khi nó gặp sự cố. "GitOps đơn giản hóa deployment" đúng về mặt triết lý, nhưng platform team phải bỏ công sức đáng kể để thiết lập và duy trì.
  • GitOps hoạt động tốt nhất với Kubernetes, nhưng không chỉ vậy. Mô hình GitOps có thể áp dụng cho Terraform (quản lý cloud resources) hay Ansible (quản lý VM), nhưng mỗi domain cần Operator riêng và mức độ hỗ trợ tooling khác nhau. Nói "GitOps chỉ dùng được cho Kubernetes" là sai — nhưng nói "GitOps dễ nhất và mạnh nhất khi dùng với Kubernetes" thì đúng hơn.

9. Failure Case: Configuration Drift có thể là chủ ý — và đó mới là nguy hiểm

Có một failure case mà nhiều team học GitOps không lường trước: đôi khi drift là hành động có chủ ý của kỹ sư on-call trong tình huống khẩn cấp.

3 giờ sáng, production đang down. Database connection pool cạn kiệt. Kỹ sư on-call không có thời gian tạo PR, chờ review, merge, rồi đợi ArgoCD sync. Anh ta vào thẳng cluster, tăng connection pool size, hệ thống hồi phục trong 2 phút.

Đây là bài toán thực tế: bạn cần một cơ chế emergency escape hatch — khả năng can thiệp thủ công khi tình huống không cho phép tuân thủ quy trình GitOps đầy đủ. Nhưng cơ chế này nếu không được kiểm soát, nó sẽ dần dần phá vỡ toàn bộ nền tảng tin cậy mà GitOps xây dựng.

Các team trưởng thành thường xử lý điều này bằng một quy tắc rõ ràng: ArgoCD ở chế độ "Self-Heal" nhưng có thể tạm thời disable theo thủ tục khẩn cấp có ghi nhận. Sau khi tình huống khẩn cấp qua đi, kỹ sư bắt buộc phải mở PR commit lại thay đổi đó vào Git trong vòng 24 giờ, và mô tả rõ lý do. Drift bị phát hiện, nhưng được xử lý một cách có kiểm soát thay vì bị phủ nhận.

Không có quy tắc đó, cơ chế emergency escape sẽ dần trở thành thói quen — và bạn quay lại đúng bài toán ban đầu mà GitOps được tạo ra để giải quyết.


10. Misconception cần phá vỡ

"GitOps chỉ là CI/CD dùng Git."

Sai. CI/CD truyền thống là pipeline push. GitOps là Operator pull. Đây là hai mô hình kiến trúc khác nhau, không phải hai cách đặt tên cho cùng một thứ.

Sự khác biệt nằm ở câu hỏi: Ai đang đảm bảo cluster phản ánh đúng Git?

  • CI/CD: Pipeline, nhưng chỉ khi có trigger.
  • GitOps: Operator, liên tục, 24/7, tự động phục hồi khi có sai lệch.

"GitOps chỉ áp dụng được cho Kubernetes."

Sai về nguyên tắc, nhưng đúng về thực tế tooling. Mô hình GitOps — desired state trong Git, operator tự động reconcile — có thể áp dụng cho bất kỳ hạ tầng có declarative API. Terraform Cloud/Enterprise có Workspace cho phép tự động apply khi detect code thay đổi trong Git. Pulumi Automation API có thể được dùng theo hướng tương tự.

Điểm mấu chốt là: bạn áp dụng được GitOps ở đâu tùy thuộc vào hạ tầng đó có hỗ trợ declarative state model không — không phải vào việc nó có phải Kubernetes không.


11. Tổng kết: Khi bạn dừng push và bắt đầu để cluster tự kéo

Quay lại câu chuyện ban đầu. Kỹ sư vô tình gõ kubectl set env và tạo ra một sai lệch âm thầm tồn tại trong ba tuần mà không ai phát hiện. Đây không phải lỗi của con người — đây là hệ quả của một kiến trúc deployment không có cơ chế phát hiện drift.

Nếu cluster đó đang chạy ArgoCD ở chế độ Self-Heal, tình huống sẽ hoàn toàn khác. Trong vòng vài phút sau khi lệnh kubectl set env được chạy, ArgoCD sẽ phát hiện sai lệch và tự động revert. Kỹ sư sẽ nhận được notification. Incident sẽ được ghi nhận. Và quan trọng nhất — cluster sẽ không bao giờ ở trạng thái không xác định trong ba tuần.

Ba điều cần nhớ:

  1. GitOps không phải là "dùng Git để deploy". GitOps là mô hình trong đó Git là nguồn chân lý duy nhất, và Operator liên tục đảm bảo cluster phản ánh đúng Git — không chỉ khi có deploy.

  2. Pull-based khác Push-based về mặt kiến trúc. Sự khác biệt không phải ở công cụ — nó nằm ở việc ai đang kiểm soát cluster và khi nào.

  3. Configuration Drift là mối nguy thực sự, không phải lý thuyết. Bất kỳ hành động thủ công nào trên cluster mà không được commit vào Git đều là một quả bom hẹn giờ chờ vào ngày deploy tiếp theo phát nổ.


Lời kết: Khi hạ tầng đã được kiểm soát, câu hỏi tiếp theo phức tạp hơn nhiều

GitOps giải quyết bài toán: "Làm thế nào để đảm bảo cluster luôn chạy đúng những gì chúng ta mô tả?" Nhưng khi cluster đã được vận hành chặt chẽ, bạn sẽ đối mặt với một lớp câu hỏi hoàn toàn mới ở tầng mạng nội bộ:

Làm thế nào để mã hóa mọi giao tiếp giữa các service mà không cần thay đổi một dòng code nào? Làm thế nào để kiểm soát traffic routing theo phần trăm mà không cần deploy lại? Làm thế nào để tự động thu thập trace từ mọi cuộc giao tiếp nội bộ mà không phụ thuộc vào developer nhớ instrumentation?

Đây là những câu hỏi mà Service Mesh được thiết kế để trả lời — và đó là chủ đề của tập tiếp theo trong series.


Góc nhìn thêm: GitOps mạnh vì nó biến thay đổi hạ tầng thành thứ có thể review như code

Giá trị lớn nhất của GitOps không nằm ở chỗ "deploy từ Git". Giá trị lớn hơn là mọi thay đổi cấu hình quan trọng bắt đầu có:

  • lịch sử rõ ràng
  • review trước khi áp dụng
  • khả năng diff
  • khả năng rollback có chủ đích

Trong microservices, nơi số lượng service và config surface tăng rất nhanh, đặc tính này cực kỳ quý. Nếu không có nó, team sẽ rất nhanh rơi vào trạng thái drift giữa những gì repo nói và những gì cluster thực sự đang chạy.

Nhìn thêm: GitOps không miễn phí, vì nó đòi kỷ luật cấu hình cao hơn

Để GitOps thật sự hữu ích, team phải chấp nhận:

  • cấu hình phải được chuẩn hóa tốt
  • secret management không thể làm kiểu copy tay
  • ai cũng phải hiểu manifest thay đổi nghĩa là gì với runtime
  • quy trình emergency fix phải được thiết kế để không phá audit trail

GitOps không cứu một đội ngũ thiếu discipline. Nó khuếch đại discipline nếu đã có, và khuếch đại sự rối nếu mọi người vẫn xem config như thứ có thể sửa tạm trên cluster rồi để đó.

🤝 Đồng hành cùng TechCraft

TechCraft là nơi chia sẻ kiến thức về Backend Engineering, Database, Distributed Systems và Production Architecture thông qua các bài viết, video và những series được xây dựng theo lộ trình.

Nếu bạn yêu thích cách tiếp cận này, hãy tiếp tục đồng hành cùng TechCraft trên các nền tảng bên dưới.

Và nếu muốn học chuyên sâu hơn, Dev Insider sẽ là nơi tập trung toàn bộ các nội dung premium được cập nhật liên tục mỗi tuần.

🚀 Dev Insider https://www.patreon.com/techcraft_official/posts/vi-sao-dev-ra-161163881?collection=2220113

📘 Facebook https://www.facebook.com/techcraft.official

🎥 YouTube https://www.youtube.com/@techcraft.official

🎵 TikTok https://www.tiktok.com/@techcraft.official

Think Beyond Code. Build Better Systems.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.