Résumé des épisodes précédents

J’ai installé Docker-ce (Community Edition) sur mon portable, et j’ai construit une image qui fait tourner du PHP. Pour l’instant, le MySQL est toujours sur le serveur et non dans un container docker. D’ailleurs, c’est pareil pour le memcached.

La persistance des données est un problème sous Docker. Pas un problème dans le sens que c’est impossible de s’en sortir, c’est juste qu’il faut bien réflechir à ce point et surtout le considérer différement. On y viendra plus tard.

J’en suis resté à cette histoire d’erreur dans mon session handler (je l’ai redéfini pour qu’il stocke les sessions dans memcached), et comme je vois pas les logs, je suis bien ennuyé.

Et pour rappel, le micro schéma d’architecture :

Brouillon d'architecture

Construction de l’image du Saas

J’ai placé les directives suivantes dans le php.ini (qui se trouve dans un endroit inhabituel)

$ cat /usr/local/etc/php/php.ini
log_errors = On
error_log = /dev/stderr

Il faut donc que je rajoute cela à mon Dockerfile.

1 - créer un fichier de configuration php.ini pour la construction du docker.

2 - une commande pour créer le fichier php.ini au bon endroit:

COPY generic-php.ini /usr/local/etc/php/php.ini

C’est pour mon image générique, mais pour la configuration du saas lui-même, j’ai aussi un fichier de configuration à emmener.

D’autre part, j’utilise un CDN (Content Deliver Network, pour l’instant, ce sera un container qui embarque avec lui les fichiers javascript et css dont les autres ont besoin - jQuery, Bootstrap, etc..) et pour ça je pense qu’un container avec uniquement NGinX peut suffire.

En tout cas, il faut absolument que je le mette en place.

Donc, je commence par arrêter et effacer les images. Ensuite, je reconstruis l’image générique et l’image qui en hérite;

(Que le diable m’emporte, la construction de l’image générique a utilisé une sorte de cache sorti de nulle part et a mis deux secondes !)

Qu’importe. J’ai reconstruit à l’identique et je me trouve dans la même situation.

Je ne peux pas me connecter, parce que la connexion implique du jquery, et comme je l’ai dit au début, je n’ai pas de CDN.

On a donc ici un choix comme dans la plupart des aventures:

  1. Mettre le CDN directement dans l’image et on verra ça plus tard
  2. Contruire le NGinX qui va héberger le CDN

On le joue au dé 6 : 1,2,3 => solution 1, sinon solution 2.

(je vais un rand(1,6) parce que là j’ai pas dé sous la main.)

3 ! Allez, va pour le CDN directement dans l’image. Au-delà de la plaisanterie, j’ai plein de choses à considérer, il faut donc faire des choix pour pouvoir avancer, sachant que ces choix vont peut-être être remis en cause par la suite.

Step 4/4 : COPY ../cdn /var/www/html
COPY failed: Forbidden path outside the build context: ../cdn ()

Tiens, c’est à savoir: les fichiers qui font partie de l’image doivent se trouver dans le repertoire de build. Bon, je le recopie directement.

Maintenant, ça marche nickel (pour ce qui est du CDN) et pour la connexion ? Marche aussi.

Il y a quelques bugs (les préférences de l’utilisateur mène vers un lien absolu qui ne marche pas (forcément)) et puis le passage à PHP7, j’ai quelques Warning parce que mes contrôleurs sont pas super bien écrits (shame on me), mais on s’en moque pour l’instant.

Je me contenterai de souligner que ce mode de fonctionnement en services séparés permets de voir plus rapidement ce qui va merdouiller quand on installer l’appli sur plusieurs machines et que c’est globalement une bonne chose.

Ceci dit, installer le CDN sur directement sur l’image a permis de valider les choses, mais ce n’est pas une solution tenable, c’est juste parce que le dé est tombé sur 3.

Mais: si j’installe le CDN dans un NGinX, il va falloir accéder aux fichiers et donc pour ça, je devrais alors écouter sur le port 8081, par exemple, et ça suppose que je modifie mes vues (enfin la vue template) pour ajouter le lien vers ce nouveau serveur. Je n’aime pas trop cette solution, elle m’a l’air de necéssiter ce truc qu’on appelle le travail.

Je pense que je ne peux pas plus longtemps éviter le routeur.

En gros, il me faut un truc qui va écouter sur le port 8080 et orienter les requêtes vers les bons containers.

Traefik

Ce truc, c’est traefik. Je l’ai pas sorti de nulle part, on me l’a conseillé: un collègue qui m’a donné pas mal de conseil sur Docker et que je citerai ici quand il m’aura donné un profil pour le mentionner…

Voilà ce que ça modifie sur le schéma :

Architecture avec Traefik

En plus, ça tourne sur et avec docker. Le programme lui-même peut tourner dans un docker, et de plus, il écoute sur la socket /var/run/docker.sock pour voir si des containers proposent un nouveau service, et automatiquement, route les requêtes vers le bon endroit.

Donc je crée un fichier de configuration traefik.toml:

logLevel = "DEBUG"
defaultEntryPoints = ["http", "https"]
[web]
address = ":8080"
[docker]
endpoint = "unix:///var/run/docker.sock"
# alderaan it's my machine - yes it will eventually explode
domain = "alderaan"

# Enable watch docker changes
watch = true

Je ramène une image

docker pull traefik

Je le build avec ça :

FROM traefik:latest
COPY config/traefik.toml /etc/traefik/traefik.toml

Lancer le build :

docker build -t saas_traefik .

Démarrer une instance de Traefik

docker run -d -p 8080:8081 -p 443:443 -p 80:80 \
  -v /var/run/docker.sock:/var/run/docker.sock --name running_traefik saas_traefik

L’interface web est dispo sur 8080 - pour l’instant il n’y a pas de backend.

Démarrage d’un nginx

Builder le NGinX en CDN avec le Dockerfile suivant :

FROM nginx:latest
COPY css /usr/share/nginx/html/css
COPY js /usr/share/nginx/html/js

Et la commande :

docker build -t cdn .

On démarre le container, cette fois en lui passant des paramètres spécifiques qui seront utilisés par Traefik. En détail, la solution que j’ai choisie, c’est Host:alderaan;PathPrefixStrip:/cdn/, ce qui signifie que le Host: de la requête http doit être alderaan, et que les requêtes commençant par le prefix /cdn/ doivent être envoyées vers ce container en “strippant” /cdn de l’URL.

docker run -d -l traefik.backend=cdn \
  -l traefik.frontend.rule="Host:alderaan;PathPrefixStrip:/cdn/" \
  -l traefik.port=80 \
  --name running_cdn cdn

Il faut d’abord que je rebuild l’image du saas sans le CDN…

je supprime le CDN que j’avais recopié dans le webroot, je rebuild, et je relance :

docker run -d -l traefik.backend=saas \
  -l traefik.frontend.rule="Host:alderaan;PathPrefixStrip:/saas" \
  -l traefik.port=80 \
  --name running_saas saas

Ça marche !

En gros, les requetes qui arrivent sur /cdn partent vers le NGinX et les autres vers le container saas. Nickel.

Pour l’instant tout le site tourne sur le port 8081 en http, ce qui n’est pas top, mais ensuite, il est prévu de mettre un serveur apache en frontal qui portera le SSL. Ou bien, tant qu’à faire, traefik pourra porter le certificat SSL, puisqu’aussi bien, il sait le faire. Je crois même qu’il est possible de l’interfacer avec LetsEncrypt pour avoir des certificats automatiquement.

Maintenant le but, c’est de faire tourner une appli qui va utiliser le Saas.

En gros, les applications ont besoin du saas pour deux choses:

  • elles utilisent l’API pour l’authentification (spoil: c’est pas terrible comme solution)
  • elles y stockent leurs sessions

Le coup de l’API pour l’authentification c’est pas une super idée. Le mieux, c’est que si les utilisateurs ne sont pas authentifiés, alors ils sont renvoyés vers le saas qui se charge de gérer ces aspects (authentification, mais aussi les trucs du genre i-forgot-my-password, i-want-a-new-account, etc.)

Qu’importe, je vais fabriquer une image avec le logiciel ludo;

Pour la configuration, c’est là que les problèmes commencent. Comment je fais, lorsque j’installe une application, pour lui dire où se trouve le saas ? Au niveau du container appli, pour se connecter sur le saas il faut passer par son adresse interne. Aujourd’hui c’est 172.17.0.2 mais demain ?

D’après certaines rumeurs, il conviendrait de passer par des variables d’environnement. C’est à dire qu’au moment de lancer l’appli, il faut lui donner l’adresse du saas.

C’est bien joli, mais si j’en ai plusieurs instances ? Je peux toujours lui donner plusieurs adresses, j’imagine. Oui, mais si j’ai de nouvelles instances ? Et si l’ancienne est remplacée par une nouvelle ?

Il est clair qu’il me faut aussi un routeur pour les micro-services. Du coup, est-ce que traefik ne peut pas s’occuper de ça aussi ?

La solution se trouve plutôt dans un outil supplémentaire, le swarm. C’est un sur ensemble de docker qui permet de faire du service discovery.

Par contre, j’ai un autre soucis, c’est le memcached. De par son fonctionnement les clients ont besoin de connaitre l’intégralité des membres du cluster memcached (qui d’ailleurs n’est pas un cluster), ce qui fait que ce n’est pas l’outil idéal pour stocker les sessions. Peut-être qu’il vaudrait mieux employer redis pour ça ?

Je vais dessiner un schéma pour mieux expliquer tout ça, comment je pense que ça doit fonctionner, etc.

Et ben voilà, j’ai du boulot ! Rendez-vous très bientôt pour l’épisode 4 : ce sera principalement un épisode théorique, avec des considérations sur les choix des moyens et des politiques à mettre en oeuvre pour arriver à nos fins.

(Et je mettrai des captures d’écrans !)

Update : La suite, c’est ici, dans l’épisode 4 : Adventures with Docker - Part 4 - The Swarm