SRE: GitOps con ArgoCD

2026-03-09 | Gabriel Garrido | 12 min de lectura
Share:

Apoya este blog

Si te resulta util este contenido, considera apoyar el blog.

Introduccion

A lo largo de esta serie de SRE cubrimos SLIs y SLOs, gestión de incidentes, observabilidad, ingeniería del caos y planificación de capacidad. Todas esas prácticas asumen que cuando cambiás algo, el cambio queda registrado, revisado, auditable y es fácil de revertir. Eso es exactamente lo que te da GitOps.


Si venís desplegando en Kubernetes con kubectl apply o pipelines de CI que pushean directo al cluster, probablemente conocés el dolor: alguien aplica un hotfix manual, otra persona ejecuta una versión diferente de un manifiesto, y antes de que te des cuenta el estado del cluster se desfasó de lo que hay en tu repositorio. Nadie sabe qué es lo que realmente está corriendo. GitOps resuelve esto haciendo de Git la única fuente de verdad y usando un controlador para reconciliar continuamente el estado del cluster con lo que está declarado en tu repositorio.


Vamos al tema.


¿Qué es GitOps?

GitOps es un modelo operativo donde el estado deseado de tu infraestructura y aplicaciones se declara en Git. Un controlador corriendo en tu cluster observa el repositorio Git y se asegura de que el estado vivo coincida con el estado declarado. Si algo se desfasa, el controlador lo corrige automáticamente.


Los principios centrales son:


  • Configuración declarativa: Todo se describe como manifiestos YAML o JSON en Git. Sin scripts imperativos, sin pasos manuales.
  • Git como la única fuente de verdad: El repositorio Git es el único lugar donde se hacen cambios. Lo que está en Git es lo que corre en el cluster.
  • Reconciliación basada en pull: En vez de que el CI pushee al cluster, un controlador dentro del cluster pullea el estado deseado desde Git. Esto es más seguro porque las credenciales del cluster nunca salen del cluster.
  • Reconciliación continua: El controlador no solo aplica cambios una vez. Compara continuamente el estado vivo con el estado deseado y corrige cualquier desfase.

Esto es fundamentalmente diferente al CI/CD tradicional basado en push donde un pipeline ejecuta kubectl apply después de un build. Con CD basado en push, si alguien cambia algo en el cluster manualmente, tu CI no se entera. Con GitOps, el controlador detecta el desfase y lo corrige.


# CI/CD basado en push (tradicional):
# Desarrollador → Git push → CI construye → CI ejecuta kubectl apply → Cluster
#                                            (CI necesita credenciales del cluster)
#                                            (el desfase no se detecta)
#
# GitOps basado en pull:
# Desarrollador → Git push → Controlador detecta cambio → Controlador aplica → Cluster
#                             (el controlador vive en el cluster, observa Git continuamente)
#                             (el desfase se detecta y corrige automáticamente)

Arquitectura de ArgoCD

ArgoCD es el controlador GitOps más popular para Kubernetes. Es un proyecto graduado de la CNCF con una arquitectura bien definida.


  • API Server: El servidor gRPC/REST que alimenta la interfaz web, la CLI y las integraciones externas. Maneja autenticación, RBAC y sirve el estado de las aplicaciones.
  • Repository Server: Clona repositorios Git y genera manifiestos de Kubernetes. Soporta YAML plano, Kustomize, Helm, Jsonnet y plugins personalizados.
  • Application Controller: El cerebro de ArgoCD. Observa los recursos Application, compara el estado deseado (de Git) con el estado vivo (del cluster) y ejecuta operaciones de sync cuando difieren.
  • Redis: Capa de caché para el repository server y el application controller.
  • ApplicationSet Controller: Gestiona los recursos ApplicationSet que generan múltiples Applications a partir de una sola definición.

# Loop de reconciliación de ArgoCD (corre cada 3 minutos por defecto):
# 1. El Application Controller lee el CRD Application
# 2. Le pide al Repo Server que obtenga y renderice manifiestos desde Git
# 3. El Controller compara los manifiestos renderizados con el estado vivo del cluster
# 4. Si difieren:
#    - Con auto-sync: el Controller aplica los cambios
#    - Sin auto-sync: el Controller marca la app como OutOfSync
# 5. El Controller actualiza el estado del Application, el loop se repite

Instalando ArgoCD

El enfoque recomendado es usando Helm. Creá el namespace e instalá:


kubectl create namespace argocd
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

Acá hay un archivo de valores listo para producción:


# argocd-values.yaml
configs:
  params:
    server.insecure: true
    timeout.reconciliation: 180s
  cm:
    statusbadge.enabled: "true"
    kustomize.buildOptions: "--enable-helm"

server:
  replicas: 2
  ingress:
    enabled: true
    ingressClassName: nginx
    hostname: argocd.example.com
    tls: true

controller:
  resources:
    requests:
      cpu: 250m
      memory: 512Mi
    limits:
      memory: 1Gi

repoServer:
  replicas: 2
  resources:
    requests:
      cpu: 100m
      memory: 256Mi
    limits:
      memory: 512Mi

helm install argocd argo/argo-cd \
  --namespace argocd \
  --values argocd-values.yaml \
  --wait

# Obtener la contraseña inicial de admin
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d

# Login y cambiar contraseña
argocd login argocd.example.com --username admin --password <tu-contraseña>
argocd account update-password

Application CRDs

El CRD Application es el bloque fundamental. Define qué desplegar, dónde y cómo mantenerlo sincronizado:


apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/kainlite/my-app-manifests
    targetRevision: main
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app
  syncPolicy:
    automated:
      prune: true       # Borrar recursos que ya no están en Git
      selfHeal: true    # Revertir cambios manuales
    syncOptions:
      - CreateNamespace=true
      - PruneLast=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas   # Ignorar si el HPA gestiona las réplicas

  • source: De dónde obtener los manifiestos. repoURL, targetRevision (rama/tag) y path (directorio dentro del repo).
  • destination: Dónde desplegar. server es el endpoint de la API de Kubernetes, namespace es el namespace destino.
  • syncPolicy: Cómo mantener las cosas sincronizadas. automated habilita auto-sync, prune borra recursos eliminados, selfHeal revierte cambios manuales.
  • ignoreDifferences: Campos a ignorar al comparar estado deseado vs. vivo, útil para campos seteados dinámicamente por el cluster.

El patrón App of Apps

Cuando tenés muchas aplicaciones, gestionar cada recurso Application individualmente se vuelve tedioso. El patrón App of Apps crea un Application padre que gestiona manifiestos de Applications hijo.


# Estructura del repositorio
gitops-repo/
├── apps/                          # El app padre apunta acá
│   ├── my-app.yaml               # Manifiestos Application hijo
│   ├── monitoring.yaml
│   ├── cert-manager.yaml
│   └── ingress-nginx.yaml
├── my-app/
│   ├── base/
│   └── overlays/
│       ├── staging/
│       └── production/
└── monitoring/

El Application padre:


apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-of-apps
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/kainlite/gitops-repo
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Las aplicaciones hijo usan anotaciones sync-wave para controlar el orden de despliegue. Componentes de infraestructura tienen wave 0, cargas de trabajo de aplicación tienen wave 2:


# apps/cert-manager.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: "0"  # Desplegar infraestructura primero
spec:
  project: default
  source:
    repoURL: https://charts.jetstack.io
    chart: cert-manager
    targetRevision: v1.16.3
    helm:
      releaseName: cert-manager
      values: |
        installCRDs: true
  destination:
    server: https://kubernetes.default.svc
    namespace: cert-manager
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Estrategias de sincronización

ArgoCD te da control granular sobre cómo y cuándo ocurren los syncs. Un patrón común es auto-sync para staging y manual para producción:


# Auto-sync: los cambios se aplican automáticamente
syncPolicy:
  automated:
    prune: true      # Borrar recursos removidos de Git
    selfHeal: true   # Revertir cambios manuales en el cluster

# Sync manual: omitir la sección automated
syncPolicy:
  syncOptions:
    - CreateNamespace=true

Las políticas de reintento manejan fallos transitorios:


syncPolicy:
  retry:
    limit: 5
    backoff:
      duration: 5s
      factor: 2
      maxDuration: 3m

Las ventanas de sync restringen cuándo ArgoCD puede sincronizar, útil para congelamientos de cambios:


# En el spec del AppProject
spec:
  syncWindows:
    - kind: allow
      schedule: "0 9 * * 1-5"   # Lun-Vie a las 9am
      duration: 8h
      applications: ["*"]
    - kind: deny
      schedule: "0 0 20 12 *"   # Congelamiento de fiestas
      duration: 336h
      applications: ["*"]
      clusters: ["production"]

Health checks y health personalizado

ArgoCD tiene health checks integrados para recursos estándar de Kubernetes. Para recursos custom (CRDs), podés escribir scripts de health check en Lua:


  • Healthy: El recurso está operando correctamente
  • Progressing: Todavía no está saludable pero está progresando
  • Degraded: El recurso tiene un error
  • Suspended: El recurso está pausado
  • Missing: El recurso no existe

# Health check personalizado para Certificate de cert-manager (en ConfigMap argocd-cm)
resource.customizations.health.cert-manager.io_Certificate: |
  hs = {}
  if obj.status ~= nil then
    if obj.status.conditions ~= nil then
      for i, condition in ipairs(obj.status.conditions) do
        if condition.type == "Ready" and condition.status == "False" then
          hs.status = "Degraded"
          hs.message = condition.message
          return hs
        end
        if condition.type == "Ready" and condition.status == "True" then
          hs.status = "Healthy"
          hs.message = condition.message
          return hs
        end
      end
    end
  end
  hs.status = "Progressing"
  hs.message = "Waiting for certificate"
  return hs

Patrones de rollback

Una de las mayores ventajas de GitOps es que el rollback es simplemente un git revert. ArgoCD también provee sus propios mecanismos de rollback para emergencias.


# La forma GitOps: revertir el commit en Git
git revert HEAD --no-edit
git push
# ArgoCD detecta el cambio y sincroniza automáticamente

# Rollback basado en historial de ArgoCD
argocd app history my-app
argocd app rollback my-app 2
# Nota: esto no revierte Git, así que auto-sync eventualmente va a re-aplicar
# Deshabilitá auto-sync primero o también revertí en Git

Gestión multi-cluster con ApplicationSets

Los ApplicationSets generan múltiples Applications a partir de un template usando generadores. En vez de crear manualmente un Application para cada cluster, definís un template y un generador que produce las variaciones.


Generador list: Proveé conjuntos de parámetros explícitos:


apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - cluster: staging
            url: https://staging-api.example.com
          - cluster: production
            url: https://production-api.example.com
  template:
    metadata:
      name: "my-app-{{cluster}}"
    spec:
      project: default
      source:
        repoURL: https://github.com/kainlite/gitops-repo
        targetRevision: main
        path: "my-app/overlays/{{cluster}}"
      destination:
        server: "{{url}}"
        namespace: my-app
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Generador cluster: Crea Applications automáticamente para cada cluster que matchea:


apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: monitoring-stack
  namespace: argocd
spec:
  generators:
    - clusters:
        selector:
          matchLabels:
            environment: production
  template:
    metadata:
      name: "monitoring-{{name}}"
    spec:
      project: monitoring
      source:
        repoURL: https://github.com/kainlite/gitops-repo
        targetRevision: main
        path: monitoring
      destination:
        server: "{{server}}"
        namespace: monitoring

Generador git: Crea Applications basándose en la estructura de directorios o archivos de config:


apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: team-apps
  namespace: argocd
spec:
  generators:
    - git:
        repoURL: https://github.com/kainlite/gitops-repo
        revision: main
        directories:
          - path: "teams/*/apps/*"
  template:
    metadata:
      name: "{{path.basename}}"
    spec:
      project: default
      source:
        repoURL: https://github.com/kainlite/gitops-repo
        targetRevision: main
        path: "{{path}}"
      destination:
        server: https://kubernetes.default.svc
        namespace: "{{path.basename}}"

Integración con Kustomize y Helm

ArgoCD soporta nativamente tanto Kustomize como Helm. Renderiza los manifiestos en el momento del sync, así que no necesitás correr estas herramientas en tu pipeline de CI.


Para Kustomize, simplemente apuntá el source del Application al directorio del overlay:


# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
patches:
  - target:
      kind: Deployment
      name: my-app
    patch: |
      - op: replace
        path: /spec/replicas
        value: 3
images:
  - name: kainlite/my-app
    newTag: v1.2.3

Para charts de Helm desde un repositorio de charts:


apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus
  namespace: argocd
spec:
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: kube-prometheus-stack
    targetRevision: 67.9.0
    helm:
      releaseName: prometheus
      values: |
        prometheus:
          prometheusSpec:
            retention: 30d
            storageSpec:
              volumeClaimTemplate:
                spec:
                  resources:
                    requests:
                      storage: 50Gi
        grafana:
          enabled: true
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring
  syncPolicy:
    syncOptions:
      - ServerSideApply=true

RBAC y SSO

Los proyectos son el mecanismo principal para restringir acceso. Cada proyecto define qué repositorios, clusters y namespaces puede usar una aplicación:


apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: payments-team
  namespace: argocd
spec:
  description: "Proyecto del equipo de pagos"
  sourceRepos:
    - "https://github.com/kainlite/payments-*"
  destinations:
    - server: https://kubernetes.default.svc
      namespace: "payments-*"
  namespaceResourceWhitelist:
    - group: "apps"
      kind: Deployment
    - group: ""
      kind: Service
    - group: ""
      kind: ConfigMap
    - group: ""
      kind: Secret
  roles:
    - name: developer
      policies:
        - p, proj:payments-team:developer, applications, get, payments-team/*, allow
        - p, proj:payments-team:developer, applications, sync, payments-team/*, allow
      groups:
        - payments-developers
    - name: admin
      policies:
        - p, proj:payments-team:admin, applications, *, payments-team/*, allow
      groups:
        - payments-admins

SSO con OIDC:


# En el ConfigMap argocd-cm
oidc.config: |
  name: Keycloak
  issuer: https://keycloak.example.com/realms/engineering
  clientID: argocd
  clientSecret: $oidc.keycloak.clientSecret
  requestedScopes: ["openid", "profile", "email", "groups"]

# En el ConfigMap argocd-rbac-cm
policy.default: role:readonly
policy.csv: |
  g, platform-admins, role:admin
  g, payments-developers, proj:payments-team:developer
  p, role:readonly, applications, get, */*, allow

Notificaciones

ArgoCD Notifications envía alertas cuando ocurren eventos de sync. Viene incluido en el chart de Helm desde ArgoCD 2.6+:


# ConfigMap argocd-notifications-cm
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token

  template.app-sync-succeeded: |
    slack:
      attachments: |
        [{"color": "#18be52", "title": "{{.app.metadata.name}} se sincronizó exitosamente",
          "fields": [
            {"title": "Revisión", "value": "{{.app.status.sync.revision}}", "short": true},
            {"title": "Namespace", "value": "{{.app.spec.destination.namespace}}", "short": true}
          ]}]

  template.app-sync-failed: |
    slack:
      attachments: |
        [{"color": "#E96D76", "title": "{{.app.metadata.name}} sync FALLÓ",
          "fields": [
            {"title": "Error", "value": "{{range .app.status.conditions}}{{.message}}{{end}}"}
          ]}]

  trigger.on-sync-succeeded: |
    - when: app.status.operationState.phase in ['Succeeded']
      send: [app-sync-succeeded]
  trigger.on-sync-failed: |
    - when: app.status.operationState.phase in ['Error', 'Failed']
      send: [app-sync-failed]

Suscribí aplicaciones a notificaciones con anotaciones:


metadata:
  annotations:
    notifications.argoproj.io/subscribe.on-sync-succeeded.slack: deployments
    notifications.argoproj.io/subscribe.on-sync-failed.slack: deployments-alerts

Monitoreando ArgoCD en sí mismo

ArgoCD expone métricas de Prometheus listas para usar. Acá están las métricas clave para observar:


  • argocd_app_info: Gauge con estado de sync y health por aplicación
  • argocd_app_sync_total: Counter de operaciones de sync (rastreá la frecuencia de despliegues)
  • argocd_app_reconcile_bucket: Histograma de duración de reconciliación
  • argocd_git_request_total: Counter de requests Git (fallos significan que ArgoCD no puede llegar a tus repos)
  • argocd_cluster_api_resource_objects: Gauge de objetos rastreados por cluster (planificación de memoria)

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: argocd-alerts
  namespace: monitoring
spec:
  groups:
    - name: argocd.rules
      rules:
        - alert: ArgoCDAppOutOfSync
          expr: argocd_app_info{sync_status="OutOfSync"} == 1
          for: 30m
          labels:
            severity: warning
          annotations:
            summary: "La app de ArgoCD {{ $labels.name }} fuera de sync por 30m+"

        - alert: ArgoCDAppUnhealthy
          expr: argocd_app_info{health_status!~"Healthy|Progressing"} == 1
          for: 15m
          labels:
            severity: critical
          annotations:
            summary: "La app de ArgoCD {{ $labels.name }} está {{ $labels.health_status }}"

        - alert: ArgoCDSyncFailing
          expr: increase(argocd_app_sync_total{phase!="Succeeded"}[1h]) > 3
          labels:
            severity: critical
          annotations:
            summary: "Más de 3 syncs fallidos en 1h para {{ $labels.name }}"

        - alert: ArgoCDGitFetchErrors
          expr: increase(argocd_git_request_total{request_type="fetch", result="error"}[10m]) > 5
          labels:
            severity: warning
          annotations:
            summary: "ArgoCD no puede fetchear de los repositorios Git"

Asegurate de que Prometheus scrapee las métricas de ArgoCD:


apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: argocd-metrics
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app.kubernetes.io/part-of: argocd
  namespaceSelector:
    matchNames: [argocd]
  endpoints:
    - port: metrics
      interval: 30s

Notas finales

GitOps con ArgoCD te da un flujo de trabajo de despliegue que es auditable, repetible y se auto-repara. Al tratar a Git como la única fuente de verdad y dejar que un controlador maneje la reconciliación, eliminás toda una clase de problemas relacionados con el drift de configuración y los despliegues manuales. La combinación de Application CRDs, el patrón App of Apps, ApplicationSets y RBAC apropiado te da una base sólida para gestionar desde un solo cluster hasta una flota de clusters a través de múltiples entornos.


Este artículo continúa la serie de SRE donde venimos construyendo las prácticas y herramientas necesarias para correr sistemas confiables. GitOps es el pegamento que une todo, porque todas las definiciones de SLO, configuraciones de monitoreo y cambios de infraestructura que cubrimos en artículos anteriores deberían fluir a través de Git y ser reconciliados por ArgoCD.


¡Espero que te haya resultado útil y lo hayas disfrutado! ¡Hasta la próxima!


Errata

Si encontrás algún error o tenés alguna sugerencia, por favor mandame un mensaje para que se corrija.

También podés revisar el código fuente y los cambios en las fuentes acá



$ Comentarios

Online: 0

Por favor inicie sesión para poder escribir comentarios.

2026-03-09 | Gabriel Garrido