\chapter{Infrastructure et composants}

Deux types d'applications peuvent être considérées: les applications consommant intensivement des données (\textit{Data-intensive applications}) et les applications consommant principalement des cycles processeur (\textit{Compute-intensive applications}).
Une application consommant principalement des données est rarement limitée par la puisse du CPU, mais plus souvent par la quantité et la complexité des structures de données, et la vitesse à laquelle celles-ci doivent être échangées. \cite[p. 3]{data_intensive}

\section{Composants}

Nous pouvons distinguer plusieurs types de composants:

\begin{enumerate}
  \item \textbf{Les composants fonctionnels}, ceux sans qui l'application fonctionnerait de manière partielle, inadéquate, ou avec des temps de réponse inadapté
  \item \textbf{Les composants périphériques}, c'est-à-dire les composants qui aident à avoir une architecture système résiliente, évolutive et maintenable
  \item \textbf{Les composants de supervision}, qui permettent de s'assurer que tout fonctionne correctement, et qu'aucun incident n'est prévu ou n'a été constaté récemment.
\end{enumerate}

\subsection{Composants fonctionnels}

\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 moderne, qui combine plusieurs composants \cite[p. 5]{data_intensive}}
\end{figure}

Une application \textit{data-intensive} est généralement composée des blocs fonctionnels suivants \cite[p. 3]{data_intensive} \footnote{De manière non-exhaustive, mais assez complète quand même.}:

\begin{tabular}{|p{0.2\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}|}
  \hline
  Type & Utilité & Exemples  \\
  \hline
  Bases de données & Stocker des données, pour pouvoir les retrouver (elles-même ou une autre application) plus tard & PostgreSQL, MySQL, MariaDB, SQLite \\
  \hline
  Caches & Conserver en mémoire des résultats gourmands, afin de pouvoir les réexploiter plus rapidement, sans avoir à les recalculer & Redis, Memcache \\
  \hline
  Moteurs d'indexation & Autoriser la recherche de résultats sur base de mots-clés ou en utilisant des filtres particuliers & ElasticSearch, Solr \\
  \hline
  Traitements asynchrones & Exécution de traitements lourds ou pouvant être démarrés \textit{a posteriori} & Celery \\
  \hline
\end{tabular}

\subsubsection{Bases de données}

\subsubsection{Tâches asynchrones}

\subsubsection{Mise en cache}

\subsection{Composants périphériques}

Les composants périphériques gravitent autour du fonctionnement normal de l'application.
Ce sont les "petites mains" qui aident à ce que l'application soit résiliente ou que la charge soit amoindrie ou répartie entre plusieurs instances.
Il s'agit des composants comme le proxy inverse ou les distributeurs de charge: ce sont des composants qui sont fortement recommandés, mais pas obligatoires à au bon fonctionnement de l'application.

\begin{quote}
  Hard disks are reported having a mean time to failure (MTTF) of about 1° to 50 years.
  Thus, on a storage cluster with 10,000 disks, we should expect on average one disk to die per day. \cite[p. 7]{data_design}
\end{quote}

Avoir des composants périphériques correctement configurés permet d'anticiper ce type d'erreurs, et donc d'augmenter la résilience de l'application. \index{Résilience}
Certaines plateformes, comme Amazon Web Services \index{AWS}, favorisent la flexibilité et l'eslasticité plutôt que la disponibilité d'une seule machine. \cite[p. 8]{data_design}
C'est donc une bonne idée de faire en sorte que des erreurs d'indisponibilité peuvent arriver \footnote{Netflix a par exemple développer le \href{https://github.com/netflix/chaosmonkey}{Chaos Monkey}, qui s'occupe d'éteindre au hasard des machines virtuels et containers dans un environnement de production, juste pour faire en sorte que les équipes soient drillées à développer toutes sortes de mécanismes de remise en service - }.

\begin{tabular}{|p{0.2\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}|}
  \hline
  Type & Utilité & Exemples \\
  \hline
  Firewall & & firewalld, UFW \\
  \hline
  Reverse Proxy &  & Nginx \\
  \hline
  Load balancers & Distribue la charge sur plusieurs serveurs, en fonction du nombre d'utilisateurs, du nombre de requêtes à gérer et du temps que prennent chacune d'entre elles & HAProxy \\
  \hline
  Serveurs d'application &  & Gunicorn, Uvicorn \\
  \hline
  Healthcheck & & Supervisord \\
  \hline
  Télémétrie & & \\
  \hline
\end{tabular}

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), 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}

\subsubsection{Firewall}


\subsubsection{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}}

\subsubsection{Répartiteur de charge (\textit{Load balancer})}

\subsubsection{Serveurs d'application (\textit{Workers})}

\subsubsection{Télémétrie}

\begin{quote}
  Once a rocket has left the ground, telemetry is essential for tracking what's happening and for understanding failures \cite[p. 10]{data_design}
\end{quote}

Le

\subsection{Composants de supervision}

\begin{tabular}{|p{0.2\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}|}
  \hline
  Type & Utilité & Exemples \\
  \hline
  Supervision des processus & & Supervisord \\
  \hline
  Flux d'évènements & & syslogd, journalctl \\
  \hline
  Notifications & & Zabbix, Nagios, Munin \\
  \hline
\end{tabular}

\subsubsection{Supervision des processus}

\subsubsection{Journaux}

La présence de journaux, leur structure et de la définition précise de leurs niveaux est essentielle; ce sont eux qui permettent d'obtenir des informations quant au statut de l'application:

\begin{itemize}
  \item Que fait-elle pour le moment ?
  \item Qu'a-t-elle fait à un moment en particulier ?
  \item Depuis quand n'a-t-elle plus émis de nouveaux évènements ?
\end{itemize}

\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, ancien consultant chez ToughtWorks
\end{quote}

Chaque évènement est associé à un niveau; celui-ci indique sa criticité et sa émet un premier tri quant à sa pertinence.

\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, \ldots
\item
  \textbf{FATAL} (ou \textbf{EXCEPTION}): \ldots généralement suivie d'une terminaison du programme - Bind raté d'un socket, etc.
\end{itemize}

\subsubsection{Flux d'évènements}

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, différents 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
  \textbf{Formattage}: à définir - mais la variante suivante est complète, lisible et pratique:
  \texttt{\{levelname\}\ \{asctime\}\ \{module\}\ \{process:d\}\ \{thread:d\}\ \{message\}}
\item
  \textbf{Handler}: juste un, qui définit un \texttt{StreamHandler}
\item
  \textbf{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}.

\subsubsection{Exploitation des journaux}

\begin{enumerate}
    \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}.


\subsubsection{Zabbix, Nagios, ...}

\subsubsection{Sentry}

\subsubsection{Greylog}


\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, ...

