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.
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:
jqPathExpressionswymaga ArgoCD ≥ 2.1. OpcjaRespectIgnoreDifferences=truewymaga ArgoCD ≥ 2.5. Jeśli korzystasz ze starszej wersji, użyjjsonPointerszamiastjqPathExpressions(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.
Pipeline CI/CD blokuje Twój zespół?
Umów bezpłatną 30-minutową rozmowę. Przejrzymy konfigurację ArgoCD i wskażemy, co naprawić od razu.