Skip to content

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 đó:

  1. Git là source of truth duy nhất cho desired state của hệ thống
  2. Một agent trong cluster liên tục reconcile actual state với desired state
  3. 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)
TriggerExternal event (merge, tag)Continuous reconciliation loop
Apply timingMột lần khi pipeline chạyLiên tục, realtime
Drift handlingKhông detect/fixTự động detect và revert
Cluster accessPipeline cần credentialsCluster tự pull, không expose credential ra ngoài
Audit trailCI/CD logsGit 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ả RootSyncRepoSync objects 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-reconciler cho root-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:

yaml
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-creds

git-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:

yaml
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.com

OCI 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:

yaml
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: gcpserviceaccount

Config 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.yaml

hydration-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:

yaml
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:

go
// 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):

  • reconciler có 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 → reconciler detect 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ặc unstructured
  • Namespace: Chạy trong config-management-system
yaml
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-creds

RepoSync

  • 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
yaml
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-creds

Với RepoSync, cluster admin phải tạo RoleBinding cho reconciler service account:

yaml
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:

yaml
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-sync

ResourceGroup 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.storageClassName

Sync 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:

yaml
metadata:
  annotations:
    client.lifecycle.config.k8s.io/deletion: detach  # Don't delete, just detach from management

Race 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:

  1. Config Sync apply spec.replicas: 3 từ Git
  2. HPA scale lên spec.replicas: 10 (high traffic)
  3. 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

bash
kubectl get rootsync root-sync -n config-management-system -o yaml

Quan trọng nhất là status.conditions:

yaml
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

bash
# 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   SYNCED

View Managed Objects

bash
# 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[]'

Official References