Structure 12 factors
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
7e6ea730de
commit
02930bd52f
|
@ -6,324 +6,7 @@ maintenabilité}\label{_fiabilituxe9_uxe9volutivituxe9_et_maintenabilituxe9}}
|
|||
\hypertarget{_12_facteurs}{%
|
||||
\subsection{12 facteurs}\label{_12_facteurs}}
|
||||
|
||||
Pour la méthode de travail et de développement, nous allons nous baser
|
||||
sur les \href{https://12factor.net/fr/}{The Twelve-factor App} - ou plus
|
||||
simplement les \textbf{12 facteurs}.
|
||||
|
||||
L'idée derrière cette méthode, et indépendamment des langages de
|
||||
développement utilisés, consiste à suivre un ensemble de douze concepts,
|
||||
afin de:
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
\textbf{Faciliter la mise en place de phases d'automatisation}; plus
|
||||
concrètement, de faciliter les mises à jour applicatives, simplifier
|
||||
la gestion de l'hôte, diminuer la divergence entre les différents
|
||||
environnements d'exécution et offrir la possibilité d'intégrer le
|
||||
projet dans un processus
|
||||
d'\href{https://en.wikipedia.org/wiki/Continuous_integration}{intégration
|
||||
continue} ou
|
||||
\href{https://en.wikipedia.org/wiki/Continuous_deployment}{déploiement
|
||||
continu}
|
||||
\item
|
||||
\textbf{Faciliter la mise à pied de nouveaux développeurs ou de
|
||||
personnes souhaitant rejoindre le projet}, dans la mesure où la mise à
|
||||
disposition d'un environnement sera grandement facilitée.
|
||||
\item
|
||||
\textbf{Minimiser les divergences entre les différents environnemens
|
||||
sur lesquels un projet pourrait être déployé}
|
||||
\item
|
||||
\textbf{Augmenter l'agilité générale du projet}, en permettant une
|
||||
meilleure évolutivité architecturale et une meilleure mise à l'échelle
|
||||
- \emph{Vous avez 5000 utilisateurs en plus? Ajoutez un serveur et on
|
||||
n'en parle plus ;-)}.
|
||||
\end{enumerate}
|
||||
|
||||
En pratique, les points ci-dessus permettront de monter facilement un
|
||||
nouvel environnement - qu'il soit sur la machine du petit nouveau dans
|
||||
l'équipe, sur un serveur Azure/Heroku/Digital Ocean ou votre nouveau
|
||||
Raspberry Pi Zéro caché à la cave - et vous feront gagner un temps
|
||||
précieux.
|
||||
|
||||
Pour reprendre de manière très brute les différentes idées derrière
|
||||
cette méthode, nous avons:
|
||||
|
||||
\textbf{\#1 - Une base de code unique, suivie par un système de contrôle
|
||||
de versions}.
|
||||
|
||||
Chaque déploiement de l'application se basera sur cette source, afin de
|
||||
minimiser les différences que l'on pourrait trouver entre deux
|
||||
environnements d'un même projet. On utilisera un dépôt Git - Github,
|
||||
Gitlab, Gitea, \ldots\hspace{0pt} Au choix.
|
||||
|
||||
\includegraphics{images/diagrams/12-factors-1.png}
|
||||
|
||||
Comme l'explique Eran Messeri, ingénieur dans le groupe Google Developer
|
||||
Infrastructure, un des avantages d'utiliser un dépôt unique de sources,
|
||||
est qu'il permet un accès facile et rapide à la forme la plus à jour du
|
||||
code, sans aucun besoin de coordination. \footnote{The DevOps Handbook,
|
||||
Part V, Chapitre 20, Convert Local Discoveries into Global
|
||||
Improvements} Ce dépôt ne sert pas seulement au code source, mais
|
||||
également à d'autres artefacts et formes de connaissance:
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
Standards de configuration (Chef recipes, Puppet manifests,
|
||||
\ldots\hspace{0pt})
|
||||
\item
|
||||
Outils de déploiement
|
||||
\item
|
||||
Standards de tests, y compris tout ce qui touche à la sécurité
|
||||
\item
|
||||
Outils de déploiement de pipeline
|
||||
\item
|
||||
Outils d'analyse et de monitoring
|
||||
\item
|
||||
Tutoriaux
|
||||
\end{itemize}
|
||||
|
||||
\textbf{\#2 - Déclarez explicitement les dépendances nécessaires au
|
||||
projet, et les isoler du reste du système lors de leur installation}
|
||||
|
||||
Chaque installation ou configuration doit toujours être faite de la même
|
||||
manière, et doit pouvoir être répétée quel que soit l'environnement
|
||||
cible.
|
||||
|
||||
Cela permet d'éviter que l'application n'utilise une dépendance qui soit
|
||||
déjà installée sur un des sytèmes de développement, et qu'elle soit
|
||||
difficile, voire impossible, à répercuter sur un autre environnement.
|
||||
Dans notre cas, cela pourra être fait au travers de
|
||||
\href{https://pypi.org/project/pip/}{PIP - Package Installer for Python}
|
||||
ou \href{https://python-poetry.org/}{Poetry}.
|
||||
|
||||
Mais dans tous les cas, chaque application doit disposer d'un
|
||||
environnement sain, qui lui est assigné, et vu le peu de ressources que
|
||||
cela coûte, il ne faut pas s'en priver.
|
||||
|
||||
Chaque dépendance pouvant être déclarée et épinglée dans un fichier, il
|
||||
suffira de créer un nouvel environment vierge, puis d'utiliser ce
|
||||
fichier comme paramètre pour installer les prérequis au bon
|
||||
fonctionnement de notre application et vérifier que cet environnement
|
||||
est bien reproductible.
|
||||
|
||||
Il est important de bien "épingler" les versions liées aux dépendances
|
||||
de l'application. Cela peut éviter des effets de bord comme une nouvelle
|
||||
version d'une librairie dans laquelle un bug aurait pu avoir été
|
||||
introduit. Parce qu'il arrive que ce genre de problème apparaisse, et
|
||||
lorsque ce sera le cas, ce sera systématiquement au mauvais moment.
|
||||
|
||||
\textbf{\#3 - Sauver la configuration directement au niveau de
|
||||
l'environnement}
|
||||
|
||||
Nous voulons éviter d'avoir à recompiler/redéployer l'application parce
|
||||
que:
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
l'adresse du serveur de messagerie a été modifiée,
|
||||
\item
|
||||
un protocole a changé en cours de route
|
||||
\item
|
||||
la base de données a été déplacée
|
||||
\item
|
||||
\ldots\hspace{0pt}
|
||||
\end{enumerate}
|
||||
|
||||
En pratique, toute information susceptible de modifier un lien vers une
|
||||
ressource annexe doit se trouver dans un fichier ou dans une variable
|
||||
d'environnement, et doit être facilement modifiable. En allant un pas
|
||||
plus loin, ceci de paramétrer facilement un environnement (par exemple,
|
||||
un container), simplement en modifiant une variable de configuration qui
|
||||
spécifierait la base de données sur laquelle l'application devra se
|
||||
connecter.
|
||||
|
||||
Toute clé de configuration (nom du serveur de base de données, adresse
|
||||
d'un service Web externe, clé d'API pour l'interrogation d'une
|
||||
ressource, \ldots\hspace{0pt}) sera définie directement au niveau de
|
||||
l'hôte - à aucun moment, nous ne devons trouver un mot de passe en clair
|
||||
dans le dépôt source ou une valeur susceptible d'évoluer, écrite en dur
|
||||
dans le code.
|
||||
|
||||
Au moment de développer une nouvelle fonctionnalité, réfléchissez si
|
||||
l'un des composants utilisés risquerait de subir une modification: ce
|
||||
composant peut concerner une nouvelle chaîne de connexion, un point de
|
||||
terminaison nécessaire à télécharger des données officielles ou un
|
||||
chemin vers un répertoire partagé pour y déposer un fichier.
|
||||
|
||||
\textbf{\#4 - Traiter les ressources externes comme des ressources
|
||||
attachées}
|
||||
|
||||
Nous parlons de bases de données, de services de mise en cache, d'API
|
||||
externes, \ldots\hspace{0pt} L'application doit être capable d'effectuer
|
||||
des changements au niveau de ces ressources sans que son code ne soit
|
||||
modifié. Nous parlons alors de \textbf{ressources attachées}, dont la
|
||||
présence est nécessaire au bon fonctionnement de l'application, mais
|
||||
pour lesquelles le \textbf{type} n'est pas obligatoirement défini.
|
||||
|
||||
Nous voulons par exemple "une base de données" et "une mémoire cache",
|
||||
et pas "une base MariaDB et une instance Memcached". De cette manière,
|
||||
les ressources peuvent être attachées et détachées d'un déploiement à la
|
||||
volée.
|
||||
|
||||
Si une base de données ne fonctionne pas correctement (problème matériel
|
||||
?), l'administrateur pourrait simplement restaurer un nouveau serveur à
|
||||
partir d'une précédente sauvegarde, et l'attacher à l'application sans
|
||||
que le code source ne soit modifié. une solution consiste à passer
|
||||
toutes ces informations (nom du serveur et type de base de données, clé
|
||||
d'authentification, \ldots\hspace{0pt}) directement \emph{via} des
|
||||
variables d'environnement.
|
||||
|
||||
\includegraphics{images/12factors/attached-resources.png}
|
||||
|
||||
Nous serons ravis de pouvoir simplement modifier une chaîne
|
||||
\texttt{sqlite:////tmp/my-tmp-sqlite.db\textquotesingle{}} en
|
||||
\texttt{psql://user:pass@127.0.0.1:8458/db} lorsque ce sera nécessaire,
|
||||
sans avoir à recompiler ou redéployer les modifications.
|
||||
|
||||
\textbf{\#5 - Séparer proprement les phases de construction, de mise à
|
||||
disposition et d'exécution}
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
La \textbf{construction} (\emph{build}) convertit un code source en un
|
||||
ensemble de fichiers exécutables, associé à une version et à une
|
||||
transaction dans le système de gestion de sources.
|
||||
\item
|
||||
La \textbf{mise à disposition} (\emph{release}) associe cet ensemble à
|
||||
une configuration prête à être exécutée,
|
||||
\item
|
||||
tandis que la phase d'\textbf{exécution} (\emph{run}) démarre les
|
||||
processus nécessaires au bon fonctionnement de l'application.
|
||||
\end{enumerate}
|
||||
|
||||
\includegraphics{images/12factors/release.png}
|
||||
|
||||
Parmi les solutions possibles, nous pourrions nous pourrions nous baser
|
||||
sur les \emph{releases} de Gitea, sur un serveur d'artefacts ou sur
|
||||
\href{https://fr.wikipedia.org/wiki/Capistrano_(logiciel)}{Capistrano}.
|
||||
|
||||
\textbf{\#6 - Les processus d'exécution ne doivent rien connaître ou
|
||||
conserver de l'état de l'application}
|
||||
|
||||
Toute information stockée en mémoire ou sur disque ne doit pas altérer
|
||||
le comportement futur de l'application, par exemple après un redémarrage
|
||||
non souhaité.
|
||||
|
||||
Pratiquement, si l'application devait rencontrer un problème, l'objectif
|
||||
est de pouvoir la redémarrer rapidement sur un autre serveur (par
|
||||
exemple suite à un problème matériel). Toute information qui aurait été
|
||||
stockée durant l'exécution de l'application sur le premier hôte serait
|
||||
donc perdue. Si une réinitialisation devait être nécessaire,
|
||||
l'application ne devra pas compter sur la présence d'une information au
|
||||
niveau du nouveau système. La solution consiste donc à jouer sur les
|
||||
variables d'environnement (cf. \#3) et sur les informations que l'on
|
||||
pourra trouver au niveau des ressources attachées (cf \#4).
|
||||
|
||||
Il serait également difficile d'appliquer une mise à l'échelle de
|
||||
l'application, en ajoutant un nouveau serveur d'exécution, si une donnée
|
||||
indispensable à son fonctionnement devait se trouver sur la seule
|
||||
machine où elle est actuellement exécutée.
|
||||
|
||||
\textbf{\#7 - Autoriser la liaison d'un port de l'application à un port
|
||||
du système hôte}
|
||||
|
||||
Les applications 12-factors sont auto-contenues et peuvent fonctionner
|
||||
en autonomie totale. Elles doivent être joignables grâce à un mécanisme
|
||||
de ponts, où l'hôte qui s'occupe de l'exécution effectue lui-même la
|
||||
redirection vers l'un des ports ouverts par l'application, typiquement,
|
||||
en HTTP ou via un autre protocole.
|
||||
|
||||
\includegraphics{images/diagrams/12-factors-7.png}
|
||||
|
||||
\textbf{\#8 - Faites confiance aux processus systèmes pour l'exécution
|
||||
de l'application}
|
||||
|
||||
Comme décrit plus haut (cf. \#6), l'application doit utiliser des
|
||||
processus \emph{stateless} (sans état). Nous pouvons créer et utiliser
|
||||
des processus supplémentaires pour tenir plus facilement une lourde
|
||||
charge, ou dédier des processus particuliers pour certaines tâches:
|
||||
requêtes HTTP \emph{via} des processus Web; \emph{long-running} jobs
|
||||
pour des processus asynchrones, \ldots\hspace{0pt} Si cela existe au
|
||||
niveau du système, ne vous fatiguez pas: utilisez le.
|
||||
|
||||
\includegraphics{images/12factors/process-types.png}
|
||||
|
||||
\textbf{\#9 - Améliorer la robustesse de l'application grâce à des
|
||||
arrêts élégants et à des démarrages rapides}
|
||||
|
||||
Par "arrêt élégant", nous voulons surtout éviter le
|
||||
\texttt{kill\ -9\ \textless{}pid\textgreater{}} ou tout autre arrêt
|
||||
brutal d'un processus qui nécessiterait une intervention urgente du
|
||||
superviseur. De cette manière, les requêtes en cours peuvent se terminer
|
||||
au mieux, tandis que le démarrage rapide de nouveaux processus
|
||||
améliorera la balance d'un processus en cours d'extinction vers des
|
||||
processus tout frais.
|
||||
|
||||
L'intégration de ces mécanismes dès les premières étapes de
|
||||
développement limitera les perturbations et facilitera la prise en
|
||||
compte d'arrêts inopinés (problème matériel, redémarrage du système
|
||||
hôte, etc.).
|
||||
|
||||
\textbf{\#10 - Conserver les différents environnements aussi similaires
|
||||
que possible, et limiter les divergences entre un environnement de
|
||||
développement et de production}
|
||||
|
||||
L'exemple donné est un développeur qui utilise macOS, NGinx et SQLite,
|
||||
tandis que l'environnement de production tourne sur une CentOS avec
|
||||
Apache2 et PostgreSQL. Faire en sorte que tous les environnements soient
|
||||
les plus similaires possibles limite les divergences entre
|
||||
environnements, facilite les déploiements et limite la casse et la
|
||||
découverte de modules non compatibles dès les premières phases de
|
||||
développement.
|
||||
|
||||
Pour vous donner un exemple tout bête, SQLite utilise un
|
||||
\href{https://www.sqlite.org/datatype3.html}{mécanisme de stockage
|
||||
dynamique}, associée à la valeur plutôt qu'au schéma, \emph{via} un
|
||||
système d'affinités. Un autre moteur de base de données définira un
|
||||
schéma statique et rigide, où la valeur sera déterminée par son
|
||||
contenant. Un champ \texttt{URLField} proposé par Django a une longeur
|
||||
maximale par défaut de
|
||||
\href{https://docs.djangoproject.com/en/3.1/ref/forms/fields/\#django.forms.URLField}{200
|
||||
caractères}. Si vous faites vos développements sous SQLite et que vous
|
||||
rencontrez une URL de plus de 200 caractères, votre développement sera
|
||||
passera parfaitement bien, mais plantera en production (ou en
|
||||
\emph{staging}, si vous faites les choses un peu mieux) parce que les
|
||||
données seront tronquées\ldots\hspace{0pt}
|
||||
|
||||
Conserver des environements similaires limite ce genre de désagréments.
|
||||
|
||||
\textbf{\#11 - Gérer les journeaux d'évènements comme des flux}
|
||||
|
||||
Une application ne doit jamais se soucier de l'endroit où ses évènements
|
||||
seront écrits, mais simplement de les envoyer sur la sortie
|
||||
\texttt{stdout}. De cette manière, que nous soyons en développement sur
|
||||
le poste d'un développeur avec une sortie console ou sur une machine de
|
||||
production avec un envoi vers une instance
|
||||
\href{https://www.graylog.org/}{Greylog} ou
|
||||
\href{https://sentry.io/welcome/}{Sentry}, le routage des journaux sera
|
||||
réalisé en fonction de sa nécessité et de sa criticité, et non pas parce
|
||||
que le développeur l'a spécifié en dur dans son code. Cette phase est
|
||||
critique, dans la mesure où les journaux d'exécution sont la seule
|
||||
manière pour une application de communiquer son état vers l'extérieur:
|
||||
recevoir une erreur interne de serveur est une chose; pouvoir obtenir un
|
||||
minimum d'informations, voire un contexte de plantage complet en est une
|
||||
autre.
|
||||
|
||||
\textbf{\#12 - Isoler les tâches administratives du reste de
|
||||
l'application}
|
||||
|
||||
Evitez qu'une migration ne puisse être démarrée depuis une URL de
|
||||
l'application, ou qu'un envoi massif de notifications ne soit accessible
|
||||
pour n'importe quel utilisateur: les tâches administratives ne doivent
|
||||
être accessibles qu'à un administrateur. Les applications 12facteurs
|
||||
favorisent les langages qui mettent un environnement REPL (pour
|
||||
\emph{Read}, \emph{Eval}, \emph{Print} et \emph{Loop}) à disposition (au
|
||||
hasard: \href{https://pythonprogramminglanguage.com/repl/}{Python} ou
|
||||
\href{https://kotlinlang.org/}{Kotlin}), ce qui facilite les étapes de
|
||||
maintenance.
|
||||
|
||||
\hypertarget{_concevoir_pour_lopuxe9rationnel}{%
|
||||
\subsection{Concevoir pour
|
||||
|
|
|
@ -17,6 +17,330 @@ La poésie est "l'art d'évoquer et de suggérer les sensations, les impressions
|
|||
|
||||
Sans aller jusqu'à demander de développer vos algorithmes sur douze pieds, la programmation reste un art régit par un ensemble de bonnes pratiques, par des règles à respecter et par la nécessité de travailler avec d'autres personnes qui ont \sout{parfois} souvent une expérience, des compétences ou une approche différente.
|
||||
|
||||
\section{12 facteurs}
|
||||
|
||||
|
||||
Pour la méthode de travail et de développement, nous allons nous baser
|
||||
sur les \href{https://12factor.net/fr/}{The Twelve-factor App} - ou plus
|
||||
simplement les \textbf{12 facteurs}.
|
||||
|
||||
L'idée derrière cette méthode, et indépendamment des langages de
|
||||
développement utilisés, consiste à suivre un ensemble de douze concepts,
|
||||
afin de:
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
\textbf{Faciliter la mise en place de phases d'automatisation}; plus
|
||||
concrètement, de faciliter les mises à jour applicatives, simplifier
|
||||
la gestion de l'hôte, diminuer la divergence entre les différents
|
||||
environnements d'exécution et offrir la possibilité d'intégrer le
|
||||
projet dans un processus
|
||||
d'\href{https://en.wikipedia.org/wiki/Continuous_integration}{intégration
|
||||
continue} ou
|
||||
\href{https://en.wikipedia.org/wiki/Continuous_deployment}{déploiement
|
||||
continu}
|
||||
\item
|
||||
\textbf{Faciliter la mise à pied de nouveaux développeurs ou de
|
||||
personnes souhaitant rejoindre le projet}, dans la mesure où la mise à
|
||||
disposition d'un environnement sera grandement facilitée.
|
||||
\item
|
||||
\textbf{Minimiser les divergences entre les différents environnemens
|
||||
sur lesquels un projet pourrait être déployé}
|
||||
\item
|
||||
\textbf{Augmenter l'agilité générale du projet}, en permettant une
|
||||
meilleure évolutivité architecturale et une meilleure mise à l'échelle
|
||||
- \emph{Vous avez 5000 utilisateurs en plus? Ajoutez un serveur et on
|
||||
n'en parle plus ;-)}.
|
||||
\end{enumerate}
|
||||
|
||||
|
||||
En pratique, les points ci-dessus permettront de monter facilement un
|
||||
nouvel environnement - qu'il soit sur la machine du petit nouveau dans
|
||||
l'équipe, sur un serveur Azure/Heroku/Digital Ocean ou votre nouveau
|
||||
Raspberry Pi Zéro caché à la cave - et vous feront gagner un temps
|
||||
précieux.
|
||||
|
||||
Pour reprendre de manière très brute les différentes idées derrière
|
||||
cette méthode, nous avons:
|
||||
|
||||
\subsection{\#1 - Une base de code unique, suivie par un système de contrôle
|
||||
de versions}.
|
||||
|
||||
Chaque déploiement de l'application se basera sur cette source, afin de
|
||||
minimiser les différences que l'on pourrait trouver entre deux
|
||||
environnements d'un même projet. On utilisera un dépôt Git - Github,
|
||||
Gitlab, Gitea, \ldots\hspace{0pt} Au choix.
|
||||
|
||||
\includegraphics{images/diagrams/12-factors-1.png}
|
||||
|
||||
Comme l'explique Eran Messeri, ingénieur dans le groupe Google Developer
|
||||
Infrastructure, un des avantages d'utiliser un dépôt unique de sources,
|
||||
est qu'il permet un accès facile et rapide à la forme la plus à jour du
|
||||
code, sans aucun besoin de coordination. \footnote{The DevOps Handbook,
|
||||
Part V, Chapitre 20, Convert Local Discoveries into Global
|
||||
Improvements} Ce dépôt ne sert pas seulement au code source, mais
|
||||
également à d'autres artefacts et formes de connaissance:
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
Standards de configuration (Chef recipes, Puppet manifests,
|
||||
\ldots\hspace{0pt})
|
||||
\item
|
||||
Outils de déploiement
|
||||
\item
|
||||
Standards de tests, y compris tout ce qui touche à la sécurité
|
||||
\item
|
||||
Outils de déploiement de pipeline
|
||||
\item
|
||||
Outils d'analyse et de monitoring
|
||||
\item
|
||||
Tutoriaux
|
||||
\end{itemize}
|
||||
|
||||
\subsection{\#2 - Déclarez explicitement les dépendances nécessaires au
|
||||
projet, et les isoler du reste du système lors de leur installation}
|
||||
|
||||
Chaque installation ou configuration doit toujours être faite de la même
|
||||
manière, et doit pouvoir être répétée quel que soit l'environnement
|
||||
cible.
|
||||
|
||||
Cela permet d'éviter que l'application n'utilise une dépendance qui soit
|
||||
déjà installée sur un des sytèmes de développement, et qu'elle soit
|
||||
difficile, voire impossible, à répercuter sur un autre environnement.
|
||||
Dans notre cas, cela pourra être fait au travers de
|
||||
\href{https://pypi.org/project/pip/}{PIP - Package Installer for Python}
|
||||
ou \href{https://python-poetry.org/}{Poetry}.
|
||||
|
||||
Mais dans tous les cas, chaque application doit disposer d'un
|
||||
environnement sain, qui lui est assigné, et vu le peu de ressources que
|
||||
cela coûte, il ne faut pas s'en priver.
|
||||
|
||||
Chaque dépendance pouvant être déclarée et épinglée dans un fichier, il
|
||||
suffira de créer un nouvel environment vierge, puis d'utiliser ce
|
||||
fichier comme paramètre pour installer les prérequis au bon
|
||||
fonctionnement de notre application et vérifier que cet environnement
|
||||
est bien reproductible.
|
||||
|
||||
Il est important de bien "épingler" les versions liées aux dépendances
|
||||
de l'application. Cela peut éviter des effets de bord comme une nouvelle
|
||||
version d'une librairie dans laquelle un bug aurait pu avoir été
|
||||
introduit. Parce qu'il arrive que ce genre de problème apparaisse, et
|
||||
lorsque ce sera le cas, ce sera systématiquement au mauvais moment.
|
||||
|
||||
\subsection{\#3 - Sauver la configuration directement au niveau de
|
||||
l'environnement}
|
||||
|
||||
Nous voulons éviter d'avoir à recompiler/redéployer l'application parce
|
||||
que:
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
l'adresse du serveur de messagerie a été modifiée,
|
||||
\item
|
||||
un protocole a changé en cours de route
|
||||
\item
|
||||
la base de données a été déplacée
|
||||
\item
|
||||
\ldots\hspace{0pt}
|
||||
\end{enumerate}
|
||||
|
||||
En pratique, toute information susceptible de modifier un lien vers une
|
||||
ressource annexe doit se trouver dans un fichier ou dans une variable
|
||||
d'environnement, et doit être facilement modifiable. En allant un pas
|
||||
plus loin, ceci de paramétrer facilement un environnement (par exemple,
|
||||
un container), simplement en modifiant une variable de configuration qui
|
||||
spécifierait la base de données sur laquelle l'application devra se
|
||||
connecter.
|
||||
|
||||
Toute clé de configuration (nom du serveur de base de données, adresse
|
||||
d'un service Web externe, clé d'API pour l'interrogation d'une
|
||||
ressource, \ldots\hspace{0pt}) sera définie directement au niveau de
|
||||
l'hôte - à aucun moment, nous ne devons trouver un mot de passe en clair
|
||||
dans le dépôt source ou une valeur susceptible d'évoluer, écrite en dur
|
||||
dans le code.
|
||||
|
||||
Au moment de développer une nouvelle fonctionnalité, réfléchissez si
|
||||
l'un des composants utilisés risquerait de subir une modification: ce
|
||||
composant peut concerner une nouvelle chaîne de connexion, un point de
|
||||
terminaison nécessaire à télécharger des données officielles ou un
|
||||
chemin vers un répertoire partagé pour y déposer un fichier.
|
||||
|
||||
\subsection{\#4 - Traiter les ressources externes comme des ressources
|
||||
attachées}
|
||||
|
||||
Nous parlons de bases de données, de services de mise en cache, d'API
|
||||
externes, \ldots\hspace{0pt} L'application doit être capable d'effectuer
|
||||
des changements au niveau de ces ressources sans que son code ne soit
|
||||
modifié. Nous parlons alors de \textbf{ressources attachées}, dont la
|
||||
présence est nécessaire au bon fonctionnement de l'application, mais
|
||||
pour lesquelles le \textbf{type} n'est pas obligatoirement défini.
|
||||
|
||||
Nous voulons par exemple "une base de données" et "une mémoire cache",
|
||||
et pas "une base MariaDB et une instance Memcached". De cette manière,
|
||||
les ressources peuvent être attachées et détachées d'un déploiement à la
|
||||
volée.
|
||||
|
||||
Si une base de données ne fonctionne pas correctement (problème matériel
|
||||
?), l'administrateur pourrait simplement restaurer un nouveau serveur à
|
||||
partir d'une précédente sauvegarde, et l'attacher à l'application sans
|
||||
que le code source ne soit modifié. une solution consiste à passer
|
||||
toutes ces informations (nom du serveur et type de base de données, clé
|
||||
d'authentification, \ldots\hspace{0pt}) directement \emph{via} des
|
||||
variables d'environnement.
|
||||
|
||||
\includegraphics{images/12factors/attached-resources.png}
|
||||
|
||||
Nous serons ravis de pouvoir simplement modifier une chaîne
|
||||
\texttt{sqlite:////tmp/my-tmp-sqlite.db\textquotesingle{}} en
|
||||
\texttt{psql://user:pass@127.0.0.1:8458/db} lorsque ce sera nécessaire,
|
||||
sans avoir à recompiler ou redéployer les modifications.
|
||||
|
||||
\subsection{\#5 - Séparer proprement les phases de construction, de mise à
|
||||
disposition et d'exécution}
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
La \textbf{construction} (\emph{build}) convertit un code source en un
|
||||
ensemble de fichiers exécutables, associé à une version et à une
|
||||
transaction dans le système de gestion de sources.
|
||||
\item
|
||||
La \textbf{mise à disposition} (\emph{release}) associe cet ensemble à
|
||||
une configuration prête à être exécutée,
|
||||
\item
|
||||
tandis que la phase d'\textbf{exécution} (\emph{run}) démarre les
|
||||
processus nécessaires au bon fonctionnement de l'application.
|
||||
\end{enumerate}
|
||||
|
||||
\includegraphics{images/12factors/release.png}
|
||||
|
||||
Parmi les solutions possibles, nous pourrions nous pourrions nous baser
|
||||
sur les \emph{releases} de Gitea, sur un serveur d'artefacts ou sur
|
||||
\href{https://fr.wikipedia.org/wiki/Capistrano_(logiciel)}{Capistrano}.
|
||||
|
||||
\subsection{\#6 - Les processus d'exécution ne doivent rien connaître ou
|
||||
conserver de l'état de l'application}
|
||||
|
||||
Toute information stockée en mémoire ou sur disque ne doit pas altérer
|
||||
le comportement futur de l'application, par exemple après un redémarrage
|
||||
non souhaité.
|
||||
|
||||
Pratiquement, si l'application devait rencontrer un problème, l'objectif
|
||||
est de pouvoir la redémarrer rapidement sur un autre serveur (par
|
||||
exemple suite à un problème matériel). Toute information qui aurait été
|
||||
stockée durant l'exécution de l'application sur le premier hôte serait
|
||||
donc perdue. Si une réinitialisation devait être nécessaire,
|
||||
l'application ne devra pas compter sur la présence d'une information au
|
||||
niveau du nouveau système. La solution consiste donc à jouer sur les
|
||||
variables d'environnement (cf. \#3) et sur les informations que l'on
|
||||
pourra trouver au niveau des ressources attachées (cf \#4).
|
||||
|
||||
Il serait également difficile d'appliquer une mise à l'échelle de
|
||||
l'application, en ajoutant un nouveau serveur d'exécution, si une donnée
|
||||
indispensable à son fonctionnement devait se trouver sur la seule
|
||||
machine où elle est actuellement exécutée.
|
||||
|
||||
\subsection{\#7 - Autoriser la liaison d'un port de l'application à un port
|
||||
du système hôte}
|
||||
|
||||
Les applications 12-factors sont auto-contenues et peuvent fonctionner
|
||||
en autonomie totale. Elles doivent être joignables grâce à un mécanisme
|
||||
de ponts, où l'hôte qui s'occupe de l'exécution effectue lui-même la
|
||||
redirection vers l'un des ports ouverts par l'application, typiquement,
|
||||
en HTTP ou via un autre protocole.
|
||||
|
||||
\includegraphics{images/diagrams/12-factors-7.png}
|
||||
|
||||
\subsection{\#8 - Faites confiance aux processus systèmes pour l'exécution
|
||||
de l'application}
|
||||
|
||||
Comme décrit plus haut (cf. \#6), l'application doit utiliser des
|
||||
processus \emph{stateless} (sans état). Nous pouvons créer et utiliser
|
||||
des processus supplémentaires pour tenir plus facilement une lourde
|
||||
charge, ou dédier des processus particuliers pour certaines tâches:
|
||||
requêtes HTTP \emph{via} des processus Web; \emph{long-running} jobs
|
||||
pour des processus asynchrones, \ldots\hspace{0pt} Si cela existe au
|
||||
niveau du système, ne vous fatiguez pas: utilisez le.
|
||||
|
||||
\includegraphics{images/12factors/process-types.png}
|
||||
|
||||
\subsection{\#9 - Améliorer la robustesse de l'application grâce à des
|
||||
arrêts élégants et à des démarrages rapides}
|
||||
|
||||
Par "arrêt élégant", nous voulons surtout éviter le
|
||||
\texttt{kill\ -9\ \textless{}pid\textgreater{}} ou tout autre arrêt
|
||||
brutal d'un processus qui nécessiterait une intervention urgente du
|
||||
superviseur. De cette manière, les requêtes en cours peuvent se terminer
|
||||
au mieux, tandis que le démarrage rapide de nouveaux processus
|
||||
améliorera la balance d'un processus en cours d'extinction vers des
|
||||
processus tout frais.
|
||||
|
||||
L'intégration de ces mécanismes dès les premières étapes de
|
||||
développement limitera les perturbations et facilitera la prise en
|
||||
compte d'arrêts inopinés (problème matériel, redémarrage du système
|
||||
hôte, etc.).
|
||||
|
||||
\subsection{\#10 - Conserver les différents environnements aussi similaires
|
||||
que possible, et limiter les divergences entre un environnement de
|
||||
développement et de production}
|
||||
|
||||
L'exemple donné est un développeur qui utilise macOS, NGinx et SQLite,
|
||||
tandis que l'environnement de production tourne sur une CentOS avec
|
||||
Apache2 et PostgreSQL. Faire en sorte que tous les environnements soient
|
||||
les plus similaires possibles limite les divergences entre
|
||||
environnements, facilite les déploiements et limite la casse et la
|
||||
découverte de modules non compatibles dès les premières phases de
|
||||
développement.
|
||||
|
||||
Pour vous donner un exemple tout bête, SQLite utilise un
|
||||
\href{https://www.sqlite.org/datatype3.html}{mécanisme de stockage
|
||||
dynamique}, associée à la valeur plutôt qu'au schéma, \emph{via} un
|
||||
système d'affinités. Un autre moteur de base de données définira un
|
||||
schéma statique et rigide, où la valeur sera déterminée par son
|
||||
contenant. Un champ \texttt{URLField} proposé par Django a une longeur
|
||||
maximale par défaut de
|
||||
\href{https://docs.djangoproject.com/en/3.1/ref/forms/fields/\#django.forms.URLField}{200
|
||||
caractères}. Si vous faites vos développements sous SQLite et que vous
|
||||
rencontrez une URL de plus de 200 caractères, votre développement sera
|
||||
passera parfaitement bien, mais plantera en production (ou en
|
||||
\emph{staging}, si vous faites les choses un peu mieux) parce que les
|
||||
données seront tronquées\ldots\hspace{0pt}
|
||||
|
||||
Conserver des environements similaires limite ce genre de désagréments.
|
||||
|
||||
\subsection{\#11 - Gérer les journeaux d'évènements comme des flux}
|
||||
|
||||
Une application ne doit jamais se soucier de l'endroit où ses évènements
|
||||
seront écrits, mais simplement de les envoyer sur la sortie
|
||||
\texttt{stdout}. De cette manière, que nous soyons en développement sur
|
||||
le poste d'un développeur avec une sortie console ou sur une machine de
|
||||
production avec un envoi vers une instance
|
||||
\href{https://www.graylog.org/}{Greylog} ou
|
||||
\href{https://sentry.io/welcome/}{Sentry}, le routage des journaux sera
|
||||
réalisé en fonction de sa nécessité et de sa criticité, et non pas parce
|
||||
que le développeur l'a spécifié en dur dans son code. Cette phase est
|
||||
critique, dans la mesure où les journaux d'exécution sont la seule
|
||||
manière pour une application de communiquer son état vers l'extérieur:
|
||||
recevoir une erreur interne de serveur est une chose; pouvoir obtenir un
|
||||
minimum d'informations, voire un contexte de plantage complet en est une
|
||||
autre.
|
||||
|
||||
\subsection{\#12 - Isoler les tâches administratives du reste de
|
||||
l'application}
|
||||
|
||||
Evitez qu'une migration ne puisse être démarrée depuis une URL de
|
||||
l'application, ou qu'un envoi massif de notifications ne soit accessible
|
||||
pour n'importe quel utilisateur: les tâches administratives ne doivent
|
||||
être accessibles qu'à un administrateur. Les applications 12facteurs
|
||||
favorisent les langages qui mettent un environnement REPL (pour
|
||||
\emph{Read}, \emph{Eval}, \emph{Print} et \emph{Loop}) à disposition (au
|
||||
hasard: \href{https://pythonprogramminglanguage.com/repl/}{Python} ou
|
||||
\href{https://kotlinlang.org/}{Kotlin}), ce qui facilite les étapes de
|
||||
maintenance.
|
||||
|
||||
|
||||
\section{Tests unitaires et d'intégration}
|
||||
|
||||
\begin{quote}
|
||||
|
|
Loading…
Reference in New Issue