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
|
@ -1,4 +1,4 @@
|
||||||
|
|
||||||
\hypertarget{_fiabilituxe9_uxe9volutivituxe9_et_maintenabilituxe9}{%
|
\hypertarget{_fiabilituxe9_uxe9volutivituxe9_et_maintenabilituxe9}{%
|
||||||
\section{Fiabilité, évolutivité et
|
\section{Fiabilité, évolutivité et
|
||||||
maintenabilité}\label{_fiabilituxe9_uxe9volutivituxe9_et_maintenabilituxe9}}
|
maintenabilité}\label{_fiabilituxe9_uxe9volutivituxe9_et_maintenabilituxe9}}
|
||||||
|
@ -6,324 +6,7 @@ maintenabilité}\label{_fiabilituxe9_uxe9volutivituxe9_et_maintenabilituxe9}}
|
||||||
\hypertarget{_12_facteurs}{%
|
\hypertarget{_12_facteurs}{%
|
||||||
\subsection{12 facteurs}\label{_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}{%
|
\hypertarget{_concevoir_pour_lopuxe9rationnel}{%
|
||||||
\subsection{Concevoir pour
|
\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.
|
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}
|
\section{Tests unitaires et d'intégration}
|
||||||
|
|
||||||
\begin{quote}
|
\begin{quote}
|
||||||
|
|
Loading…
Reference in New Issue