174 lines
6.1 KiB
TeX
174 lines
6.1 KiB
TeX
\chapter{Infrastructure et composants}
|
|
|
|
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}
|
|
|