Je vais essayer de rapprocher mon exemple d'un cas d'utilisation Production Ready afin que cet exemple soit le plus concret possible...(enfin je vais essayer 😜)

Le but va être de publier une application web (Ghost) et sa base de données (Mariadb) derrière un reverse proxy (Nginx) avec du HTTPS.

Podman v2

Tout d'abord je vais devoir installer Podman sur ma machine. J'ai réalisé ce tutoriel sur Fedora 32 et avec Podman 2.0.1.

Je n'ai pas l'habitude d'utiliser Fedora, ce changement n'est dû qu'à un souhait : Essayer Podman sur cette distribution avec le support du cgroupv2. Cela n'a aucun impact sur le reste de ce tutoriel.

Pour installer cette version sur Fedora, rien de plus simple :

sudo dnf -y install --enablerepo updates-testing podman

Si vous souhaitez utiliser Ubuntu/Debian ou tout autres systèmes, vous pouvez suivre les directives d'installation de Podman présentes ici :

Podman Installation
Podman is a daemonless container engine for developing, managing, and running OCI Containers on your Linux System. Containers can either be run as root or in rootless mode.

Ou compiler votre propre version :

Compiler Podman v2 sur Ubuntu 20.04
Vous souhaitez essayer et compiler Podman 2.0 sur Ubuntu 20.04 ? Je vous montre comment le faire !

Container Rootless & Pod

L'intégralité des lignes qui vont suivre sont donc exécutées avec l'utilisateur fedora qui est présent sur ma machine ( VPS ) :

[fedora@s1-2-gra7 ~]$ id
uid=1000(fedora) gid=1000(fedora) groups=1000(fedora),4(adm),10(wheel),190(systemd-journal)
Si je dois effectuer certaines commandes en root alors je préciserai l'utilisation de la commande sudo.

Commençons immédiatement avec la création de notre Pod.

Pour rappel, un pod est un ensemble de container qui vont partager certaines ressources dont le réseau :
Source : https://developers.redhat.com/blog/2019/01/15/podman-managing-containers-pods/

Mon pod, que je vais nommer ici myblog :

podman pod create --name myblog -p 80:80,443:443

Mon application web va donc être disponible sur le port 80 et 443.

Je vais ensuite créer ma base de données avec la ligne suivante :

podman run -dt --name db \
--pod myblog \
--restart on-failure \
--env-file=$HOME/myblog/.mariadb.env \
-v mariadb-data:/var/lib/mysql \
docker.io/mariadb:10

Voici le contenu de mon fichier de variables d'environnements .mariadb.env que je stocke dans un dossier myblog dans mon répertoire personnel :

MYSQL_RANDOM_ROOT_PASSWORD=yes
MYSQL_PASSWORD=MYPASSWORD
MYSQL_DATABASE=MYDATABASE
MYSQL_USER=MYUSER

Vous allez obtenir une erreur car le Pod va démarrer avec votre base de données. Et celui-ci souhaite s'accaparer les ports 80 et 443, mais votre utilisateur courant ne possède pas ce droit ... ! On lance en root !? Sûrement pas !

Nous allons autoriser notre utilisateur à bind les ports < 1024 ( qui sont considérés comme privilégiés ) :

sudo sysctl net.ipv4.ip_unprivileged_port_start=80

Vous pouvez l'ajouter dans le fichier /etc/sysctl.conf afin d'être pris en compte au démarrage :

net.ipv4.ip_unprivileged_port_start = 80
/etc/sysctl.conf

Vous pouvez supprimer le container db et le créer de nouveau :

podman rm db
podman run -dt --name db \
--pod myblog \
--restart on-failure \
--env-file=$HOME/myblog/.mariadb.env \
-v mariadb-data:/var/lib/mysql \
docker.io/mariadb:10

Vérifiez son démarrage avec la commande logs :

podman logs -f db

Il ne me reste plus qu'à lancer Ghost :

podman run -dt --name app \
--restart on-failure \
--pod myblog \
-e url=https://myblog.mydomain.com \
--env-file=$HOME/myblog/.ghost.env \
-v www-data:/var/lib/ghost/content \
docker.io/ghost:3

Et mon fichier  .ghost.env, qui est dans mon dossier myblog :

database__client=mysql
database__connection__host=127.0.0.1
database__connection__user=MYUSER
database__connection__password=MYPASSWORD
database__connection__database=MYDATABASE
À la grande différence de Docker, grâce à mon Pod je peux déclarer pour Ghost l'utilisation d'une base de données sur l'interface locale alors qu'il s'agit bien de deux containers différents.

Il ne reste qu'à mettre en place Nginx comme reverse-proxy pour notre application avec un certificat SSL. Je vais utiliser certbot pour générer des certificats let's encrypt.

Nginx et HTTPS

Il va d'abord être nécessaire d'installer certbot :

sudo dnf -y install certbot

Ensuite je vais exécuter celui-ci avec mon compte fedora :

certbot certonly --standalone --config-dir=$HOME/config/ --work-dir=$HOME/work/ --logs-dir=$HOME/logs/ -d myblog.mydomain.com

Si votre Pod est en cours d'éxécution il va être nécessaire de le stopper pour générer le certificat :

podman pod stop myblog

Enfin je vais lancer Nginx :

podman run -dt --name nginx \
--pod myblog \
--restart on-failure \
-v $HOME/myblog/nginx.conf:/etc/nginx/nginx.conf:ro \
-v $HOME/config/live/myblog.dubarbu.fr/fullchain.pem:/etc/letsencrypt/live/myblog.dubarbu.fr/fullchain.pem:ro \
-v $HOME/config/live/myblog.dubarbu.fr/privkey.pem:/etc/letsencrypt/live/myblog.dubarbu.fr/privkey.pem:ro \
docker.io/nginx

Voici mon fichier de configuration nginx.conf :


user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;

    server {
       listen 80;
       server_name myblog.mydomain.com;
       return 301 https://myblog.mydomain.com$request_uri;

    }
    server {
       server_name myblog.mydomain.com;
       listen 443 ssl;

       ssl_certificate /etc/letsencrypt/live/myblog.mydomain.com/fullchain.pem;
       ssl_certificate_key /etc/letsencrypt/live/myblog.mydomain.com/privkey.pem;

       location / {
         max_ranges 0;
         proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
         proxy_set_header   Host                 $http_host;
         proxy_set_header   X-Forwarded-Proto    $scheme;

         proxy_pass http://127.0.0.1:2368;
       }
    }
}
nginx.conf

La configuration est minimaliste mais elle fait le boulot. Le but de cet article n'étant pas de configurer Nginx, je ne vais pas m'y attarder !

🚩 Et là, le drame ... ! Pour le moment le container Nginx renvoie systématiquement une erreur de permission sur le fichier de configuration. Je n'ai pour le moment trouvé qu'une solution :

  • Désactiver selinux ... :
sudo setenforce 0

Pour rendre la modification permanente, éditez le fichier /etc/selinux/config et passez la variable SELINUX à disabled :

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#       enforcing - SELinux security policy is enforced.
#       permissive - SELinux prints warnings instead of enforcing.
#       disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of these two values:
#       targeted - Targeted processes are protected,
#       mls - Multi Level Security protection.
SELINUXTYPE=targeted
/etc/selinux/config

Oui, cette modification n'est pas très Production Ready ... 🚩

Supprimez le container nginx et relancez le. Vous ne devriez plus avoir d'erreur.

Il ne reste plus qu'à essayer la connexion vers votre blog : HTTPS://<YOUR_DOMAIN>

Ghost blog over Podman + Nginx

Ok mais si je redémarre mon serveur !?

Let's Go

Nous allons générer les fichiers nécessaires à systemd pour lancer notre stack au démarrage de notre machine.

Dans un premier temps, créez le dossier suivant à la racine votre $HOME :

mkdir -p $HOME/.config/systemd/user/ 

Enfin générons nos fichiers de service avec Podman :

cd $HOME/.config/systemd/user
podman generate systemd --files --name --new myblog

Enfin il faut les activer :

systemctl --user enable pod-myblog.service
systemctl --user enable container-db.service
systemctl --user enable container-app.service
systemctl --user enable container-nginx.service

Oui mais :

The systemd user instance is started after the first login of a user and killed after the last session of the user is closed.

Il va donc falloir modifier cela afin que vos services ne se coupent pas lors de votre déconnexion du serveur :

sudo loginctl enable-linger username

Un reboot devrait vous permettre de valider tout ça !


Mise à jour :

Pour garder SELinux actif avec Nginx, vous pouvez remplacer la commande de lancement du container avec la suivante :

podman run -dt --name nginx \
--pod myblog \
--restart on-failure \
-v $HOME/myblog/nginx.conf:/etc/nginx/nginx.conf:ro,Z \
-v $HOME/config:/etc/letsencrypt:ro,Z \
docker.io/nginx

SELinux ne posera plus aucun souci de droit avec les volumes !

Merci à ruskofd_ pour son retour sur Twitter !


Nous avons pu voir au cours de cet article, comment mettre en place une stack web avec Podman en mode Rootless.

Il s'agit d'une première découverte et il reste énormément de chose à découvrir sur ce sujet. J'avais d'ailleurs initialement prévu un encart Feedback à cet article afin de vous donner mon ressenti sur cette première intégration et de discuter autour des bugs /soucis de configuration (PEBKAC) rencontrés lors de mes essais.

Mais l'article ayant déjà une taille conséquente - et mon encart Feedback également 😉 - je reviendrai un peu plus tard sur cette intégration !

En quelques mots , j'apprécie toujours autant avancer sur ce sujet : la sécurité de nos containers et le principe de moindre privilège !

Bien évidemment il est difficile de conseiller - pour l'instant - Podman en production ( hormis pour des projets très simples, comme l'exemple d'aujourd'hui ) mais la solution est déjà très fonctionnelle.  

Le sujet amène aussi beaucoup de questions : Traefik comme reverse-proxy, c'est possible ? Comment avoir mon site en haute disponibilité avec Podman ? Et si j'héberge plusieurs services sur la même adresse IP ? Etc ... !

En tout cas  n'hésitez pas à m'apporter des remarques ou des commentaires sur Twitter, ou ici 👇

Petite aparté : Sachez simplement que la rédaction de ces articles, demandent un temps conséquent et engage quelques dépenses ( location des serveurs notamment... ). Alors, si vous souhaitez soutenir le blog, alors n'hésitez plus!

buymeacoff.ee/lfache

J'en profite pour remercier - très chaleureusement - mon premier supporter sur la plateforme ! Merci à toi pour le geste !

Mais aussi tout ceux qui me suivent régulièrement, c'est grâce à vous que je garde la motivation pour continuer ce projet ! 😘

Vous êtes de plus en plus nombreux à venir sur le site et j'expliquerai dans un futur article ce que je souhaite construire avec vous autour des solutions de conteneurisation  afin de faire découvrir tout ceci au plus grand nombre !