Módulo 5: Gitops With Argocd

3. Manos a la obra: Gitea en el clúster

Descripción

Esta lección instala la primera pieza real del mecanismo que la lección 2 explicó: un servidor Git completo, corriendo dentro de andes-cargo-cluster, con el Helm chart oficial del proyecto Gitea. Al final, vas a tener un repositorio (andes-cargo/andes-cargo-k8s) con los nueve manifiestos heredados de los Módulos 1-4, subidos con git push real — la "libreta" que la analogía del termostato de la lección anterior necesita antes de que exista ningún termostato que la lea. Todo lo que sigue corrió de verdad, incluida una verificación de dominio que hizo falta resolver antes del primer comando: la documentación de Gitea cambió, en algún punto, el dominio de su Helm chart.

Conexión con el módulo

Esta lección construye la mitad "Git" del mecanismo de la lección 2. La lección 4 construye la otra mitad —ArgoCD—, y la lección 6 conecta ambas por primera vez.


Paso 0 — Verifica el dominio del Helm chart antes de instalar nada

Antes de escribir un solo helm install, esta guía verificó algo que no se puede asumir del entrenamiento de un modelo de lenguaje ni de una guía vieja: el dominio exacto donde vive el Helm chart oficial de Gitea. Proyectos open-source migran de dominio con el tiempo —y Gitea lo hizo—, así que un comando copiado de una fuente desactualizada falla en el primer paso, con un mensaje de error que no dice "dominio equivocado" de forma obvia.

La documentación oficial de Gitea, verificada contra docs.gitea.com/installation/install-on-kubernetes al momento de escribir esta lección, confirma el comando exacto:

helm repo add gitea-charts https://dl.gitea.com/charts/

dl.gitea.com, no dl.gitea.io. El proyecto migró su dominio de distribución de .io a .com — si buscas ejemplos de Gitea en internet, vas a encontrar los dos, y solo uno funciona hoy. Confírmalo tú mismo antes de seguir:

helm repo add gitea-charts https://dl.gitea.com/charts/
helm repo update gitea-charts
helm search repo gitea-charts/gitea --versions

Qué esperar (literal, ejecutado — la versión exacta del chart es tu valor variable si Gitea publicó una versión más nueva desde que se escribió esta lección):

"gitea-charts" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "gitea-charts" chart repository
Update Complete. ⎈Happy Helming!⎈
NAME              	CHART VERSION	APP VERSION 	DESCRIPTION
gitea-charts/gitea	12.7.0       	1.27.0      	Gitea Helm chart for Kubernetes

Chart 12.7.0, aplicación Gitea 1.27.0 — las versiones reales que instala el resto de esta lección.


Paso 1 — El values.yaml de este laboratorio, y el motivo de cada línea

Un laboratorio de $0 no necesita alta disponibilidad ni una base de datos externa: andes-cargo-cluster corre en tu máquina, no en producción. Este values.yaml desactiva explícitamente PostgreSQL (la base de datos que el chart instala por defecto) y las dependencias de Redis, para quedarte con la base de datos embebida más simple que Gitea soporta:

# gitea-values.yaml
gitea:
  admin:
    username: "andes-cargo"
    password: "AndesCargo2026!"
    email: "platform@andes-cargo.example"
  config:
    server:
      OFFLINE_MODE: true
      ROOT_URL: "http://gitea-http.gitea.svc.cluster.local:3000/"
    database:
      DB_TYPE: sqlite3
    session:
      PROVIDER: memory
    cache:
      ADAPTER: memory
    queue:
      TYPE: level
postgresql:
  enabled: false
postgresql-ha:
  enabled: false
persistence:
  enabled: true
  size: 1Gi
redis-cluster:
  enabled: false
valkey-cluster:
  enabled: false
  • gitea.admin — el chart crea este usuario administrador en el primer arranque; vas a usarlo en el Paso 3 para crear el repositorio vía API.
  • ROOT_URL — el nombre DNS interno que Gitea usa para construir enlaces dentro de su propia UI. gitea-http.gitea.svc.cluster.local:3000 es el nombre de Service estándar de Kubernetes (<service>.<namespace>.svc.cluster.local) — el mismo patrón de nombre DNS que el ConfigMap de andes-cargo-status-api ya usa para el endpoint de LocalStack (Módulo 3), aunque, a diferencia de Gitea, ese Service nunca llegue a instalarse en esta guía.
  • database.DB_TYPE: sqlite3 — la línea que resuelve el primer error real de esta lección (Paso 2). Sin ella, el chart espera una base de datos PostgreSQL externa que este laboratorio nunca declaró.
  • session/cache/queue — las tres, en memoria o en disco local en vez de un servicio externo (Redis). El propio chart advierte, al instalar, que esta configuración "no se recomienda para producción" — una advertencia honesta y correcta que este laboratorio acepta a propósito, por la misma razón que aceptó SQLite: $0, sin servicios externos, para un repositorio con nueve archivos YAML, no para una organización con cientos de desarrolladores.

Paso 2 — El error real que dejó el primer intento, y cómo se corrige

La primera vez que se instaló este chart para escribir esta lección, el values.yaml no tenía la línea database.DB_TYPE: sqlite3 — solo desactivaba PostgreSQL. El resultado, real, fue este:

kubectl create namespace gitea
helm install gitea gitea-charts/gitea --namespace gitea -f gitea-values.yaml --version 12.7.0 --wait --timeout 4m

Qué pasó (literal — el Pod entró en un bucle de reinicio):

NAME                     READY   STATUS       RESTARTS      AGE
gitea-64bdc78cdb-qb77n   0/1     Init:Error   4 (72s ago)   2m4s
kubectl logs -n gitea gitea-64bdc78cdb-qb77n -c configure-gitea --tail=60
==== BEGIN GITEA CONFIGURATION ====
2026/08/14 21:48:21 cmd/helper.go:58:initDB() [F] Database settings are missing from the configuration file: "/data/gitea/conf/app.ini".
Ensure you are running in the correct environment or set the correct configuration file with -c.
If this is the intended configuration file complete the [database] section.
Gitea migrate might fail due to database connection...This init-container will try again in a few seconds

El mensaje es preciso: desactivar postgresql.enabled le dice al chart "no instales una base de datos", pero no le dice qué base de datos usar en su lugar — sin database.DB_TYPE explícito, el init-container configure-gitea no tiene ninguna sección [database] que escribir en app.ini, y gitea migrate (el paso que prepara el esquema de la base de datos) falla antes de arrancar. La corrección es la línea que ya viste en el Paso 1: database.DB_TYPE: sqlite3 le dice, sin ambigüedad, qué motor usar.


Paso 3 — Instala el chart (con el values.yaml corregido)

kubectl create namespace gitea
helm install gitea gitea-charts/gitea \
  --namespace gitea \
  -f gitea-values.yaml \
  --version 12.7.0 \
  --wait --timeout 4m

Qué esperar (literal, ejecutado):

NAME: gitea
LAST DEPLOYED: Fri Aug 14 15:49:05 2026
NAMESPACE: gitea
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
NOTES:
1. Get the application URL by running these commands:
  echo "Visit http://127.0.0.1:3000 to use your application"
  kubectl --namespace gitea port-forward svc/gitea-http 3000:3000
2. Review these warnings:
  - Gitea uses 'memory' for caching which is not recommended for production use. See https://docs.gitea.com/next/admi...
  - Gitea uses 'leveldb' for queue actions which is not recommended for production use. See https://docs.gitea.com/ne...
  - Gitea uses 'memory' for sessions which is not recommended for production use. See https://docs.gitea.com/next/adm...

Las tres advertencias son, exactamente, las tres líneas que decidiste a propósito en el Paso 1 — el chart las señala porque es honesto sobre sus propios defaults de producción, no porque algo esté mal para este laboratorio.

kubectl get pods -n gitea
kubectl get svc -n gitea

Qué esperar (literal — el nombre del Pod con sufijo hash es tu valor variable):

NAME                     READY   STATUS    RESTARTS   AGE
gitea-7578cc4c79-pljlz   1/1     Running   0          24s

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
gitea-http   ClusterIP   None         <none>        3000/TCP   24s
gitea-ssh    ClusterIP   None         <none>        22/TCP     24s

gitea-http es un Service de tipo ClusterIP sin IP asignada (None) — es un headless Service, un patrón que el chart usa para que un único Pod de Gitea sea alcanzable por su nombre DNS sin pasar por el balanceo de un ClusterIP normal. Para el resto de esta guía, lo único que importa es que gitea-http.gitea.svc.cluster.local:3000 responde — que es exactamente lo que confirmas a continuación.


Paso 4 — Abre un túnel temporal y confirma la versión vía API

kubectl port-forward -n gitea svc/gitea-http 3000:3000

En otra terminal:

curl -s http://localhost:3000/api/v1/version -u andes-cargo:AndesCargo2026!

Qué esperar (literal, ejecutado):

{"version":"1.27.0"}

1.27.0 — la misma versión de aplicación que confirmó helm search repo en el Paso 0. El port-forward de este paso es temporal, igual que el que usaste en los Módulos 2-4: sirve para que , desde tu máquina, alcances Gitea. ArgoCD, en la lección 4, nunca va a necesitar este túnel — habla con Gitea por DNS interno, dentro del mismo clúster.


Paso 5 — Crea el repositorio andes-cargo/andes-cargo-k8s vía API

Gitea tiene una API REST completa, documentada en /api/swagger de tu propia instancia. Usarla, en vez de la UI web, deja un comando reproducible en vez de una secuencia de clics:

curl -s -X POST http://localhost:3000/api/v1/user/repos \
  -u andes-cargo:AndesCargo2026! \
  -H "Content-Type: application/json" \
  -d '{
        "name": "andes-cargo-k8s",
        "description": "GitOps source of truth for andes-cargo-status-api on Kubernetes",
        "private": false,
        "auto_init": false
      }'

Qué esperar (literal, ejecutado — el id numérico y los timestamps son variables; full_name y clone_url son literales):

HTTP/1.1 201 Created
{"id":2,...,"full_name":"andes-cargo/andes-cargo-k8s","clone_url":"http://localhost:3000/andes-cargo/andes-cargo-k8s.git","default_branch":"main",...}

andes-cargo es, en este laboratorio, tanto el usuario administrador (Paso 1) como el "dueño" del repositorio en la URL — Gitea, igual que GitHub, usa el nombre de usuario como el primer segmento de owner/repo cuando el repositorio no vive dentro de una organización separada. El resultado (andes-cargo/andes-cargo-k8s) es idéntico al que verías si andes-cargo fuera una organización — la ruta que el resto de esta guía usa es esta, sin cambios.

Nota sobre las credenciales de este laboratorio. AndesCargo2026! es una contraseña de laboratorio, visible en texto plano en este archivo a propósito — el mismo criterio de honestidad que esta guía ya aplicó con test/test para LocalStack desde el Módulo 3. Nada de esto sale de tu máquina: Gitea corre dentro de andes-cargo-cluster, sin ningún puerto expuesto a internet.


Paso 6 — Sube los nueve manifiestos heredados con git push

Los nueve manifiestos ya existen — son, literalmente, los mismos archivos que aplicaste a mano en los Módulos 1-4, sin ningún cambio de contenido. Este paso solo los organiza en un repositorio Git nuevo:

mkdir andes-cargo-k8s && cd andes-cargo-k8s
cp /ruta/a/tus/manifiestos/{namespace,deployment,service,configmap,secret,hpa,ingress,networkpolicy-default-deny,networkpolicy-allow-ingress-nginx}.yaml .
ls
configmap.yaml
deployment.yaml
hpa.yaml
ingress.yaml
namespace.yaml
networkpolicy-allow-ingress-nginx.yaml
networkpolicy-default-deny.yaml
secret.yaml
service.yaml

Nueve archivos, cero líneas nuevas de YAML de aplicación — exactamente el estado con el que cerró el Módulo 4.

git init
git config user.email "platform@andes-cargo.example"
git config user.name "Andes Cargo Platform"
git branch -m main
git add -A
git commit -m "Initial GitOps source: namespace, deployment, service, config, hpa, ingress, networkpolicy (inherited from M1-M4)"
git remote add origin http://andes-cargo:AndesCargo2026!@localhost:3000/andes-cargo/andes-cargo-k8s.git
git push -u origin main

Qué esperar (literal, ejecutado):

To http://localhost:3000/andes-cargo/andes-cargo-k8s.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

Confirma con un git clone limpio, desde otra carpeta, que el contenido llegó de verdad:

git clone http://andes-cargo:AndesCargo2026!@localhost:3000/andes-cargo/andes-cargo-k8s.git
Cloning into 'andes-cargo-k8s'...
git log --oneline
45b14f6 Initial GitOps source: namespace, deployment, service, config, hpa, ingress, networkpolicy (inherited from M1-M4)

45b14f6 es el hash real de este commit — el tuyo va a ser distinto, porque un hash de Git depende del contenido exacto, el autor y el timestamp de cada commit; nadie, ni siquiera repitiendo los mismos comandos, produce el mismo hash dos veces. Lo literal es que existe un commit con este contenido en la rama main del repositorio remoto — eso es lo que confirmas con git log después de clonar.


El resumen visual: dónde vive cada pieza, hoy

                    andes-cargo-cluster (kind)

  namespace: gitea                    namespace: andes-cargo
  ┌─────────────────────┐            ┌──────────────────────────┐
  │  gitea (Pod)          │            │  andes-cargo-status-api    │
  │  ├─ SQLite embebido   │            │  (3 réplicas, Ingress,      │
  │  └─ repo:              │            │   NetworkPolicy — M1-M4)    │
  │     andes-cargo/       │            └──────────────────────────┘
  │     andes-cargo-k8s    │
  │     (9 manifiestos,    │            namespace: argocd
  │      commit 45b14f6)   │            ┌──────────────────────────┐
  └─────────────────────┘            │  (todavía no existe —       │
                                       │   próxima lección)          │
                                       └──────────────────────────┘

Gitea ya tiene la "libreta" completa. Todavía no existe ningún "termostato" que la lea — eso es exactamente lo que instala la lección 4.


Errores comunes

Copiar dl.gitea.io de una fuente vieja en vez de dl.gitea.com (el error que esta misma lección tuvo que resolver antes de escribirse). Qué pasa: helm repo add contra el dominio viejo puede fallar directamente, o —peor— resolver a un contenido obsoleto si el dominio viejo sigue respondiendo con una versión congelada del índice de charts. Cómo detectarlo: si helm search repo gitea-charts/gitea --versions no muestra la versión más reciente que confirma la documentación oficial actual. Cómo corregirlo: usa siempre https://dl.gitea.com/charts/, verificado contra docs.gitea.com al momento de escribir esta lección — y, si vuelves a este laboratorio mucho después, vuelve a confirmar el dominio contra la documentación oficial antes de asumir que no cambió otra vez.

Olvidar database.DB_TYPE: sqlite3 y asumir que desactivar PostgreSQL alcanza (el error real del Paso 2, documentado con su log literal). Qué pasa: el Pod de Gitea entra en Init:Error, reiniciando indefinidamente. Cómo detectarlo: kubectl logs <pod> -c configure-gitea muestra Database settings are missing from the configuration file. Cómo corregirlo: cualquier chart que soporte múltiples motores de base de datos necesita que elijas uno explícitamente cuando desactivas el default (PostgreSQL, en este chart) — sqlite3 es la elección correcta para un laboratorio de un solo Pod sin alta disponibilidad.

Usar la URL de clone_url con localhost desde dentro de un Pod (de continuidad, relevante recién en la lección 4). Qué pasa: alguien copia http://localhost:3000/andes-cargo/andes-cargo-k8s.git (la URL que Gitea reporta, pensada para tu máquina vía port-forward) y la usa como repoURL de un Application de ArgoCD. Cómo detectarlo: ArgoCD reporta un error de conexión rechazada al intentar sincronizar. Cómo corregirlo: localhost dentro de un Pod de argocd no apunta a Gitea — apunta al propio Pod de ArgoCD. El repoURL correcto para cualquier cosa que corra dentro del clúster es el nombre DNS interno del Service: http://gitea-http.gitea.svc.cluster.local:3000/andes-cargo/andes-cargo-k8s.git — la lección 6 lo usa exactamente así.


Ejercicios

Ejercicio 1 — Diagnostica el error del Paso 2 sin leer la solución. Antes de mirar el Paso 2 de nuevo, con solo el mensaje Database settings are missing from the configuration file: "/data/gitea/conf/app.ini", ¿qué línea del values.yaml falta, y por qué "desactivar PostgreSQL" no es suficiente por sí solo?

Ver solución

Falta gitea.config.database.DB_TYPE: sqlite3. Desactivar postgresql.enabled: false le dice al chart "no instales una base de datos administrada por ti" — pero no completa la sección [database] de app.ini con ningún motor alternativo. Sin esa sección, gitea migrate (el paso que prepara el esquema) no sabe contra qué base de datos ejecutar las migraciones, y falla antes de que Gitea pueda arrancar.

Ejercicio 2 — Explica por qué localhost funciona para ti pero no para ArgoCD. En dos o tres frases, explica a un colega por qué http://localhost:3000/... funciona cuando tú lo usas (con port-forward activo) pero va a fallar cuando ArgoCD, en la lección 6, intente usar la misma URL.

Ver solución

Una explicación razonable: "kubectl port-forward crea un túnel temporal entre un puerto de mi máquina y el Service de Gitea dentro del clúster — 'localhost' en ese contexto significa 'mi propia máquina', y el túnel hace el resto del trabajo. ArgoCD, en cambio, corre como un Pod dentro del propio clúster: si le doy 'localhost', va a interpretar eso como 'este mismo Pod de ArgoCD', no como mi máquina ni como Gitea. Necesita el nombre DNS interno del Service de Gitea (gitea-http.gitea.svc.cluster.local), que resuelve correctamente desde cualquier Pod del clúster, sin depender de ningún túnel."

Ejercicio 3 — Diseña la verificación de que el commit llegó sin depender de git log en la misma carpeta. Sin usar la carpeta donde hiciste el commit original, ¿cómo confirmarías, con evidencia independiente, que el contenido llegó de verdad al servidor?

Ver solución

El Paso 6 ya lo hace: un git clone fresco, desde una carpeta distinta, seguido de git log --oneline sobre esa copia recién clonada. Si el commit aparece ahí, la prueba es independiente de cualquier estado local que pudiera estar "mintiendo" (por ejemplo, si el push hubiera fallado silenciosamente y el commit solo existiera en tu copia local, el clon fresco no lo mostraría). Una alternativa igual de válida: consultar la API de Gitea directamente (curl http://localhost:3000/api/v1/repos/andes-cargo/andes-cargo-k8s/commits), que consulta el estado del servidor sin pasar por ningún clon local.


Resumen y siguiente paso

Esta lección instaló la primera pieza real del mecanismo de GitOps: Gitea, corriendo dentro de andes-cargo-cluster con el Helm chart oficial (gitea-charts/gitea, versión 12.7.0, aplicación 1.27.0), verificado contra el dominio actual de distribución (dl.gitea.com, no el .io de fuentes desactualizadas). Creaste el repositorio andes-cargo/andes-cargo-k8s vía API, subiste los nueve manifiestos heredados de los Módulos 1-4 con git push real, y confirmaste con un git clone independiente que el contenido llegó al servidor — no solo a tu copia local. También documentaste, con el log literal, el único error real de esta lección: un values.yaml que desactiva PostgreSQL sin declarar un motor alternativo deja a Gitea sin base de datos donde arrancar.

Antes de avanzar deberías poder: explicar por qué database.DB_TYPE: sqlite3 es necesario; distinguir cuándo usar localhost (tu máquina, vía port-forward) frente al nombre DNS interno (gitea-http.gitea.svc.cluster.local, para cualquier cosa que corra dentro del clúster); y confirmar, con tu propia terminal, que andes-cargo/andes-cargo-k8s tiene los nueve manifiestos en main.

Siguiente lección: manos a la obra, instalando ArgoCD. Ahí instalas el "termostato" que va a leer, cada pocos segundos, el repositorio que acabas de crear — la segunda mitad del mecanismo de la lección 2, y la última pieza antes de la primera sincronización real de la lección 6.

Recursos

  1. Gitea — Installation with Kubernetes — documentación oficial, fuente del comando helm repo add y del dominio dl.gitea.com verificados en esta lección.
  2. Gitea — Config Cheat Sheet, database section — referencia completa de DB_TYPE y el resto de las opciones de [database] usadas en gitea-values.yaml.
  3. Gitea — API Usage — documentación de la API REST usada en el Paso 5 para crear el repositorio sin la UI web.
  4. kubernetes-and-eks-in-production-guide (NIEVA), Módulo 4, lección 8 — el origen exacto de los nueve manifiestos que este repositorio versiona, sin ningún cambio de contenido.