Config Sync — GitOps Engine Bên Trong
GitOps — Mental Model Cần Sửa
Nhiều kỹ sư hiểu "GitOps" là "CI/CD pipeline dùng Git làm trigger". Đây là hiểu lầm quan trọng.
GitOps thực sự là một mô hình vận hành trong đó:
- Git là source of truth duy nhất cho desired state của hệ thống
- Một agent trong cluster liên tục reconcile actual state với desired state
- Không có "push" từ bên ngoài — cluster tự pull và tự apply
Khác biệt then chốt với CD pipeline truyền thống:
| CD Pipeline (Push) | GitOps (Pull) | |
|---|---|---|
| Trigger | External event (merge, tag) | Continuous reconciliation loop |
| Apply timing | Một lần khi pipeline chạy | Liên tục, realtime |
| Drift handling | Không detect/fix | Tự động detect và revert |
| Cluster access | Pipeline cần credentials | Cluster tự pull, không expose credential ra ngoài |
| Audit trail | CI/CD logs | Git history + Kubernetes events |
Config Sync implement mô hình GitOps này cho Kubernetes. Nó không phải CI/CD tool — nó là continuous reconciliation engine.
Kiến Trúc Config Sync — Các Components
Khi Config Sync được enable trên cluster, các Kubernetes resources sau được deploy:
1. Reconciler Manager
Namespace: config-management-system
Resource: Deployment reconciler-manager (1 replica, 2 containers)
Đây là "brain" của Config Sync. Nó:
- Watch tất cả
RootSyncvàRepoSyncobjects trong cluster - Với mỗi RootSync/RepoSync, tạo (và manage lifecycle của) một Reconciler Pod riêng biệt
- Nếu Reconciler Pod crash → Reconciler Manager restart nó
- Nếu RootSync/RepoSync bị xóa → Reconciler Manager xóa Reconciler Pod tương ứng
2. Per-Source Reconciler Pods
Với mỗi RootSync object, Reconciler Manager tạo một Deployment:
- Tên:
root-reconciler-{ROOTSYNC_NAME}(default:root-reconcilerchoroot-sync) - Namespace:
config-management-system - Replicas: 1 (không scale horizontally — một reconciler per source)
Với mỗi RepoSync object:
- Tên:
ns-reconciler-{NAMESPACE}-{REPOSYNC_NAME}-{NAME_LENGTH} - Namespace:
config-management-system
Reconciler Pod chứa 3–5 containers tùy configuration:
root-reconciler pod
├── reconciler # Core sync và drift remediation (luôn có)
├── otel-agent # Metrics collection (luôn có)
├── git-sync # Fetch từ Git (khi sourceType: git)
HOẶC oci-sync # Pull OCI image (khi sourceType: oci)
HOẶC helm-sync # Pull Helm chart (khi sourceType: helm)
├── hydration-controller # Build Kustomize (khi có kustomize.yaml)
└── gcenode-askpass # Cache Git credentials (optional)3. ResourceGroup Controller
Namespace: resource-group-system
Resource: Deployment resource-group-controller-manager
Với mỗi RootSync/RepoSync, Config Sync tự động tạo một ResourceGroup object — một danh sách tất cả Kubernetes objects đang được managed bởi sync source đó.
ResourceGroup Controller:
- Monitor tất cả objects được listed trong ResourceGroups
- Update status trên ResourceGroup khi object state thay đổi
- Cho phép query "tất cả objects được sync bởi RootSync X đang trong trạng thái gì?"
4. Admission Webhook (tùy chọn)
Namespace: config-management-system
Resource: Deployment với 2 replicas
Đây là cơ chế drift prevention proactive (mặc định: disabled). Khi enabled:
- Intercept mọi CREATE/UPDATE/DELETE request lên managed objects
- Nếu request vi phạm desired state → reject request ngay tại admission phase
- Không để drift xảy ra, thay vì reactive self-healing sau khi drift đã xảy ra
Reconciler Pipeline — Luồng Xử Lý Từng Bước
Reconciler Pod thực hiện một vòng lặp liên tục:
Bước 1: Source Fetch
Container git-sync / oci-sync / helm-sync fetch config từ source:
Git source:
spec:
sourceType: git
git:
repo: https://github.com/my-org/k8s-configs
branch: main
dir: clusters/prod
period: 15s # Poll interval
auth: token # Authentication type
secretRef:
name: git-credsgit-sync pull repo mỗi period giây (default 15s). Nó check nếu HEAD commit thay đổi — nếu không thay đổi, skip render và apply. Chỉ khi có commit mới, pipeline tiếp tục.
OCI source:
spec:
sourceType: oci
oci:
image: us-central1-docker.pkg.dev/my-project/configs/prod:latest
dir: /
period: 30s
auth: gcpserviceaccount
gcpServiceAccountEmail: config-sync-sa@my-project.iam.gserviceaccount.comOCI source pull Docker/OCI image chứa config files. Đây là alternative khi không muốn expose Git repo cho cluster. Image được push bởi CI pipeline sau mỗi commit.
Helm source:
spec:
sourceType: helm
helm:
repo: oci://us-central1-docker.pkg.dev/my-project/helm-charts
chart: my-platform
version: 1.2.3
releaseName: my-platform
namespace: default
auth: gcpserviceaccountConfig Sync pull Helm chart và render nó — output là plain Kubernetes YAML. Khác helm install truyền thống: không có Helm release state trong cluster, chỉ có rendered manifests được applied.
Bước 2: Render (Hydration)
Container hydration-controller render configs nếu có kustomize.yaml:
Git repo structure:
clusters/prod/
├── kustomization.yaml
├── base/
│ ├── deployment.yaml
│ └── service.yaml
└── overlays/
└── prod/
├── kustomization.yaml
└── patch.yamlhydration-controller chạy kustomize build clusters/prod/overlays/prod → output rendered YAML. Nếu không có Kustomize, bước này được skip.
Bước 3: Parse và Validate
Container reconciler parse rendered YAML:
- Validate YAML syntax
- Validate Kubernetes object structure
- Detect conflicts (ví dụ: hai objects có cùng name/namespace/kind)
- Detect immutable field changes (lỗi
KNV2009)
Nếu parse/validate fail → sync dừng lại, không apply gì hết. RootSync/RepoSync status điều phản ánh lỗi:
status:
conditions:
- type: Syncing
status: "False"
reason: Error
message: "KNV2009: cannot change immutable field 'spec.storageClassName'"Bước 4: Apply (Server-Side Apply)
Container reconciler apply changes lên cluster dùng server-side apply:
// Config Sync internal logic (simplified):
for _, obj := range desiredObjects {
client.Apply(obj, fieldManager="configsync.gke.io/config-sync")
}Field ownership qua SSA: Config Sync claim ownership của fields nó manage. Nếu field manager khác (ví dụ HPA) cũng manage một field (ví dụ spec.replicas), SSA handle conflict thông qua field ownership. Config Sync sẽ không override field owned bởi manager khác trừ khi explicitly configured.
Bước 5: Drift Detection và Remediation
Sau khi apply xong, reconciler tiếp tục watch cluster state:
Reactive self-healing (default):
reconcilercó informer watch tất cả managed resources- Nếu bất kỳ managed resource nào bị modify/delete bởi ai đó ngoài Config Sync →
reconcilerdetect change → re-apply desired state trong vài giây
Proactive prevention (với admission webhook):
- Admission webhook intercept modify/delete request trước khi Kubernetes API server process nó
- Nếu object đang được managed bởi Config Sync → webhook reject request
- Không cần "heal" vì change không bao giờ xảy ra
RootSync vs RepoSync — Phân Quyền và Scope
RootSync
- Scope: Cluster-wide
- Permissions: Mặc định
cluster-admin(quản lý bất kỳ resource nào trong cluster) - Use case: Cluster admin quản lý infrastructure-level configs: CRDs, ClusterRoles, ClusterRoleBindings, StorageClasses, custom controllers, cross-namespace resources
- Source format:
hierarchy(structured) hoặcunstructured - Namespace: Chạy trong
config-management-system
apiVersion: configsync.gke.io/v1beta1
kind: RootSync
metadata:
name: root-sync
namespace: config-management-system
spec:
sourceType: git
git:
repo: https://github.com/my-org/cluster-configs
branch: main
dir: /
auth: token
secretRef:
name: git-credsRepoSync
- Scope: Namespace-scoped
- Permissions: Custom permissions được define bởi cluster admin (thường limited đến một namespace)
- Use case: Tenant/team quản lý configs trong namespace của họ — Deployments, Services, ConfigMaps, Secrets
- Source format: Chỉ
unstructured - Namespace: RepoSync object sống trong namespace mà nó manage
apiVersion: configsync.gke.io/v1beta1
kind: RepoSync
metadata:
name: team-a-sync
namespace: team-a # RepoSync này manage namespace team-a
spec:
sourceType: git
git:
repo: https://github.com/my-org/team-a-configs
branch: main
dir: team-a/
auth: token
secretRef:
name: git-credsVới RepoSync, cluster admin phải tạo RoleBinding cho reconciler service account:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: config-sync-team-a
namespace: team-a
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: team-a-deployer
subjects:
- kind: ServiceAccount
name: ns-reconciler-team-a-team-a-sync-12 # Auto-generated SA name
namespace: config-management-systemÝ nghĩa quan trọng của RootSync vs RepoSync: Đây là mô hình delegation — cluster admin dùng RootSync quản lý cluster-level resources, trong khi teams dùng RepoSync quản lý namespace-level resources của họ. Mỗi team có repo riêng, không thể affect clusters khác hay namespaces khác.
ResourceGroup — Inventory và Status Tracking
ResourceGroup là CRD được tạo tự động bởi Config Sync cho mỗi RootSync/RepoSync. Nó chứa danh sách đầy đủ tất cả managed resources:
apiVersion: kpt.dev/v1alpha1
kind: ResourceGroup
metadata:
name: root-sync
namespace: config-management-system
spec:
resources:
- group: apps
version: v1
kind: Deployment
namespace: default
name: my-app
- group: ""
version: v1
kind: Service
namespace: default
name: my-app-svc
# ... tất cả objects được manage bởi root-syncResourceGroup Controller theo dõi status của tất cả objects này và aggregate vào ResourceGroup status. Giúp xem nhanh "tất cả 47 objects được sync bởi RootSync X đang healthy" hoặc "object Y đang drift".
Constraints và Failure Modes
Immutable Field Changes (KNV2009)
Một số Kubernetes fields không thể thay đổi sau khi resource được tạo (spec.storageClassName của PVC, spec.selector của Service v.v). Nếu Git config thay đổi những fields này:
Error: KNV2009: cannot change immutable field spec.storageClassNameSync dừng hoàn toàn — không apply bất kỳ thay đổi nào khác cho đến khi lỗi được fix trong Git hoặc resource cũ bị xóa và tạo lại.
Đây là fail-safe behavior quan trọng: thà không sync gì cả còn hơn apply partial changes có thể gây inconsistent state.
Deletion của Managed Resources
Nếu xóa object khỏi Git source, Config Sync sẽ xóa object đó khỏi cluster. Đây là GitOps đúng nghĩa: Git là source of truth, cluster reflect Git state.
Điều này có implications:
- Không được xóa objects trong Git trừ khi thực sự muốn xóa khỏi cluster
- Objects quan trọng (production Deployments) cần được bảo vệ bởi Git branch policies và code review
Có thể annotate object để Config Sync không xóa dù bị xóa khỏi Git:
metadata:
annotations:
client.lifecycle.config.k8s.io/deletion: detach # Don't delete, just detach from managementRace Condition với HPA và replicas
HPA manage spec.replicas của Deployment. Config Sync quản lý cùng Deployment. Race condition tiềm ẩn:
- Config Sync apply
spec.replicas: 3từ Git - HPA scale lên
spec.replicas: 10(high traffic) - Config Sync detect "drift" → apply lại
spec.replicas: 3→ break auto-scaling
Giải pháp: không include spec.replicas trong Git config khi dùng HPA. Thay vào đó, set spec.replicas trong HPA spec, không trong Deployment manifest. Config Sync sẽ không override field không có trong Git.
Hoặc dùng client.lifecycle.config.k8s.io/mutation: ignore annotation để Config Sync không manage specific fields.
Debugging Config Sync
Check RootSync Status
kubectl get rootsync root-sync -n config-management-system -o yamlQuan trọng nhất là status.conditions:
status:
conditions:
- type: Syncing
status: "True" # True = đang sync, False = error hoặc up-to-date
reason: Syncing
message: "Syncing"
lastTransitionTime: ...
- type: Reconciling
status: "False" # False = đã sync xong
sync:
commit: "abc123def456" # Git commit hash đã được sync
lastSyncTime: ...nomos status
# Xem status tất cả managed clusters (cần nomos CLI)
nomos status
# Output:
# Cluster: gke_my-project_us-central1_prod-cluster
# SYNCED abc123 (2024-01-15 10:30:00 UTC)
# Namespace Name Status
# default root-sync SYNCEDView Managed Objects
# List tất cả objects được manage bởi root-sync
kubectl get resourcegroup root-sync -n config-management-system -o json \
| jq '.spec.resources[]'