
🌐 También en: English · Deutsch · Français
Deep Dive de la serie en torno al Ansible Tour. Nivel: administración Linux avanzada. El tour explica los roles; los deep dives explican la técnica que hay debajo – cómo funciona, por qué está aquí, qué hace exactamente esta instalación con ella, cómo operarla en el día a día y qué otras opciones habría habido.
Este blog es WordPress, corriendo en un clúster K3s de un solo nodo (la parte 3 del tour muestra los roles). WordPress da por hecho un servidor web clásico: escribe en su propio directorio, instala y actualiza plugins desde su página de administración y programa tareas colgándose de las visitas. Kubernetes da por hecho lo contrario – contenedores reemplazables, configuración desde fuera, nada de estado en la imagen. Este artículo explica cómo se encuentran ambos aquí.
🧭 Cómo funciona WordPress
El camino de una petición
Una petición llega al ingress controller (TLS, límites de tasa, cabeceras de seguridad) y después al sidecar nginx del pod de WordPress. nginx busca primero un fichero HTML ya renderizado de la caché de páginas; si existe, PHP ni siquiera interviene. Si no, pasa la petición a PHP-FPM en el mismo pod, donde WordPress carga su núcleo, los plugins y el tema, pide a Redis los objetos cacheados y a MariaDB el resto, y renderiza la página.
Dónde guarda WordPress su estado
- Ficheros:
wp-content/– plugins, temas, subidas, la caché de páginas, ficheros de idioma. WordPress escribe aquí por sí mismo, por ejemplo al actualizar un plugin. - Base de datos: entradas, páginas, usuarios y la tabla
wp_options, donde WordPress y cada plugin guardan ajustes – y transients, valores cacheados de vida corta. wp-config.php: conexión a la base de datos y claves. En la imagen oficial lo genera el entrypoint a partir de variables de entorno.
Modelo de extensión
Todo en WordPress es un hook: acciones («ahora ocurre X») y filtros («este valor pasa por aquí, cámbialo si quieres»). Plugins y temas cuelgan funciones de los hooks. Los must-use plugins de wp-content/mu-plugins/ se cargan automáticamente, antes que los plugins normales, y no se pueden desactivar desde la administración – el sitio adecuado para reglas que deben aplicarse siempre.
WP-Cron
WordPress no tiene demonio. Por defecto comprueba en cada visita si hay tareas programadas pendientes y, si las hay, lanza una petición a wp-cron.php. En un sitio tranquilo las tareas se ejecutan tarde; en uno con tráfico la comprobación cuesta tiempo en muchas peticiones.
🛡️ Por qué está aquí
- Un editor de verdad y un ecosistema de plugins. SEO, compresión de imágenes, login de dos factores, una tabla de contenidos – cada cosa está a un plugin de distancia.
- Cuatro idiomas. Polylang gestiona las versiones en inglés, alemán, francés y español de cada entrada con traducciones enlazadas.
- Código abierto y portable. Un volcado de la base de datos más
wp-contentes el sitio entero; puede mudarse a cualquier host.
🔧 Qué hace este repositorio
Pods y volúmenes
| Pod | Contenedores | Estado |
|---|---|---|
| wordpress | nginx, PHP-FPM, php-fpm_exporter | /var/www/html desde un hostPath (/srv/wordpress-www) |
| mariadb | MariaDB, mysqld_exporter | hostPath /srv/wordpress-db |
| redis | Redis | ninguno (solo memoria) |
| CronJobs | wordpress-cron cada 5 min, wordpress-autoupdate a las 03:30 | el mismo volumen, brevemente |
Las imágenes no tienen estado; el estado vive en dos directorios del host con sus propias etiquetas SELinux, de modo que el contenedor puede escribir ahí y en ningún otro sitio. El contenedor de PHP-FPM tiene un sistema de ficheros raíz de solo lectura y solo cinco capabilities – las que el entrypoint de la imagen necesita de verdad para desempaquetar WordPress en el volumen en el primer arranque y pasar de root a www-data; cada una se probó quitándola. WP-CLI lo descarga un init container en un volumen fuera de la raíz web: nginx solo se niega a servir ficheros .php, y un .phar en la raíz habría sido una descarga de 7 MB para cualquiera.
WP-CLI como centro de control
Ansible no hace clic en la administración. Cada cambio pasa por WP-CLI dentro del contenedor en marcha:
k3s kubectl exec deployment/wordpress -n wordpress -c wordpress-fpm -- \
su -s /bin/sh www-data -c \
"php /usr/local/lib/wp-cli/wp-cli.phar --path=/var/www/html plugin list --format=json"El rol blog_wordpress lo usa para la instalación, la lista de plugins, los ajustes, el menú y los cinco must-use plugins. Cada tarea lee primero y cambia solo lo que difiere – la tarea de plugins, por ejemplo, instala solo los que faltan y activa solo los inactivos; una versión anterior reinstalaba todos los plugins en cada ejecución y los marcaba todos como «changed». Una lección de la operación: WP-CLI pertenece a www-data. Un vaciado de caché lanzado como root con --allow-root dejó una vez un directorio de caché propiedad de root en el que WordPress ya no podía escribir. La instalación de plugins sigue corriendo como root por razones históricas, seguida de un chown -R en un bloque always:, para que la propiedad se restablezca aunque falle una instalación.
Cinco must-use plugins
| mu-plugin | Hace |
|---|---|
| security-hardening | Oculta las versiones, desactiva el editor de ficheros de la administración (una sesión de administrador sería si no ejecución de código), cierra la enumeración de usuarios por la API REST. |
| upload-images | Convierte cada imagen subida a WebP, como máximo 1.536 px y por debajo de 280 KB – el límite a partir del cual los mensajeros descartan la imagen de vista previa. |
| performance-hints | Precarga la fuente, carga de inmediato la imagen más grande de una página. |
| language-links | «También disponible en …» bajo cada entrada, a partir de los enlaces de traducción de Polylang. |
| ai-disclosure | Un aviso discreto en el pie y la página sobre el uso de IA. |
Caché en tres capas
- Caché de páginas (WP Super Cache): ficheros HTML terminados en disco; nginx los sirve directamente, con una comprobación de cookies para que los usuarios con sesión y quienes comentan reciban siempre páginas frescas.
- Caché de objetos (Redis): resultados de consultas a la base de datos dentro de PHP.
- OPcache: código PHP compilado, con comprobación de fechas cada 60 s – porque los plugins se actualizan en su sitio por la noche.
Seguridad en el borde
xmlrpc.php,install.php, restos de copias y de editores (.bak,.sql,~,.phar) y la enumeración por?author=responden 403.- Límites de tasa en el ingress, por IP de cliente: login 5 peticiones por segundo con una ráfaga de 5, búsqueda 2 por segundo con una ráfaga de 10; por encima, 429.
- Login y
/wp-admin/solo desde redes alemanas, con la misma lista de IP que protege SSH (el filtro geográfico);admin-ajax.phpsigue abierto porque el sitio público lo necesita. Además, Limit Login Attempts y login de dos factores para administradores. - Una Content Security Policy en modo report-only – la parte 5 explica por qué aún no se aplica.
Cron y actualizaciones sin visitantes
DISABLE_WP_CRON apaga el cron disparado por las visitas; en su lugar, un CronJob de Kubernetes ejecuta php wp-cron.php cada cinco minutos, en su propio contenedor con el mismo volumen (concurrencyPolicy: Forbid, para que una ejecución lenta nunca se solape con la siguiente). Un segundo CronJob a las 03:30 actualiza el núcleo (solo versiones menores), plugins, temas y paquetes de idioma, ejecuta las migraciones de la base de datos, vacía la caché y comprueba que la portada sigue respondiendo. Las actualizaciones mayores y los cambios de imagen pasan deliberadamente por blog-update.yml.
📊 Medido
| Valor | |
|---|---|
| WordPress | 7.1, PHP 8.4, tema MoreNews |
| Entradas publicadas (los cuatro idiomas) | 488 |
| Páginas | 16 |
| Base de datos | 49 MB |
| Subidas | 83 MB en 1.253 ficheros (tras la migración a WebP) |
| Plugins | 10 normales, 5 must-use, 2 drop-ins (caché de páginas, caché de objetos) |
| Eventos cron programados | 33 |
| Tiempo de servidor hasta el primer byte, página cacheada | ~46 ms |
| Tiempo de servidor hasta el primer byte, renderizada por PHP | ~140 ms |
| Peticiones PHP por día | ~21.700 |
Los tiempos se midieron desde fuera, descontando el handshake TLS, así que aún incluyen un viaje de ida y vuelta por la red. La caché de páginas ahorra unos 90 ms por petición – y, sobre todo, ahorra workers de PHP: una página cacheada le cuesta a nginx una lectura de fichero, nada más.
🔍 Qué se perdería una reconstrucción solo desde Ansible
El sentido de Ansible es poder reconstruir el servidor desde el repositorio. Para WordPress eso es cierto para la plataforma y solo en parte para el sitio:
- Contenidos y ajustes – entradas, la configuración de idiomas de Polylang, los ajustes de plugins, la configuración del tema – están en la base de datos. Vienen de la copia nocturna, no del repositorio. Es intencionado.
- Un plugin no está en Ansible. Easy Table of Contents se instaló desde la administración y está documentado como tal; una instalación nueva desde el playbook no lo tendría, una restauración desde la copia sí. En cualquier caso tiene que ser una decisión consciente, por eso la guía de operación lo menciona.
- All-in-One WP Migration se eliminó en octubre de 2026: sus archivos
.wpressen la raíz web solo estaban protegidos por un fichero.htaccess, que nginx no lee. nginx los bloqueaba de todos modos; el plugin se fue porque la copia nocturna yblog.ymlya hacen su trabajo.
🧰 Operación diaria
# a WP-CLI shell function on the host
wp() { k3s kubectl -n wordpress exec deploy/wordpress -c wordpress-fpm -- \
su -s /bin/sh www-data -c "php /usr/local/lib/wp-cli/wp-cli.phar --path=/var/www/html $*"; }
wp core version; wp plugin list
wp cron event list --fields=hook,next_run_relative
wp eval 'wp_cache_clear_cache();' # WP Super Cache (wp cache flush = object cache only)
wp db export - | gzip > before-change.sql.gz
# nightly update log
k3s kubectl -n wordpress logs job/$(k3s kubectl -n wordpress get jobs \
-o name | grep autoupdate | tail -1 | cut -d/ -f2)- Dos cachés, dos comandos.
wp cache flushvacía Redis; la caché de páginas necesitawp_cache_clear_cache(). Tras editar una entrada fuera del editor: las dos. - Copias de seguridad. Una máquina de copias recoge cada noche un volcado de la base de datos y el directorio
wp-content; restaurar es el volcado en la base y los ficheros en/srv/wordpress-www. - Deshacer una actualización. Las imágenes están fijadas en
versions.yml: devolver el pin y ejecutar el playbook. Un plugin:wp plugin install <slug> --version=<antigua> --force. - Editar contenido por script. Escribir el contenido de las entradas solo con funciones de WordPress que escapen correctamente – una actualización directa eliminó una vez las barras invertidas de los ejemplos de código de una docena de entradas.
🆘 Chuleta: síntoma → comando
La versión corta de este artículo para el momento en que algo va mal. Ejecutar en el host como root; <…> son marcadores. occ y wp son las funciones de shell de los deep dives de Nextcloud y WordPress.
| Síntoma | Comando |
|---|---|
| La página muestra contenido antiguo | wp eval 'wp_cache_clear_cache();' # page cache |
| Página en blanco o error 500 | k3s kubectl -n wordpress logs deploy/wordpress -c wordpress-fpm --tail=50 |
| Un plugin ha roto el sitio | wp plugin deactivate <slug> |
| ¿Qué versiones hay, qué está pendiente? | wp core version |
| Las tareas programadas no se ejecutan | wp cron event list --fields=hook,next_run_relative |
| ¿Ha funcionado la actualización nocturna? | k3s kubectl -n wordpress get jobs | grep autoupdate |
| Antes de cualquier cambio en contenido o plugins | wp db export - | gzip > before-change.sql.gz |
| Ficheros de repente propiedad de root | # wp-cli only as www-data (the function above), never --allow-root |
🔭 Lo que viene
- Aplicar la CSP en cuanto la fase report-only ya no traiga sorpresas.
- Una caché de página completa en el ingress ahorraría el viaje hasta el pod; con ~46 ms no es urgente.
- Las subidas en almacenamiento de objetos harían el pod independiente del directorio del host – relevante solo con más de un nodo.
⚖️ Alternativas – y por qué WordPress
| Opción | Punto fuerte | Por qué no aquí |
|---|---|---|
| Hugo / sitio estático | Lo más rápido y seguro: sin PHP, sin base de datos | Sin editor web, sin comentarios, traducciones a mano. |
| Ghost | Moderno, rápido, buen editor | Node.js, muchos menos plugins, multilingüe solo con rodeos. |
| WordPress headless | WordPress como editor, un frontend estático o JS | Dos sistemas en lugar de uno para un blog de un solo autor. |
| WordPress | Editor, plugins, Polylang, y el autor lo conoce | Elegido. |
Ejecutar WordPress en Kubernetes tiene menos que ver con Kubernetes que con ser honesto sobre dónde está el estado. Aquí está en dos directorios y una base de datos, los tres con copia, todo lo demás reemplazable – y el centro de control no es la administración sino WP-CLI, llamado por Ansible, con el usuario correcto.


