Un viernes por la tarde, hace unos 14 meses, desplegué la versión incorrecta de nuestro servicio de pagos en producción. No fue un error de código — fue un error de proceso. Corrí kubectl apply -f . desde mi laptop con cambios locales sin commitear, y durante 23 minutos el sistema respondió con errores 500. Cuatro personas en el equipo, silencio total en Slack, y yo mirando los logs sin saber exactamente qué había pasado.
Esa semana empecé a investigar GitOps de verdad.
Por qué terminé con ArgoCD y no con Flux
Revisé las dos opciones principales: Flux v2 y ArgoCD. Ambas resuelven el mismo problema fundamental — que el estado de tu clúster refleje lo que está en Git, no lo que alguien corrió desde su terminal. Pero hay diferencias que importan dependiendo de tu contexto.
Flux es más "nativo de Kubernetes" en el sentido de que todo son CRDs y controladores pequeños. Si te gusta componer piezas, Flux tiene esa filosofía. ArgoCD viene con una UI bastante buena out of the box, un modelo de Application más explícito, y — honestamente — más documentación de comunidad cuando algo falla a las 2am.
Para un equipo pequeño como el mío, la UI fue el factor decisivo. No porque sea indispensable, sino porque cuando alguien que no vive en la terminal necesita saber el estado de un despliegue, puede abrir un navegador en vez de pedirme que corra kubectl. Eso vale mucho.
(Hay una versión de este artículo donde termino eligiendo Flux. Depende mucho de si tu equipo ya tiene familiaridad con el modelo operacional de cada herramienta.)
Instalación: la parte que los tutoriales resumen en dos líneas
La mayoría de guías hacen esto:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Y sí, funciona. Pero hay problemas específicos que me encontré en mi setup (EKS en AWS, con un ALB Controller ya instalado) que me hubiera gustado saber antes.
Primero: si estás en un clúster con restricciones de red, el pod argocd-server va a tener problemas para hacer git clone desde dentro del clúster. Esto me tomó dos horas diagnosticar porque los logs no son particularmente descriptivos al respecto. Terminé configurando un egress rule explícito en los security groups.
Segundo: la versión stable del manifiesto no siempre apunta a lo que uno espera. Yo instalé ArgoCD 2.9.3 creyendo instalar algo más reciente. Verifica siempre:
kubectl get pods -n argocd -o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\n"}{end}'
Para producción, yo recomendaría instalar con Helm. Tienes más control sobre los valores y el upgrade path es más limpio:
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
--version 6.7.3 \
--set server.service.type=ClusterIP \
--set configs.params."server\.insecure"=true
El flag server.insecure=true lo usé porque tenía terminación TLS en el ALB, no en ArgoCD directamente. Si tu setup es diferente, probablemente no lo necesitas.
Para acceder al servidor inicialmente:
# Obtén la contraseña inicial del admin
kubectl get secret argocd-initial-admin-secret \
-n argocd \
-o jsonpath="{.data.password}" | base64 -d
# Port-forward para acceder localmente
kubectl port-forward svc/argocd-server -n argocd 8080:443
Una cosa que me sorprendió: la contraseña inicial está en un Secret llamado argocd-initial-admin-secret, que ArgoCD borra automáticamente después de que la cambias por primera vez. Bien pensado, pero me agarró desprevenido cuando fui a buscarla semanas después y no estaba.
Conectando tu repositorio y creando tu primera Application
Aquí es donde GitOps empieza a tomar forma real. La idea: tienes un repositorio con tus manifiestos de Kubernetes (o charts de Helm, o Kustomize), y ArgoCD observa ese repositorio y sincroniza el estado del clúster con él.
Primero, registra tu repositorio. Si es privado — que probablemente lo es — necesitas credenciales:
# Con SSH (mi preferencia)
argocd repo add git@github.com:tu-org/tu-repo-k8s.git \
--ssh-private-key-path ~/.ssh/id_rsa
# O con HTTPS y un token de GitHub
argocd repo add https://github.com/tu-org/tu-repo-k8s.git \
--username tu-usuario \
--password ghp_tu_token_aqui
Luego, creas tu primera Application. Puedes hacerlo desde la UI o con un manifiesto YAML — yo prefiero YAML porque vive en Git (GitOps hasta el final):
# argocd/apps/mi-servicio.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mi-servicio
namespace: argocd
finalizers:
# Borra los recursos del clúster al eliminar esta Application
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/tu-org/tu-repo-k8s.git
targetRevision: main
path: manifests/mi-servicio
destination:
server: https://kubernetes.default.svc
namespace: mi-servicio
syncPolicy:
automated:
prune: true # Borra recursos que ya no están en Git
selfHeal: true # Revierte cambios manuales en el clúster
syncOptions:
- CreateNamespace=true
El campo selfHeal: true es el que hace que GitOps sea GitOps de verdad. Si alguien (yo, a las 11pm, "solo para probar algo rápido") hace un kubectl edit directamente en el clúster, ArgoCD lo detecta y lo revierte. Al principio esto molesta. Después lo agradeces.
prune: true también merece atención. Con esta opción, si eliminas un manifiesto del repositorio, ArgoCD elimina el recurso correspondiente del clúster. Sin ella, los recursos huérfanos se acumulan silenciosamente. Lo aprendí por las malas — tenía Services de versiones anteriores flotando por ahí sin hacer nada útil.
El error que me costó tres horas un martes por la noche
Aquí va la parte que me da un poco de vergüenza documentar, pero que probablemente le ahorrará tiempo a alguien.
Configuré ArgoCD con sync automático habilitado y prune: true. Funcionó perfectamente durante semanas. Luego, durante una refactorización, moví varios manifiestos de directorio — básicamente reorganicé la estructura de carpetas del repositorio.
ArgoCD detectó que los recursos en las rutas antiguas ya no existían en Git. Con prune: true, los borró. Los recursos en las rutas nuevas todavía no habían sido sincronizados. Por cuatro minutos, varios Deployments estuvieron en cero réplicas.
El problema no fue ArgoCD — el comportamiento es exactamente el documentado. El problema fue que yo no había pensado en el orden de operaciones de una migración de estructura de directorios.
Lo que debería haber hecho:
- Deshabilitar el sync automático antes del PR
- Hacer el merge de la reorganización
- Sincronizar manualmente y verificar que todo esté correcto
- Volver a habilitar el sync automático
Right, so — si vas a hacer cambios estructurales en tu repositorio de manifiestos, trata ese PR como si fuera una migración de base de datos. Con cuidado, con un plan de rollback, y de preferencia no un martes por la noche.
Desde entonces uso syncWindows para limitar cuándo ArgoCD puede sincronizar automáticamente en producción:
# Configurado en el Project de ArgoCD, no en la Application
spec:
syncWindows:
- kind: allow
schedule: '0 9-17 * * 1-5' # Solo horario laboral, lunes a viernes
duration: 8h
applications:
- 'produccion-*'
namespaces:
- produccion
Esto no es para todo el mundo. Si despliegas con mucha frecuencia fuera de horario, va a ser un obstáculo. Pero para nuestro equipo, eliminar los syncs sorpresa a las 3am valió la configuración extra.
Multi-clúster y lo que los docs no enfatizan suficiente
Tenemos tres clústeres: desarrollo, staging y producción. ArgoCD corre en uno solo — producción, con acceso muy restringido — y gestiona los tres. Para registrar un clúster externo:
# Asegúrate de tener el contexto correcto en tu kubeconfig
kubectl config get-contexts
# Registra el clúster de staging
argocd cluster add arn:aws:eks:us-east-1:123456789:cluster/staging \
--name staging
Lo que los docs no enfatizan suficientemente: el ServiceAccount que ArgoCD crea en el clúster remoto tiene permisos de cluster-admin por defecto. Para empezar está bien, pero en producción querrás ajustar eso. Yo no lo hice hasta que alguien de seguridad me preguntó explícitamente — mejor enterarse antes de que te lo pregunten.
Una cosa que todavía no tengo completamente resuelta: la gestión de secrets entre entornos. Usamos AWS Secrets Manager con el External Secrets Operator, pero la integración con ArgoCD a veces produce sync states confusos cuando el external secret tarda en popularse. No es exactamente un bug — es más una cuestión de timing y de cómo ArgoCD evalúa el health de los recursos. No estoy 100% seguro de que nuestra solución actual escale bien más allá de los 12 servicios que tenemos ahora. Si alguien tiene una solución elegante para esto, genuinamente me interesa saberlo.
Lo que recomendaría hoy
Después de usar esto en producción por más de un año con un equipo de 4 personas y unos 12 servicios, esto es lo que diría:
Empieza sin sync automático. Dos semanas manejando sincronización manual te enseñan más que cualquier tutorial — entiendes qué hace prune, cómo leer el diff que ArgoCD muestra antes de aplicar, y por qué el selfHeal eventualmente te va a salvar de algo. Habilita el sync automático solo cuando confíes en tus manifiestos y en tu proceso de revisión de PRs. No antes.
Usa ApplicationSets si tienes más de tres o cuatro Applications con estructura similar. Te ahorras repetición y los cambios de configuración global son mucho más manejables. Tardé demasiado en adoptar esto.
Invierte tiempo en el control de acceso desde el principio — no después. ArgoCD tiene RBAC basado en proyectos con políticas de Casbin: flexible, pero bastante más difícil de retrofitear cuando ya tienes diez personas tocando el sistema y no quieres romper nada mientras ajustas permisos.
Y sobre el debate ArgoCD vs Flux: si tu equipo usa la UI para operaciones del día a día, ArgoCD. Si todo el mundo vive en la terminal y prefiere componer controladores pequeños, Flux. No hay una respuesta universal, pero sí hay una respuesta correcta para tu contexto específico.
Para mi equipo, ArgoCD fue la decisión correcta. El viernes siguiente al incidente del servicio de pagos, hicimos un despliegue que tardó exactamente lo que tardó el merge del PR. Nadie corrió comandos locales. Nadie tuvo que confiar en que alguien había hecho checkout del branch correcto. El historial de despliegues estaba en Git, con el nombre del autor y el mensaje del commit.
Eso, más que cualquier feature específica, es lo que hace que GitOps valga la pena el esfuerzo inicial.
Top comments (0)