SRE: GitOps con ArgoCD
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) ypath(directorio dentro del repo).- destination: Dónde desplegar.
serveres el endpoint de la API de Kubernetes,namespacees el namespace destino.- syncPolicy: Cómo mantener las cosas sincronizadas.
automatedhabilita auto-sync,pruneborra recursos eliminados,selfHealrevierte 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: 0Por favor inicie sesión para poder escribir comentarios.