Les reverses proxy avec Docker
Prêt à faire de la mise en prod avec du HTTPS ? 😄
Avant de commencer dans le vif du sujet, je voulais vous remercier pour l’engouement autour des tutoriels, je ne m’y attendais pas du tout ! 🤩🤩🤩
Donc aujourd’hui nous allons apprendre les reverse proxy ! Une notion très importante dans la conteneurisation et aussi pour vos futures tâches cloud ! Ça va vous apporter pas mal de notion.
Jusqu’a présent, avec nos docker compose dans les précédents tutoriel, nous ouvrions différents port sur notre machine hôte pour accéder au différents services. Sympa mais pas pratique pour une mise en prod.
Dans le plus beau des mondes, nous aurions juste le port 443/80 d’ouvert pour tout nos sites et chaque site pourrais avoir un nom de domaine différents, et bah du coup, bonjour le reverses proxy !
Petit explication en schéma :
Comme vous l’avez compris, le reverse proxy va nous permettre de rediriger les requêtes de différents noms de domaines vers les bon conteneurs ! Il y a d’autre fonctionnalité que nous allons découvrir petit à petit !
Maintenant que vous avez compris le concept, voici une liste de solution remplissant ce rôles :
- NGINX: C’est un serveur web très populaire qui peut également être utilisé comme reverse proxy, load balancer, et cache HTTP. Il est connu pour sa performance et sa faible utilisation des ressources.
- Caddy: C’est un serveur web moderne qui inclut le support automatique de HTTPS. Il est apprécié pour sa simplicité et sa configuration automatique.
- Traefik: C’est un reverse proxy et un load balancer spécialement conçu pour les conteneurs. Il se distingue par sa facilité d’intégration avec les plateformes d’orchestration de conteneurs comme Docker et Kubernetes.
- NGINX Proxy Manager: Il s’agit d’une interface graphique pour gérer facilement vos proxy NGINX. Elle est souvent utilisée pour simplifier la configuration de NGINX pour ceux qui préfèrent une interface utilisateur à une ligne de commande.
- Apache HTTP Server (Apache2): Bien qu’Apache ne soit pas principalement reconnu comme un reverse proxy, il est capable de fonctionner en tant que tel grâce à ses modules mod_proxy et mod_ssl.
Chaque solutions à des documentations complètes, pour ma part je travaille principalement avec Traefik. Pour ce tutoriel je vais vous montrer un exemple avec Nginx Proxy Manager, avec une interface d’administration assez sympathique. Puis en second partie, on verra Traefik.
Nginx Proxy Manager
Alors, je vais partir d’un docker compose sans nginx, ici j’intègre jenkins en plus qui est une solution de CI/CD pour nos amis développeur !
version: '3.7'
services: # MySQL Service mysql: image: mysql:latest # Utilise l'image MySQL version 8 container_name: mysql-server volumes: - ./mysql_data:/var/lib/mysql # Persiste les données de MySQL dans un volume nommé environment: MYSQL_ROOT_PASSWORD: rootpassroot # Définit le mot de passe root (à changer pour un mot de passe sécurisé) MYSQL_DATABASE: wordpress # Crée une base de données WordPress automatiquement MYSQL_USER: wordpressuser # Définit l'utilisateur de la base de données MYSQL_PASSWORD: wordpresspass # Définit le mot de passe pour l'utilisateur de la base de données (à changer pour un mot de passe sécurisé) restart: always # Redémarre toujours le service en cas d'arrêt networks: - wordpress-network # Utilise le réseau wordpress-network - monitor-network # Utilise le réseau monitor-network
# WordPress Service wordpress: depends_on: - mysql # Dépend du service MySQL pour fonctionner image: wordpress:latest # Utilise la dernière version de WordPress container_name: wordpress-server ports: - "8000:80" # Expose WordPress sur le port 8000 de la machine hôte environment: WORDPRESS_DB_HOST: mysql-server # Utilise le service MySQL pour la base de données WORDPRESS_DB_USER: wordpressuser # Utilisateur de la base de données WORDPRESS_DB_PASSWORD: wordpresspass # Mot de passe de la base de données WORDPRESS_DB_NAME: wordpress # Nom de la base de données restart: always # Redémarre toujours le service en cas d'arrêt networks: - wordpress-network # Utilise le réseau wordpress-network - monitor-network # Utilise le réseau monitor-network
# Jenkins Service jenkins: image: jenkins/jenkins:lts # Utilise l'image Jenkins Long-Term Support (LTS) container_name: jenkins-server ports: - "8080:8080" # Expose Jenkins sur le port 8080 de la machine hôte volumes: - ./jenkins_data:/var/jenkins_home # Persiste les données de Jenkins dans un volume nommé restart: always # Redémarre toujours le service en cas d'arrêt networks: - jenkins-network # Utilise le réseau jenkins-network - monitor-network # Utilise le réseau monitor-network
# Uptime Kuma Service uptimekuma: image: louislam/uptime-kuma:1 # Utilise l'image Uptime Kuma container_name: uptimekuma-server ports: - "3001:3001" # Expose Uptime Kuma sur le port 3001 de la machine hôte volumes: - ./uptimekuma_data:/app/data # Persiste les données de Uptime Kuma dans un volume nommé restart: always # Redémarre toujours le service en cas d'arrêt networks: - uptimekuma-network # Utilise le réseau uptimekuma-network - monitor-network # Utilise le réseau monitor-network
networks: # Réseau pour WordPress et MySQL wordpress-network: driver: bridge # Réseau pour Jenkins jenkins-network: driver: bridge # Réseau pour Uptime Kuma uptimekuma-network: driver: bridge # Réseau pour les services de monitoring monitor-network: driver: bridge
Voici le docker compose par défaut que je vais déjà exécuter pour vérifier le fonctionnement. Tout roule !
Ok maintenant passons à l’intégration du reverse proxy nginx, à partir de ce moment je vais passer sur un vps ovh à quelques euros, vous pouvez toujours ouvrir les ports 443 de votre box mais je ne vous conseille pas pour votre sécurité 🙂
Allez hop petit vps connexion par clé ssh, fail2ban d’activé, on est bien là !
Je vous laisse faire l’installation de docker, voici un petit script qui peut vous aider :
#!/bin/bashsudo apt install -y ca-certificates curl gnupg lsb-releasesudo mkdir -m 0755 -p /etc/apt/keyringscurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpgecho "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullsudo apt update -ysudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Maintenant, je vais rajouter le reverse proxy au docker compose, direction la doc ! 😝
https://nginxproxymanager.com/guide/#quick-setup
Voici le bout de code que je vais rajouter, tout en supprimant les ports des autres conteneurs :
proxymanager: image: 'jc21/nginx-proxy-manager:latest' restart: unless-stopped ports: - '80:80' - '81:81' - '443:443' volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt
Voici le nouveau docker-compose :
version: '3.7'
services: # MySQL Service mysql: image: mysql:latest # Utilise l'image MySQL version 8 container_name: mysql-server volumes: - ./mysql_data:/var/lib/mysql # Persiste les données de MySQL dans un volume nommé environment: MYSQL_ROOT_PASSWORD: rootpassroot # Définit le mot de passe root (à changer pour un mot de passe sécurisé) MYSQL_DATABASE: wordpress # Crée une base de données WordPress automatiquement MYSQL_USER: wordpressuser # Définit l'utilisateur de la base de données MYSQL_PASSWORD: wordpresspass # Définit le mot de passe pour l'utilisateur de la base de données (à changer pour un mot de passe sécurisé) restart: always # Redémarre toujours le service en cas d'arrêt networks: - wordpress-network # Utilise le réseau wordpress-network - monitor-network # Utilise le réseau monitor-network
# WordPress Service wordpress: depends_on: - mysql # Dépend du service MySQL pour fonctionner image: wordpress:latest # Utilise la dernière version de WordPress container_name: wordpress-server ports: - "8000:80" # Expose WordPress sur le port 8000 de la machine hôte environment: WORDPRESS_DB_HOST: mysql-server # Utilise le service MySQL pour la base de données WORDPRESS_DB_USER: wordpressuser # Utilisateur de la base de données WORDPRESS_DB_PASSWORD: wordpresspass # Mot de passe de la base de données WORDPRESS_DB_NAME: wordpress # Nom de la base de données restart: always # Redémarre toujours le service en cas d'arrêt networks: - wordpress-network # Utilise le réseau wordpress-network - monitor-network # Utilise le réseau monitor-network - frontend # Utilise le réseau frontend proxymanager: image: 'jc21/nginx-proxy-manager:latest' # Utilise l'image Nginx Proxy Manager container_name: nginx-proxy-manager # Nomme le conteneur nginx-proxy-manager restart: unless-stopped # Redémarre le service en cas d'arrêt ports: # Expose les ports 80, 81 et 443 de la machine hôte - '80:80' - '81:81' - '443:443' volumes: # Persiste les données de Nginx Proxy Manager dans des volumes nommés - ./data:/data - ./letsencrypt:/etc/letsencrypt networks: - monitor-network # Utilise le réseau monitor-network - frontend # Utilise le réseau frontend # Jenkins Service jenkins: image: jenkins/jenkins:lts # Utilise l'image Jenkins Long-Term Support (LTS) container_name: jenkins-server user: root ports: - "8080:8080" # Expose Jenkins sur le port 8080 de la machine hôte volumes: - ./jenkins_data:/var/jenkins_home # Persiste les données de Jenkins dans un volume nommé restart: always # Redémarre toujours le service en cas d'arrêt networks: - jenkins-network # Utilise le réseau jenkins-network - monitor-network # Utilise le réseau monitor-network - frontend # Utilise le réseau frontend
# Uptime Kuma Service uptimekuma: image: louislam/uptime-kuma:1 # Utilise l'image Uptime Kuma container_name: uptimekuma-server ports: - "3001:3001" # Expose Uptime Kuma sur le port 3001 de la machine hôte volumes: - ./uptimekuma_data:/app/data # Persiste les données de Uptime Kuma dans un volume nommé restart: always # Redémarre toujours le service en cas d'arrêt networks: - uptimekuma-network # Utilise le réseau uptimekuma-network - monitor-network # Utilise le réseau monitor-network - frontend # Utilise le réseau frontend
networks: # Réseau pour WordPress et MySQL wordpress-network: driver: bridge # Réseau pour Jenkins jenkins-network: driver: bridge # Réseau pour Uptime Kuma uptimekuma-network: driver: bridge # Réseau pour les services de monitoring monitor-network: driver: bridge # Réseau pour le reverse proxy frontend: driver: bridge
Et là ce qui est trop cool, comme nous n’avons pas encore de data persistante et que nos services sont dans un fichier, on va pouvoir copier coller le code dans notre vps sans problème. Je vais donc faire un nano docker-compose.yaml et copier le code.
C’est parti !
Une fois le déploiement fini, rendez-vous sur http://votre_ip_public:81
Rendez-vous sur http://ip_public:81 Identifiant par défaut : email: admin@example.com mot de passe: changeme
Au démarrage vous serez invité à changer le mail ainsi que le mot de passe, je vous conseilles de mettre des identifiants fort même si plus tard nous fermerons le port 81.
Super ! Avant de découvrir quelques fonctionnalités, je vais allez faire 3 entrées de type A de mon nom de domaine sur l’ip publique du VPS OVH.
Maintenant que c’est fait nous allons créer des certificats SSL pour sécuriser les connexions à nos sites.
Nous allons utiliser le challenge HTTP01
En gros du gros, Let’s encrypt va demander de mettre à disposition une url, nginx va s’en occuper et ensuite signer avec clé un fichier pour que let’s encrypt délivre le certificat.
(https://letsencrypt.org/fr/how-it-works/)
Maintenant que vous avez un aperçu du mécanisme on peut retourner dans notre console de nginx proxy manager.
Dans la partie SSL certificates, nous allons créer 3 certificats.
Et voila nos 3 certificats !
Maintenant que nous avons nos certificats de stockés, on va allez faire les redirection vers les conteneurs. Allez dans la partie Hosts et Proxy Hosts
Dans domain name, nous indiquons le nom de domaine qui sera affilié au conteneur, ensuite on choisis sur quelle conteneur on redirige et sur quel port.
Ensuite dans la partie SSL on va choisir notre certificat.
Une fois les redirection configurer, on test ? 😋
Sur Jenkins :
Petit problème sur uptime, on va regarder si on a une option websocket dans Nginx manager ?
On modifie notre configuration :
Et voilà le résultat :
C’est simple non ? Vous pouvez bien sur allez plus loins, je vous laisse découvrir par vous mêmes.
Si vous souhaitez héberger un WordPress, un vps chez ovh, un nom de domaine et pour 7€ par mois vous avez votre site en ligne. Personnellement, je trouve ça raisonnable mais pour les personnes qui font juste des sites vitrines, regardez cotée serverless 👀
Bon, on va passer à une partie qui me hype un peu plus je vous avoue, Traefik.
Traefik peut être un peu plus compliqué, mais il est très très puissant, de plus si vous avez besoin pendant votre phase de développement, vous pouvez générer un certificat ssl en local, incroyable.
Ici je vais bien sûr vous montrer comment générer nos certificats avec Let’s Encrypt mais si vous êtes intéressé pour avoir un certificat en local → https://yoandev.co/du-https-en-local-avec-docker-traefik-traefik.me-et-lets-encrypt/
Avec Traefik nous allons ajouter des routes, pour accéder au conteneur, nous pouvons le configurer via des fichier YAML, TOML ou en cli directement dans le docker compose.
Personnellement j’utilise beaucoup la méthode cli, car je trouve ça rapide et tout est dans mon docker compose.
La petite documentation qu’on aime bien : https://doc.traefik.io/traefik/providers/docker/
Fini le bla-bla on va créer notre conteneur Traefik, j’enlève donc nginx proxy manager dans le docker compose.
Voici un exemple de conteneur simple Traefik :
traefik: restart: unless-stopped image: traefik:v2.10.4 command: - --providers.docker=true - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 ports: - "80:80" - "443:443" volumes: - "./letsencrypt:/letsencrypt" - /var/run/docker.sock:/var/run/docker.sock:ro networks: - frontend
Ici nous indiquons les points d’entrée à utiliser, le port 80 va servir à let’s encrypt. L’accès au service docker permet de vérifier les conteneurs en fonctionnement.
Ensuite, je vais ajouter un resolver pour résoudre les challenges let’s encrypt.
traefik: restart: unless-stopped image: traefik:v2.10.4 command: - --providers.docker=true - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --certificatesresolvers.myresolver.acme.email=youremail@mail.com - --certificatesresolvers.myresolver.acme.caserver=https://acme-v02.api.letsencrypt.org/directory - --certificatesresolvers.myresolver.acme.keytype=RSA4096 - --certificatesresolvers.myresolver.acme.tlschallenge=true - --certificatesresolvers.myresolver.acme.httpchallenge=true - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json ports: - "80:80" - "443:443" volumes: - "./letsencrypt:/letsencrypt" - /var/run/docker.sock:/var/run/docker.sock:ro networks: - frontend
Maintenant pour les conteneurs en frontend je vais ajouter une route et utiliser le resolver pour avoir un certificat SSL.
Nous allons ajouter les indications de traefik avec une section label, ce qui corresponds à des étiquettes, voici pourquoi traefik doit avoir accès à docker.
uptimekuma: image: louislam/uptime-kuma:1 # Utilise l'image Uptime Kuma container_name: uptimekuma-server labels: - "traefik.enable=true" - traefik.http.routers.uptime.rule=Host(`uptime.yourdomain.com`) - traefik.http.routers.uptime.tls=true - traefik.http.routers.uptime.tls.certresolver=myresolver volumes: - ./uptimekuma_data:/app/data # Persiste les données de Uptime Kuma dans un volume nommé restart: always # Redémarre toujours le service en cas d'arrêt networks: - uptimekuma-network # Utilise le réseau uptimekuma-network - monitor-network # Utilise le réseau monitor-network - frontend # Utilise le réseau frontend
Bien, ça m’a l’air pas mal, je vais juste ajouter une ligne pour avoir tout les logs info et voir ce qui ce passe.
Voici le docker compose final :
version: '3.7'
services: # MySQL Service mysql: image: mysql:latest # Utilise l'image MySQL version 8 container_name: mysql-server volumes: - ./mysql_data:/var/lib/mysql # Persiste les données de MySQL dans un volume nommé environment: MYSQL_ROOT_PASSWORD: rootpassroot # Définit le mot de passe root (à changer pour un mot de passe sécurisé) MYSQL_DATABASE: wordpress # Crée une base de données WordPress automatiquement MYSQL_USER: wordpressuser # Définit l'utilisateur de la base de données MYSQL_PASSWORD: wordpresspass # Définit le mot de passe pour l'utilisateur de la base de données (à changer pour un mot de passe sécurisé) restart: always # Redémarre toujours le service en cas d'arrêt networks: - wordpress-network # Utilise le réseau wordpress-network - monitor-network # Utilise le réseau monitor-network
# WordPress Service wordpress: depends_on: - mysql # Dépend du service MySQL pour fonctionner image: wordpress:latest # Utilise la dernière version de WordPress container_name: wordpress-server labels: # Définit les labels pour Traefik - "traefik.enable=true" # Active Traefik pour ce service - traefik.http.routers.tutopress.rule=Host(`tutopress.ninapepite.ovh`) # Définit le nom de domaine pour ce service - traefik.http.routers.tutopress.tls=true # Active le TLS pour ce service - traefik.http.routers.tutopress.tls.certresolver=myresolver # Définit le résolveur de certificats pour ce service environment: WORDPRESS_DB_HOST: mysql-server # Utilise le service MySQL pour la base de données WORDPRESS_DB_USER: wordpressuser # Utilisateur de la base de données WORDPRESS_DB_PASSWORD: wordpresspass # Mot de passe de la base de données WORDPRESS_DB_NAME: wordpress # Nom de la base de données restart: always # Redémarre toujours le service en cas d'arrêt networks: - wordpress-network # Utilise le réseau wordpress-network - monitor-network # Utilise le réseau monitor-network - frontend # Utilise le réseau frontend
traefik: # Définit le service Traefik restart: unless-stopped # Redémarre toujours le service en cas d'arrêt image: traefik:v2.10.4 # Utilise l'image Traefik version 2.10.4 command: # Définit les commandes pour Traefik - --providers.docker=true # Active le provider Docker - --entrypoints.web.address=:80 # Définit le port 80 pour le service web - --entrypoints.websecure.address=:443 # Définit le port 443 pour le service websecure - --log.level=INFO # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC - --certificatesresolvers.myresolver.acme.email=youremail@mail.com # Définit l'adresse email pour les certificats - --certificatesresolvers.myresolver.acme.caserver=https://acme-v02.api.letsencrypt.org/directory # Définit le serveur ACME pour les certificats - --certificatesresolvers.myresolver.acme.keytype=RSA4096 # Définit le type de clé pour les certificats - --certificatesresolvers.myresolver.acme.tlschallenge=true # Active le TLS challenge pour les certificats - --certificatesresolvers.myresolver.acme.httpchallenge=true # Active le HTTP challenge pour les certificats - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web # Définit le port 80 pour le HTTP challenge - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json # Définit le chemin pour le stockage des certificats ports: - "80:80" # Expose le port 80 - "443:443" # Expose le port 443 volumes: - "./letsencrypt:/letsencrypt" # Persiste les données des certificats dans un volume persistant - /var/run/docker.sock:/var/run/docker.sock:ro # Montre le socket Docker networks: # Définit les réseaux pour Traefik - frontend # Utilise le réseau frontend # Uptime Kuma Service
# Jenkins Service jenkins: image: jenkins/jenkins:lts # Utilise l'image Jenkins Long-Term Support (LTS) container_name: jenkins-server user: root labels: # Définit les labels pour Traefik - "traefik.enable=true" # Active Traefik pour ce service - traefik.http.routers.jenkins.rule=Host(`jenkins.ninapepite.ovh`) # Définit le nom de domaine pour ce service - traefik.http.routers.jenkins.tls=true # Active le TLS pour ce service - traefik.http.routers.jenkins.tls.certresolver=myresolver # Définit le résolveur de certificats pour ce service volumes: - ./jenkins_data:/var/jenkins_home:rw # Persiste les données de Jenkins dans un volume nommé restart: always # Redémarre toujours le service en cas d'arrêt networks: - jenkins-network # Utilise le réseau jenkins-network - monitor-network # Utilise le réseau monitor-network - frontend # Utilise le réseau frontend
uptimekuma: image: louislam/uptime-kuma:1 # Utilise l'image Uptime Kuma container_name: uptimekuma-server labels: # Définit les labels pour Traefik - "traefik.enable=true" # Active Traefik pour ce service - traefik.http.routers.uptime.rule=Host(`uptime.ninapepite.ovh`) # Définit le nom de domaine pour ce service - traefik.http.routers.uptime.tls=true # Active le TLS pour ce service - traefik.http.routers.uptime.tls.certresolver=myresolver # Définit le résolveur de certificats pour ce service volumes: - ./uptimekuma_data:/app/data # Persiste les données de Uptime Kuma dans un volume nommé restart: always # Redémarre toujours le service en cas d'arrêt networks: - uptimekuma-network # Utilise le réseau uptimekuma-network - monitor-network # Utilise le réseau monitor-network - frontend # Utilise le réseau frontend
networks: # Réseau pour WordPress et MySQL wordpress-network: driver: bridge # Réseau pour Jenkins jenkins-network: driver: bridge # Réseau pour Uptime Kuma uptimekuma-network: driver: bridge # Réseau pour les services de monitoring monitor-network: driver: bridge # Réseau pour le reverse proxy frontend: driver: bridge
Go test !
Petit docker logs sur le service traefik, on vois que le ACME est chargé pour les certificats SSL.
On a sur chaque site pour vérifier.
(sur un site j’ai eu une error 504 Gateway, un restart du conteneur Traefik a suffit)
Tada !
Et là pas de soucis de websocket avec Uptime ! 😀
Petit tips, avec Uptime Kuma, vous pouvez surveiller vos certificats et vérifier que le renouvellement se fait bien sans problèmes.
Si vous avez compris les mécanismes et pratiquer en même temps, je pense que vous avez pas mal de connaissance pour commencer à mettre en ligne des projets dans des conteneurs. Si besoin voici le github avec les trois docker compose : https://github.com/Ninapepite/medium-reverse-proxy
Merci pour votre lecture !