gwift-book/chapters/infrastructure.tex

180 lines
6.4 KiB
TeX

\chapter{Infrastructure et composants}
\begin{figure}[H]
\centering
\scalebox{1.0}{\includegraphics[max size={\textwidth}{\textheight}]{images/diagrams/infrastructure.drawio.png}}
\caption{Une architecture possible pour un système de données, qui combine plusieurs composants \cite[p. 5]{data_intensive}}
\end{figure}
Pour une mise ne production, le standard \emph{de facto} est le suivant:
\begin{itemize}
\item
Nginx comme reverse proxy
\item
HAProxy pour la distribution de charge
\item
Gunicorn ou Uvicorn comme serveur d'application
\item
Supervisor pour le monitoring
\item
PostgreSQL ou MySQL/MariaDB comme bases de données.
\item
Celery et RabbitMQ pour l'exécution de tâches asynchrones
\item
Redis / Memcache pour la mise à en cache (et pour les sessions ? A
vérifier).
\item
Sentry, pour le suivi des bugs
\end{itemize}
Si nous schématisons l'infrastructure et le chemin parcouru par une requête, nous pourrions arriver à la synthèse suivante:
\begin{enumerate}
\item
L'utilisateur fait une requête via son navigateur (Firefox ou Chrome)
\item
Le navigateur envoie une requête http, sa version, un verbe (GET,
POST, \ldots\hspace{0pt}), un port et éventuellement du contenu
\item
Le firewall du serveur (Debian GNU/Linux, CentOS, ...
vérifie si la requête peut être prise en compte
\item
La requête est transmise à l'application qui écoute sur le port
(probablement 80 ou 443; et \emph{a priori} Nginx)
\item
Elle est ensuite transmise par socket et est prise en compte par un
des \emph{workers} (= un processus Python) instancié par Gunicorn. Si
l'un de ces travailleurs venait à planter, il serait automatiquement
réinstancié par Supervisord.
\item
Qui la transmet ensuite à l'un de ses \emph{workers} (= un processus
Python).
\item
Après exécution, une réponse est renvoyée à l'utilisateur.
\end{enumerate}
\includegraphics{images/diagrams/architecture.png}
\section{Reverse proxy}
Le principe du \textbf{proxy inverse} est de pouvoir rediriger du trafic entrant vers une application hébergée sur le système. Il serait tout à fait possible de rendre notre application directement accessible depuis l'extérieur, mais le proxy a aussi l'intérêt de pouvoir élever la sécurité du serveur (SSL) et décharger le serveur applicatif grâce à un mécanisme de cache ou en compressant certains résultats \footnote{\url{https://fr.wikipedia.org/wiki/Proxy_inverse}}
\section{Load balancer}
\section{Workers}
\section{Supervision des processus}
\section{Bases de données}
\section{Tâches asynchrones}
\section{Mise en cache}
\section{Niveau application}
Au niveau logiciel (la partie mise en subrillance ci-dessus), la requête arrive dans les mains du processus Python, qui doit encore
\begin{enumerate}
\item
effectuer le routage des données,
\item
trouver la bonne fonction à exécuter,
\item
récupérer les données depuis la base de données,
\item
effectuer le rendu ou la conversion des données,
\item
et renvoyer une réponse à l'utilisateur.
\end{enumerate}
Comme nous l'avons vu dans la première partie, Django est un framework complet, intégrant tous les mécanismes nécessaires à la bonne évolution d'une application. Il est possible de démarrer petit, et de suivre l'évolution des besoins en fonction de la charge estimée ou ressentie, d'ajouter un mécanisme de mise en cache, des logiciels de suivi, ...
\section{Supervision globale}
\subsection{Journaux}
La structure des niveaux de journaux est essentielle.
\begin{quote}
When deciding whether a message should be ERROR or WARN, imagine being woken up at 4 a.m.
Low printer toner is not an ERROR.
--- Dan North former ToughtWorks consultant
\end{quote}
\begin{itemize}
\item
\textbf{DEBUG}: Il s'agit des informations qui concernent tout ce qui peut se passer durant l'exécution de l'application. Généralement, ce niveau est désactivé pour une application qui passe en production,
sauf s'il est nécessaire d'isoler un comportement en particulier, auquel cas il suffit de le réactiver temporairement.
\item
\textbf{INFO}: Enregistre les actions pilotées par un utilisateur - Démarrage de la transaction de paiement, ...
\item
\textbf{WARN}: Regroupe les informations qui pourraient potentiellement devenir des erreurs.
\item
\textbf{ERROR}: Indique les informations internes - Erreur lors de l'appel d'une API, erreur interne, ...
\item
\textbf{FATAL} (ou \textbf{EXCEPTION}): ... généralement suivie d'une terminaison du programme - Bind raté
d'un socket, etc.
\end{itemize}
La configuration des \emph{loggers} est relativement simple, un peu plus complexe si nous nous penchons dessus, et franchement complète si nous creusons encore. Il est ainsi possible de définir des formattages,
gestionnaires (\emph{handlers}) et loggers distincts, en fonction de nos applications.
Sauf que comme nous l'avons vu avec les 12 facteurs, nous devons traiter les informations de notre application comme un flux d'évènements. Il n'est donc pas réellement nécessaire de chipoter la configuration,
puisque la seule classe qui va réellement nous intéresser concerne les \texttt{StreamHandler}. La configuration que nous allons utiliser est celle-ci:
\begin{enumerate}
\item
Formattage: à définir - mais la variante suivante est complète, lisible et pratique:
\texttt{\{levelname\}\ \{asctime\}\ \{module\}\ \{process:d\}\ \{thread:d\}\ \{message\}}
\item
Handler: juste un, qui définit un \texttt{StreamHandler}
\item
Logger: pour celui-ci, nous avons besoin d'un niveau (\texttt{level})
et de savoir s'il faut propager les informations vers les
sous-paquets, auquel cas il nous suffira de fixer la valeur de
\texttt{propagate} à \texttt{True}.
\end{enumerate}
Pour utiliser nos loggers, il suffit de copier le petit bout de code suivant:
\begin{minted}{python}
import logging
logger = logging.getLogger(__name__)
logger.debug('helloworld')
\end{minted}
\href{https://docs.djangoproject.com/en/stable/topics/logging/\#examples}{Par
exemples}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Sentry via sentry\_sdk
\item
Nagios
\item
LibreNMS
\item
Zabbix
\end{enumerate}
Il existe également \href{https://munin-monitoring.org}{Munin},
\href{https://www.elastic.co}{Logstash, ElasticSearch et Kibana
(ELK-Stack)} ou \href{https://www.fluentd.org}{Fluentd}.
\subsection{Zabbix, Nagios, ...}
\subsection{Sentry}
\subsection{Greylog}