ArgoCD Kubernetes GitOps

ArgoCD i HPA: ciągły OutOfSync loop przez spec.replicas

Naprawa nieskończonej pętli OutOfSync w ArgoCD spowodowanej konfliktem między HPA a deklaratywnym zarządzaniem spec.replicas.

·
ArgoCD nieustannie pokazuje OutOfSync dla Deploymentu lub HPA, mimo że nic się nie zmieniło w Git. Po każdym sync status natychmiast wraca do OutOfSync. Ten runbook pokazuje jak rozwiązać konflikt między HPA a ArgoCD.

Ten runbook jest częścią serii o blokadach synchronizacji ArgoCD. Zobacz też: etcd request too large i deadlock operacji. Jeśli interesuje Cię szerszy kontekst GitOps i ArgoCD, przeczytaj nasz post o GitOps i ArgoCD.

Objaw

ArgoCD pokazuje status OutOfSync dla zasobu HorizontalPodAutoscaler lub Deployment. Diff wskazuje na zmianę spec.replicas, która nie pochodzi z Git:

# ArgoCD shows OutOfSync for HPA resources continuously
# Diff shows spec.replicas changing between desired state and live state
argocd app diff <APP_NAME> --local ./chart

# Output shows:
# HorizontalPodAutoscaler my-app-hpa:
#   spec.replicas: 3 -> 7 (live != desired)

Synchronizacja jest technicznie możliwa, ale po każdym sync HPA natychmiast zmienia repliki z powrotem, powodując nieskończoną pętlę OutOfSync → Sync → OutOfSync. W środowiskach z włączonym selfHeal: true ArgoCD próbuje naprawiać ten „drift” w kółko, generując zbędne operacje i obciążając controller.

Przyczyna

HPA (HorizontalPodAutoscaler) automatycznie skaluje liczbę replik na podstawie metryk (CPU, memory, custom metrics). Kiedy HPA zmienia spec.replicas w Deployment, ta wartość odbiega od stanu zapisanego w Git. ArgoCD traktuje to jako drift — rozbieżność między desired state a live state.

To klasyczny konflikt własności pola (field ownership) między dwoma controllerami: ArgoCD chce utrzymać stan z Git, a HPA chce dostosować repliki do obciążenia. Problem dotyczy też GitHub ARC (Actions Runner Controller), który dynamicznie skaluje runner pods — identyczny mechanizm konfliktu.

Rozwiązanie

Użyj ignoreDifferences z jqPathExpressions, aby ArgoCD ignorował pola zarządzane przez HPA. Dodatkowo włącz RespectIgnoreDifferences sync option, żeby ArgoCD nie nadpisywał tych pól podczas synchronizacji:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/app.git
    targetRevision: main
    path: k8s/
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jqPathExpressions:
        - .spec.replicas
    - group: autoscaling
      kind: HorizontalPodAutoscaler
      jqPathExpressions:
        - .spec.metrics[].resource.target.averageUtilization
        - .status
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - RespectIgnoreDifferences=true

Uwaga dotycząca wersji: jqPathExpressions wymaga ArgoCD ≥ 2.1. Opcja RespectIgnoreDifferences=true wymaga ArgoCD ≥ 2.5. Jeśli korzystasz ze starszej wersji, użyj jsonPointers zamiast jqPathExpressions (np. /spec/replicas).

# Apply the updated Application manifest
kubectl apply -f application-with-ignore.yaml

# Verify sync status returns to Synced
argocd app get my-app --output json | jq '.status.sync.status'
# Expected: "Synced"

Po zastosowaniu tej konfiguracji ArgoCD nie będzie traktował zmian w spec.replicas jako driftu. HPA może swobodnie skalować Deployment, a Application pozostanie w stanie Synced. To rozwiązanie jest preferowane nad alternatywą (usunięcie replicas z manifestów w Git), ponieważ zachowuje jawną deklarację wartości domyślnej w repozytorium.

Identyczny wzorzec stosuje się do innych controllerów dynamicznie modyfikujących zasoby — np. Istio injection dodający sidecary, cert-manager aktualizujący status certyfikatów czy VPA (Vertical Pod Autoscaler) zmieniający requests i limits.

Walidacja

# 1. Verify sync status is Synced (not OutOfSync)
argocd app get <APP_NAME> --output json | jq '.status.sync.status'
# Expected: "Synced"

# 2. Wait for HPA to scale (2-3 minutes) and check again
sleep 180
argocd app get <APP_NAME> --output json | jq '.status.sync.status'
# Expected: still "Synced" despite HPA changing replicas

# 3. Verify health status
argocd app get <APP_NAME> --output json | jq '.status.health.status'
# Expected: "Healthy"

# 4. Check that HPA is actually scaling
kubectl get hpa -n <NAMESPACE>
# Expected: TARGETS show current utilization, REPLICAS may differ from Git

Jeśli po kilku cyklach HPA status pozostaje Synced, problem jest rozwiązany. Kluczowe jest odczekanie przynajmniej jednego pełnego cyklu autoskalowania (2-3 minuty), aby potwierdzić, że pętla OutOfSync nie wraca.

Jerzy Kopaczewski

Pipeline CI/CD blokuje Twój zespół?

Umów bezpłatną 30-minutową rozmowę. Przejrzymy konfigurację ArgoCD i wskażemy, co naprawić od razu.