Problema
Al crear un entorno nuevo, la infraestructura (máquinas virtuales, redes, almacenamiento) suele estar declarada con herramientas IaC como OpenTofu o Terraform. Una vez que la infraestructura está lista, el siguiente paso es convertirla en un clúster Kubernetes operativo y, a partir de ahí, delegar todo el resto a un motor GitOps (por ejemplo, Argo CD). El punto crítico es el bootstrap: ¿qué ejecuta la primera instalación de CNI, controlador de ingress, y el propio Argo CD sin romper la filosofía declarativa?
Muchos administradores terminan con scripts ad‑hoc que corren una sola vez, o con proveedores Terraform que intentan instalar Helm charts directamente, lo que genera dependencias circulares y dificulta la reproducibilidad. El reto es definir una frontera clara entre lo que pertenece a la capa de infraestructura y lo que se gestiona mediante GitOps, manteniendo la capacidad de volver a crear el clúster desde cero con un solo comando.
Causa
- Falta de separación de responsabilidades – Cuando el mismo motor IaC intenta provisionar tanto la VM como los recursos internos de Kubernetes, se pierde la visión de “infraestructura externa vs. configuración interna”.
- Dependencias implícitas – Herramientas como
terraform-provider-helmcrean recursos en un clúster que aún no existe, obligando a ejecutar pasos manuales o a usarnull_resourcecon scripts que no son idempotentes. - Ausencia de un “bootstrap layer” – Sin un mecanismo que garantice que los componentes esenciales (CNI, control plane, Argo CD) se instalen antes de que cualquier otro manifiesto entre en juego, los pipelines GitOps fallan en la primera ejecución.
- Variabilidad del entorno – En homelabs, la combinación de Proxmox, Talos y Cilium no es estándar; cada pieza tiene su propio proceso de inicialización (por ejemplo, Talos necesita un
talosctl apply-config). Ignorar esas particularidades genera errores de sincronización.
Solución
Adoptar un bootstrap de dos fases que mantiene la pureza de IaC y permite que GitOps tome el control una vez el clúster está mínimamente funcional.
1. IaC crea la infraestructura externa
- Usa OpenTofu para declarar VMs, redes y discos en Proxmox.
- Exporta los parámetros necesarios (IP, MAC, IDs) como outputs.
- No intentes instalar Helm charts aquí; solo entrega la máquina lista para ser convertida en nodo Talos.
2. Script de bootstrap ejecutado una sola vez
- Un contenedor o una máquina de gestión (puede ser tu workstation) toma los outputs de OpenTofu y corre
talosctlpara aplicar la configuración del nodo control plane y los workers. - El script instala Cilium (CNI) y Argo CD mediante Helm, usando valores mínimos que apuntan a un repositorio Git interno.
- Al terminar, el clúster ya tiene Argo CD corriendo y listo para sincronizar el resto de los manifests declarados en Git.
3. GitOps asume la gestión completa
- Todos los charts, CRDs y configuraciones adicionales se versionan en un repositorio Git.
- Argo CD observa el repo y aplica los cambios de forma declarativa.
- Si necesitas volver a crear el clúster, simplemente destruye la infraestructura con OpenTofu y repite el proceso; el script de bootstrap siempre será el mismo.
Ventajas
- Idempotencia: OpenTofu gestiona la capa externa; el script de bootstrap es idempotente porque
talosctl apply-configy Helm son seguros al re‑ejecutarse. - Separación clara: No hay recursos de Kubernetes creados directamente desde IaC, evitando ciclos de dependencia.
- Reproducibilidad: Todo el proceso está versionado (Terraform/Terraform, script, repositorio Git).
- Escalabilidad: Añadir nodos o actualizar componentes se hace exclusivamente vía GitOps; el bootstrap solo se ejecuta una vez.
Cuándo aplicar esta solución
- Entornos donde la infraestructura se declara con IaC (OpenTofu, Terraform, Pulumi) y el clúster se despliega sobre máquinas virtuales o bare‑metal.
- Clusters basados en Talos, Ubuntu Core o cualquier OS immutable que requiera una fase de “apply‑config”.
- Homelabs y entornos de producción pequeños‑medianos que quieren mantener GitOps como única fuente de verdad después del arranque.
- No aplicar si el clúster ya está gestionado por una plataforma que incluye su propio bootstrap (EKS, GKE, AKS), ya que esas soluciones proveen mecanismos propios de provisioning.
Código
#!/usr/bin/env bash
set -euo pipefail
# 1. Obtener outputs de OpenTofu (asume que están en tofu output -json)
TF_OUTPUT=$(tofu output -json)
CONTROL_IP=$(echo "$TF_OUTPUT" | jq -r .control_ip.value)
WORKER_IPS=$(echo "$TF_OUTPUT" | jq -r .worker_ips.value | jq -r .[])
# 2. Generar config de Talos (se asume que talosctl está configurado)
talosctl gen config mycluster https://$CONTROL_IP:6443 > talosconfig.yaml
# 3. Aplicar config al control plane
talosctl apply-config --nodes $CONTROL_IP --file talosconfig.yaml --mode controlplane
# 4. Aplicar config a los workers
for ip in $WORKER_IPS; do
talosctl apply-config --nodes $ip --file talosconfig.yaml --mode worker
done
# 5. Esperar a que el kubeconfig esté listo
talosctl kubeconfig --nodes $CONTROL_IP --force
# 6. Instalar Cilium (CNI) vía Helm
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium \
--namespace kube-system \
--create-namespace \
--set kubeProxyReplacement=strict \
--set ipam.mode=cluster-pool
# 7. Instalar Argo CD
helm repo add argo https://argoproj.github.io/argo-helm
helm install argo-cd argo/argo-cd \
--namespace argocd \
--create-namespace \
--set configs.repoServer.repoURL=https://github.com/mi-org/infra-gitops.git \
--set server.service.type=LoadBalancer
echo "Bootstrap completado. Argo CD está disponible en https://$(kubectl -n argocd get svc argo-cd-server -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
Verificación
-
Conexión al clúster
kubectl get nodesTodos los nodos deben aparecer con el estado
Ready. -
Cilium operativo
kubectl -n kube-system get pods -l k8s-app=ciliumLos pods deben estar
Runningy sin errores en los logs. -
Argo CD accesible
- Accede a la URL mostrada al final del script.
- Inicia sesión con la contraseña generada (
admin/$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)). - Verifica que el proyecto
infra-gitopsestá sincronizado y que los recursos declarados en el repo aparecen en la UI.
-
Sincronización automática
- Haz un cambio menor en el repo (por ejemplo, agregar un ConfigMap).
- Confirma que Argo CD lo detecta y lo aplica sin intervención manual.
Notas adicionales
- Persistencia del kubeconfig: el script sobrescribe
~/.kube/configcon la salida detalosctl kubeconfig. Si gestionas varios clústeres, guarda cada archivo con un nombre distintivo (kubeconfig‑mycluster). - Versionado de Helm charts: fija versiones explícitas en los
--seto en unChart.yamldentro del repo Git; evita usarlatestpara que el bootstrap sea reproducible. - Cilium y firewall: en Proxmox, abre los puertos UDP 8472 (VXLAN) y TCP 6443 (API) entre nodos; de lo contrario Cilium fallará al crear la red overlay.
- Escalado posterior: para añadir workers, simplemente ejecuta la parte “Aplicar config a los workers” del script con las nuevas IPs; Argo CD no necesita intervención.
- Destrucción segura: antes de
tofu destroy, elimina los recursos de Argo CD (helm uninstall argo-cd -n argocd) para evitar que intente sincronizar contra un clúster que ya no existe.
Con este patrón de bootstrap de dos fases, la línea entre infraestructura y GitOps queda bien definida y cualquier nuevo clúster puede levantarse de forma automática y reproducible.