Ghost + Monitoreo en Podman

Despliega Ghost + Uptime Kuma en Podman para una gestión integrada y eficiente de tus recursos.

Ghost + Monitoreo en Podman

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

🧠
Conocimientos básicos de: Gnu/Linux, Conceptos de contenedores, Conceptos de Servidores
🐳
Puedes utilizar Docker y Docker Compose en lugar de Podman. Sin embargo, estás por tu cuenta al momento de adaptar los pasos.

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
  • Una distribución Gnu/Linux que tenga systemd y podman 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.

⚠️
Este tutorial se hizo con un entorno local o de pruebas en mente. Puedes utilizar Ghost como tu wiki personal o como sitio de documentación para proyectos internos. Si piensas usar este tutorial para alojar tu sitio en un VPS o en otro proveedor de servicios de alojamiento, deberás hacer las modificaciones pertinentes a los pasos aquí mostrados.

¿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?

Fuentes: Article Podman: Managing pods and containers in a local container runtime [Red Hat Developer Blog (15/01/2019)]

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:

$ sudo adduser
# Sigue las instrucciones de la pantalla y confirma la información del usuario. Puedes dejar los campos en blanco si lo deseas.

Escribe esto en la terminal de tu servidor

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:

~$ mkdir mysql_data uptimekuma_data ghost_data

Escribe esto en la terminal de tu servidor

Descargar las imágenes deseadas

Para descargar las imágenes de podman necesarias para este proyecto, primero debemos comprobar que podemos utilizar podman:

~$ podman -v
podman version 4.3.1

Escribe esto en la terminal de tu servidor

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 
⚠️
Es importante que revises en Docker Hub la versión de las imágenes. En caso de que se muestren desactualizadas es fundamental que revises los cambios que hubo. O bien, esperas actualizaciones en esta guía.

Puedes comprobar si tienes todas las imágenes necesarias para comenzar con el siguiente comando:

~$ podman image ls

Escribe esto en la terminal de tu servidor

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:

REPOSITORY                      TAG         IMAGE ID      CREATED       SIZE
docker.io/library/mysql         8.0-debian  b1c0bd217d59  6 days ago    606 MB
docker.io/library/ghost         5-alpine    52e4c3a59d60  10 days ago   582 MB
docker.io/louislam/uptime-kuma  1           b23ac695b1b4  3 months ago  460 MB

Salida del comando podman image ls

Errores comunes

Es probable que te encuentres con un error al descargar las imágenes de Podman. El texto del error dice lo siguiente:

denied: requested access to the resource is denied
unauthorized: authentication required

Error de Podman al usar docker.io como registro de contenedores.

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:

~$ podman pod create --name BlogPod -p 2368:2368 -p 3001:3001 -p 3307:3306

Ejecuta esto en la terminal de tu servidor. Recuerda revisar que los puertos del host estén disponibles en tu sistema. Cámbialos si es necesario.

¿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.

💡
Recuerda que la sintaxis de podman es 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:

~$ podman pod ps

POD ID        NAME        STATUS      CREATED        INFRA ID      # OF CONTAINERS
247ca738a6e7  BlogPod     Created     3 minutes ago  342efbfd3f7c  1

Ejecuta esto en la terminal de tu servidor

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
🔒
Por seguridad te recomiendo que no levantes un contenedor de Podman de esta manera a menos que tengas total confianza en la seguridad de tu servidor. En su lugar, crea el contenedor sin variables de entorno. Ingresa con el comando: 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:

  1. Usando podman ps y podman 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.

  1. 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:

Imagen de la conexión hecha en DBeaver

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:

  1. 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
  1. Ingresando a la dirección 0.0.0.0:3001 donde 0.0.0.0 es la dirección IP de tu servidor:
Si ves esta pantalla. Significa que el contenedor de Uptime Kuma se logró crear con éxito.

Configura tu usuario administrador de Uptime Kuma. Una vez hecho eso, busca el siguiente botón en tu interfaz:

Botón de nuevo monitor

En la lista llamada Monitor Type selecciona la opción MySQL/MariaDB:

Tipo de monitor a elegir

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í:

Imágen con propósitos demostrativos. Esto no es un entorno productivo.
💡
Al estar en la misma red que la BD, podemos usar la dirección 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.
Disfruta de tu monitoreo gratuito para tu BD

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 
💡
Hay dos cosas importantes a tener en cuenta en este comando. El primero es que en la variable de entorno 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.
Si vas a ejecutar esto en un entorno productivo, deberás eliminar la variable de entorno 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:

  1. Con el comando podman logs ghost-blog sustituyendo ghost-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
  1. 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:
Ghost con su tema por defecto

Puedes ingresar a la ruta /ghost para comenzar a configurar tu sitio y tu cuenta de administrador:

Panel de configuración de Ghost

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:

Valores con propósitos de demostración. Esto no es un entorno productivo.

Si seguiste los pasos correctamente, al añadir el nuevo monitor obtendrás una respuesta UP en color verde:

Monitoreo para tu blog 😄
👁️
Te recomiendo explorar las opciones que Uptime Kuma tiene para ti. No es una aplicación muy grande. Podrás configurarle direcciones de correo electrónico, bots de Telegram, Discord y distintos medios para que lleguen notificaciones a tu bandeja de entrada si uno de tus servicios llega a fallar.

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:

# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-4.3.1
apiVersion: v1
kind: Pod
metadata:
  annotations:
    com.docker.official-images.bashbrew.arch/ghost-blog: amd64
    com.docker.official-images.bashbrew.arch/mysql-8-ghost: amd64
    io.kubernetes.cri-o.ContainerType/ghost-blog: container
    io.kubernetes.cri-o.ContainerType/mysql-8-ghost: container
    io.kubernetes.cri-o.ContainerType/uptime-ghost: container
    io.kubernetes.cri-o.SandboxID/ghost-blog: 342efbfd3f7ccab45ee47e2dbc6fe15fbfb48d4c638d9b09c817f848d66df09
    io.kubernetes.cri-o.SandboxID/mysql-8-ghost: 342efbfd3f7ccab45ee47e2dbc6fe15fbfb48d4c638d9b09c817f848d66df09
    io.kubernetes.cri-o.SandboxID/uptime-ghost: 342efbfd3f7ccab45ee47e2dbc6fe15fbfb48d4c638d9b09c817f848d66df09
    io.kubernetes.cri-o.TTY/ghost-blog: "false"
    io.kubernetes.cri-o.TTY/mysql-8-ghost: "false"
    io.kubernetes.cri-o.TTY/uptime-ghost: "false"
    io.podman.annotations.autoremove/ghost-blog: "FALSE"
    io.podman.annotations.autoremove/mysql-8-ghost: "FALSE"
    io.podman.annotations.autoremove/uptime-ghost: "FALSE"
    io.podman.annotations.init/ghost-blog: "FALSE"
    io.podman.annotations.init/mysql-8-ghost: "FALSE"
    io.podman.annotations.init/uptime-ghost: "FALSE"
    io.podman.annotations.privileged/ghost-blog: "FALSE"
    io.podman.annotations.privileged/mysql-8-ghost: "FALSE"
    io.podman.annotations.privileged/uptime-ghost: "FALSE"
    io.podman.annotations.publish-all/ghost-blog: "FALSE"
    io.podman.annotations.publish-all/mysql-8-ghost: "FALSE"
    io.podman.annotations.publish-all/uptime-ghost: "FALSE"
    org.opencontainers.image.base.digest/ghost-blog: sha256:62ce0df0c57930a42a9f6025b33f165c73217159dcf7681148ecee10
    org.opencontainers.image.base.digest/mysql-8-ghost: sha256:993f5593466f84c9200e3e877ab5902dfc0e4a792f291c25c365dbe8
    org.opencontainers.image.base.name/ghost-blog: node:18-alpine3.19
    org.opencontainers.image.base.name/mysql-8-ghost: debian:bookworm-slim
    org.opencontainers.image.created/ghost-blog: "2024-03-22T20:19:13Z"
    org.opencontainers.image.created/mysql-8-ghost: "2024-03-26T03:50:44Z"
    org.opencontainers.image.revision/ghost-blog: 847b8a45a6108ad80d25e53333f65a6f91205659
    org.opencontainers.image.revision/mysql-8-ghost: 831e58702aa316b69cdfaa115fc134bfede4c418
    org.opencontainers.image.source/ghost-blog: https://github.com/docker-library/ghost.git#847b8a45a6108ad80d2
    org.opencontainers.image.source/mysql-8-ghost: https://github.com/docker-library/mysql.git#831e58702aa316b69cd
    org.opencontainers.image.url/ghost-blog: https://hub.docker.com/_/ghost
    org.opencontainers.image.url/mysql-8-ghost: https://hub.docker.com/_/mysql
    org.opencontainers.image.version/ghost-blog: 5.81.0-alpine
    org.opencontainers.image.version/mysql-8-ghost: 8.0.36-bookworm
  creationTimestamp: "2024-04-02T05:21:58Z"
  labels:
    app: BlogPod
  name: BlogPod
spec:
  automountServiceAccountToken: false
  containers:
  - args:
    - mysqld
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: ejemplo
    - name: MYSQL_PASSWORD
      value: ejemplo2
    - name: MYSQL_USER
      value: usuarioghost
    - name: MYSQL_DATABASE
      value: ghost
    image: docker.io/library/mysql:8.0-debian
    name: mysql-8-ghost
    ports:
    - containerPort: 2368
      hostPort: 2368
    - containerPort: 3306
      hostPort: 3307
    - containerPort: 3001
      hostPort: 3001
    resources: {}
    securityContext:
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_NET_RAW
        - CAP_AUDIT_WRITE
    volumeMounts:
    - mountPath: /var/lib/mysql
      name: mysql_data-pvc
  - args:
    - node
    - server/server.js
    image: docker.io/louislam/uptime-kuma:1
    name: uptime-ghost
    resources: {}
    securityContext:
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_NET_RAW
        - CAP_AUDIT_WRITE
    volumeMounts:
    - mountPath: /app/data
      name: uptimekuma_data-pvc
  - args:
    - node
    - current/index.js
    env:
    - name: database__connection__host
      value: mysql-8-ghost
    - name: database__connection__password
      value: ejemplo2
    - name: NODE_ENV
      value: development
    - name: database__client
      value: mysql
    - name: database__connection__user
      value: usuarioghost
    - name: database__connection__database
      value: ghost
    image: docker.io/library/ghost:5-alpine
    name: ghost-blog
    resources: {}
    securityContext:
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_NET_RAW
        - CAP_AUDIT_WRITE
    volumeMounts:
    - mountPath: /var/lib/ghost/content
      name: ghost_data-pvc
  enableServiceLinks: false
  hostname: BlogPod
  restartPolicy: Always
  volumes:
  - name: mysql_data-pvc
    persistentVolumeClaim:
      claimName: mysql_data
  - name: uptimekuma_data-pvc
    persistentVolumeClaim:
      claimName: uptimekuma_data
  - name: ghost_data-pvc
    persistentVolumeClaim:
      claimName: ghost_data
status: {}

Archivo ghost-blog-pod.yml

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:

~$ podman pod stop BlogPod && podman pod rm BlogPod && podman volume prune

Comando para borrar todo alv

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:

Un Uptime Kuma nuevecito

¿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;
    }
}

Canción triste del día

I'm a lost installment in your ear…

Contenido extra para miembros 🌟