Test des solutions serverless

Il existe plusieurs solutions open sources ou hébergées pour monter sa propre architecture serverless. J’ai choisi de tester les deux solutions open sources plus populaires, ainsi que deux des plus gros acteurs actuellement sur le marché afin de me faire une idée du fonctionnement de chaque solution. Je vais déployer un simple hello world sur l’ensemble des outils afin de tester l’utilisation et les temps de réponses.

Je ne connais aucunes des solutions, mais je vais me faire mon analyse autour des 5 points suivants :

  • Est-ce qu’il est possible d’avoir une solution hautement disponible ?
  • Est-ce qu’il y a un engouement autour de la solution ? (articles, conf, contributions)
  • Est-ce que c’est simple à utiliser pendant les développements ?
  • Est-ce que la solution est scallable ?
  • Est-ce que c’est utilisable/utilisé en prod ?

Pour les solutions hébergées je vais toujours prendre, si possible,  un datacenter proche de la France. Pour les solutions open sources je vais utiliser un serveur Scaleway hébergé en France avec 4 coeurs, 4 Go de ram et 300Mo/s de bande passante entièrement dédiée au test. Chaque test sera fait sur une installation « propre » de Debian Stretch.

Bien évidement il y a d’autres facteurs qui entrent en jeux avant de choisir un solution plutôt qu’une autre tel que les SLA en cas de problème, la fiabilité du partenaire (Est-ce qu’il sera toujours présent dans 5 ans ?), le pays dans lequel les serveurs sont hébergées… Mais dans le cas présent je m’intéresse surtout à la fiabilité du service et je n’ai ni impératifs de production ni données sensibles à hébergées.

A la suite du test je choisirais une solution pour construire une petite application.

Amazon Lambda

Présentation

Le plus célèbre acteur du marcher est Amazon avec ses lambdas functions. Ce qui est vraiment pas mal avec cette solution c’est que pour un petit projet vous pouvez vous en sortir sans rien avoir à payer car le premier million de hit est gratuit.

Il y a plusieurs version de java, node, python et .Net de supportées par Amazon Lambda.

Mise en place

Les fonctions lambda sont intégrées à la solution AWS et peuvent être lancées par beaucoup d’évènements différents. Dans mon cas ce qui m’intéresse c’est les fonctions lambda déclenchées par une API Gateway. Il n’y a aucunes installation à faire avant de pouvoir les utiliser. 

Hello world

Cette lambda est un bête hello world :

Développement /Déploiement

Une fois enregistrée ma lambda est automatiquement disponible via HTTPS sur une URL unique. Il est possible d’activer ou non une couche d’authentification, mais pour mon hello world je ne vais pas la mettre en place.

Via le framework serverless, dont je parlerais dans un prochain article,  le déploiement / mise à jours peux se faire en ligne de commande en quelques secondes.

Benchmark

Je vais lancer un petit benchmark dessus pour voir les temps moyens de réponses sur un hello world.

➜  ~ ab -n 1000 -c 10 https://XXXXXXXXXX.execute-api.eu-central-1.amazonaws.com/default/test
[...]
Concurrency Level:      10
Time taken for tests:   16.969 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      305000 bytes
HTML transferred:       19000 bytes
Requests per second:    58.93 [#/sec] (mean)
Time per request:       169.686 [ms] (mean)
Time per request:       16.969 [ms] (mean, across all concurrent requests)
Transfer rate:          17.55 [Kbytes/sec] received
[...]

Les temps de réponses ne sont vraiment pas mauvais, en moyenne 58 requêtes par seconde.

Conférences

Une conférence en français d’amazon présente assez bien les fonctions lambda :

Ce qui est très sympa avec cette solution c’est que c’est clef en main. On a plus qu’à se concentrer sur notre application en s’abstrayant de la partie gestion des serveurs et de la problématique de haute disponibilité. 


Cloud function Google

Présentation

Google, second grand acteur du serverless avec ses cloud function propose aussi un « forfait » gratuit pour les deux premiers millions d’appel.

Tout comme amazon lambda il est possible de créer et d’administer ses lambda via la console web. Il faut patienter une bonne minute entre le moment ou on crée une fonction et le moment ou elle est mise à disposition. Tout comme les lambdas d’amazon il est possible de déclencher les Cloud functions via des évènements autres que HTTP (ex: modification dans une base firebase).

Mise en place

Il n’y a rien de spécifique à faire à part avoir un compte google développeur.

Hello world

Voici le code de ma cloud function :

Développement / Déploiement

De même que pour Amazon Lambda le framework serverless va permettre de travailler avec Google Cloud function. 

 Ce qui est vraiment un plus, non négligeable, c’est qu’il est possible de lancer les fonctions en locale avec l’emulateur node js pour google cloud.

Benchmark

➜  ~ ab -n 1000 -c 10 https://europe-west1-XXXXXXX-XXXXX.cloudfunctions.net/function-1
Concurrency Level:      10
Time taken for tests:   15.187 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      361008 bytes
HTML transferred:       11000 bytes
Requests per second:    65.85 [#/sec] (mean)
Time per request:       151.870 [ms] (mean)
Time per request:       15.187 [ms] (mean, across all concurrent requests)
Transfer rate:          23.21 [Kbytes/sec] received

Google Cloud function ne support que du python 3.7 et du node js 6 et 8.


Apache OpenWhisk

Présentation

OpenWhisk est la solution open source serverless de la fondation apache. Il est possible mettre en place cette solution sur du kubernates, du mesos ou simplement via docker-compose.

Le support des langages est assez étendu pour les fonctions hébergées sur la solution. Il y a notamment Nodejs, python, PHP, java, Go … 

Les fonctions, appelées Actions,  peuvent être déclenchées par des évènements (trigger), des appels HTTP ou encore via des modifications sur un flux d’information (feed).

Mise en place

J’ai suivi la documentation pour une installation docker compose. Malgrès plusieurs tentative je n’ai jamais réussi à faire tourner OpenWhisk sur mon serveur. Le script d’install a crashé de multiples fois sur des étapes différentes et la seule fois (sur 7) ou il n’a pas crashé pendant les install il n’a pas réussi à configurer le client car la stack ne renvoyait que des 500.

J’ai donc décidé de tester sur mon macbook pro 2017. Sur le mac je n’ai eu aucun problème à l’installation.

Hello world

function main() {
    return {payload: 'Hello world'};
}

Développement /Déploiement

Un client en ligne de commande est disponible pour gérer les functions déployées. Il est donc assez simple et rapide de mettre à jours une fonction pour la tester.

J’ai eu beaucoup de difficultés pour trouver l’URL pour appeler ma fonction, les crédentials pour m’authentifier sur le endpoint (une fois trouvé). 

Benchmark

Le benchmark n’a pas de sens car l’éxecution se fait sur mon mac dans la docker machine. En moyenne les temps de réponses étaient aux alentours de 150ms.


OpenFaas

Présentation

OpenFaas est une solution opensource basée sur des orchestrateurs docker. Pour mon test je vais utiliser swarm avec un seul node. OpenFaas vient par défaut avec une interface web d’administration, une installation prés-configurée de prometheus afin de collecter des metrics sur la plateforme.

OpenFaas permet de créer des fonctions dans divers langages (Go, Python, Node, Ruby, Java…) mais aussi faire une passerelle entre OpenFaas et des binaires. La solution propose aussi un support des lambdas d’amazon.

La documentation est centralisée et assez complète. On peux y trouver  beaucoup d’exemple sur l’ensemble des langages supportés.

Ce qui est important de noter c’est qu’il est possible de déployer openfaas sur des serveurs ARM (Raspberry par exemple).

Mise en place

L’installation sur un docker swarm se fait en moins d’une minute en lancant le script présenter dans la documentation. Pour intéragir avec openfaas il client en ligne de commande est disponible en plus de l’interface web.

Chaque fonction est une image docker qui doit être présente sur un registry docker

Hello world

"use strict"

module.exports = (context, callback) => {
    callback(undefined, {status: "Hello World"});
}

Développement /Déploiement

faas new hello-world --lang node --gateway http://51.15.84.114:8080
# coder la fonction dans le fichier hello-world/handler.js
faas build -f hello-world.yml
faas push -f hello-world.yml
faas deploy -f hello-world.yml

Benchmark

➜  ab -n 1000 -c 10 http://51.15.84.114:8080/function/hello-world
Concurrency Level:      10
Time taken for tests:   49.517 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      257000 bytes
HTML transferred:       26000 bytes
Requests per second:    20.20 [#/sec] (mean)
Time per request:       495.166 [ms] (mean)
Time per request:       49.517 [ms] (mean, across all concurrent requests)
Transfer rate:          5.07 [Kbytes/sec] received

Conférences


J’ai aussi regardé du côté de Iron-io mais la solution, bien qu’elle fournisse un interface HTTP, semble plus axée sur les traitements asynchrones. Ce n’est pas spécialement ce qui m’intéresse. Mais pour du traitement de masse comme du travail sur des images, de l’envoie de mail… c’est une solution à explorer.

Résultat du comparatif

[table id=3 /]

OpenFaas est une solution très interessante qu’il faut surveiller mais qui, pour le moment ne me paraît pas être encore assez mature pour héberger une application de production.

Actuellement je dirais que Amazon lambda est la solution la plus fiable/stable/simple car elle est très bien intégrée à l’environnement AWS, il y a une bonne documentation, elle s’appuie sur l’expertise d’un des leaders sur le sujet de l’hébergement.

C’est quoi le serverless ?

Le serverless, je ne connais pas du tout. L’idée est de faire une série d’articles découvertes sur cette architecture. L’idée est d’aller jusqu’à la création d’une petite application.

Serverless – Google trand

Depuis deux ans on parle de plus en plus de serverless mais il n’est pas toujours simple de voir exactement de ce que cela implique pour une application d’être serverless. Autre point, est-ce qu’être serverless implique qu’il n’y ait pas de serveur du tout ? 

Les principes du serverless

Le serverless est une nouvelle façon d’héberger ses applications. Avec docker et des orchestrateurs comme kubernetes il est possible de faire de l’auto-scalling pour adapter le nombre d’instances d’une application en fonction de l’utilisation de l’application. M6 Web a d’ailleurs fait un retour d’expérience d’auto-scalling via la mise en place de kubernates

Le serverless est l’étape suivante de l’auto-scalling tout en apportant une approche micro-service. Quand personne ne va utiliser une application, il n’y aura aucune instance qui tourne (selon les solutions). Et c’est seulement au premier hit que l’application sera bootée. En fonction du nombre d’appels, il y aura plus ou moins d’instances qui tourneront en même temps. L’application est donc fortement liée au backend utilisé.

Cette approche permet, selon la solution choisie, de limiter les coûts d’hébergement car les providers (Amazon, Google, Microsoft,…) facturent l’hébergement à la seconde, ou même au centième de seconde d’exécution. Si personne n’utilise l’application, les coûts d’infrastructure seront quasiment nuls.

Et c’est bien là, la force de cette approche. Quand on loue des serveurs à l’année on est obligé de payer pour un nombre de serveurs permettant d’absorber les pics maximum de charge. Le reste du temps les serveurs sont sous-exploités.

La rapidité

Le serverless n’est possible que si on est capable de démarrer un service rapidement. Si l’application met 30 secondes à démarrer, il faudra toujours conserver au moins une instance de disponible. A ce moment là on en revient à une solution d’autoscalling classique.

La fiabilité

La mise en place d’une architecture serverless déporte une partie de la complexité de l’application sur l’infrastructure qui va orchestrer nos fonctions. Il est donc primordial qu’elle soit fiable et résiliante. Il est donc préférable, pour une application de production d’utiliser une solution hautement disponible (HA). 

La scallabilité

Par nature une architecture serverless est scallable car elle va devoir instancier à la demande des applications. Mais pour que ce soit possible il faut que l’infrastructure l’autorise (allocation/ libération de ressources à la volée).

Le nouveau paradigme

Cette infrastructure impose que chaque fonction soit stateless, mais vous avez la possibilité d’interagir avec d’autres services comme des bases de données ou des APIs.

Le développeur est pleinement conscient du fonctionnement de l’infrastructure. Comme on l’a vu précédemment, la frontière est mince entre l’application et la solution d’hébergement.

Cette façon de travailler permet, en théorie, de réduire le temps de déploiement d’une fonctionnalité car la complexité de chaque fonction est plus faible que celle d’une application monolithe. Néanmoins le risque est d’avoir autant de façons de faire que de fonctions hébergées. Comme chaque développeur va pouvoir travailler « dans son coin » en faisant fi de l’existant, les dérives et les mauvaises pratiques sont vites arrivées. Il y a, à mon avis, un besoin de revue de code et de tests encore plus important sur ce type d’architecture.

Le dernier point et que l’on peut avoir un langage commun sur les problèmes de performance avec la hiérarchie. Comme chaque seconde de traitement augmente la facture, il est plus simple de mettre en avant des projets techniques d’optimisation.  Un temps de traitement divisé par deux c’est une fonctionnalité qui coûte deux fois moins cher. Ce n’est pas réellement le cas sur une application classique.

En conclusion, le serverless est une nouvelle approche sur la façon de concevoir une application en poussant au maximum les approches devops et micro-services afin de concevoir une application hautement scallable tout en maîtrisant les coûts.

Monter un cluster swarm avec des Raspberry pi et HypriotOS

HypriotOs est une version de raspbian dans laquelle docker, et docker swarm sont pré installés. La particularité est que HypriotOs tourne sur des architectures ARM telles que les Raspberry, le Nvidia shield et quelques autres cartes comme les ODROID C2.

Dans cet article, nous allons voir comment monter un petit cluster swarm avec trois noeuds (1 manager et 2 workers). Une fois la partie serveur montée, nous ferons tourner des services dessus à la fois en lignes de commandes et via une interface graphique.

Swarm

Swarm est l’orchestrateur historiquement intégré dans docker. Il permet de distribuer automatiquement les containers sur l’un ou l’autre des noeuds du cluster en fonction de l’indisponibilité ou de la charge d’un noeud.  Docker inc a été testé pour scal

ler jusqu’à 1 000 nœuds et  50 000 conteneurs sans dégradation de performance. A mon avis nous allons avoir du mal à faire tourner autant de conteneurs sur nos Raspberry.

Il existe deux types de noeuds, les managers et les workers. Pour faire simple, les managers peuvent administrer ce qui tourne sur le cluster et répartir le travail sur les workers tandis que les workers sont de simples exécutants qui lancent des containers.

De base, le cluster va faire du mesh routing. Cela signifie, par exemple, que si vous lancez un serveur web sur le port 80 quelque part sur votre cluster, vous pourrez y accéder en attaquant n’importe quelle machine (worker ou manager) qui compose le cluster. Swarm va aussi nous proposer un système de load balancing afin de pouvoir distribuer la charge entre différentes instances d’un service. 

https://docs.docker.com/engine/swarm/ingress/#publish-a-port-for-a-service

Les deux points précédents sont disponibles, sans configuration, dès la création de votre cluster.

Choix des matériaux

Pour monter mon cluster avec trois noeuds il va me falloir le matériel suivant :

Matériel pour un cluster swarm de raspberry pie

Installer HypriotOs sur les cartes

Pour les utilisateurs de Linux et MacOs il existe un outil proposé par la communauté de Hypriot pour flasher les cartes. Malheureusement, pour les utilisateurs de Windows, les choses sont un peu plus compliquées.

Pour linux et mac

Hypriot flash est un utilitaire en lignes de commandes qui va s’occuper de tout, de la décompression de l’image à l’installation sur la carte SIM. Pour l’installation, tout est précisé dans le readme du dépôt.

Télécharger la dernière image de hypriotos

wget https://github.com/hypriot/image-builder-rpi/releases/download/v1.9.0/hypriotos-rpi-v1.9.0.img.zip

Vérifier le checksum de l’image

➜  cat ../Downloads/hypriotos-rpi-v1.9.0.img.zip.sha256
be21a702887817d8c84c053f9ee4c54e04fd55e9fb9692edc3904801b14c33a8  hypriotos-rpi-v1.9.0.img.zip

# sur linux sha256sum / sur mac gsha256sum

➜  gsha256sum hypriotos-rpi-v1.9.0.img.zip
be21a702887817d8c84c053f9ee4c54e04fd55e9fb9692edc3904801b14c33a8  hypriotos-rpi-v1.9.0.img.zip

Flasher les cartes SD

Pour notre manager:

flash hypriotos-rpi-v1.9.0.img.zip --hostname manager-01

Pour nos workers :

flash hypriotos-rpi-v1.9.0.img.zip --hostname worker-01
flash hypriotos-rpi-v1.9.0.img.zip --hostname worker-02

Votre mot de passe root vous sera demandé pendant l’installation de l’image sur la carte.

Vous devriez avoir une sortie console proche de celle ci:

flash --hostname worker-01 hypriotos-rpi-v1.9.0.img.zip
Using cached image /tmp/hypriotos-rpi-v1.9.0.img

Is /dev/disk2 correct? yes
Unmounting /dev/disk2 ...
Unmount of all volumes on disk2 was successful
Unmount of all volumes on disk2 was successful
Flashing /tmp/hypriotos-rpi-v1.9.0.img to /dev/rdisk2 ...
No 'pv' command found, so no progress available.
Press CTRL+T if you want to see the current info of dd command.
Password:
930+1 records in
930+1 records out
975765504 bytes transferred in 111.983538 secs (8713473 bytes/sec)
Mounting Disk
Mounting /dev/disk2 to customize...
Set hostname=worker-01
Unmounting /dev/disk2 ...
"disk2" ejected.
Finished.

Pour windows

Je n’ai jamais flashé mes cartes SD depuis Windows. Mais vous pourrez trouver une procédure sur le site de hypriot. 

Dans le cas de Windows il faudra, si possible, éditer le fichier user-data  (c’est un yml sans extension) à la racine de la carte SD pour changer le hostname qui est présent dedans. Il faudra en nommer un manager-01 et les deux autres worker-01 et worker-02 histoire de pouvoir suivre dans la suite de l’article.

Les branchements

Rien de bien complexe, les câbles ethernet dans les prises ethernets, les cables d’alimentation dans les micro usb et les cartes SD dans les slots prévus à cet effet. Et hop, le tour est joué. Vous devriez pouvoir détecter les machines sur votre réseau local.

Branchements raspberry pi pour un cluster swarm

Se connecter en SSH sur les différents noeuds

Sur mac, j’utilise l’application LanScan Pro depuis des années pour scanner mon réseau local.

Détection des machines du cluster swarm sur le réseau

J’ai bien mes trois Raspberry qui sont présents sur le réseau avec les ip 192.168.1.22, 23 et 24.

Pour se connecter en ssh sur les Raspberry il faut utiliser le login/ mot de passe par défaut de hypriotOS. A savoir pirate/ hypriot.

Première connexion SSH sur hypriot

A cette étape je vous conseille de faire les mises à jour avant d’aller plus loin. Ce serait dommage de faire toute sa configuration sur une version qui a des failles de sécu. De plus il est préférable d’avoir les versions de docker sur l’ensemble du cluster.

Le temps des mises à jour vous avez le temps d’aller prendre un café 😀

Les versions de docker

Création du cluster swarm

Creation du manager

Sur le rasp nommé manager-01 je vais créer le cluster swarm :

docker swarm init

La commande va créer le cluster, vous enregistrer en temps que leader et vous donner la commande pour ajouter des workers.

La création du cluster swarm
La liste des noeuds du cluster après création

Ajout des workers

Pour ajouter les workers on va utiliser la commande donnée au moment de la création du cluster en lançant sur les Raspberry worker-01 et worker-02.

Si vous avez perdu la commande vous pouvez la récupérer en vous connectant sur un manager et en exécutant la ligne de commande :

docker swarm join-token worker
Récupération de la commande pour joindre un cluster swarm

On va donc se connecter en ssh sur worker-01 et worker-02 et lancer la commande de join. Vous devriez avoir le résultat suivant :

Joindre un cluster swarm

Sur le manager on va voir apparaitre les noeuds en tant que worker si on utilise la commande docker node ls

La liste des noeuds d’un cluster swarm

Installation et utilisation de Portainer

Portainer est un outil de visualisation et d’administration et de visualisation open source pour docker. Il est assez intéressant dans notre cas car il va nous permettre de visualiser ce qu’il se passe sur l’ensemble de notre cluster depuis une interface WEB. 

Installation de portainer

On va lancer un container Portainer sur le noeud manager-01.

docker run -d -p 9000:9000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

Portainer est maintenant compatible avec notre architecture arm, il est donc possible d’utiliser l’image officielle. 

Une fois le container lancé, vous pouvez vous rendre, via votre navigateur, sur le port 9000 de votre manager. Pour moi http://192.168.1.22:9000.

Vous allez être redirigé sur la page de création du compte administrateur. Il  est préférable de ne pas conserver le nom d’utilisateur par défaut.

Installation portainer

La seconde étape de l’installation est le choix du type de connexion à docker. Nous allons choisir local car nous avons monté le socket docker lors de la création du container.

Installation portainer

Maintenant que Portainer est configuré nous allons pouvoir consulter notre cluster swarm, mais aussi lancer des services si nous le souhaitons. 

On remarque déjà, que notre cluster est présent et que nos 3 noeuds sont bien détectés.

Cluster swarm dans portainer

En l’état notre cluster est composé de 12 coeurs et de 3 Go de ram.

Cluster visualizer

Lancer un service sur swarm

Pour lancer un service, il y a deux possibilité. Soit passer par portainer soit le faire en lignes de commandes. A vous de voir ce que vous préférez. J’ai préparé une petite image pour faire cette démo. Je m’excuse d’avance du poids de l’image (120Mo) pour faire un hello world…

J’ai buildé une image apache/ php qui affiche le hostname courant (celui du container). L’idée est de déployer un service sur le cluster et de voir comment il réagit et quel est le comportement que l’on obtient.

Déployer un service en lignes de commandes

Nous allons lancer une instance de notre image sur le cluster. Nous n’avons pas à nous soucier du choix de la machine sur laquelle l’application va tourner car le routing mesh nous permettra d’y accéder depuis n’importe quel noeud.  Mais vous pouvez le savoir dans portainer en retournant sur la vue Cluster visualisation.

docker service create -p 80:80 --name hello-world docker.io/lahaxearnaud/swarm-hello-world
Création d’un service dans swarm

Il est possible de préciser beaucoup d’options pour limiter les ressources que le container pourra utiliser, la façon de vérifier que l’application est toujours up (health check)…

Déployer un service avec Portainer

Toutes les options que l’on peut donner via le cli sont disponibles via portainer pour la création de service. La mise en place de nouveau service est très simplifié grâce à l’interface graphique.

Creation d’un service dans portainer

Pour notre test nous n’avons pas besoin des configurations avancées.

Tester le load balancing

Nous avons maintenant un service qui tourne sur notre cluster et qui sert une « page » web contenant le nom du container qui le fait tourner. Pour tester le load balancing on fait une série d’appels et on va regarder si on obtient toujours le même nom de container.

for ((i=1;i<=10;i++)); do curl "http://192.168.1.22"; echo ""; done

Sans surprise pour le moment je vais obtenir 10 fois la même valeur, car nous n’avons qu’une instance qui tourne :

Dans un premier temps nous allons faire scaller notre application pour faire tourner 9 instances.

docker service scale hello-world=9

Vous pouvez aussi le faire via portainer en cliquant sur le service hello-world dans la section service et en modifiant la valeur dans le champs Replicas.

Scaller un service avec portainer

Il va falloir attendre un peu que l’image soit téléchargée sur les différents Raspberry et que les containers soient lancés. Mais une fois que tout est prêt, on va pouvoir relancer notre commande.

On se rend bien compte que la charge est distribuée entre les différentes machines. Sur les 10 hits on a touché les 9 instances.

Consulter les logs

Sur le manager vous pouvez faire un tail sur les logs d’un service via la commande docker service log -ft hello-world.

Visualisation des logs d’un service swarm

Dans portainer par contre il semblerait qu’il y ait un bug et qu’il ne remonte que les logs des instances présentes sur le manager. 

Que mettre sur le cluster ?

Pour ma part, j’utilise swarm depuis au moins deux ans pour ma domotique. Comme je voulais jouer un peu avec l’IoT j’ai mis des capteurs un peu partout dans l’appartement (luminosité, température, mouvements, capteurs d’ouverture de porte et de fenêtre…) et chaque élément envoie des messages dans un rabbitmq. Mon cluster contient l’ensemble des consommateurs et l’application symfony qui sert l’interface graphique. Les consommateurs sont des workers symfony 4 qui utilisent le composant symfony messenger

Avoir cette architecture en cluster permet de perdre ou d’ajouter un ou plusieurs serveurs sans devoir déplacer des applications à la main. Là où il faut être attentif c’est que swarm peut continuer de fonctionner en perdant un worker, mais la perte des managers est plus problématique. Il est préférable d’avoir plusieurs managers pour éviter que le cluster se retrouve sans manager en cas de panne. Un nombre impair de managers est préférable pour s’assurer que l’algorithme (Raft) arrive à élire un nouveau leader.

Retour d’expérience

La technologie est sympa et la fiabilité est au rendez-vous sur tout ce qui est worker et application web. Par contre, j’ai rencontré pas mal de problèmes en voulant mettre un rabbitmq, un elastic search ou encore un mysql dans le cluster. Après plusieurs crashes je les ai installés sur une machine séparée. Je ne sais pas à quel niveau se situe le problème, mais depuis que c’est sur un rasp séparé il n’y a plus de problèmes.

L’autre point est que j’ai une livebox à la maison, c’est elle qui me sert de DHCP (c’est dans ma todo de faire autrement). Elle a tendance à sortir du réseau les machines avec des ips fixes au bout de quelques jours. Je me suis retrouvé à plusieurs reprises avec un cluster qui était HS car toutes les machines étaient déconnectées.

Le fait d’être sur une architecture ARM nous empêche d’utiliser la plupart des images disponibles sur les hubs. Il faut donc passer du temps à faire ses propres images. Ce n’est pas forcement un mal !

Faq

Comment retirer proprement un noeud d’un cluster swarm ?

Si c’est un manager il faut se poser la question de promouvoir un worker en manager avant de commencer docker node promote worker-02 puis vous devez retirer le role manager au noeud à supprimer docker node demote manager-01. La seconde étape est de dire à l’orchestrateur d’enlever tout ce qui tourne sur le noeud docker node update --availability drain worker-01. Une fois qu’il n’y a plus rien sur le noeud on peut le sortir du cluster via la commande docker node rm worker-01. De cette manière il n’y aura pas d’interruption de service.

Comment supprimer le cluster swarm ?

Dans un premier temps il faut supprimer les services qui tournent dessus : docker service rm mon-service. Une fois que c’est fait vous pouvez supprimer les noeuds un à un via docker node rm -f node-name. Puis sur le manager faire un docker swarm leave -f.

Est-ce qu’il est possible de deployer une image locale ?

Non, il n’est pas possible de déployer une image qui n’est pas dans un registry. Mais vous pouvez monter votre propre registry et l’utiliser pour déployer vos images sur vos workers.

Est-il possible de déployer un fichier docker-compose sur swarm ?

Oui c’est possible via docker stack deploy

Est-ce que l’on peut monter des volumes dans les services ?

Oui vous pouvez, maintenant il y aura autant de volume que de noeuds sur lesquels tourneront vos applications.

Symfony Messenger et rabbitmq

Symfony 4 apporte un nouveau composant qui va nous permettre de brancher notre application sur un, ou des, brokers de messages. Grâce à ce composant, on va pouvoir accélérer notre application en traitant de façon asynchrone tout ce qui n’est pas strictement nécessaire à l’affichage de nos pages. Ce module maintenu par la Core Team Symfony va à terme remplacer les bundles existants. 

Use case

Un exemple couramment utilisé pour expliquer pourquoi c’est nécessaire d’utiliser ce genre de technique est le suivant:

Un utilisateur passe une commande sur mon site de e-commerce. Il a payé sa commande et attend juste le message de confirmation. Si je traite tout en synchrone je vais devoir réaliser plusieurs traitements avant de lui rendre la main:

  1. Changer le statut de sa commande
  2. Mettre à jour les stocks et invalider les caches qui correspondent
  3. Envoyer un email de confirmation au client
  4. Prévenir le service de préparation de cette nouvelle commande
  5. Envoyer des metrics pour suivre le volume des ventes

Sur les 5 tâches citées, le client n’a besoin que de la première de façon immédiate. Les autres peuvent se faire dans les secondes ou minutes qui suivent. 

Au-delà du simple point de performance il y a aussi le problème de responsabilité unique. Si toute cette logique est dans le contrôleur, vous allez devoir gérer tout un ensemble de règles disparates. Le contrôleur va forcement devenir inmaintenable au bout d’un moment.

AMQP

Pour pouvoir différer les autres tâches, il va falloir que je pousse des messages dans des queues (type rabbitmq, SQS) et que des workers viennent les consommer pour réaliser les traitements.

Sur cet exemple, de Microsoft, on voit bien le principe d’un broker de message. Un ou plusieurs producteurs (senders) envoient des messages dans une queue et ils sont dépilés par un ou plusieurs consommateurs (receivers).

Dans le meilleur des mondes, vous n’avez qu’une technologie de service de queue et peut-être même qu’un seul serveur à interroger. Dans ce cas, c’est assez simple et vous utilisez sûrement déjà un client php pour le faire.

Mais si vous êtes à cheval sur plusieurs technos/ serveurs, c’est vite un casse-tête car il va falloir gérer plusieurs protocoles et serializers.

Symfony Messenger

Le composant Messenger va vous permettre de gérer les problèmes de techno et de serveurs grâce à de la configuration YML. Dans votre implémentation vous n’aurez pas à vous soucier de comment va partir votre message, ni de comment le sérializer. Le composant est compatible avec les brokers de messages AMQP (la plupart).

  • Message : Un objet PHP serializable
  • Bus : Le composant qui va s’occuper de la communication avec le queue manager et d’appliquer les middlewares que l’on aura pu enregistrer (logs, debug,…)
  • Handler : La classe qui va recevoir un message à exécuter. C’est cette classe qui va tenir la logique métier.
  • Receiver : Déserialize le message reçu via le bus et le transmet au bon handler.
  • Sender : Sérialize le message et le transmet au queue manager via le bus.

Nous allons appliquer ce principe pour le cas de l’envoi du mail de confirmation de commande. Il faudra répéter le pattern pour chacun des autres types d’action à effectuer.

Mise en application

Installation du composant

Le composant s’installe à l’aide de composer via la commande :

composer require symfony/messenger

Symfony flex va s’occuper automatiquement d’enregistrer le bundle et créer le fichier de configuration par défaut.  Le composant vient avec son intégration à la Symfony toolbar et au profiler. Il est donc possible de voir en détail les messages dispatchés lors d’un hit.

Configuration du composant

Je vais prendre le cas d’un rabbitmq. Il va falloir veiller à installer et activer l’extension php amqp.

Une fois que c’est fait, nous pouvons éditer le fichier de configuration de messenger pour y ajouter nos transports.

Dans votre fichier .env à la racine de votre dépôt, il faudra ajouter les différents Data Source Name.

Création de notre message

Le message est un simple objet php.

L’interface par défaut de mes messages (Optionnel mais pratique à l’usage)
L’interface des objets de type mail

Dans mon message j’envoie les id des différentes entités. Selon vos besoins vous pouvez directement mettre les entités.

Envoie du message dans le broker

Maintenant que nous avons notre objet de message nous allons pouvoir le pousser.

Traitement du message

Pour le moment, votre application ne fonctionnera pas car Symfony Messenger refusera de prendre en compte un message dont il ne connait pas le handler.

Il faut maintenant enregistrer notre handler dans le container avec le tag ‘messenger.message_handler’.

À partir de ce moment, Symfony va vous autoriser à dispatcher les messages ConfirmCommandMailer. Pour cet exemple, j’ai utilisé  Swift Mailer mais libre à vous d’utiliser une autre librairie.

Le composant va utiliser la reflection PHP pour détecter le handler qui doit être utilisé pour un message. Il va regarder le type du paramètre passé à la fonction __invoke. 

Lancer le worker

Maintenant que l’on a tout ce qu’il nous faut, il ne reste plus qu’à lancer notre consommateur. Il faudra lancer au minimum autant de workers que de channels.

bin/console messenger:consume-messages amqp_mailer

Il y a pas mal d’options disponibles pour limiter la durée de vie du daemon, la mémoire allouée, le temps de pause entre chaque message traité… Pour ma part je lance toujours au moins deux consommateurs pour un type de message et je fais en sorte qu’ils se tuent automatiquement tous les n messages traités.

Voici une démonstration sous forme de GIF de ce que l’on vient de faire. Pour simplifier la démonstration, j’ai fait une seconde commande Symfony qui pousse notre message dans la queue.

En production

En production il faut automatiser le lancement du daemon et le relancer en cas de crash. Pour ce faire vous pouvez utiliser supervisor avec la config suivante :

Généralement je fais tourner les workers dans des conteners docker dans un cluster swarm. De cette manière je peux gérer le nombre de consommateurs par type de message à la volée. Voici un exemple de Docker file qui peut faire tourner un worker symfony messenger. Je l’ai fait pour un projet perso, il n’est donc pas parfaitement optimisé pour de la vraie prod. 

Exemple de docker file simple pour un worker Symfony messenger

Et voici comment l’utiliser dans le cadre d’un docker compose :

Vous pouvez maintenant faire un docker-compose up ou le lancer sur un cluster swarm via la commande docker stack deploy.

Limitation

Pour le moment il n’y a pas de solution « out of the box » pour gérer le re-jeux des messages en cas d’erreur. C’est à vous de catcher les erreurs et soit de les pousser dans une autre queue, d’écrire un log, une métrique, ou de les stocker en base pour les identifier au besoin. C’est une lacune assez importante de la librairie qui devrait être corrigée.

Les problèmes possibles

Que veux dire l’erreur : Attempted to load class « AMQPConnection » from the global namespace.
Did you forget a « use » statement?

Ce message vous signale que vous n’avez pas l’extension amqp d’activée sur votre machine. Il faut installer et activer l’extension php-amqp.

Comment résoudre l’erreur: [ErrorException] Declaration of SymfonyFlexParallelDownloader::getRemoteContents($originUrl, $fileUrl, $context) should be compatible with ComposerUtilRemoteFilesystem::getRemoteContents($originUrl, $fileUrl, $context, ?array & $responseHeaders = NULL)

Il faut exécuter composer update –no-plugins pour mettre Symfony flex à jour.

Comment corriger l’erreur : [SymfonyComponentMessengerExceptionNoHandlerForMessageException]
No handler for message « AppMessageConfirmCommandMailer ».

Il faut veiller à deux points pour trouver la source de cette erreur. Premièrement que vous avez bel et bien créé un handler avec une méthode __invoke qui prend un objet de type AppMessageConfirmCommandMailer en premier et unique paramètre. Et dans un second temps que vous avez bien ajouté votre handler dans votre fichier services.yml avec le tag « messenger.message_handler »

Cocktailand – Ajouter du cache HTTP dans mon symfony

C’est quoi un ESI ?

Les ESI ou Edge Side Includes sont un balisage supporté par Varnish qui permet de gérer des temps de cache différents pour des blocs de la même page.

Dans le cadre de Cocktailand, certains blocs sont actualisés régulièrement comme le « Cocktail du jour » mais d’autres ne changent quasiment jamais comme la liste des catégories.

Voici donc le découpage que j’ai fait sur la page principale. Pour la barre de menu, c’est bien évidemment le contenu du méga menu que j’ai voulu mettre en évidence.

Il est donc intéressant de ne pas avoir à invalider toute la page lorsque le cocktail du jour est changé. Le second avantage en termes de performance est que les blocs peuvent être utilisés sur différentes pages. Cela signifie qu’un ESI présent sur toutes les pages du site ne sera généré qu’une seule fois. Lors des autres appels, Varnish utilisera son cache.

Configuration de varnish

Pour Cocktailand, la configuration de varnish est assez simple car je ne fais pas de purge et parce qu’il n’y a pas d’espace connecté sur le site.

Voici la configuration que j’ai:

La mise en place dans Symfony 4

ESI

Le support des ESI dans Symfony est intégré nativement dans le framework.

Dans le fichier config/packages/framework.yaml il faut activer les ESIs.

Toutes vos routes qui servent des ESI devront commencer par _fragment.

Dans vos vues twig, vous avez des helpers à disposition pour poser vos tags.

Cache http

En utilisant le package sensio/framework-extra-bundle on peut gérer le cache sur les controller avec des annotations.

Temps de réponse

Une fois les ESIs et le cache HTTP mis en place, on peut analyser les performances avec Webpagetest.

L’outil va nous donner des informations sur le temps de réponse de l’application et surtout des indications sur ce qu’il faudrait améliorer.

Par exemple, sur ce test, il me dit que je peux potentiellement améliorer la gestion des fonts et des images.

// @todo activer gzip sur les fonts

Ce que l’on peut remarquer, c’est que le site commence à envoyer le HTML après 231ms. Dans ce temps il y a en moyenne 40ms de DNS Lookup, 30ms de connexion et 80ms de négociation SSL. Malheureusement sur cette partie je n’ai pas la main, c’est donc l’overhead de base pour toute page du site. Mais comme j’utilise HTTP2 je vais mutualiser toute cette partie pour les images assets servies sur le même domaine.

Les nouvelles problématiques

Tout mettre en cache, c’est bien pour les performances, mais malheureusement certaines informations ont besoin de « temps réel ».

Quand un visiteur ajoute une note sur une recette, il est nécessaire que cette note mise à jour si le visiteur rafraîchit la page.

Il y a plusieurs solutions:

  • Faire des bans de cache lors de l’ajout d’une note
  • Afficher les données du cache et rafraîchir les données en AJAX

Bien évidemment, la première solution est celle qui devrait être implémentée. Maintenant, c’est pas mal de code et de configuration sur varnish. C’est d’autant plus compliqué que, dans sa version gratuite, Varnish ne permet pas de faire un ban sur plusieurs instances.

Faire les bans impliquerait de maintenir une liste des varnishs qui tournent (si jamais je fais scaller cette brique) et de faire n appels curl pour faire un ban partout.

Pour le moment, j’ai fait le choix de la requête AJAX non cachée. Si jamais le site gagne en popularité, il faudra retravailler sur ce point.

Conclusion

Sur un blog ou un site dans lequel le visiteur ne peut quasiment pas interagir avec vos données, c’est très simple de mettre en place du cache varnish et le gain de performance est énorme. Maintenant, si vous avez des besoins plus complexes (site transactionnel, forum,…) vous allez devoir mettre en place une mécanique d’invalidation de cache.