Módulo 5: Gitops With Argocd
6. Manos a la obra: sincronizando `andes-cargo-status-api` desde Git
Descripción
Esta es la lección donde las tres piezas —Gitea (lección 3), ArgoCD (lección 4), y el Application que la lección 5 diseccionó— se conectan por primera vez. Vas a aplicar un único kubectl apply, y vas a ver a ArgoCD tomar control de los nueve manifiestos que hasta ahora aplicaste tú mismo, a mano, uno por uno, en cuatro módulos distintos. También vas a encontrar un problema real, heredado de una decisión honesta del Módulo 4: metrics-server nunca se reinstaló después de que ese módulo recreó el clúster, y eso deja a ArgoCD reportando el Application completo como Degraded — no por ningún error de este módulo, sino por una condición preexistente del clúster que ArgoCD, correctamente, sí detecta.
Conexión con el módulo
Esta lección es la primera convergencia real de todo el módulo — el momento donde la analogía del termostato de la lección 2 deja de ser una promesa y se convierte en un kubectl get application que puedes correr tú mismo. La lección 8 (proyecto final) construye directamente sobre este estado: el mismo Application, ya sincronizado, recibiendo un cambio real.
Paso 1 — El problema del arranque: quién aplica el primer Application
Antes de aplicar nada, vale la pena resolver una pregunta que la lección 5 dejó pendiente a propósito: si ArgoCD sincroniza todo lo que Git declara, ¿quién sincroniza el propio Application, la primera vez? La respuesta es un patrón estándar de GitOps, no una improvisación de este laboratorio: el primer Application de un clúster siempre se aplica a mano, una sola vez — es el equivalente de encender el termostato por primera vez, un acto que, por definición, no puede depender del propio termostato. Después de ese primer kubectl apply, el Application queda administrado por Git como cualquier otro recurso (de hecho, ya viste esto en la lección 3: application.yaml vive dentro del mismo repositorio andes-cargo-k8s, junto a los otros nueve manifiestos) — cambios futuros a ese mismo archivo sí van a fluir por Git, sin ningún kubectl apply adicional.
EL ARRANQUE DE GitOps, UNA SOLA VEZ
kubectl apply -f application.yaml ← el único paso manual
│ de todo este módulo
▼
Application creado en el clúster
│
▼
A partir de aquí, TODO cambio futuro a application.yaml
(o a cualquiera de los otros nueve manifiestos) fluye
por git push — nunca más un kubectl apply manual
Paso 2 — Aplica el Application
kubectl apply -f application.yaml
Qué esperar (literal, ejecutado):
application.argoproj.io/andes-cargo-status-api created
kubectl get application -n argocd
Qué esperar (literal, ejecutado — inmediatamente después de crearlo, todavía sin evaluar):
NAME SYNC STATUS HEALTH STATUS
andes-cargo-status-api
Las dos columnas están vacías por un instante — ArgoCD acaba de registrar el objeto, pero el primer ciclo de comparación (el bucle de la lección 2) todavía no corrió ni una sola vez.
Paso 3 — El primer resultado: Synced, pero Degraded
Después de unos segundos, vuelve a consultar:
kubectl get application andes-cargo-status-api -n argocd -o jsonpath='{.status.sync.status} {.status.health.status}{"\n"}'
Qué esperar (literal, ejecutado):
Synced Degraded
Synced es la mitad buena: ArgoCD ya aplicó los nueve manifiestos —incluida la corrección de que ya existían en el clúster desde los Módulos 1-4, así que en la práctica no cambió nada, solo "tomó posesión" de recursos que ya coincidían con Git—. Degraded, en cambio, merece investigación:
argocd app get andes-cargo-status-api
Qué esperar (literal, ejecutado — el hash de commit 820515f es tu valor variable, el que corresponda a tu propio git push de la lección 3):
Name: argocd/andes-cargo-status-api
Project: default
Server: https://kubernetes.default.svc
Namespace: andes-cargo
URL: https://localhost:8080/applications/andes-cargo-status-api
Source:
- Repo: http://gitea-http.gitea.svc.cluster.local:3000/andes-cargo/andes-cargo-k8s.git
Target: main
Path: .
SyncWindow: Sync Allowed
Sync Policy: Automated (Prune)
Sync Status: Synced to main (820515f)
Health Status: Degraded
GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
Namespace andes-cargo andes-cargo Running Synced namespace/andes-cargo configured
networking.k8s.io NetworkPolicy andes-cargo default-deny-ingress Synced networkpolicy.networking.k8s.io/default-deny-ingress configured
networking.k8s.io NetworkPolicy andes-cargo allow-from-ingress-nginx Synced networkpolicy.networking.k8s.io/allow-from-ingress-nginx configured
Secret andes-cargo andes-cargo-status-api-secrets Synced secret/andes-cargo-status-api-secrets configured
ConfigMap andes-cargo andes-cargo-status-api-config Synced configmap/andes-cargo-status-api-config configured
Service andes-cargo status-api-service Synced Healthy service/status-api-service configured
apps Deployment andes-cargo andes-cargo-status-api Synced Healthy deployment.apps/andes-cargo-status-api configured
autoscaling HorizontalPodAutoscaler andes-cargo andes-cargo-status-api-hpa Synced Degraded horizontalpodautoscaler.autoscaling/andes-cargo-status-api-hpa configured
networking.k8s.io Ingress andes-cargo status-api-ingress Synced Healthy ingress.networking.k8s.io/status-api-ingress configured
argoproj.io Application argocd andes-cargo-status-api Synced application.argoproj.io/andes-cargo-status-api configured
Namespace andes-cargo Synced
Ahí está la fuente exacta: HorizontalPodAutoscaler andes-cargo-status-api-hpa es el único recurso con HEALTH: Degraded. Todo lo demás —Deployment, Service, Ingress— está Healthy. Application como un todo hereda el peor estado de salud de cualquiera de sus recursos, así que un solo HPA degradado alcanza para que el Application completo se reporte como Degraded, aunque andes-cargo-status-api esté sirviendo tráfico perfectamente.
Paso 4 — El diagnóstico: la misma causa que ya viste en el Módulo 4
kubectl describe hpa andes-cargo-status-api-hpa -n andes-cargo
Qué esperar (literal, ejecutado — el fragmento relevante):
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True SucceededGetScale the HPA controller was able to get the target's current scale
ScalingActive False FailedGetResourceMetric the HPA was unable to compute the replica count: failed to get cpu utilization: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)
Este es el mismo hallazgo que el proyecto del Módulo 4 ya documentó, con una nota explícita: "HorizontalPodAutoscaler muestra cpu: <unknown>/50% porque este clúster se recreó en la lección 4 y metrics-server (Módulo 3, lección 7) todavía no se reinstaló". ArgoCD no inventó ningún problema nuevo — simplemente es la primera herramienta de este laboratorio que traduce esa condición preexistente en un estado de salud visible a nivel de aplicación completa, en vez de dejarla escondida dentro de un kubectl describe hpa que nadie corre a menos que sospeche algo.
Corrígelo instalando metrics-server, exactamente como el Módulo 3 ya explicó:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl rollout status deployment/metrics-server -n kube-system --timeout=120s
Qué esperar (literal, ejecutado — el --kubelet-insecure-tls es el mismo gotcha que ya documentó el Módulo 3, lección 7, por los certificados autofirmados de kind):
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
deployment.apps/metrics-server patched
Waiting for deployment "metrics-server" rollout to finish: 1 old replicas are pending termination...
deployment "metrics-server" successfully rolled out
Espera unos veinte segundos —metrics-server necesita un par de ciclos de recolección antes de reportar valores reales— y confirma:
kubectl get hpa -n andes-cargo
kubectl get application andes-cargo-status-api -n argocd -o jsonpath='{.status.sync.status} {.status.health.status}{"\n"}'
Qué esperar (literal, ejecutado):
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
andes-cargo-status-api-hpa Deployment/andes-cargo-status-api cpu: 1%/50% 2 6 3 76m
Synced Healthy
cpu: 1%/50%, un número real en vez de <unknown> — y Application completo pasa de Degraded a Healthy, sin que nadie haya tocado ningún manifiesto de Git. ArgoCD reevalúa la salud de cada recurso en cada ciclo de comparación, con total independencia de si sincronizó algo nuevo o no.
Paso 5 — Confirma el estado completo, con las dos herramientas
argocd app list
Qué esperar (literal, ejecutado):
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
argocd/andes-cargo-status-api https://kubernetes.default.svc andes-cargo default Synced Healthy Auto-Prune <none> http://gitea-http.gitea.svc.cluster.local:3000/andes-cargo/andes-cargo-k8s.git . main
kubectl get all -n andes-cargo
Qué esperar (literal, ejecutado — nombres de Pod con sufijo hash y AGE son tus valores variables; el resto, incluido el patrón de tres réplicas, es literal en este punto del módulo):
NAME READY STATUS RESTARTS AGE
pod/andes-cargo-status-api-548966dd97-bpqjq 1/1 Running 0 76m
pod/andes-cargo-status-api-548966dd97-ls2bc 1/1 Running 0 76m
pod/andes-cargo-status-api-548966dd97-z7bpd 1/1 Running 0 41s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/status-api-service ClusterIP 10.96.239.125 <none> 80/TCP 79m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/andes-cargo-status-api 3/3 3 3 79m
NAME DESIRED CURRENT READY AGE
replicaset.apps/andes-cargo-status-api-548966dd97 3 3 3 79m
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
horizontalpodautoscaler.autoscaling/andes-cargo-status-api-hpa Deployment/andes-cargo-status-api cpu: 1%/50% 2 6 3 79m
argocd app list (la vista de ArgoCD) y kubectl get all (la vista nativa de Kubernetes) describen exactamente el mismo estado, desde dos ángulos distintos — ArgoCD nunca reemplaza la API de Kubernetes, solo agrega una capa de comparación continua encima de ella. Esta es la primera vez, en toda esta guía, que el estado del namespace andes-cargo refleja el contenido de un repositorio Git, no el historial de comandos que corriste tú mismo.
Errores comunes
Alarmarse por Degraded sin leer qué recurso específico lo causa (de flujo, el error más probable en esta lección). Qué pasa: alguien ve Health Status: Degraded en la salida general y asume que algo se rompió gravemente, sin revisar la tabla de recursos que sigue debajo. Cómo detectarlo: si tu primera reacción fue buscar qué manifiesto está mal escrito, en vez de mirar la columna HEALTH de argocd app get. Cómo corregirlo: la tabla de recursos siempre señala exactamente qué objeto causa el estado degradado — en esta lección, un único HorizontalPodAutoscaler, con una causa ya conocida desde el Módulo 4. Revisa siempre esa tabla antes de asumir un problema nuevo.
Aplicar application.yaml una segunda vez pensando que "no se sincronizó" (de impaciencia, el mismo patrón de la lección 2 sobre la latencia de sondeo). Qué pasa: alguien, al ver que SYNC STATUS sigue vacío inmediatamente después del Paso 2, corre kubectl apply -f application.yaml de nuevo. Cómo detectarlo: si no esperaste al menos unos segundos antes de repetir el comando. Cómo corregirlo: el primer ciclo de comparación de un Application recién creado tarda un momento en correr — repetir kubectl apply no acelera nada (el objeto ya existe, Kubernetes simplemente confirma que no cambió nada) y puede generar confusión sobre si el Application se creó una o dos veces. Espera al Paso 3 antes de investigar.
Olvidar que metrics-server es infraestructura del laboratorio, no parte del repositorio andes-cargo-k8s (de alcance, importa para el resto de esta guía). Qué pasa: alguien busca metrics-server dentro de andes-cargo-k8s y no lo encuentra, o se pregunta por qué ArgoCD no lo instaló solo. Cómo detectarlo: si esperabas ver un manifiesto de metrics-server en el repositorio de Gitea. Cómo corregirlo: metrics-server vive en kube-system, es infraestructura del propio clúster (como ingress-nginx o local-path-storage), no un recurso de negocio de Andes Cargo — el Application de este módulo solo administra lo que vive en andes-cargo, definido explícitamente por destination.namespace en la lección 5.
Ejercicios
Ejercicio 1 — Explica por qué Degraded no significa "ArgoCD encontró un error en tu YAML". En dos o tres frases, explica a un colega la diferencia entre Sync Status: Synced (que sí confirmaste en esta lección) y Health Status: Degraded (que también confirmaste) — ¿por qué pueden ser ciertos los dos al mismo tiempo?
Ver solución
Una explicación razonable: "Sync Status responde '¿el clúster tiene exactamente lo que Git declara?' — y la respuesta fue sí, ArgoCD aplicó los nueve manifiestos sin ningún error de sintaxis ni de permisos. Health Status responde una pregunta distinta: '¿los recursos que ya existen están funcionando bien?' — y ahí la respuesta fue no, porque el HorizontalPodAutoscaler no podía leer métricas de CPU, por una condición del clúster (falta de metrics-server) que no tiene nada que ver con si el YAML estaba bien escrito. Un Application puede estar perfectamente sincronizado con Git y, al mismo tiempo, degradado en salud, porque son dos preguntas distintas."
Ejercicio 2 — Diagnostica sin argocd app get. Si solo tuvieras kubectl disponible (sin el CLI de argocd), ¿qué comando usarías para llegar al mismo diagnóstico del Paso 4 —que el HorizontalPodAutoscaler es la causa del estado Degraded—?
Ver solución
kubectl get hpa -n andes-cargo mostraría cpu: <unknown>/50% en vez de un porcentaje real —la misma señal que ya viste en el Módulo 4—, y kubectl describe hpa andes-cargo-status-api-hpa -n andes-cargo mostraría la condición ScalingActive: False con el mensaje FailedGetResourceMetric, exactamente el mismo texto que capturó el Paso 4. argocd app get no descubre nada que kubectl no pudiera mostrar por su cuenta — lo que agrega es la vista consolidada, que junta el estado de los nueve recursos en un solo lugar, sin que tengas que revisar cada uno por separado.
Ejercicio 3 — Predice qué pasaría si borraras andes-cargo-status-api-hpa a mano ahora mismo. Con selfHeal: true activo (lección 5) y el Application ya sincronizado (esta lección), si corrieras kubectl delete hpa andes-cargo-status-api-hpa -n andes-cargo, ¿qué esperarías ver segundos después?
Ver solución
El HorizontalPodAutoscaler reaparecería solo, recreado por ArgoCD — el mismo mecanismo que la lección 5 demostró con kubectl scale, aplicado aquí a un borrado completo en vez de a un cambio de campo. selfHeal: true no distingue entre "alguien cambió un valor" y "alguien borró el recurso entero": en ambos casos, el estado real del clúster dejó de coincidir con lo que hpa.yaml declara en Git, y ArgoCD corrige la diferencia en su próximo ciclo de comparación, sin que nadie ejecute kubectl apply ni git push.
Resumen y siguiente paso
Esta lección aplicó el único paso manual de todo este módulo —kubectl apply -f application.yaml, el arranque necesario que ningún GitOps puede evitarse a sí mismo— y confirmó, con evidencia literal, la primera convergencia real: nueve manifiestos, administrados desde ese momento por ArgoCD en vez de por tu historial de comandos. También encontraste, diagnosticaste y corregiste un problema real (metrics-server ausente, heredado de una decisión honesta del Módulo 4), que dejó al Application en Degraded hasta que lo resolviste — la primera vez, en esta guía, que una herramienta de GitOps traduce una condición del clúster en un estado de salud visible a nivel de aplicación completa.
Antes de avanzar deberías poder: explicar por qué el primer Application de un clúster siempre se aplica a mano; distinguir Sync Status de Health Status con un ejemplo propio; y diagnosticar, con argocd app get o solo con kubectl, qué recurso específico causa un estado Degraded.
Siguiente lección: estrategias de despliegue. Ahí conoces, con precisión técnica, cómo Kubernetes reemplaza Pods cuando el Deployment cambia —RollingUpdate, ya configurado en deployment.yaml desde el Módulo 2— frente a lo que un clúster necesitaría agregar para blue/green o canary, antes del proyecto final del módulo.
Recursos
- Argo CD — Automated Sync Policy — el mecanismo de sincronización automática que esta lección ejecuta por primera vez.
- Argo CD — Health — documentación oficial de cómo ArgoCD calcula el estado de salud de cada tipo de recurso, incluido
HorizontalPodAutoscaler. - Kubernetes — Horizontal Pod Autoscaling — referencia del mecanismo detrás del diagnóstico del Paso 4.
kubernetes-and-eks-in-production-guide(NIEVA), Módulo 4, lección 8 — la nota original sobremetrics-servery la recreación del clúster que esta lección retoma con evidencia nueva.