
🌐 También en: English · Deutsch · Français
En algunos de mis artículos recientes comenté que estaba intentando averiguar cómo publicar el código de automatización de mi homelab *sin liberar por accidente toda mi infraestructura de paso* 😅
Resulta que es un problema sorprendentemente peliagudo cuando tu inventario de Ansible parece un cofre del tesoro cyberpunk lleno de vaults, secretos y variables específicas de cada host. 🔐🐉
Tras unas cuantas pruebas, errores, cafeína y decisiones vitales cuestionables… por fin encontré una configuración que funciona de maravilla:
👉 dos remotos de git separados.
Una **instancia privada y autoalojada de Gitea** guarda el repositorio *de verdad*, incluidos los secretos cifrados, los archivos de vault y todos los detalles internos más picantes.
Mientras tanto, un **repositorio público en GitHub** solo recibe el código de automatización limpio; los archivos sensibles quedan excluidos con `.gitignore` y sustituidos por amables plantillas `.example`. 🧼✨
De todos modos, todo lo secreto está cifrado con **Ansible Vault (AES-256)**, así que, aunque algo se colara, un atacante solo obtendría un galimatías cifrado. 🧠💾
📦 Repositorio:
👉 github.com/aptupgrademe/www_k3s
El proyecto despliega **Nextcloud** o **WordPress** en un **clúster K3s de un solo nodo** recién instalado sobre **AlmaLinux 9**.
Un comando.
Cero clics.
Máxima energía friki. ⚡🤓
🧠 La idea
Ejecuta:
ansible-playbook blog.yml --limit <host> --ask-vault-pass…contra un VPS completamente nuevo y observa cómo se transforma en un servidor WordPress o Nextcloud totalmente bastionado, con TLS y monitorización, como una especie de evolución Pokémon de infraestructura. 🐛➡️🐉
Nada de trastear a mano por SSH.
Nada de pasos de instalación misteriosos.
Nada de cementerios de «TODO: documentar más tarde». 💀
Todo es idempotente: puedes volver a ejecutarlo cuando quieras sin cargarte tus datos ni invocar a los demonios de la deriva de configuración.
⚙️ El stack de un vistazo
- 🏗️ K3s: Kubernetes sin el trauma empresarial
- 🔀 F5 nginx-ingress: se ejecuta como DaemonSet con
hostNetwork: true - 🔒 cert-manager: certificados automáticos de Let’s Encrypt, porque los navegadores suspiran por candados verdes
- 📝 WordPress 6.9-fpm + sidecar de nginx + MariaDB 10.11
- ☁️ Nextcloud 33-fpm + Redis + Collabora CODE para una ofimática autoalojada de calidad friki
- 📊 Prometheus + Grafana: porque si no puedes hacerle una gráfica, ¿existe de verdad?
- 🛡️ iptables: DROP por defecto, detección de escaneo de puertos, limitación de ICMP, un pequeño foso digital 🏰
- 🔐 Fail2Ban, auditd, rkhunter, dnf-automatic: paranoia as code™
🚀 Lo que hace de verdad una sola ejecución del playbook
- 🔑 Bastiona SSH: puerto personalizado, autenticación solo con clave, configuración más estricta
- 🔥 Crea reglas de cortafuegos para IPv4 + IPv6 con detección de escaneo de puertos mediante
xt_recent - 🧱 Activa SELinux en modo enforcing con los contextos correctos para los contenedores
- ☸️ Instala K3s, Helm, cert-manager y nginx-ingress
- 📦 Despliega automáticamente todo el stack de cargas de trabajo de Kubernetes
- 📝 Configura WordPress con WP-CLI como un civilizado duende de la automatización
- ☁️ Configura Nextcloud con
occ, incluidos Redis, SMTP y dominios de confianza - 📈 Monta los exporters de monitorización, el scraping de Prometheus y los paneles de Grafana
- 🛡️ Despliega Fail2Ban, auditd, escaneos de rkhunter y actualizaciones de seguridad desatendidas
- ☕ Probablemente consume menos café que yo mientras lo depuraba
🔐 Secretos y seguridad
Todas las credenciales están cifradas con Ansible Vault (AES-256).
Archivos sensibles como:
vault.ymlvars.ymlhosts.yml
…nunca tocan el repositorio público. 🚫🌍
El repositorio de GitHub solo contiene código de automatización reutilizable y plantillas `.example`:
vars.yml.example
vault.yml.example
hosts.yml.exampleClonar → rellenar valores → cifrar → desplegar. ✨
🙃 Cosas que fueron mucho más difíciles de lo esperado
- El challenge HTTP-01 de Let’s Encrypt devolvía un 301
Resulta que redirigirlo todo a HTTPS también redirige el challenge de ACME. ¿Quién lo iba a decir? 🙃
Solucionado con los Mergeable Ingresses de F5 (patrón master/minion) para que/.well-known/se salte las redirecciones.
- El challenge HTTP-01 de Let’s Encrypt devolvía un 301
“`
- Bucle infinito de redirecciones HTTPS
Mi primera lógica de nginx básicamente se hizo un DDoS filosófico a sí misma.
Solucionado con un patrón de redirección más limpio de tres variables.
“`
- Bucle infinito de redirecciones HTTPS
“`
- mysqld_exporter v0.19.0 se cargó
DATA_SOURCE_NAME
Ahora las credenciales necesitan un archivo.my.cnfmontado.
También descubrí que los permisos0600no sirven de nada si el contenedor se ejecuta comonobody. 🤡
“`
- mysqld_exporter v0.19.0 se cargó
“`
- iptables OUTPUT DROP rompió las tripas de K3s
Porque, por lo visto, a Kubernetes le encanta hablar consigo mismo sin parar.
Solucionado con reglas ACCEPT explícitas para los CIDR de pods y servicios.
“`
- iptables OUTPUT DROP rompió las tripas de K3s
“`
- El panel 1860 de Grafana mostraba «No data»
Los paneles modernos de Grafana ya no contienen__inputs.
Solución: un diminuto script de parcheo en Python que charla con la API de Grafana como un pequeño duende de la automatización.
“`
🧪 Pruébalo tú mismo
git clone https://github.com/aptupgrademe/www_k3s.git
cd www_k3s
cp inventory/hosts.yml.example inventory/hosts.yml
cp inventory/host_vars/test/vars.yml.example inventory/host_vars/myhost/vars.yml
# fill in your values, then:
ansible-vault encrypt inventory/host_vars/myhost/vault.yml
ansible-playbook blog.yml --limit myhost --ask-vault-passEl quickstart completo, el resumen de roles y la documentación de variables están en el README de GitHub.
PR, comentarios y demás frikis de la infraestructura son bienvenidos. 🚀🐧☸️




