Một cái điều nhiệt (thermostat) làm hai việc ở hai nhịp rất khác nhau. Nó liên tục cảm nhận nhiệt độ phòng và bật máy sưởi ngay khi phòng lệch khỏi mức đặt — phản ứng tức thì. Nhưng nó chỉ đọc lại lịch hẹn giờ theo chu kỳ: bạn đổi mức đặt cho buổi tối, nó không thấy ngay, phải tới nhịp đọc lịch tiếp theo. Hai độ trễ ấy khác nhau vì hai nguồn thông tin được xử lý khác nhau — một cái theo dõi liên tục, một cái hỏi lại định kỳ. Argo CD hành xử đúng như cái điều nhiệt, và bài này cài nó lên cụm thật để đo cả hai nhịp đó.
Phần 46 đo Helm và thấy nó không chống được trôi cấu hình. Ta cài Argo CD, dựng một kho Git trong cụm, rồi đo hai độ trễ khác hẳn nhau.
Hai độ trễ, chênh nhau gần trăm lần
| Sự kiện | Thời gian tới lúc cụm đổi |
|---|---|
| Commit mới trong Git | 159 giây |
| Ai đó sửa trực tiếp trên cụm | 2,1 / 1,1 / 17,0 giây |
| Xoá hẳn một Deployment Argo quản lý | 2,1 giây |
Chênh lệch này không phải ngẫu nhiên, và nó là toàn bộ mô hình gói trong một câu: Argo CD theo dõi cụm, nhưng quét Git. Cụm được theo dõi bằng watch — mọi thay đổi đẩy tới ngay lập tức, đúng cái cảm biến nhiệt độ. Git thì không có watch, nên Argo phải hỏi lại theo chu kỳ, mặc định 180 giây — đúng nhịp đọc lịch. Kết quả đo 159 giây khớp với con số đó: commit của tôi rơi vào khoảng giữa một chu kỳ.
Hệ quả cho công việc hằng ngày
Người ta hay gặp chuyện này ở lần dùng Argo đầu tiên: đẩy commit, mở giao diện, không thấy gì, tưởng cấu hình sai. Không có gì sai cả — chỉ là chu kỳ 180 giây, và con số đó không hiện ở đâu trong giao diện. Ba cách xử lý, theo thứ tự nên chọn:
# 1. Webhook từ Git — cách đúng, áp ngay trong vài giây
# Git đẩy POST /api/webhook mỗi khi có commit
# 2. Ép làm mới một ứng dụng
kubectl annotate app <ten> -n argocd argocd.argoproj.io/refresh=hard --overwrite
# 3. Rút ngắn chu kỳ quét — cẩn thận
kubectl patch cm argocd-cm -n argocd --type=merge \
-p '{"data":{"timeout.reconciliation":"60s"}}'
Cách 3 nghe hấp dẫn nhất nhưng đáng cân nhắc nhất: mỗi chu kỳ là một lần Argo clone lại mọi kho của mọi ứng dụng. Với hai chục ứng dụng thì hạ xuống 30 giây là bắt đầu nện vào máy chủ Git. Webhook giải quyết đúng vấn đề đó mà không tốn gì.
selfHeal: nhanh, nhưng có giải lui
Tôi kubectl scale lên 7 bản, và Argo đưa về 3 sau 2,1 giây. Lần hai: 1,1 giây. Lần ba: 17,0 giây. Lần thứ ba không phải nhiễu. Argo CD có cơ chế giải lui khi phải sửa cùng một thứ nhiều lần liên tiếp — để không đánh nhau vô tận với một bộ điều khiển khác cũng đang ghi vào đối tượng đó.
Đây là hành vi đúng và đáng biết trước, vì nó tạo ra một triệu chứng khó hiểu: selfHeal "chậm dần" ở đúng ứng dụng có xung đột, tức là đúng ứng dụng bạn đang cần nó nhanh nhất. Xung đột hay gặp nhất là HPA: HPA đặt replicas, Argo đặt lại theo Git, HPA đặt lại. Cách chữa là bảo Argo bỏ qua trường đó:
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers: ["/spec/replicas"]
Xoá một đối tượng thì nó quay lại sau 2 giây
kubectl delete deploy -n dich-vu web
# 2,1 giây sau
web 3/3 3 3 2s
Đây là điều Helm không làm được, đã đo ở phần 46: xoá tay một Service do Helm tạo thì helm status vẫn báo deployed và chẳng có gì dựng lại. Khác biệt nằm ở chỗ Argo CD là một bộ điều khiển chạy thường trực trong cụm, còn Helm là một lệnh chạy rồi thoát. Đó là toàn bộ lý do người ta ghép Helm với Argo thay vì chọn một trong hai.
Mặt trái, và nó thật: gỡ rối bằng tay không còn hoạt động. Sửa nhanh một tham số để thử nghiệm sẽ bị hoàn tác trong hai giây. Muốn thử thì phải tắt selfHeal, hoặc sửa trong Git — và đó chính là điều GitOps muốn ép bạn làm.
Trạng thái Argo giữ
sync = Synced
health = Healthy
rev = d8db9b097814d3a51a5e59d094dd55ea67ff9c56
rev là mã băm commit thật. Đây là thứ Helm không có: Argo luôn biết cụm đang chạy đúng phiên bản nào của kho mã. Hai trường sync và health cố ý tách nhau, và phân biệt này đáng nhớ:
Synced= cụm khớp với Git.Healthy= thứ đang chạy thật sự khoẻ.
Chúng độc lập. Một ứng dụng có thể Synced mà Degraded — bạn đã triển khai đúng thứ trong Git, và thứ đó hỏng. Đọc nhầm hai cột này dẫn tới kết luận sai về chỗ cần sửa.
Ba điều nên quyết trước khi bắt đầu
prune: true hay không. Bật thì bỏ tệp khỏi Git là đối tượng bị xoá khỏi cụm — đây chính là thứ Kustomize thiếu ở phần 47. Nhưng nó cũng nghĩa là một lần xoá tệp nhầm sẽ xoá thật. Bật, nhưng dùng Application riêng cho những thứ không được phép mất.
Một kho hay nhiều kho. Một kho cho tất cả thì dễ tìm; nhiều kho thì phân quyền rõ. Argo không ép, nhưng đổi về sau tốn công.
Ai được sửa Git. Sau khi có GitOps, quyền ghi vào kho chính là quyền ghi vào cụm. Phân quyền RBAC trên cụm không còn là chốt chặn duy nhất — và người ta hay quên siết chỗ mới.
Mẫu số chung: vòng lặp đối chiếu kéo trạng thái thực về trạng thái khai báo
Cái làm Argo hoàn tác mọi sửa tay là một mẫu nền tảng: vòng lặp đối chiếu (reconciliation loop) hướng tới một trạng thái mong muốn khai báo. Bạn khai bạn muốn gì (Git là nguồn sự thật), và một bộ điều khiển chạy thường trực liên tục so trạng thái thực với mong muốn rồi vá phần lệch — thay vì thực thi một chuỗi lệnh rồi quên. Điều nhiệt làm đúng vậy với nhiệt độ; Argo làm vậy với cụm. Và hai độ trễ khác nhau (2 giây so với 159) đến từ hai nguồn được đối chiếu theo hai cách: cái theo dõi được (watch/push) sửa tức thì, cái phải hỏi lại (poll) trễ theo chu kỳ.
Trong thế giới Java và frontend, đây đúng là cơ chế của React: bạn khai báo UI mong muốn theo state, và React đối chiếu (diff virtual DOM) rồi vá đúng phần đổi — bạn không tự gọi lệnh sửa DOM. Còn ở hạ tầng, đây là khác biệt cốt lõi giữa Argo và Terraform: Terraform cũng khai báo trạng thái mong muốn, nhưng plan/apply là một lần chạy rồi thoát (giống Helm) — nó không có vòng lặp thường trực nên không tự sửa trôi cấu hình giữa hai lần apply. Chính bộ điều khiển chạy mãi mới là thứ biến "khai báo mong muốn" thành "tự chữa".
Mẫu số chung: một vòng lặp đối chiếu liên tục kéo trạng thái thực về trạng thái khai báo, nên nó tự chữa trôi cấu hình — nhưng chỉ ở nhịp mà nó thấy được thay đổi (watch tức thì, poll trễ theo chu kỳ), và khi hai vòng lặp cùng ghi một thứ thì phải có giải lui để chúng đừng đánh nhau. Câu hỏi đúng khi vận hành một hệ khai-báo-mong-muốn không phải "tôi đã apply chưa" mà là "có bộ điều khiển nào đang chạy để giữ nó đúng không, nó thấy thay đổi nhanh cỡ nào, và có ai khác đang tranh ghi cùng trường không". Với Argo, câu trả lời cho độ trễ Git là webhook, cho tranh ghi là ignoreDifferences.
Muốn tự xem ứng dụng nào đang lệch khỏi Git, và lệch bao lâu rồi:
kubectl get applications -A -o json | python3 -c '
import sys, json
for a in json.load(sys.stdin)["items"]:
s = a.get("status", {})
sy = s.get("sync", {}).get("status")
he = s.get("health", {}).get("status")
rev = (s.get("sync", {}).get("revision") or "")[:8]
if sy != "Synced" or he != "Healthy":
print("%-28s sync=%-12s health=%-12s rev=%s" % (
a["metadata"]["name"], sy, he, rev))
'
OutOfSync kéo dài nghĩa là có ai đó sửa tay mà selfHeal chưa bật, hoặc có xung đột với một bộ điều khiển khác. Synced nhưng Degraded thì Git đúng còn ứng dụng hỏng — hai chỗ sửa hoàn toàn khác nhau.
Phần sau: các mẫu triển khai — blue/green, canary, và đo cái giá thật của mỗi cách.