Forum Linux.général Docker + Traefik: besoin d'avis extérieurs pour un besoin un peu spécifique

Posté par  . Licence CC By‑SA.
Étiquettes :
0
25
mai
2022

Sommaire

Bonjour à tous,

J'ai un besoin assez spécifique pour lequel je m'arrache les cheveux depuis quelques jours.

Contexte

En gros, j'ai X conteneurs différents :

  • conteneur1
  • conteneur2
  • conteneur3
  • etc

Chaque conteneur :

  • écoute sur le port 5000 en interne
  • est mappé sur un port unique à l'extérieur / côté hôte. Exemple:

    • conteneur1: 6000 -> 5000
    • conteneur2: 6001 -> 5000
    • conteneur3: 6002 -> 5000
  • démarre un serveur web qui écoute le même endpoint: /app

  • une requête sur le endpoint /app va exécuter du code: ce code est différent selon les conteneurs

Jusqu'à maintenant les requêtes sont faites ainsi par une interface web:

Ce n'est sûrement pas la façon de faire la plus élégante qui soit mais ça fonctionne et ça a le mérite d'être super simple :-)

J'ai besoin de démarrer un autre daemon dans chaque conteneur + ce daemon écoutera sur un autre port interne : je sais ce n'est pas du tout la philosophie mais pour l'instant nous ne pouvons pas faire autrement :-)

Ce daemon c'est Jupyter Notebook. Par défaut il écoute sur le port 8888 et réponds à "/".
Mais son url de base est configurable: elle est configurée pour répondre à /jupyter-notebook (nous verrons après pourquoi).

Alors j'arrive à démarrer mes deux daemons en utilisant supervisor: pas de soucis
Maintenant j'ai cette topologie interne:

  • conteneur1:

    • port 5000: utilisé par le daemon 1 qui réponds à /app
    • port 8888: utilisé par Jupyter Notebook qui réponds à /jupyter-notebook
  • conteneur2:

    • port 5000: utilisé par le daemon 1 qui réponds à /app
    • port 8888: utilisé par Jupyter Notebook qui réponds à /jupyter-notebook
  • conteneur3:

    • port 5000: utilisé par le daemon 1 qui réponds à /app
    • port 8888: utilisé par Jupyter Notebook qui réponds à /jupyter-notebook

Problème: mon interface web qui interroge ces URL's:

Doit maintenant être capable d'interroger 2 ports par conteneur : c'est assez difficile à changer (ça implique trop de chose) donc on m'a demandé de trouver une alternative.

L'alternative sur laquelle je travaille c'est d'intercaler un Traefik.

Solution qui fonctionne mais qui ne me convient pas

J'ai réussi à faire quelque chose qui fonctionne mais je n'aime pas trop l'idée…

J'ai créé une instance Traefik par conteneur et c'est Traefik qui map le port 600X et qui ensuite va communiquer soit avec le port 5000 soit avec le port 8888.

En résumé ça donne ceci:

  • combinaison Traefik1 + conteneur1:

    • Traefik écoute sur le port 6000
    • 127.0.0.1:6000/app --> Traefik 1 --> :5000
    • 127.0.0.1:6000/jupyter-notebook --> Traefik 1 --> :8888
  • combinaison Traefik2 + conteneur2:

    • Traefik écoute sur le port 6001
    • 127.0.0.1:6001/app --> Traefik 2 --> :5000
    • 127.0.0.1:6001/jupyter-notebook --> Traefik 2 --> :8888
  • combinaison Traefik3 + conteneur3:

    • Traefik écoute sur le port 6002
    • 127.0.0.1:6002/app --> Traefik 3 --> :5000
    • 127.0.0.1:6002/jupyter-notebook --> Traefik 3 --> :8888

Alors certes ça fonctionne mais :

  • ça demande de jouer avec une "contrainte" dans Traefik pour éviter que les configurations dynamiques ne remontent dans la mauvaise instance
  • ça fait démarrer plusieurs instances de Traefik pour pas grand chose

Solution que j'aurais souhaité avoir

J'aurais préféré avoir un Traefik commun à tous mes conteneurs.

Test violent n°1 : un prefix dans l'URL

J'ai pensé à ceci :
* Traefik en écoute sur le port 6000
* une "location" dans l'URL qui est le conteneur à joindre:
* 127.0.0.1:6000/conteneur1/app --> conteneur1:5000/app
* 127.0.0.1:6000/conteneur1/jupyter-notebook --> conteneur1:8888/jupyter-notebook
* 127.0.0.1:6000/conteneur2/app --> conteneur2:5000/app
* 127.0.0.1:6000/conteneur2/jupyter-notebook --> conteneur2:8888/jupyter-notebook
* 127.0.0.1:6000/conteneur3/app --> conteneur3:5000/app
* 127.0.0.1:6000/conteneur3/jupyter-notebook --> conteneur3:8888/jupyter-notebook
* utiliser un "stripprefix" pour supprimer le "/conteneur1" (sinon le backend final ne comprends pas la requête)

Ça fonctionne bien pour /app mais pas pour /jupyter-notebook qui a une facheuse tendance à faire une redirection sur "/jupyter-notebook"…

Test violent n°2 : une URL par conteneur

J'ai pensé à créer des labels de configuration comme ceci:

  • labels pour conteneur1:

    • si hôte est conteneur1.mydomain.com et si PathPrefix est /app alors conteneur1 port 5000
    • si hôte est conteneur1.mydomain.com et si PathPrefix est /jupyter-notebook alors conteneur1 port 8888
  • labels pour conteneur2:

    • si hôte est conteneur2.mydomain.com et si PathPrefix est /app alors conteneur2 port 5000
    • si hôte est conteneur2.mydomain.com et si PathPrefix est /jupyter-notebook alors conteneur2 port 8888
  • labels pour conteneur3:

    • si hôte est conteneur3.mydomain.com et si PathPrefix est /app alors conteneur2 port 5000
    • si hôte est conteneur3.mydomain.com et si PathPrefix est /jupyter-notebook alors conteneur2 port 8888

Problème : les conteneurs peuvent être créés / détruit X fois par jour donc il faudrait trouver une solution qui fait que l'URL fonctionne à tous les coups
J'avais pensé avoir une espèce de FQDN *.mydomain.com qui réponds toujours 127.0.0.1 pour arriver à ceci:

  • conteneur1.mydomain.com -> 127.0.0.1
  • conteneur2.mydomain.com -> 127.0.0.1
  • conteneur3.mydomain.com -> 127.0.0.1

Problème :

  • /etc/hosts ne permets pas l'utilisation de wildcard
  • j'ai testé dnsmasq mais ça pétillait pas mal avec SystemD Resolved :
    • si je désactive SystemD ResolveD la résolution côté hôte fonctionne
    • mais dans les conteneurs Docker non…
  • quand bien même je trouve à régler le problème de résolution dans Docker, j'ai des serveurs sur lesquels le réseau est bizarrement configuré : les serveurs DNS sont poussés par le DHCP sur lequel je n'ai pas la main donc ça devient vite un sac de nœuds…

Désolé

Désolé pour le pavé mais il n'était pas évident à expliquer en plus court :-)

Au secours

J'ai sûrement mal fait quelque chose à un moment car je suis fatigué donc désolé si j'ai loupé une évidence :)

Est-ce que quelqu'un aurait une autre idée svp ?
Ou un approfondissement d'un de mes tests à suggérer svp ?

Je vous remercie d'avance :)

  • # des pistes et sinon faut changer de reverse proxy

    Posté par  . Évalué à 3.

    PLusieurs manieres de voir, mais tu sembles jongler avec differentes manieres d'aller chercher tes applis

    1°) soit c'est le port qui determine le daemon (5000, 8888) à ce moment là, tu n'as pas besoin de la "baseURL" /app ou /jupyter puisque ce sont des daemons differents

    2°) soit c'est un seul daemon, qui ecoute sur le meme port (5000 mais plusieurs APP) et tu les distingues alors par les baseURL /app1, /app2, /app3

    on peut imaginer avoir 1 domaine par conteneur
    conteneurX.domain.tld => reverse proxy (peut importe le port)

    ton client va demander soit l'APP, soit le port
    idealement

    • conteneurX.domain.tld:8888/ l'enverra sur le jupyter,
    • conteneurX.domain.tld:5000/ l'enverra sur l'app

    ou bien

    • conteneurX.domain.tld/app => envoie sur le contenerX port 5000 et
    • conteneurX.domain.tld/jupyter => envoie sur le conteneur, port 8888

    Autres pistes :
    traefik n'est qu'un reverse proxy parmi d'autre

    ngnix, haproxy en sont d'autres,
    l'un d'eux permet peut-etre de faire ce que tu veux

    • [^] # Re: des pistes et sinon faut changer de reverse proxy

      Posté par  . Évalué à 3.

      Merci pour ton retour :)

      Alors concernant cette approche :

      idealement
      
          conteneurX.domain.tld:8888/ l'enverra sur le jupyter,
          conteneurX.domain.tld:5000/ l'enverra sur l'app
      

      C'est justement celle que je cherche à éviter pour 3 raisons principales :

      • quand l'équipe de dev a formulé sa demande, ils m'ont dit "tu peux nous mettre un Nginx pour qu'on ait pas à tout changer de notre côté et que l'on continue de requêter sur un seul port ?" (nous parlons ici du client web). Alors à force de négociation évidemment je peux leur faire changer du code mais si je trouve une solution élégante de mon côté je pense qu'ils seront contents :)

      • j'ai expliqué l'idée générale avec 3 conteneurs mais nous avons déjà une bonne vingtaine de conteneur montés en permanence + au travers du client il est possible d'en monter à la demande sans que j'ai le contrôle la dessus. Du coup ça peut vite devenir le bims dans les ports à gérer pour les développeurs :-/

      • je trouvais l'idée d'un point d'entrée un peu plus sexy quand même

      Concernant celle-ci :

      ou bien
      
          conteneurX.domain.tld/app => envoie sur le contenerX port 5000 et
          conteneurX.domain.tld/jupyter => envoie sur le conteneur, port 8888
      

      C'est celle que je vise et que j'ai pu obtenir avec Traefik mais à condition d'avoir une instance Traefik en face de chaque conteneurX :-/
      Alors :

      • ça fonctionne
      • je n'ai bien qu'un port en écoute pour exposer et mon app et mon Jupyter : je trouve ça plus évolutif pour la suite si on me demande un troisième daemon

      Mais je trouve qu'avoir une instance de Traefik par conteneur est un peu overkill non ?
      Évidemment si je n'ai pas mieux cette option sera conservée puisqu'elle fonctionne :-)

      ngnix, haproxy en sont d'autres,
      l'un d'eux permet peut-etre de faire ce que tu veux
      

      Comme je le disais précédemment, la demande initiale de l'équipe de développement était un Nginx.
      Ce à quoi j'ai répondu quelque chose comme "laissez moi jouer avec Traefik d'abord : sur le papier ça m'a l'air plus adapté". Je ne connaissais pas Traefik donc j'ai découvert au travers de ce petit projet et c'est franchement pas mal :-)

      Nginx et HAProxy sont des produits que je connais assez bien (peut-être pas à 100%) et malheureusement je ne vois pas trop trop comment je pourrais faire mieux.
      Car justement le gros avantage de Traefik c'est qu'il se configure tout seul sur base des labels que l'on défini sur les conteneurX + qu'il trouve l'ip du conteneurX cible tout seul.

      Si je trouve une solution propre pour avoir *.domain.tld -> 127.0.0.1 de manière dynamique (aucune entrée à créer) alors en théorie l'approche conteneurX.domain.tld/<endpoint> fonctionnera dans Traefik.

      Maintenant si je considère HAProxy et/ou Nginx je ne vois pas comment je règlerais les problèmes en faites :-/

      Imaginons que l'on ne prenne que Nginx (ça facilite l'explication du fait d'un seul vocabulaire du fait que HAProxy et Nginx n'utilisent pas forcément les même terminologies).

      Soit il me faut quand même une URL par conteneur et donc je me retrouve avec un VirtualHost par conteneur :

      • comment régler ce fichu problème de DNS ?
      • comment déployer ces configurations dynamiquement sans avoir à demander aux équipes de développement de produire des VirtualHost à chaque fois qu'un conteneur est ajouté ?

      Soit je joue avec les "location" de Nginx mais c'est pareil :

      • conteneurX réponds à "/app" et c'est codé comme ça : ce n'est pas configurable
      • Jupyter lui permets de configurer le point de terminaison (endpoint) via un paramètre "base_url". Moi j'ai configuré ça sur "/jupyter-notebook" mais par défaut c'est "/". Le problème c'est que Jupyter a dans son code des redirections vers "". Donc dès que je tente de joindre 127.0.0.1:/jupyter-notebook ça fonctionne. Mais si je rajoute un paramètre fictif (niveau Traefik donc) comme 127.0.0.1:/conteneur1/jupyter-notebook alors Jupyter redirige vers 127.0.0.1:/jupyter-notebook et donc l'ami Traefik ne comprends plus la requête

      Est-ce que tu avais quelque chose en tête que je n'ai pas vu ? Désolé c'est le matin :-D

      Quand je dis que c'est à s'arracher les cheveux ;-)

      Une approche qui pourrait fonctionner serait de définir le "base_url" de Jupyter sur /conteneurX/jupyter-notebook.

      Il faut que je test mais dans l'idée :

      • je créé une variable d'environnement qui contient le nom du conteneur (conteneur1, conteneur2, conteneur3)
      • je réutilise cette variable pour la passer à Jupyer via quelque chose comme "--NotebookApp.base_url ${MA_VARIABLE}/jupyter-notebook"
      • côté Traefik idem j'utilise ${MA_VARIABLE} pour amener un chemin dynamique

      Je vais tester et voir ce que si ça fonctionne :-)

      Dans tous les cas aucune solution n'est parfaite et toutes mes approches demandent des changements côté développeur. C'est juste que les changements sont plus ou moins compliqués / profonds. Par exemple j'utilise Docker Compose pour jouer avec Traefik là ou pour l'instant le client (l'application web) exécute des commandes Docker natives. Je PENSE (je n'ai pas encore annoncé la bonne nouvelle à mes collègues développeurs) que ce changement est relativement minime car il est assez ciblé / centralisé contrairement aux requêtes sur les conteneurs :)

      • [^] # Re: des pistes et sinon faut changer de reverse proxy

        Posté par  . Évalué à 4.

        Mais je trouve qu'avoir une instance de Traefik par conteneur est un peu overkill non ?

        il me semblait que traefik est prevu pour,
        et que la creation d'un conteneur peut generer automatiquement l'instance traefik qui va bien

        • [^] # Re: des pistes et sinon faut changer de reverse proxy

          Posté par  . Évalué à 2.

          Comme c'est une solution qui fonctionne je vais évidemment la proposer à mon manager mais parfois j'ai des tocs :-D

          Hier quand j'ai posté mon message initial j'ai focus en me disant "ce n'est pas la bonne approche, revoir l'approche".

          Mais en continuant à travailler sur le sujet aujourd'hui, après coup, je pense qu'effectivement la solution est une candidate sérieuse qui n'est pas si sale / vilaine que cela :-)

  • # Proxy frontal + mappage de port

    Posté par  . Évalué à 1.

    Je sais pas si je comprends bien mais j'ai l'impression que :

    • tu map 2 port côté host par container
    • tu met un proxy frontal avec du proxypass

    Ex de conf de port docker (syntax docker-compose) :
    Container1 :
    - port: 127.0.0.1:11001:5000
    - port: 127.0.0.1:12001:6000
    Container2 :
    - port: 127.0.0.1:11002:5000
    - port: 127.0.0.1:12002:6000

    Ansi de suite.

    Et un apache frontal, avec une conf par container, qui va donc faire proxy

    Serveurname container1.domaine.com

    Proxypass /app http://12.0.0.1:11001/app
    Proxypass /jupyter-notebook http://127.0.0.1:12001/jupyter-notebook

    Quelques chose de ce style.

    Et un reload apache a l'ajout ou la suppression d'une config.

    Et pour simplifier la gestion des ports, tu peux faire
    De l'incrémentale sur le numéro, quitte a avoir un reset de temps en temps pour garder les plage de port 11xxx et 12xxx associé au port interne des container.

    (Je ne connais pas le roulement/durée de vie de tes containers).

    J'espère que cela peut aider à ta réflexion.

    Pour avoir un truc simple, tu incrémente le nombre (celui de l'url d'accès) et cest ce nombre qui est aussi utilisé pour le port. Ex : tu démarre le container124.domain.com et donc tu utiliseras dans les conf le port 11124 et 12124 pour les conf d'accès à ce container.

    • [^] # Re: Proxy frontal + mappage de port

      Posté par  . Évalué à 1.

      Et pour ton souci de fichier hosts, pourquoi ne pas la modifier a chaque démarrage de container ?

      Cest un peut bourrin mais si tu réutilise tes nom de domaine par la suite, tu auras rapidement une liste terminé dans le hosts. (Je connais pas assez les contraintes qu'il y a sur le hosts pour savoir les risques a le modifier souvent).

      • [^] # Re: Proxy frontal + mappage de port

        Posté par  . Évalué à 1.

        Alors sur le papier ça semble une bonne idée mais je t'avoue que de bon matin elle m'effraie / n'est pas totalement claire dans ma tête.
        Je te réponds maintenant mais j'ai besoin d'y réfléchir un peu plus pour voir comment je pourrais faire ça.

        Car là ce serait un mix des deux mondes :

        • côté développeur ils continuent de ne connaître qu'un seul port exposé : celui du Traefik / Apache / Nginx / HAProxy pour n'avoir à requêter que 127.0.0.1:
        • côté sysadmin (mon côté donc) moi il faut que je me débrouille à manipuler les X ports (dans ce cas de figure 2 ports) par conteneur + à reconfigurer le proxy dynamiquement

        En théorie je ne vois rien qui fait que ça ne fonctionnerait pas. Par contre en pratique ça n'a pas l'air trop trop simple à gérer si ?

        Car justement si j'ai préféré tester Traefik c'est que je connaissais la théorie à propos de son côté "dynamique" : pas de fichier de configuration à gérer pour ajouter un backend.

        Avec un produit plus classique (Apache / Nginx / HAProxy) il faut que je trouve le moyen de rajouter / supprimer des vhosts dynamiquement quand un nouveau conteneur apparaît et le tout sans faire faire trop de changement aux développeurs.

        Tu sais si Docker permets d'exécuter des scripts pre/post déploiement ? J'avoue je n'ai pas encore cherché par moi même.
        Ce que je cherchais déjà c'était de voir si l'ami Docker serait assez aimable pour me fournir des variables qui contiennent des choses comme l'IP, le nom du conteneur déployé, etc mais je n'ai pas encore trouvé ça…

        Concernant le fichier /etc/hosts :

        • si c'est fait côté développeur alors c'est chaud car :

          • ça rentrerait en conflit avec mon automation Ansible à moi : je déploie des entrées /etc/hosts (très peu certes, mais j'en ai quelques unes) qui seraient donc potentiellement écrasées et/ou c'est moi qui écraseraient celles des développeurs
          • le client est exécuté en tant que Tomcat (bon ok possible de faire du sudo, etc)
        • si c'est fait de mon côté alors on en revient à la détection d'un changement :/

        • [^] # Re: Proxy frontal + mappage de port

          Posté par  . Évalué à 1.

          Je n'avais pas compris que les dev accédait au serveur.

          Ajouter des conf avec un reload du proxy ne me semble pas être problématique dans le sens où il me semble que tu active/désactive pas des containers toute les 2min (ou j'ai mal compris le besoin).

          Ton ansible peu très bien déployer les conf : une conf apache (mais pareil avec nginx) + un docker-compose.yml +modif du hosts, pour chaque container a mettre en route.

          Et avoir un reload du proxy + demarrage du container après la mise en place de la config.

          Je comprend pas bien le lien entre ce que tu veux mettre en place et le dev de votre app.

          Si le problème, cest que se sont les dev qui démarre une commande docker run, je me dis que le "plus simple" est sûrement d'avoir une page web classic où le dev demande un nouveau service, et a la validation, cela crée tes templates (par exemple) ansible et démarre ton playbook. Dans ce cas, les dev n'aurais pas besoin d'accéder au serveur, et donc tu pourrais gérer comme bon te semble la partie serveur.

          En tout cas cest une problématique intéressante, je vais y réfléchir plus posément :)

          • [^] # Re: Proxy frontal + mappage de port

            Posté par  . Évalué à 2. Dernière modification le 26 mai 2022 à 14:20.

            Ajouter des conf avec un reload du proxy ne me semble pas être problématique dans le sens où il me semble que tu active/désactive pas des containers toute les 2min (ou j'ai mal compris le besoin).
            
            Je comprend pas bien le lien entre ce que tu veux mettre en place et le dev de votre app.
            

            En faites j'ai pensé bien faire en ne donnant pas tout le contexte pour éviter de trop perdre les personnes qui, comme toi, ont pris de leur temps pour me lire et surtout me répondre :)

            Je vais tenter de donner plus de détails en évitant de trop perdre tout le monde quand même (c'est compliqué même pour moi :-D). Nous avons une interface web développée en interne par nos développeurs.
            Cette interface permets à nos utilisateurs de lancer des traitements informatisés.
            Chaque traitement est finalement un algorithme disponible au travers de notre interface web.

            On va parler de "processor" ou "d'application".
            Chaque "application" est à l'arrivée un conteneur Docker.

            Si un utilisateur veut lancer un traitement X existant dans le logiciel :

            • on a déjà tout ce qu'il faut sous la main
            • l'interface démarre le conteneur (docker run …) si nécessaire
            • l'interface exécute les requêtes dans le conteneur
            • l'interface remonte les informations / données résultantes à l'utilisateur à l'origine de la requête

            Si un utilisateur en a les capacités, il peut écrire son propre algorithme (exemple : si c'est quelque chose dont il a besoin et qu'on ne le propose pas) en utilisant nos API.
            Dans ce cas il upload un fichier compressé (on explique ce qu'il doit contenir, etc) et notre logiciel va tenter de construire le conteneur pour après rentrer dans le workflow d'exécution.

            Donc le lien fort entre développeurs / moi / tout ce bims il est là :

            • une bonne partie des choses sont réalisées par le logiciel donc par du code
            • code géré par des développeurs
            • je n'ai donc pas la main à 100% :-)

            Alors comme tu as pu le comprendre en me lisant dans ce post, je ne suis fermé à aucune approche et donc je test plusieurs pistes pour voir laquelle est la meilleure :)

            La définition de meilleure est évidemment très personnelle.
            Je cherche le "keep it simple" :

            • le moins d'effort possible pour tout le monde
            • ce qui sera le plus simple à maintenir sur le long terme

            Le tout dans un écosystème assez compliqué car j'ai des serveurs chez plusieurs fournisseurs donc avec une installation de base qui diffère.
            Je parlais dans certains de mes messages du réseau sur certains serveurs.

            Pour être concret :

            • j'ai des serveurs qui semblent utiliser Netplan + systemd-networkd sur lesquels changer les serveurs DNS est assez facile en étant prudent
            • mais j'ai aussi des serveurs qui utilisent ifupdown et pour lesquels le DHCP m'écrasent le /etc/resolv.conf au démarrage : alors oui je peux y aller à coup de chattr -i mais voilà quoi :-D

            Du coup une belle équation bien tordue avec plein de contraintes :) Même si parfois de la fumée sort de la tête, c'est ce qui fait que j'aime beaucoup mon job :)

            En réponse globale je vais poster un message qui ne sera pas une réponse à ton message ou à un message en particulier pour donner un état des lieux car évidemment depuis hier soir j'ai progressé un peu :-)

  • # Pour le souci de wildcard dans le hosts

    Posté par  . Évalué à 1.

    J'ai découvert cela https://github.com/hubdotcom/marlon-tools suite à ta remarque. A voir si ça peut aider.

    • [^] # Re: Pour le souci de wildcard dans le hosts

      Posté par  . Évalué à 1.

      Alors ça ça l'air juste génial pour éviter un dnsmasq qui fait à peu près tout sauf le café :-)

      Par contre il faut que je vois car le fonctionnement reste le même que dnsmasq et donc :

      • je dois dégager SystemD Resolved : ça c'est faisable
      • j'ai toujours un problème avec certains serveurs pour lesquels ça utilise je ne sais quelle "solution" pour le réseau mais qui fait que les serveurs DNS sont écrasés par le DHCP :-/
      • j'ai toujours ce fameux problème de résolution à l'intérieur des conteneurs Docker :/
      • [^] # Re: Pour le souci de wildcard dans le hosts

        Posté par  . Évalué à 2.

        Pour la résolution à l'intérieur des conteneurs docker : c'est réglé.
        Hier lors de mon premier test je n'avais pas fais trop attention mais dnsmasq écoutait sur 127.0.0.1:53 au lieu de *:53

        • [^] # Re: Pour le souci de wildcard dans le hosts

          Posté par  . Évalué à 1. Dernière modification le 26 mai 2022 à 10:35.

          Attention a un point sensible sur docker (on se fait vite avoir).

          Si tu ne précise pas l'interface réseau sur lequel le container écoute, le mappage est fait directement en écoute sur 0.0.0.0, donc toutes les interface réseau de ton host.

          Si le firewall est mal configuré, les containers peuvent se retrouver joignable depuis une connexion publique.

          Si ta plusieurs interface réseau sur ton serveur il est préférable de préciser l'interface réseau.

          Je le dis juste au cas où, vu que tu test pas mal de config.

          Autre point qui peut être pratique, cest que tu peux monter ton propre network docker avec la plage d'ip qui t'intéresse, donc il y a peut-être un moyen simple de gestion pour toi pour connaître les ip.

          • [^] # Re: Pour le souci de wildcard dans le hosts

            Posté par  . Évalué à 2.

            Alors à cet instant T c'est assez "vilain" : les conteneurs sont démarrés dans le réseau "bridge" créé par défaut.

            Ça ne fait que 6 mois que je suis dans l'entreprise donc je ne peux pas être partout en même temps. Du coup j'ai profité de ce besoin pour gratter un peu et voir quels seraient les axes d'amélioration possibles.

            Un des axes a été de créer un réseau dédié (docker network create) pour plugger les conteneurs dedans car j'ai trouvé une documentation officielle qui :

            • explique les différences entre le réseau "bridge" par défaut et un réseau créé avec docker network create
            • recommande quand même de monter les conteneurs dans ce réseau dédié (comme tu l'as souligné dans ton message)
  • # Un petit état des lieux

    Posté par  . Évalué à 3.

    Donc depuis hier soir j'ai progressé et j'ai donc maintenant deux solutions qui fonctionnent :)

    Solution 1 - celle que j'avais déjà

    La solution "avoir un Traefik en face de chaque conteneur final" fonctionne.

    Pour ce faire je dois juste jouer avec le paramètre --providers.docker.constraints de Traefik pour que les configurations dynamiques soient interceptées par la bonne instance de Traefik.

    Dans les grandes lignes :

    • Traefik expose un port : exemple 6000
    • l'application finale écoute sur son port interne (non exposé) : 5000
    • Jupyter écoute sur son port interne (non exposé) : 8888

    Traefik redirige :

    • les requêtes de /jupyter-notebook vers le port 8888
    • les autres requêtes vers le port 5000

    Les avantages :

    • un seul fichier docker-compose.yml à gérer par application : on est plutôt aligné avec la philosophie
    • c'est relativement simple et efficace à mettre en œuvre : aucune bidouille de DNS, peu de fichiers de configuration à manipuler, etc
    • peu de changement du côté de l'application :

      • seule les commandes docker exécutées sont à remplacer
      • nous pouvons requêter sur 127.0.0.1: comme aujourd'hui
    • s'il devait y avoir un autre besoin un peu "tordu" ce serait assez flexible (vu que le Traefik est décié à une application en backend) pour gérer d'autres backends

    Les inconvénients :

    • ça fait démarrer un Traefik par conteneur (donc potentiellement une consommation de ressources inutiles bien qu'au repos ça doit être faible)

    Solution 2 - un Traefik commun + un FQDN qui résout 127.0.0.1

    Ici l'idée est d'avoir un domain (app-container.local) qui, quelque soit l'entrée, réponds toujours 127.0.0.1 :

    • exemple1.app-container.local -> 127.0.0.1
    • exemple2.app-container.local -> 127.0.0.1
    • etc

    Pour ce faire j'ai utilisé dnsmasq :

    • j'ai désactivé systemd-resolved
    • j'ai supprimé le lien symbolique /etc/resolv.conf qui pointait sur ../run/systemd/resolve/stub-resolv.conf
    • j'ai installé + configuré dnsmasq :
    address=/app-container.local/127.0.0.1
    

    J'ai créé un fichier docker-compose dédié à Traefik + j'ai démarré une instance de Traefik.

    Ensuite j'ai démarré mon application avec docker-compose :

    • en settant une variable COMPOSE_PROJECT_NAME qui me sert à :

      • forcer un peu le destin du nom du conteneur
      • avoir un nom "unique" (à condition que la valeur passée soit unique) dans Traefik :
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.processor.rule=Host(`${COMPOSE_PROJECT_NAME}.app-container.local`)"
          - "traefik.http.routers.processor.entrypoints=web"
          - "traefik.http.routers.processor.service=svc-${COMPOSE_PROJECT_NAME}-processor"
          - "traefik.http.routers.processor.priority=10"
          - "traefik.http.services.svc-processor.loadbalancer.server.port=5000"
    
          - "traefik.http.routers.jupyter-notebook.rule=Host(`${COMPOSE_PROJECT_NAME}.app-container.local`) && PathPrefix(`/jupyter-notebook`)"
          - "traefik.http.routers.jupyter-notebook.entrypoints=web"
          - "traefik.http.routers.jupyter-notebook.service=svc-${COMPOSE_PROJECT_NAME}-jupyter-notebook"
          - "traefik.http.routers.jupyter-notebook.priority=20"
          - "traefik.http.services.svc-jupyter-notebook.loadbalancer.server.port=8888"
    

    Les avantages :

    • Traefik est commun et donc consomme moins de ressources
    • même s'il y a une bidouille de résolution, chaque application possède maintenant sa propre URL
    • c'est un peu plus compliqué à mettre en œuvre que la solution n°1 à cause du DNS mais j'ai l'impression que ça reste encore acceptable

    Les inconvénients :

    • ça implique des changements en terme de DNS qui pourrait devenir lourds sur les quelques serveurs qui utilisent ifupdown
    • nous ajoutons 2 dépendances majeurs à notre écosystème logiciel :

      • si Traefik est down : nos applications sont down
      • si dnsmasq est down : nos applications sont down
    • peut-être que côté développeur c'est plus lourd / compliqué que ce que j'imagine

    • [^] # Re: Un petit état des lieux

      Posté par  . Évalué à 2.

      Nouvel inconvénient de la solution 2 (un Traefik commun) que je n'avais pas vu venir :

      • si Traefik est redémarré, il n'a plus connaissance des backends

      De plus en plus j'ai l'impression que dans mon cas d'usage bien précis, la solution élégante consiste à utiliser une instance Traefik par backend.

      • [^] # Re: Un petit état des lieux

        Posté par  . Évalué à 3.

        Solution 2 - un Traefik commun + un FQDN qui résout 127.0.0.1

        pas besoin de DNS, le FQDN qui envoie sur 127.0.0.1 c'est localhost
        http://localhost:5000 => http://127.0.0.1:5000

        sauf si tu as mis 127.0.0.1 pour ne pas nous donner l'IP d'hebergement de ton traefik

        note aussi, que 127.0.0.1 c'est la machine de l'utilisateur
        ou si c'est le process sur ton serveur qui tente de se connecter, c'est la machine ou tourne le process

        • [^] # Re: Un petit état des lieux

          Posté par  . Évalué à 1.

          Non je n'ai pas "masqué" l'IP : c'était bien de 127.0.0.1 dont je parlais :)

          Mais dans ce cas de figure, à savoir UNE instance de Traefik commune à tous les conteneurs, je me retrouve avec :

          • conteneur1
          • conteneur2
          • conteneur3

          Comment les distinguer ?
          Comment savoir que la requête qui arrive sur "localhost:5000" est pour conteneur1 et pas conteneur2 ?

          C'était ça l'idée derrière le FQDN propre à chaque conteneur :)
          Avec un FQDN tel que "conteneur1.container.local" j'ai pu créer une règle qui dit à Traefik :

          si Host == conteneur1.container.local alors envoie moi le trafic stp
          

          Ça fonctionne mais cette solution a un gros inconvénient finalement : si Traefik est redémarré, il n'y a plus aucune règle dedans et donc tous les backends deviennent injoignables sauf si je les redémarre

          • [^] # Re: Un petit état des lieux

            Posté par  . Évalué à 3.

            alors il ne faut pas utiliser le fqdn:port mais localhost/app1, localhost/app2, localhost/app3

            qui vont respectivement vers conteneur1, conteneur2, conteneur3

            • [^] # Re: Un petit état des lieux

              Posté par  . Évalué à 1.

              Oui mais comme je le disais dans un de mes messages ça vient avec un sacré problème du côté de Jupyter-Notebook :-/

              • [^] # Re: Un petit état des lieux

                Posté par  . Évalué à 3.

                • 127.0.0.1:6000/conteneur3/jupyter-notebook --> conteneur3:8888/jupyter-notebook
                • utiliser un "stripprefix" pour supprimer le "/conteneur1" (sinon le backend final ne comprends pas la requête)

                Ça fonctionne bien pour /app mais pas pour /jupyter-notebook qui a une facheuse tendance à faire une redirection sur "/jupyter-notebook"…

                indique à ton jupyter-notebook que son baseURL est /conteneur1/jupyter-notebook
                et il fera la reecriture qui va bien quand il va creer ses URLs

                • [^] # Re: Un petit état des lieux

                  Posté par  . Évalué à 1.

                  Je n'ai pas relu tous mes messages mais il me semble que j'avais considéré cette option effectivement :)

                  En théorie ça devrait fonctionner mais je ne l'ai pas testé réellement.
                  Le problème avec une instance Traefik commune est que si Traefik est redémarré pour X ou Y raisons après les conteneurs "backends", alors Traefik ne sera pas configuré pour router des flux vers ces backends.

                  Du coup pour l'instant j'ai eu une petite préférence pour la solution "un Traefik par backend" à défaut de mieux.

                  J'avoue ne pas avoir cherché explicitement s'il était possible de forcer une sorte de "refresh" de Traefik car lors de mes autres recherches je n'ai rien vu en ce sens :/

                  • [^] # Re: Un petit état des lieux

                    Posté par  . Évalué à 3.

                    en suivant la documentation je penses que tu trouveras ton bonheur

                    https://doc.traefik.io/traefik/providers/docker/

                    ca semble prevu pour,

                    en gros une option dans le dockerfile ou la ligne de commande, et hop, traefik detecte qu'un service est dispo

                    et en gros, traefik n'a pas besoin de "redemarrer", il recharge la config lui meme.

  • # Conclusion

    Posté par  . Évalué à 2.

    J'ai eu une discussion avec mon manager hier et il s'est rendu compte d'un petit détail : il s'est trompé en exprimant son besoin :D

    Du coup avoir une instance de Jupyter dans nos conteneurs existants n'est pas ce qu'il veut.
    Je dois donc laisser tomber ce sujet pour me concentrer sur son vrai besoin qui n'est peut-être pas plus simple :-)

    En conclusion si ça peut aider quelqu'un qui aurait un besoin similaire, personnellement mes recommandations seraient :
    1/ si besoin de démarrer X daemons dans un conteneur, d'utiliser supervisor
    2/ si besoin d'un reverse proxy magique pour router vers X ports, d'utiliser Traefik
    Dans mon cas j'aurais probablement poussé pour avoir une instance de Traefik pour chaque conteneur backend pour profiter au maximum du côté dynamique.

    Je remercie toutes les personnes qui ont pris du temps pour me répondre :)
    C'était un échange constructif et bien que le besoin ait changé j'ai appris pas mal de choses intéressantes de mon côté

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.