• # Explication

    Posté par (site web personnel) . En réponse au journal Douze facteurs dans ta tronche. Évalué à 10.

    1 - One Codebase tracked in revision control, many deploys

    Je ne l'interprète pas comme "une application = un dépôt", mais plutôt comme "on déploie a partir de ce qui est dans le dépôt".

    Si tu as un mono-dépôt, contenant l'ensemble de tes services/applications, tu déploies à partir de ce dépôt. Si tu as un dépôt par application, tu déploies à partir de chacun d'eux. Si tu as une application découpée en plusieurs dépôt (plusieurs modules par exemple), tu as un dépôt parmi eux qui décrit ton déploiement, et tu déploies à partir de ce dernier.

    L'idée ici suggère simplement que le dépôt est la source de vérité.

    2 - Explicitly declare and isolate dependencies

    On ne veut pas qu'un service/application impacte une autre. Pour Python, cela veut dire des virtualenv pour chaque application, pour NodeJS c'est le node_modules, pour Go et Rust c'est un binaire statique. On oublie pas non plus de lock les dépendances pour un max de reproductibilité.

    3 - Store config in the environment

    Tu ne sais pas comment ton application va être déployée, via un unit systemd ? via un conteneur docker ? via Kubernetes ? via une plateforme Function-as-a-Service (AWS Lambda ou autre) ? via Heroku ?

    Les variables d'environnement sont le dénominateur commun de toutes ces plateformes, il est donc logique de les utiliser pour donner le plus de flexibilité à l'Ops qui se chargera de mettre ton code en prod.

    Note que j'ai déjà vu des applications simplement configurer via les variables d'environment les accès à un Vault (hashicorp) et allait ensuite chercher sa configuration dedans. C'est parfaitement viable, l'Ops sait que la conf est dans le Vault, et sais comment configurer (via l'env) les accès Vault de l'application.

    4 - Treat backing services as attached resources

    Il s'agit simplement de considérer chaque service (local ou remote) qu'utilise l'application (base de données, serveur mail, API, etc...) comme remplaçable. Il ne faut pas hard-coder ce genre de service dans le code mais le rendre configurable pour que chaque déploiement puisse adapter l'infrastructure selon ses besoins.

    Bref, on mets l'accent sur le déploiement

    5 - Strictly separate build and run stages

    C'est simplement une bonne pratique de CI/CD. Tu ne veux pas installer les outils de dev pour build ton application sur le serveur de prod. Tu veux aussi pouvoir rollback aisément.

    Cela veut donc dire que tu dois avoir un endroit ou tu stockes tes releases après chaque build (Nexus? Docker Registry? Dépôt PyPI/Maven/whatever privé? un simple FTP/NFS/whatever?). L'Ops ensuite déploiera une release, et il a l'historique pour rollback.

    Sans cette séparation, il faudrait faire quelques git revert sur la branche main pour pouvoir rollback, c'est source d'erreur, et en général les gens ne connaissent que git commit/git push, donc ça va pas passer non plus.

    6 - Execute the app as one or more stateless processes

    L'idée ici est encore une fois de simplifier la vie lors des déploiements. Si tu as besoin de persister des données, tu délègues ça a un "backing service". On ne sait pas ou est déployée ton application, donc ça se trouve le système de fichier ne va pas persister entre 2 exécutions du service/application.

    De plus, avoir un backing service (même si c'est toi l'auteur) pour la persistance, permet de le réutiliser/mutualiser/etc... pour l'ensemble des services.

    Ce backing service, cela peut être : un GlusterFS, un FTP, une base de données, un PersistantVolume dans Kubernetes, etc...

    7 - Export services via port binding

    En gros, on fait du "proxy pass" dans notre gateway. Cela simplifie drastiquement le déploiement, peu importe ou et comment on déploie.

    Que ce soit aller modifier un NginX/Apache pour ajouter le vhost, ajouter un Ingress dans notre Kubernetes, ou aller ajouter un CNAME sur notre DNS, ou une règle de redirection dans iptables/pf, cela revient au même.

    8 - Scale out via the process model

    Un service devrait être découpé en plusieurs processus qui font une seule chose (web, worker, ...). Ainsi lors du déploiement, on peut configurer combien de replicas pour chaque workloads (je veux 4 process web pour gérer les requêtes HTTP, 16 process worker pour gérer les tâches de fond, etc...).

    Encore une fois, l'idée est de donner plus de flexibilité au déploiement.

    9 - Maximize robustness with fast startup and graceful shutdown

    Gère SIGTERM correctement. Et spécifie quand ton service/application est ready.

    Pour Kubernetes, on va configurer des healthcheck. Cela peut être une commande qui fait une requête HTTP au service et s'assure qu'elle reçoit un 200 OK. Ou alors vérifier si un fichier /var/run/myservice.ready existe, ou tout ce que tu peux imaginer.

    L'idée est de permettre à la plateforme ou on déploie de s'assurer que le service s'est bien lancé. Ensuite, si on a de l'autoscaling, il se peut que notre instance se voit décommissionnée, il faut donc libérer les ressources proprement pour éviter toute corruption (si un upload de fichier est en cours, on attend qu'il soit fini par exemple avant de quitter).

    10 - Keep development, staging and production as similar as possible

    Bah oui, on veut éviter le "ça marche sur ma machine pourtant". Si le développeur est capable de tester en condition de production, c'est pas mal de bug trouvés à la source.

    11 - Treat logs as event streams

    En gros, oui l'idée c'est d'envoyer tout sur stdout et stderr.

    SystemD permet de rediriger stdout/stderr dans un fichier et de gérer la rotation/compression de logs.
    Je peux configurer une stack ELK ou autre pour récupérer les logs depuis /var/log (rempli par SystemD), depuis Docker, depuis Kubernetes, etc...

    Mon application elle? Elle print sur stdout et s'en fou de tout ça.

    Si chaque application fait comme elle sent, c'est d'autant plus de complexité pour le déploiement, car autant de cas spécifique à gérer. Cela a aussi au passage l'effet de simplifier le code de l'application qui n'a pas besoin de se préoccuper de cela.

    12 - Run admin/management tasks as one-off processes

    Que cela soit fait par des tâches ansible, par un rundeck, par un jenkins, par un init container dans Kubernetes, etc.. On s'en fout. Ce n'est tout simplement pas à l'application lors de son lancement de faire ce genre de chose.

    Idéalement, ton application ne tourne pas en tant que root. Donc il se peut en plus qu'elle n'ait pas les permissions pour faire telle ou telle tâche d'admin.


    Ensuite, sur https://12factor.net chaque point est un lien vers un article plus détaillé, expliquant le point de vu.

    Je conclurai tout simplement pour dire que cette philosophie, que tu es libre de ne pas suivre et d'adapter a ton besoin, mets l'accent sur le déploiement.

    Après des gens qui voient ça comme une religion a ne surtout pas blasphémer, c'est parce que l'humain est un stupide singe overclocké. Ce n'est pas la faute de 12factor.

    https://link-society.com - https://kubirds.com - https://github.com/link-society/flowg