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

  1. Argo CD — Automated Sync Policy — el mecanismo de sincronización automática que esta lección ejecuta por primera vez.
  2. Argo CD — Health — documentación oficial de cómo ArgoCD calcula el estado de salud de cada tipo de recurso, incluido HorizontalPodAutoscaler.
  3. Kubernetes — Horizontal Pod Autoscaling — referencia del mecanismo detrás del diagnóstico del Paso 4.
  4. kubernetes-and-eks-in-production-guide (NIEVA), Módulo 4, lección 8 — la nota original sobre metrics-server y la recreación del clúster que esta lección retoma con evidencia nueva.