Fase 4 — GitOps¶
Objetivo¶
Desplegar la plataforma completa vía Argo CD desde
gitops/: storage, MetalLB, Gateway API, Dex +
GitHub OAuth, y waves de operaciones.
Qué aprendes¶
GitOps: el clúster converge al estado declarado en Git; un solo
kubectl apply (ApplicationSet homelab-root) y Argo CD sincroniza el resto. Separación clara:
Ansible bootstrap vs GitOps continuo.
Stack de esta fase¶
flowchart LR
subgraph wave0 [Wave_0]
GA[Gateway_API_CRDs]
CM[cert_manager]
SS[Sealed_Secrets]
OB[OpenEBS]
SC[StorageClasses]
ML[MetalLB]
end
subgraph wave1 [Wave_1]
MLC[MetalLB_config]
CMC[CA_del_HomeLab]
GW[Gateway_Kong]
LH[Longhorn]
end
subgraph wave2 [Wave_2]
AR[ArgoCD_HTTPRoute]
ACFG[ArgoCD_Dex]
end
subgraph wave3 [Wave_3]
UPG[K3s_upgrade]
end
subgraph manual [Manual]
VC[vCluster]
CAPN[CAPN_demo]
end
root[root_app] --> wave0 --> wave1 --> wave2 --> wave3
wave2 -.-> manual
- Argo CD — GitOps; toda la fase.
- MetalLB — LoadBalancer LAN (red local); wave 0–1.
- Gateway API — API estándar de Kubernetes para publicar servicios HTTP(S) (reemplaza a
Ingress); sus CRDs van en wave 0. - Kong Ingress Controller (KIC) — controlador de Gateway API predeterminado; wave 1. Alternativas: Traefik y NGINX Gateway Fabric.
- cert-manager — emite los certificados TLS con una CA (autoridad certificadora) propia del HomeLab; waves 0–1.
- OpenEBS / Longhorn — Storage; waves 0–1.
- Sealed Secrets — Secretos en git; wave 0.
- Dex + GitHub — SSO (inicio de sesión único); wave 2.
- 1Password Python SDK — OAuth fuera de git; paso 4.7 (app local, DesktopAuth).
- system-upgrade-controller — Upgrades K3s (distribución ligera de Kubernetes); wave 3.
Por qué Gateway API y no Ingress
El proyecto Ingress NGINX se archivó en marzo de 2026 y Ingress está
congelado en Kubernetes. El HomeLab publica todo con Gateway y
HTTPRoute. Se activa un solo controlador a la vez (Kong, Traefik o
NGINX Gateway Fabric): ver Cambiar el controlador de Gateway.
Profundización: Catálogo GitOps · Storage · Secretos
Antes de empezar¶
- Fase 3 — K3s completada (
argocd-serverRunning). -
kubectlconfigurado en tu estación. - Fase 0 — GitHub y 1Password completado (Apps, repos e items de 1Password).
- En el router, el rango
192.168.23.200–192.168.23.220fuera del DHCP (reservado): es el pool de MetalLB para los serviciosLoadBalancer, como el Gateway (esquema de IPs).
4.1 — Acceso kubectl¶
Copia a tu estación el kubeconfig del control plane (deborah), como
~/.kube/homelab-k3s.yaml: es el archivo que usan los playbooks y los
procedimientos de esta guía. Si en la Fase 3 corriste playbook-k3s.yml
completo, ya lo tienes (k3s_install.fetch_kubeconfig.enabled: true).
El rol k3s_fetch_kubeconfig lee /etc/rancher/k3s/k3s.yaml en
deborah, cambia la dirección del API (Application Programming
Interface) por la IP real del control plane (o la VIP si usas kube-vip)
y lo guarda en ~/.kube/homelab-k3s.yaml con permisos 0600.
El archivo en el nodo es de root con permisos 600, así que se lee con
sudo; después se cambia 127.0.0.1 por la IP del control plane:
Úsalo y comprueba el acceso:
Alternativa web (Fase 5)
Tras desplegar vCluster Platform, puedes descargar
el kubeconfig del management K3s desde
https://vcluster.homelab.local (connected cluster), con SSO GitHub vía Dex.
El kubeconfig local de arriba sigue siendo el camino para el bootstrap, antes de la Fase 5.
4.2 — Bootstrap GitOps (ApplicationSet)¶
Un único apply manual; a partir de aquí Argo CD gestiona el resto (auto).
Primero, los secretos de plataforma. symintel/gitops es privado: sin
las credenciales de la App symintel-argocd, ArgoCD no puede leer el repo. El playbook los lee de
1Password y los deja en el clúster. Igual que en la Fase 0, la app de
1Password tiene que estar desbloqueada y ONEPASSWORD_ACCOUNT_NAME con el
nombre de tu cuenta (el de la barra lateral de la app).
export ONEPASSWORD_ACCOUNT_NAME="MCO Family" # tu cuenta
cd ansible
ansible-playbook -i inventory.ini playbook-platform-secrets.yml
cd ..
| Secret | Namespace | Para qué |
|---|---|---|
repo-gitops |
argocd |
App symintel-argocd para https://github.com/symintel/gitops.git |
arc-runners-github-app |
arc-runners |
Registro del runner de ARC |
symintel-terraform-app, tofu-vars |
arc-runners |
Credenciales de OpenTofu (TF_VAR_*) para el pipeline |
También crea el namespace terraform, donde OpenTofu guarda su state. Usa
siempre ~/.kube/homelab-k3s.yaml, no el contexto actual de kubectl. Es
idempotente: re-córrelo cuando rotes algo en 1Password. Detalle:
Plataforma symintel — GitHub.
Después, el ApplicationSet de bootstrap:
bootstrap/root-appset.yaml
genera una Application raíz, homelab-root, con las apps que están
descomentadas en su lista apps. Al principio solo está argocd (ArgoCD administrándose a sí mismo)
(Dex, RBAC y el health check que necesitan las olas), para ir probando cada
componente de a uno:
- Descomenta la siguiente app de la lista (van en orden de ola) y haz push
al repo
gitops. -
Vuelve a aplicar el ApplicationSet: ArgoCD no lo gestiona (lo creaste tú con
kubectl), así que el push solo no cambia la lista: -
ArgoCD la despliega; espera a que quede
Healthy(kubectl get applications -n argocd). - Sigue con la próxima.
Olas: como todas las apps se sincronizan juntas en homelab-root, ArgoCD
respeta su anotación argocd.argoproj.io/sync-wave y despliega por olas
(-1 → 0 → 1 → 2 → 3), esperando a que cada una esté Healthy antes de la
siguiente. Las secciones 4.3 a 4.9 siguen ese orden.
Para desactivar una app, vuelve a comentarla: homelab-root borra su
Application (los recursos que creó quedan en el clúster).
Verificar:
UI (interfaz de usuario) provisional (sin Gateway aún; argocd-server habla HTTP, abre http://localhost:8080):
kubectl port-forward svc/argocd-server -n argocd 8080:80
kubectl get secret argocd-initial-admin-secret -n argocd \
-o jsonpath='{.data.password}' | base64 -d; echo
4.3 — Wave 0: secretos + storage + MetalLB controller¶
Espera Healthy en: gateway-api, cert-manager, sealed-secrets, openebs, homelab-storage, metallb.
| Application | Producto | Para qué sirve |
|---|---|---|
gateway-api |
Gateway API | CRDs (Custom Resource Definition) Gateway, HTTPRoute, etc., canal standard |
cert-manager |
cert-manager | Emite certificados; crea el de cada Gateway con la anotación cert-manager.io/cluster-issuer |
sealed-secrets |
Sealed Secrets | Descifra SealedSecret en el clúster; usa la llave de 1Password (item sealed-secrets) si el playbook de secretos ya la creó |
openebs |
OpenEBS | LocalPV default |
homelab-storage |
— | StorageClasses openebs-hostpath, longhorn-mixto |
metallb |
MetalLB | Controller LoadBalancer |
Verificar:
4.4 — Wave 1: pool MetalLB + Gateway + Longhorn¶
Espera Healthy en: metallb-config, cert-manager-config, kong y longhorn.
| Application | Producto | Para qué sirve |
|---|---|---|
metallb-config |
MetalLB | Pool L2 192.168.23.200–.220 |
cert-manager-config |
cert-manager | CA propia del HomeLab (ClusterIssuer homelab-ca) |
kong |
Kong Ingress Controller | Controlador de Gateway API y Gateway homelab (HTTP 80 y HTTPS 443, *.homelab.local) |
longhorn |
Longhorn | Storage replicado HA |
Verificar:
4.5 — Wave 2: ArgoCD en LAN + Dex¶
Espera Healthy en argocd-route y argocd.
| Recurso | Producto | Para qué sirve |
|---|---|---|
argocd-route |
Gateway API | HTTPRoute hacia https://argocd.homelab.local (el Gateway termina el TLS; argocd-server queda en HTTP con server.insecure) |
argocd |
ArgoCD y Dex | ArgoCD se actualiza solo (tag stable); suma el connector GitHub, los staticClients de vCluster e Incus UI, el RBAC y el health check de Application (Actualizar ArgoCD) |
4.6 — kube-vip vs MetalLB¶
| kube-vip | MetalLB | |
|---|---|---|
| Quién lo instala | Ansible (Fase 3) | Argo CD (Fase 4) |
| Qué VIPea | Solo API :6443 |
Services LoadBalancer |
| Config clave | svc_enable=false |
Pool 192.168.23.200–.220 |
No uses kube-vip para Services de aplicación.
Análisis de trade-offs — kube-vip vs MetalLB¶
| Alternativa | Ventaja | Coste / riesgo |
|---|---|---|
| kube-vip (Fase 3) | VIP fija solo para API :6443 |
No expone Gateways ni Services LB |
| MetalLB (Fase 4) | LoadBalancer para el Gateway y apps |
Pool L2 192.168.23.200–.220 debe estar libre en LAN |
| Ambos (default HomeLab) | Responsabilidades separadas | Dos mecanismos VIP distintos — no mezclar roles |
4.7 — Secretos OAuth (GitHub + vCluster + Incus UI)¶
Los valores sensibles no van en git. Guárdalos en 1Password e inyecta
argocd-secret.
OAuth App ya creada en la Fase 0
Si seguiste la Fase 0,
la OAuth App Symintel Dex y sus campos GITHUB_* ya existen: solo
falta correr el playbook de más abajo.
GitHub OAuth App en la org symintel (symintel → Settings → Developer settings → OAuth Apps → New OAuth App).
Creada en la org, Dex puede leer sus teams sin aprobarla como app de terceros:
| Campo GitHub | Valor |
|---|---|
| Application name | Symintel Dex (o similar) |
| Homepage URL | https://argocd.homelab.local |
| Redirect URIs | https://argocd.homelab.local/api/dex/callback (solo esta) |
Homepage URL es informativa; el campo crítico es Redirect URIs (en versiones anteriores de GitHub se llamaba Authorization callback URL): tiene que ser exactamente la URL de arriba.
Ítems 1Password (cuenta personal, bóveda HomeLab por defecto): ver
argocd/secrets/1password.md.
Probar lectura (mismo módulo que el apply):
export ONEPASSWORD_ACCOUNT_NAME="MCO Family" # tu cuenta personal
export ONEPASSWORD_VAULT="HomeLab" # o HomeLab
python3 ansible/scripts/test-1password.py GITHUB_CLIENT_ID
Inyectar en el clúster (recomendado — Ansible):
Los campos INCUS_CLIENT_SECRET y VCLUSTER_CLIENT_SECRET se generan en
1Password automáticamente si no existen. Crea antes en 1Password (manual)
GITHUB_CLIENT_ID y GITHUB_CLIENT_SECRET desde la OAuth App de GitHub.
Alternativa script Python:
pip install onepassword-sdk
python3 gitops/argocd/secrets/apply-from-1password.py.example
kubectl rollout restart deployment argocd-dex-server -n argocd
Connector GitHub (solo el team devops de la org symintel) y staticClients vCluster e Incus UI
en dex.config.
INCUS_CLIENT_SECRET y VCLUSTER_CLIENT_SECRET no vienen de GitHub.
Ansible los genera en 1Password si faltan y los aplica según destino:
| Campo | Destinos (tags Ansible) |
|---|---|
INCUS_CLIENT_SECRET |
argocd-secret (argocd) + Incus OIDC (incus) |
VCLUSTER_CLIENT_SECRET |
argocd-secret (argocd) + Helm Platform (vcluster) |
Incus UI OIDC¶
Requisitos: UI instalada en Fase 2,
argocd Healthy, secretos inyectados y Dex reiniciado.
Ansible (recomendado) — en group_vars/dex_oauth_secrets.yml activa
apply_incus: true y configure_incus_auth: true, luego:
cd ansible
ansible-playbook -i inventory.ini playbook-dex-oauth-secrets.yml --tags ensure,argocd,incus,incus_auth
Eso genera INCUS_CLIENT_SECRET en 1Password si falta, lo aplica en
argocd-secret, reinicia Dex y configura OIDC (OpenID Connect) + grupos en invincible.
Alternativa manual en el nodo bootstrap (invincible):
incus config set oidc.issuer=https://argocd.homelab.local/api/dex
incus config set oidc.client.id=incus-ui
incus config set oidc.client.secret=<INCUS_CLIENT_SECRET>
incus config set oidc.groups.claim=groups
Permisos: denegar por defecto¶
Con OIDC activo, un usuario que inicia sesión no ve ni puede gestionar nada hasta que lo asignes a un grupo con permisos. Solo los miembros de grupos IdP mapeados reciben acceso.
Crea un grupo de administradores y mapéalo al claim groups que emite Dex/GitHub
(con el connector actual: symintel:devops, formato <org>:<team>):
incus auth group create homelab-admins
incus auth group permission add homelab-admins server admin
# Nombre = valor exacto del claim groups en el token OIDC (<org>:<team>)
incus auth identity-provider-group create symintel:devops
incus auth identity-provider-group group add symintel:devops homelab-admins
Para dar acceso limitado (solo un proyecto, solo viewer, etc.), crea más grupos con permisos granulares y mapéalos a equipos de GitHub u otros claims de Dex. Ver Autorización Incus.
Verificar:
- Usuario sin grupo mapeado → login SSO OK, sin recursos visibles
- Usuario del team
devopsdesymintel→ acceso admin del clúster https://incus.homelab.local:8443→ Login with SSO → GitHub
Flujo SSO¶
sequenceDiagram
participant U as Usuario
participant Argo as argocd_homelab_local
participant Dex as Dex
participant GH as GitHub
U->>Argo: Login_SSO
Argo->>Dex: OIDC
Dex->>GH: OAuth
GH->>Dex: token
Dex->>U: sesion
| Decisión | Opción A (default HomeLab) | Opción B |
|---|---|---|
| Secretos OAuth | SDK local; cuenta Personal, bóveda HomeLab |
ONEPASSWORD_ACCOUNT_NAME / ONEPASSWORD_VAULT o kubectl patch |
Análisis de trade-offs — secretos OAuth¶
| Alternativa | Ventaja | Coste / riesgo |
|---|---|---|
| Ansible + 1Password SDK | Genera INCUS_* / VCLUSTER_*; idempotente |
App 1Password desbloqueada en la estación |
| Script Python manual | Mismo SDK sin playbook | Pasos sueltos; fácil olvidar restart Dex |
kubectl patch directo |
Sin 1Password | Secretos en historial; no reproducible |
4.8 — /etc/hosts¶
<IP-del-Gateway> es la IP que MetalLB le da al Gateway homelab;
incus apunta siempre a invincible. Obtén la IP del Gateway:
El certificado lo firma la CA del HomeLab: para que el navegador confíe,
importa su certificado raíz (kubectl -n cert-manager get secret homelab-ca -o jsonpath='{.data.ca\.crt}' | base64 -d > homelab-ca.crt)
o usa curl -k mientras tanto.
4.9 — Wave 3: actualizaciones K3s (opcional)¶
Espera Healthy en k3s-upgrade (system-upgrade-controller).
4.10 — Checklist Fase 4¶
| Comprobación | Criterio |
|---|---|
| Nodos Ready | kubectl get nodes → 3 Ready |
| Apps auto Healthy | kubectl get applications -n argocd |
| Storage default | PVC openebs-hostpath provisiona |
| Gateway responde | curl -k https://argocd.homelab.local |
| SSO Dex | Login GitHub en ArgoCD |
| Incus UI OIDC | Login SSO en https://incus.homelab.local:8443 |
| Runner ARC | arc-runners Healthy; runner listado en symintel → Settings → Actions → Runners (4.11) |
| Pipeline OpenTofu | Primer tofu apply de infra en verde; existe terraform/tfstate-default-github (4.11) |
Checklist global (URLs, /etc/hosts, nodos): Resumen del HomeLab.
4.11 — ARC y pipeline de OpenTofu¶
Con arc-helm-repo, arc-controller, terraform-rbac y arc-runners descomentadas en homelab-root, ArgoCD despliega el runner de GitHub Actions:
| Application | Qué despliega |
|---|---|
arc-controller |
Controller de ARC (arc-systems) |
terraform-rbac |
ServiceAccount tofu-runner, con acceso solo a Secrets/Leases del namespace terraform |
arc-runners |
El único scale set (hasta 3 runners), para CI, deploys y OpenTofu |
kubectl get applications -n argocd | grep -E 'arc|terraform'
kubectl get pods -n arc-systems # controller + listener de arc-runners
El runner aparece en symintel → Settings → Actions → Runners.
Primer pipeline. En symintel/infra, lanza Actions → tofu → Run
workflow sobre main, o haz un push a main. El workflow reusable
tofu.yml
corre en arc-runners: hace plan en los PRs (pull requests) y apply en main. La
primera ejecución crea el repo api con sus environments y secrets FTP (File Transfer Protocol).
A partir de acá, cada repo nuevo se agrega con el
procedimiento de rutina — Agregar un repo: credenciales FTP en
1Password (si despliega), entrada en el mapa de
infra por PR y el
workflow de CI/CD.
Opciones post-Fase 4¶
| Componente | Sync | Siguiente paso |
|---|---|---|
| vCluster Platform | manual | Fase 5 |
| CAPN | manual | Fase 6 |
| CNI externo (Canal / Calico / Cilium) | manual | Fase 3 — CNI; normalmente vía Ansible k3s_cni, no Fase 4 |
Análisis de trade-offs — post-Fase 4¶
| Alternativa | Ventaja | Coste / riesgo |
|---|---|---|
| Terminar en Fase 4 | Management GitOps completo; menos superficie | Sin vClusters ni workload clusters CAPN |
| Fase 5 vCluster | Kubeconfig web; clústeres virtuales ligeros | Más RAM en K3s; OAuth + permisos Platform |
| Fase 6 CAPN | Clusters kubeadm reales sobre Incus | Alto consumo RAM; sync manual obligatorio |
| CNI vía GitOps | Reinstalar Canal/Calico/Cilium sin Ansible | Solo con K3s en flannel-backend=none; no mezclar CNIs |
| Sync manual (5/6) | No borra clusters efímeros por prune | Paso extra en Argo CD UI |
Si falla¶
| Síntoma | Revisar |
|---|---|
Application Degraded |
kubectl describe application -n argocd <nombre> |
| Gateway sin dirección | metallb-config, pool libre |
| SSO falla | Secretos 1Password + restart argocd-dex-server |
homelab-root ComparisonError / repo no accesible |
Falta argocd/repo-gitops, o la App symintel-argocd no está instalada en symintel/gitops: re-correr playbook-platform-secrets.yml |
arc-runners OutOfSync y el pod del listener se reinicia cada pocos minutos |
ArgoCD poda en bucle los recursos que crea el controlador de ARC (AutoscalingListener, Role, RoleBinding: copian la etiqueta app.kubernetes.io/instance). Se corrige con application.resourceTrackingMethod: annotation en argocd-cm (ya está en gitops/argocd/config). Comprueba: kubectl -n argocd get cm argocd-cm -o jsonpath='{.data.application\.resourceTrackingMethod}' → annotation; si no, sincroniza argocd y reinicia el controlador: kubectl -n argocd rollout restart statefulset argocd-application-controller |
Apps en Error: ComparisonError … terminatingReplicas: field not declared in schema |
ArgoCD es anterior a la 3.5 y el clúster tiene Kubernetes 1.34 o más nuevo: Actualizar ArgoCD |
| Runner no aparece en GitHub | kubectl logs -n arc-systems del listener; Secret arc-runners/arc-runners-github-app (App instalada en symintel) |
Pipeline tofu sin runner o sin credenciales |
Runner arc-runners Online; Secrets symintel-terraform-app/tofu-vars en arc-runners |
| Longhorn pending | Etiquetas Longhorn (playbook-options) |
Catálogo y troubleshooting extendido: GitOps — profundización.
Siguiente¶
Opcional:
O terminar aquí si solo necesitas el management cluster GitOps.