Ghost + Monitoreo en Podman
Despliega Ghost + Uptime Kuma en Podman para una gestión integrada y eficiente de tus recursos.
Me falta una verdad. Me sobran cien excusas ~ Joaquín Sabina (Seis Tequilas)
Un blog no es, desde el punto de vista de quien escribe, un proyecto fácil de mantener. Parir un párrafo es sencillo, hacerlo que cobre sentido es… complicado.
Luego de más de medio año con el blog juntando polvo en la obsolencia de la novena página de los resultados de Google, por fin ocurrió una de esas situaciones donde la solución evidente no está al cien por ciento documentada en Inglés, tristemente, mucho menos, en Español.
Describir como afortunado o desafortunado, queda, en gran parte, a merced de quien está leyendo este texto.
He tenido múltiples oportunidades con servidores Linux y contenedores Podman recientemente. Por lo que pensé ¿Qué diablos? Vamos a escribir un tutorial para levantar un blog Ghost en Podman.
Principalmente, porque es el primer blog que escribo desde 0 en esta plataforma. (El resto de entradas debería estar presente aquí).
No perderé más tiempo con la introducción, vamos al tutorial 👍
Requisitos
Para este proyecto vamos a necesitar los siguientes programas / artefactos:
- Podman
- Imágenes de Docker de:
- MySQL 8
- Desconozco la razón de porque MariaDB no está contemplada como alternativa. Incluso la documentación de Ghost recomienda migrar de MariaDB a MySQL 8.
- Ghost
- Uptime Kuma
- MySQL 8
- Una distribución Gnu/Linux que tenga
systemd
ypodman
instalados o disponibles en sus repositorios.
Si planeas subir tu blog Ghost a un entorno productivo también necesitarás:
- Un dominio y acceso al panel de control.
- Correos de dominio activados
Si no cuentas con correo de tu dominio, puedes utilizar la integración de Gmail de Ghost. Hay un tutorial oficial muy completo para hacer eso.
¿Docker Compose? Yo prefiero los pods.
Si no has leído mi tutorial de conceptos básicos de Podman. Te recomiendo, vayas a leerlo.
Reconozco que docker-compose
es una herramienta útil y muy popular. Muy probablemente más utilizada que Podman, sin embargo, aunque existe un podman-compose
por motivos de compatiblidad, la manera de hacer las cosas con Podman es usando los pods
.
¿Qué es un pod? ¿Cual es la diferencia?
Un pod en Podman es una unidad de ejecución que puede contener uno o varios contenedores. Es similar a un grupo de contenedores que se ejecutan juntos y comparten recursos y espacio de red. A diferencia de docker-compose
, que se centra principalmente en la definición y ejecución de infraestructuras o aplicaciones con múltiples contenedores en un entorno, Podman se enfoca en la gestión de estos pods, esto hace terriblemente sencillo ejecutar y gestionar múltiples contenedores de forma conjunta. También podemos utilizar Podman Desktop para manejar nuestros pods.
Además de que podman puede hacer contenedores rootless, esto también aplica para los pods. Pueden ser rootless. Esto significa que los usuarios regulares pueden ejecutar contenedores de forma segura sin necesidad de privilegios de administrador, lo que reduce los riesgos de seguridad asociados con la ejecución de contenedores con privilegios elevados. Uno de estos riesgos es, por ejemplo, el acceso no autorizado a los recursos del sistema host. Pues podemos limitar la cantidad de recursos que utiliza un pod, así como monitorearlo a través de otros recursos como Cockpit, Kubernetes, Dashboard, Portainer, etc.
Crear un usuario para los contenedores rootless
En tu distribución. Sea un entorno casero o en un VPS, necesitarás ejecutar el siguiente comando como superusuario:
En mi caso yo hice un usuario ghost
.
Crear los directorios para los volúmenes del pod
Para preservar los datos de nuestros pods podemos generar directorios que montaremos como volúmenes de los contenedores. Podemos asignar los volúmenes al pod o a los contenedores. En este caso, le asignaremos los volúmenes a los contenedores por separado.
Sin embargo, antes de hacer eso, debemos generar los directorios pertinentes:
Descargar las imágenes deseadas
Para descargar las imágenes de podman necesarias para este proyecto, primero debemos comprobar que podemos utilizar podman:
Con este comando le pedimos a Podman imprimir su versión. En mi caso, estoy utilizando la versión 4.3.1 que viene en Debian.
Vamos a descargar las imágenes de podman que necesitamos con el siguiente comando:
~$ podman pull docker.io/louislam/uptime-kuma:1 docker.io/ghost/ghost:5-alpine docker.io/mysql/mysql:8.0-debian
Puedes comprobar si tienes todas las imágenes necesarias para comenzar con el siguiente comando:
Si la salida contiene las siguientes líneas, quiere decir que las imágenes de los contenedores ya se encuentran disponibles para que comencemos a usarlas:
Errores comunes
Es probable que te encuentres con un error al descargar las imágenes de Podman. El texto del error dice lo siguiente:
En caso de encontrarte con este error, puedes solucionarlo de la siguiente manera:
Crear un pod
Vamos a comenzar con los pods. Como ya expliqué antes, un pod es nuestra alternativa a docker-compose
en Podman. Pero antes de trabajar con los mismos, será necesario que hagamos un Pod primero:
¿Qué significa esta orden? Simple. Le estamos diciendo a podman que genere un nuevo pod y que exponga los puertos 2368, 3001 y 3307 en el host.
puerto_host:puerto_contenedor
. Por ejemplo: 80:8080
expondrá el puerto 80 en tu servidor, pero se conectará con el puerto 8080
del Pod de Podman.Si la orden fue correcta recibiremos el ID de nuestro pod de regreso. También podemos comprobar el estado de nuestro pod de la siguiente forma:
Añadir contenedores a un pod
Para añadir contenedores a un pod solo debemos hacer los mismos pasos para levantar un contenedor pero con la bandera --pod
y sin los puertos. Vamos a ver como hacer esto…
Añadir la base de datos
Lo primero es añadir la base de datos MySQL 8.0 al servidor:
~$ podman run -d --name mysql-8-ghost --pod BlogPod --restart always -e MYSQL_ROOT_PASSWORD=ejemplo -e MYSQL_PASSWORD=ejemplo2 -e MYSQL_USER=usuarioghost -e MYSQL_DATABASE=ghost -v mysql_data:/var/lib/mysql mysql:8.0-debian
podman exec -it mysql-8-ghost sh
invoca la orden mysql -u root -p
La contraseña autogenerada la podrás encontrar con la orden podman logs mysql-8-ghost
. Una vez tengas acceso a la consola de MySQL, es importante que trates de crear tu usuario y BD de Ghost con buenas prácticas de MySQL.Si el contenedor se creó correctamente, el comando de podman nos regresará el ID de nuestro contenedor.
Comprobar que existe la base de datos:
- Usando
podman ps
ypodman pod ps
:
La manera más rápida de comprobar si nuestra base de datos funciona, es ejecutando los comandos podman ps
y podman pod ps
para verificar si hubo cambios en nuestro entorno de contenedores:
~$ podman ps
podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
342efbfd3f7c localhost/podman-pause:4.3.1-0 About an hour ago Up 57 seconds ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:4001->3001/tcp 247ca738a6e7-infra
ff603a5b1e04 docker.io/library/mysql:8.0-debian mysqld 59 seconds ago Up 57 seconds ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:4001->3001/tcp mysql-8-ghost
~$ podman pod ps
POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
247ca738a6e7 BlogPod Running About an hour ago 342efbfd3f7c 2
Aquí podemos ver que el número de contenedores aumentó a 2.
- Intentando conectarnos con un cliente de BD:
Si queremos estar realmente seguros que nuestra base de datos funciona, podemos utilizar un cliente de bases de datos como DBeaver Community para conectarnos a la BD de nuestro servidor:
Una vez creado nuestro contenedor de MySQL podemos continuar con la creación de nuestro blog.
Problemas comunes
Si no puedes conectarte al servidor de MySQL o cometiste un error al crear los contenedores, puedes hacer rollback con el comando podman stop mysql-8-ghost && podman rm mysql-8-ghost
. Seguido de esos dos comandos ejecuta podman volume prune
para deshacerte del contenido huérfano de los contenedores.
Añadir el monitor del sistema
Vamos a repetir el mismo paso que con el contenedor anterior, pero ahora con el monitor de los servicios. En este caso, Uptime Kuma actuará como nuestra alternativa a Uptime Robot:
~$ podman run -d --name uptime-ghost --pod BlogPod --restart always -v uptimekuma_data:/app/data louislam/uptime-kuma:1
De nuevo, si nuestro contenedor se creó correctamente, podemos comprobarlo de la siguiente forma:
- Usando
podman ps
:
~$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
342efbfd3f7c localhost/podman-pause:4.3.1-0 About an hour ago Up 25 minutes ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:3001->3001/tcp 247ca738a6e7-infra
4728fe29cc22 docker.io/library/mysql:8.0-debian mysqld 8 minutes ago Up 8 minutes ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:3001->3001/tcp mysql-8-ghost
d28e9514a2af docker.io/louislam/uptime-kuma:1 node server/serve... 44 seconds ago Up 38 seconds ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:3001->3001/tcp uptime-ghost
- Ingresando a la dirección
0.0.0.0:3001
donde0.0.0.0
es la dirección IP de tu servidor:
Configura tu usuario administrador de Uptime Kuma. Una vez hecho eso, busca el siguiente botón en tu interfaz:
En la lista llamada Monitor Type selecciona la opción MySQL/MariaDB:
La parte complicada es la de llenar los detalles. Tendrás que armar la cadena de conexión por tu cuenta, dependiendo de los detalles de tu BD, usuario y demás.
El connection string que nos da uptime kuma por defecto es: mysql://username:password@host:port/database
Reemplaza los valores por los del usuario para Ghost, su contraseña y la base de datos que utilizará la aplicación. En el caso de este ejemplo, los ajustes de mi monitor se ven así:
0.0.0.0
para conectarnos a la BD. También es importante notar que se está utilizando el puerto interno del Pod (3306
) y no el puerto del host 3307
.Añadir Ghost al pod
Es momento de terminar lo que empezamos. Solo falta configurar un servicio en nuestro Pod. Ese es el blog Ghost:
~$ podman run -d --name ghost-blog --restart always --pod BlogPod -v ghost_data:/var/lib/ghost/content -e NODE_ENV=development -e database__client=mysql -e database__connection__host=mysql-8-ghost -e database__connection__user=usuarioghost -e database__connection__password=ejemplo2 -e database__connection__database=ghost ghost:5-alpine
database__connection_host
pusimos el valor mysql-8-ghost
podemos hacer esto para que podman se encargue de colorar la dirección de nuestros contenedores en los valores que necesitemos.NODE_ENV
, además de que, deberás proporcionar la variable de entorno: url
. Como requisito adicional, Ghost requiere que tengas funcionando un Reverse Proxy de cualquier tipo con los headers X-Forwarded-For
, X-Forwarded-Host
y X-Forwarded-Proto
configurados correctamente, así como conexión https
.Podemos comprobar que nuestra instancia de Ghost se levantó correctamente de las siguientes formas:
- Con el comando
podman logs ghost-blog
sustituyendoghost-blog
por el nombre que hayas elegido para tu contenedor de Ghost. Si todo salió correctamente verás unos mensajes como estos:
[2024-04-02 04:49:27] INFO Ghost is running in development...
[2024-04-02 04:49:27] INFO Listening on: :::2368
[2024-04-02 04:49:27] INFO Url configured as: http://localhost:2368/
[2024-04-02 04:49:27] INFO Ctrl+C to shut down
[2024-04-02 04:49:27] INFO Ghost server started in 3.497s
[2024-04-02 04:49:28] WARN Database state requires initialisation.
[2024-04-02 04:49:29] INFO Creating table: newsletters
- Ingresando a la IP de nuestro servidor en el puerto
2368
, debemos esperar un par de minutos, pero si todo salió bien, podremos ver la siguiente pantalla:
Puedes ingresar a la ruta /ghost
para comenzar a configurar tu sitio y tu cuenta de administrador:
Añadir Ghost al monitor de servicios
Finalmente, vamos a añadir el nuevo sitio de Ghost al monitor de servicios. De nuevo dirígete a la URL de tu UptimeKuma y vuelve a presionar el botón “Add New Monitor”. Esta vez, deja el monitor por defecto HTTP(s)
si no está seleccionado por defecto, selecciónalo en la lista. En el campo URL
deberás poner la URL de tu sitio de Ghost:
Si seguiste los pasos correctamente, al añadir el nuevo monitor obtendrás una respuesta UP
en color verde:
Generar un archivo .yml de kubernetes para reutilizar el entorno
Como mencioné al inicio del artículo. Es posible reemplazar a docker-compose
utilizando los pods de podman. Afortunadamente, podemos pedirle a podman que exporte nuestro archivo del pod actual con el siguiente comando:
~$ podman generate kube BlogPod >> ghost-blog-pod.yml
El contenido de tu archivo generado se verá similar al siguiente:
Puedes utilizar este archivo y guardarlo con el mismo nombre que usé arriba. Podrás replicar este entorno usando el comando:
Primero detendré y eliminaré el Pod anterior con todos sus datos para empezar desde cero:
Vamos a levantar el entorno desde cero usando el YML que acabamos de generar:
~$ podman play kube ./ghost-blog-pod.yml
Vamos a ver que nos da la salida de ese comando:
~$ podman play kube ./ghost-blog-pod.yml
Trying to pull docker.io/library/ghost:5-alpine...
Getting image source signatures
Copying blob 9fab4aa5bbaf done
Copying blob 2694e4502e24 skipped: already exists
Copying blob 9fab4aa5bbaf done
Copying blob 2694e4502e24 skipped: already exists
Copying blob 4abcf2066143 skipped: already exists
Copying blob d3da4a73e4df skipped: already exists
Copying blob f8ecf2fb4bd9 skipped: already exists
Copying blob 9fab4aa5bbaf done
Copying blob 2694e4502e24 skipped: already exists
Copying blob 4abcf2066143 skipped: already exists
Copying blob d3da4a73e4df skipped: already exists
Copying blob f8ecf2fb4bd9 skipped: already exists
Copying blob 9fab4aa5bbaf done
Copying blob 2694e4502e24 skipped: already exists
Copying blob 4abcf2066143 skipped: already exists
Copying blob d3da4a73e4df skipped: already exists
Copying blob f8ecf2fb4bd9 skipped: already exists
Copying blob 9fab4aa5bbaf done
Copying blob 9fab4aa5bbaf done
Copying blob 2694e4502e24 skipped: already exists
Copying blob 4abcf2066143 skipped: already exists
Copying blob d3da4a73e4df skipped: already exists
Copying blob f8ecf2fb4bd9 skipped: already exists
Copying blob 8b2811e0f552 done
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob eb9280e4e5ef done
Copying blob 5efa127de768 done
Copying blob 05b4dbaae345 done
Copying config b9a619d762 done
Writing manifest to image destination
Storing signatures
Pod:
490c4b0b0734318dd093294b3216084f8a81460f211fb4801eae1b4cd2b505fa
Containers:
f06ed222a671248e6eb414ced4d0412f25202cda761b9fe6fbc5889e2f880970
4ab31d4cb23a92e5f38dcb75c73566e022d55672e797ee04033ead2e8c18fb2f
153708275cbe8cbe9ac3272b4ed828c1db59dce83f82613214b2d1f5f965d7ac
Vamos a comprobar si el pod levantó:
~$ podman pod ls
POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
490c4b0b0734 BlogPod Running 5 minutes ago fcdd9abb03b8 4
~$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fcdd9abb03b8 localhost/podman-pause:4.3.1-0 5 minutes ago Up 3 minutes ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:3001->3001/tcp 490c4b0b0734-infra
f06ed222a671 docker.io/library/mysql:8.0-debian mysqld 5 minutes ago Up 3 minutes ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:3001->3001/tcp BlogPod-mysql-8-ghost
4ab31d4cb23a docker.io/louislam/uptime-kuma:1 node server/serve... 5 minutes ago Up 3 minutes ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:3001->3001/tcp BlogPod-uptime-ghost
153708275cbe docker.io/library/ghost:5-alpine node current/inde... 4 minutes ago Up 1 second ago 0.0.0.0:2368->2368/tcp, 0.0.0.0:3307->3306/tcp, 0.0.0.0:3001->3001/tcp BlogPod-ghost-blog
Si entramos a la URL con el puerto deUptime Kuma, nos vamos a encontrar con la siguiente sorpresa:
¿Qué tal? 😁 Ahora si podrás llevarte tus apps a donde sea que haya podman. Olvídate de ejecutar tus contenedores con sudo
y de necesitar compose
pues los pods de podman son bastante seguros y, me animo a decir production-ready.
Oh otra cosa, puedes usar ese mismo archivo para, más tarde y si lo necesitas, migrar tus Pods de Podman a Kubernetes, son 100% compatibles 🤯
Generar un servicio de systemd para iniciar o detener los contenedores
Hemos aprendido como crear un blog Ghost con su base de datos y un monitor para ambos con Podman. ¡Todo dentro de un pod de kubernetes!
Finalmente, si queremos un servicio de systemd podemos utilizar el comando:
~$ podman generate systemd --files --name BlogPod
/home/ghost/pod-BlogPod.service
/home/ghost/container-BlogPod-mysql-8-ghost.service
/home/ghost/container-BlogPod-uptime-ghost.service
/home/ghost/container-BlogPod-ghost-blog.service
El contenido del archivo pod-BlogPod.service
generado debería verse similar a este:
[Unit]
Description=Podman pod-BlogPod.service
Documentation=man:podman-generate-systemd(1)
Requires=container-BlogPod-mysql-8-ghost.service container-BlogPod-uptime-ghost.service container-BlogPod-ghost-blog.service
Before=container-BlogPod-mysql-8-ghost.service container-BlogPod-uptime-ghost.service container-BlogPod-ghost-blog.service
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/var/run/container/storage
[Service]
Restart=on-failure
ExecStart=/usr/bin/podman start 77a818221650-infra
ExecStop=/usr/bin/podman stop \
-t 10 490c4b0b0734-infra
KillMode=none
Type=forking
PIDFile=/run/user/1007/overlay-containers/ccfd5c71a088768774ca7bd05888d55cc287698dde06f475c8b02f696a25adcd/userdata/conmon.pid
[Install]
WantedBy=default.target
Mueve los archivos generados a $HOME/.config/systemd/user
, puedes habilitar y activar tu nuevo Pod de inmediato con el comando systemctl enable --now pod-BlogPod
Conclusión
¿Te gustó aprender a usar los Pods en Podman? ¿Se te ocurren otras implementaciones?
Te invito a reflexionar y experimentar con los recursos presentados en este blog 😄. Puedes aplicar lo que aprendiste aquí en tu servidor casero, en el trabajo o en tus proyectos personales. Las posibilidades son ilimitadas 😍.
¿Cual es tu opinión acerca del nuevo formato del blog? ¿Te gusta? ¿No te gusta? Puedes dejarme tu opinión en las redes sociales de La Esquina Gris 👍 estaré encantado de escuchar tu feedback.
Atrévete a experimentar
¿Crees poder lograr los siguientes retos? 👀
Separar el monitor en otro servidor
El layout actual es suficiente para un blog muy pequeño o una wiki/documentación interna para equipos de desarrollo. El monitor (Uptime Kuma) debería servir en caso de que uno de los contenedores falle, sin embargo ¿Sabes que hacer si toda la máquina falla.
Podrías intentar hacer un segundo servidor o una segunda máquina virtual donde se encuentre el monitor de Uptime Kuma.
Usar NGINX Reverse Proxy Manager
Prueba a añadir el contenedor NGINX Reverse Proxy Manager con la imágen docker.io/jc21/nginx-proxy-manager:latest
a tu Pod de Podman.
¿Crees poder configurarlo en un VPS o en tu servidor local para colocar un nombre de dominio y acceder a el?
¿Qué colocarías en la siguiente pantalla?
Configurar NGINX como Reverse Proxy
Si estás en un entorno productivo o en un servidor casero y deseas redirigir los puertos de tu pod a un dominio local o en internet deberás hacer un virtual host por cada contenedor expuesto en el pod. Te dejo el archivo de ejemplo para que lo adaptes a tus necesidades:
upstream uptime_app {
server localhost:3001;
}
server
server_name ejemplo.com;
location / {
try_files $uri @app;
}
location @uptime_app {
proxy_pass http://uptime_app;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}