Working on environment isolation
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Fred Pauchet 2022-04-24 19:00:11 +02:00
parent 9cd685bddd
commit 208ea90e2f
7 changed files with 507 additions and 556 deletions

View File

@ -1,205 +1,5 @@
\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{def}\NormalTok{ test\_add():}
\ControlFlowTok{assert} \DecValTok{1} \OperatorTok{+} \DecValTok{1} \OperatorTok{==} \StringTok{"argh"}
\end{Highlighting}
\end{Shaded}
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{λ }\ExtensionTok{pytest}
\NormalTok{============================= }\BuiltInTok{test}\NormalTok{ session starts ====================================}
\ExtensionTok{platform}\NormalTok{ ...}
\ExtensionTok{rootdir}\NormalTok{: ...}
\ExtensionTok{plugins}\NormalTok{: django{-}4.1.0}
\ExtensionTok{collected}\NormalTok{ 1 item}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}test\_models.py F [100\%]}
\NormalTok{================================== }\ExtensionTok{FAILURES}\NormalTok{ ==========================================}
\ExtensionTok{\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_}\NormalTok{ test\_basic\_add \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_}
\ExtensionTok{def}\NormalTok{ test\_basic\_add()}\BuiltInTok{:}
\OperatorTok{\textgreater{}} \ExtensionTok{assert}\NormalTok{ 1 + 1 == }\StringTok{"argh"}
\ExtensionTok{E}\NormalTok{ AssertionError: assert (1 + 1) == }\StringTok{\textquotesingle{}argh\textquotesingle{}}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}test\_models.py:2: AssertionError}
\NormalTok{=========================== }\ExtensionTok{short}\NormalTok{ test summary info ==================================}
\ExtensionTok{FAILED}\NormalTok{ gwift/test\_models.py::test\_basic\_add {-} AssertionError: assert (1 + 1) == }\StringTok{\textquotesingle{}argh\textquotesingle{}}
\NormalTok{============================== }\ExtensionTok{1}\NormalTok{ failed in 0.10s =====================================}
\end{Highlighting}
\end{Shaded}
\hypertarget{_couverture_de_code}{%
\subsubsection{Couverture de code}\label{_couverture_de_code}}
\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# requirements/base.text}
\NormalTok{[}\ExtensionTok{...}\NormalTok{]}
\ExtensionTok{django\_coverage\_plugin}
\end{Highlighting}
\end{Shaded}
\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# .coveragerc to control coverage.py}
\NormalTok{[}\ExtensionTok{run}\NormalTok{]}
\ExtensionTok{branch}\NormalTok{ = True}
\ExtensionTok{omit}\NormalTok{ = ../*migrations*}
\ExtensionTok{plugins}\NormalTok{ =}
\ExtensionTok{django\_coverage\_plugin}
\NormalTok{[}\ExtensionTok{report}\NormalTok{]}
\ExtensionTok{ignore\_errors}\NormalTok{ = True}
\NormalTok{[}\ExtensionTok{html}\NormalTok{]}
\ExtensionTok{directory}\NormalTok{ = coverage\_html\_report}
\end{Highlighting}
\end{Shaded}
\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{coverage}\NormalTok{ run {-}{-}source }\StringTok{"."}\NormalTok{ manage.py test}
\NormalTok{$ }\ExtensionTok{coverage}\NormalTok{ report}
\ExtensionTok{Name}\NormalTok{ Stmts Miss Cover}
\ExtensionTok{{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}gwift\textbackslash{}\_\_init\_\_.py 0 0 100\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}gwift\textbackslash{}settings.py 17 0 100\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}gwift\textbackslash{}urls.py 5 5 0\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}gwift\textbackslash{}wsgi.py 4 4 0\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}manage.py 6 0 100\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}wish\textbackslash{}\_\_init\_\_.py 0 0 100\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}wish\textbackslash{}admin.py 1 0 100\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}wish\textbackslash{}models.py 49 16 67\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}wish\textbackslash{}tests.py 1 1 0\%}
\ExtensionTok{gwift}\NormalTok{\textbackslash{}wish\textbackslash{}views.py 6 6 0\%}
\ExtensionTok{{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}}
\ExtensionTok{TOTAL}\NormalTok{ 89 32 64\%}
\ExtensionTok{{-}{-}{-}{-}}
\NormalTok{$ }\ExtensionTok{coverage}\NormalTok{ html}
\end{Highlighting}
\end{Shaded}
\hypertarget{_duxe9marrer_un_nouveau_projet}{%
\section{Démarrer un nouveau
projet}\label{_duxe9marrer_un_nouveau_projet}}
\hypertarget{_travailler_en_isolation}{%
\subsection{Travailler en isolation}\label{_travailler_en_isolation}}
Nous allons aborder la gestion et l'isolation des dépendances. Cette
section est aussi utile pour une personne travaillant seule, que pour
transmettre les connaissances à un nouveau membre de l'équipe ou pour
déployer l'application elle-même.
Il en était déjà question au deuxième point des 12 facteurs: même dans
le cas de petits projets, il est déconseillé de s'en passer. Cela évite
les déploiements effectués à l'arrache à grand renfort de \texttt{sudo}
et d'installation globale de dépendances, pouvant potentiellement
occasioner des conflits entre les applications déployées:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Il est tout à fait envisagable que deux applications différentes
soient déployées sur un même hôte, et nécessitent chacune deux
versions différentes d'une même dépendance.
\item
Pour la reproductibilité d'un environnement spécifique, cela évite
notamment les réponses type "Ca juste marche chez moi", puisque la
construction d'un nouvel environnement fait partie intégrante du
processus de construction et de la documentation du projet; grâce à
elle, nous avons la possibilité de construire un environnement sain et
d'appliquer des dépendances identiques, quelle que soit la machine
hôte.
\end{enumerate}
\includegraphics{images/it-works-on-my-machine.jpg}
Dans la suite de ce chapitre, nous allons considérer deux projets
différents:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Gwift, une application permettant de gérer des listes de souhaits
\item
Khana, une application de suivi d'apprentissage pour des élèves ou
étudiants.
\end{enumerate}
\hypertarget{_roulements_de_versions}{%
\subsubsection{Roulements de versions}\label{_roulements_de_versions}}
Django fonctionne sur un
\href{https://docs.djangoproject.com/en/dev/internals/release-process/}{roulement
de trois versions mineures pour une version majeure}, clôturé par une
version LTS (\emph{Long Term Support}).
\includegraphics{images/django-support-lts.png}
La version utilisée sera une bonne indication à prendre en considération
pour nos dépendances, puisqu'en visant une version particulière, nous ne
devrons pratiquement pas nous soucier (bon, un peu quand même, mais nous
le verrons plus tard\ldots\hspace{0pt}) des dépendances à installer,
pour peu que l'on reste sous un certain seuil.
Dans les étapes ci-dessous, nous épinglerons une version LTS afin de
nous assurer une certaine sérénité d'esprit (= dont nous ne occuperons
pas pendant les 3 prochaines années).
\hypertarget{_environnements_virtuels}{%
\subsubsection{Environnements virtuels}\label{_environnements_virtuels}}
\begin{figure}
\centering
\includegraphics{images/xkcd-1987.png}
\caption{\url{https://xkcd.com/1987}}
\end{figure}
Un des reproches que l'on peut faire au langage concerne sa versatilité:
il est possible de réaliser beaucoup de choses, mais celles-ci ne sont
pas toujours simples ou directes. Pour quelqu'un qui débarquererait, la
quantité d'options différentes peut paraître rebutante. Nous pensons
notamment aux environnements virtuels: ils sont géniaux à utiliser, mais
on est passé par virtualenv (l'ancêtre), virtualenvwrapper (sa version
améliorée et plus ergonomique), \texttt{venv} (la version intégrée
depuis la version 3.3 de l'interpréteur, et
\href{https://docs.python.org/3/library/venv.html}{la manière
recommandée} de créer un environnement depuis la 3.5).
Pour créer un nouvel environnement, vous aurez donc besoin:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
D'une installation de Python - \url{https://www.python.org/}
\item
D'un terminal - voir le point
\href{../environment/_index.xml\#un-terminal}{Un terminal}
\end{enumerate}
Il existe plusieurs autres modules permettant d'arriver au même
résultat, avec quelques avantages et inconvénients pour chacun d'entre
eux. Le plus prometteur d'entre eux est
\href{https://python-poetry.org/}{Poetry}, qui dispose d'une interface
en ligne de commande plus propre et plus moderne que ce que PIP propose.
Poetry se propose de gérer le projet au travers d'un fichier
pyproject.toml. TOML (du nom de son géniteur, Tom Preston-Werner,
légèrement CEO de GitHub à ses heures), se place comme alternative aux
formats comme JSON, YAML ou INI.
\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{La}\NormalTok{ commande poetry new }\OperatorTok{\textless{}}\NormalTok{project}\OperatorTok{\textgreater{}}\NormalTok{ créera une structure par défaut relativement compréhensible:}

View File

@ -1,11 +1,21 @@
\chapter{Démarrer un nouveau projet}
chaque application est créée par défaut avec un fichier \textbf{tests.py}, qui inclut la classe \texttt{TestCase} depuis le package \texttt{django.test}:
Comme indiqué ci-dessus, Django propose son propre cadre de tests, au travers du package \texttt{django.tests}.
Django fonctionne sur un
\href{https://docs.djangoproject.com/en/dev/internals/release-process/}{roulement de trois versions mineures pour une version majeure}, clôturé par une version LTS (\emph{Long Term Support}).
\includegraphics{images/django-support-lts.png}
La version utilisée sera une bonne indication à prendre en considération pour nos dépendances, puisqu'en visant une version particulière, nous ne devrons pratiquement pas nous soucier (bon, un peu quand même, mais nous le verrons plus tard\ldots\hspace{0pt}) des dépendances à installer, pour peu que l'on reste sous un certain seuil.
Dans les étapes ci-dessous, nous épinglerons une version LTS afin de nous assurer une certaine sérénité d'esprit (= dont nous ne occuperons pas pendant les 3 prochaines années).
\section{Tests unitaires}
Chaque application est créée par défaut avec un fichier \textbf{tests.py}, qui inclut la classe \texttt{TestCase} depuis le package \texttt{django.test}:
On a deux choix ici:
\begin{enumerate}
@ -26,3 +36,58 @@ On a deux choix ici:
\subsection{Pytest}
\subsection{Couverture de code}
La couverture de code est une analyse qui donne un pourcentage lié à la quantité de code couvert par les tests. Il ne s'agit pas de vérifier que le code est bien testé, mais de vérifier quelle partie du code est testée.
Le paquet coverage se charge dévaluer le pourcentage de code couvert par les tests.
Avec pytest, il convient dutiliser le paquet pytest-cov, suivi de la commande pytest
--cov=gwift tests/.
Si vous préférez rester avec le cadre de tests de Django, vous pouvez passer par le paquet django-coverage-plugin.
Ajoutez-le dans le fichier requirements/base.txt, et lancez une couverture de code grâce à la commande coverage.
La configuration peut se faire dans un fichier .coveragerc que vous placerez à la racine de votre projet, et qui sera lu lors de lexécution.
\begin{verbatim}
# requirements/base.text
[...]
django_coverage_plugin
\end{verbatim}
\begin{verbatim}
# .coveragerc to control coverage.py
[run]
branch = True
omit = ../*migrations*
plugins =
django_coverage_plugin
[report]
ignore_errors = True
[html]
directory = coverage_html_report
\end{verbatim}
\begin{verbatim}
$ coverage run --source "." manage.py test
$ coverage report
Name Stmts Miss Cover
---------------------------------------------
gwift\gwift\__init__.py 0 0 100%
gwift\gwift\settings.py 17 0 100%
gwift\gwift\urls.py 5 5 0%
gwift\gwift\wsgi.py 4 4 0%
gwift\manage.py 6 0 100%
gwift\wish\__init__.py 0 0 100%
gwift\wish\admin.py 1 0 100%
gwift\wish\models.py 49 16 67%
gwift\wish\tests.py 1 1 0%
gwift\wish\views.py 6 6 0%
---------------------------------------------
TOTAL 89 32 64%
----
$ coverage html
\end{verbatim}

View File

@ -695,50 +695,6 @@ TODO: Intérêt des containers.
Décrire le fichier setup.cfg.
\section{Docker \& Dockerfile}
\begin{listing}
\begin{verbatim}
# Dockerfile
# Pull base image
FROM python:3.8-slim-buster
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV DEBIAN_FRONTEND noninteractive
ENV ACCEPT_EULA=Y
# install Microsoft SQL Server requirements.
ENV ACCEPT_EULA=Y
RUN apt-get update -y && apt-get update \
&& apt-get install -y --no-install-recommends curl gcc g++ gnupg
# Add SQL Server ODBC Driver 17
RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
RUN curl https://packages.microsoft.com/config/debian/10/prod.list > /etc/apt/sources.list.d/mssql-release.list
RUN apt-get update \
&& apt-get install -y msodbcsql17 unixodbc-dev
# clean the install.
RUN apt-get -y clean
# Set work directory
WORKDIR /code
# Install dependencies
COPY ./requirements/base.txt /code/requirements/
RUN pip install --upgrade pip
RUN pip install -r ./requirements/base.txt
# Copy project
COPY . /code/
\end{verbatim}
\caption{Un exemple de Dockerfile}
\end{listing}
\section{Makefile}
@ -772,6 +728,7 @@ reste extrêmement efficace.
\section{Conclusions (et intégration continue)}
Mypy + black + pylint + flake8 + pyflakes + ...

View File

@ -180,314 +180,4 @@ Most websites don't need that kind of throughput. \cite{consider_sqlite}
\end{quote}
\section{Un système de virtualisation}
Par "\emph{système de virtualisation}", nous entendons n'importe quel application, système d'exploitation, système de containeurisation, ... qui permette de créer ou recréer un environnement de
développement aussi proche que celui en production.
Les solutions sont nombreuses:
\begin{itemize}
\item
\href{https://www.virtualbox.org/}{VirtualBox}
\item
\href{https://www.vagrantup.com/}{Vagrant}
\item
\href{https://www.docker.com/}{Docker}
\item
\href{https://linuxcontainers.org/lxc/}{Linux Containers (LXC)}
\item
\href{https://docs.microsoft.com/fr-fr/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v}{Hyper-V}
\end{itemize}
Ces quelques propositions se situent un cran plus loin que la "simple" isolation d'un environnement, puisqu'elles vous permettront de construire un environnement complet.
Elles constituent donc une étape supplémentaires dans la configuration de votre espace de travail, mais en amélioreront la qualité.
Dans la suite, nous détaillerons Vagrant et Docker, qui constituent deux solutions automatisables et multiplateformes, dont la configuration peut faire partie intégrante de vos sources.
\subsection{Vagrant}
Vagrant consiste en un outil de création et de gestion d'environnements virtualisés, en respectant toujours une même manière de travailler, indépendamment des choix techniques et de l'infrastructure que vous pourriez sélectionner.
\begin{quote}
Vagrant is a tool for building and managing virtual machine environments
in a single workflow. With an easy-to-use workflow and focus on
automation, Vagrant lowers development environment setup time, increases
production parity, and makes the "works on my machine" excuse a relic of
the past. \footnote{\url{https://www.vagrantup.com/intro}}
\end{quote}
La partie la plus importante de la configuration de Vagrant pour votre
projet consiste à placer un fichier \texttt{Vagrantfile} - \emph{a
priori} à la racine de votre projet - et qui contiendra les information
suivantes:
\begin{itemize}
\item
Le choix du \emph{fournisseur} (\textbf{provider}) de virtualisation
(Virtualbox, Hyper-V et Docker sont natifs; il est également possible
de passer par VMWare, AWS, etc.)
\item
Une \emph{box}, qui consiste à lui indiquer le type et la version
attendue du système virtualisé (Debian 10, Ubuntu 20.04, etc. - et
\href{https://app.vagrantup.com/boxes/search}{il y a du choix}).
\item
La manière dont la fourniture (\textbf{provisioning}) de
l'environnement doit être réalisée: scripts Shell, fichiers, Ansible,
Puppet, Chef, \ldots\hspace{0pt} Choisissez votre favori :-) même s'il
est toujours possible de passer par une installation et une
maintenance manuelle, après s'être connecté sur la machine.
\item
Si un espace de stockage doit être partagé entre la machine virtuelle
et l'hôte
\item
Les ports qui doivent être transmis de la machine virtuelle vers
l'hôte.
\end{itemize}
La syntaxe de ce fichier \texttt{Vagrantfile} est en \href{https://www.ruby-lang.org/en/}{Ruby}.
Vous trouverez ci-dessous un exemple, généré (et nettoyé) après avoir exécuté la commande \texttt{vagrant\ init}:
\begin{listing}[H]
\begin{minted}{ruby}
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip:
"127.0.0.1"
config.vm.provider "virtualbox" do |vb|
vb.gui = true
vb.memory = "1024"
end
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y nginx
SHELL
end
\end{minted}
\end{listing}
Dans le fichier ci-dessus, nous créons:
\begin{itemize}
\item
Une nouvelle machine virtuelle (ie. \emph{invitée}) sous Ubuntu Bionic Beaver, en x64
\item
Avec une correspondance du port \texttt{80} de la machine vers le port \texttt{8080} de l'hôte, en limitant l'accès à celui-ci - accédez à \texttt{localhost:8080} et vous accéderez au port \texttt{80} de la machine virtuelle.
\item
En utilisant Virtualbox comme backend - la mémoire vive allouée sera limitée à 1Go de RAM et nous ne voulons pas voir l'interface graphique au démarrage
\item
Et pour finir, nous voulons appliquer un script de mise à jour \texttt{apt-get\ update} et installer le paquet \texttt{nginx}
\end{itemize}
Par défaut, le répertoire courant (ie. le répertoire dans lequel notre fichier \texttt{Vagrantfile} se trouve) sera synchronisé dans le répertoire \texttt{/vagrant} sur la machine invitée.
\subsection{Docker}
(copié/collé de cookie-cutter)
\begin{listing}[H]
\begin{verbatim}
version: '3'
volumes:
local_postgres_data: {}
local_postgres_data_backups: {}
services:
<< description des services >>
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
django: &django
build:
context: .
dockerfile: ./compose/local/django/Dockerfile
image: khana_local_django
container_name: django
depends_on:
- postgres
volumes:
- .:/app:z
env_file:
- ./.envs/.local/.django
- ./.envs/.local/.postgres
ports:
- "8000:8000"
command: /start
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
postgres:
build:
context: .
dockerfile: ./compose/production/postgres/Dockerfile
image: khana_production_postgres
container_name: postgres
volumes:
- local_postgres_data:/var/lib/postgresql/data:Z
- local_postgres_data_backups:/backups:z
env_file:
- ./.envs/.local/.postgres
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
docs:
image: khana_local_docs
container_name: docs
build:
context: .
dockerfile: ./compose/local/docs/Dockerfile
env_file:
- ./.envs/.local/.django
volumes:
- ./docs:/docs:z
- ./config:/app/config:z
- ./khana:/app/khana:z
ports:
- "7000:7000"
command: /start-docs
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
redis:
image: redis:5.0
container_name: redis
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
celeryworker:
<<: *django
image: khana_local_celeryworker
container_name: celeryworker
depends_on:
- redis
- postgres
ports: []
command: /start-celeryworker
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
celerybeat:
<<: *django
image: khana_local_celerybeat
container_name: celerybeat
depends_on:
- redis
- postgres
ports: []
command: /start-celerybeat
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
flower:
<<: *django
image: khana_local_flower
container_name: flower
ports:
- "5555:5555"
command: /start-flower
\end{verbatim}
\end{listing}
\subsection{Docker-compose}
\begin{listing}[H]
\begin{verbatim}
# docker-compose.yml
version: '3.8'
services:
web:
build: .
command: python /code/manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- 8000:8000
depends_on:
- slqserver
slqserver:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
- "ACCEPT_EULA=Y"
- "SA_PASSWORD=sqklgjqihagrtdgqk12§!"
ports:
- 1433:1433
volumes:
- ../sqlserver/data:/var/opt/mssql/data
- ../sqlserver/log:/var/opt/mssql/log
- ../sqlserver/secrets:/var/opt/mssql/secrets
\end{verbatim}
\end{listing}
\subsection{Dockerfile}
\begin{listing}[H]
\begin{verbatim}
FROM python:3.8-slim-buster
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
RUN apt-get update \
# dependencies for building Python packages
&& apt-get install -y build-essential \
# psycopg2 dependencies
&& apt-get install -y libpq-dev \
# Translations dependencies
&& apt-get install -y gettext \
# cleaning up unused files
&& apt-get purge -y --auto-remove -o
APT::AutoRemove::RecommendsImportant=false \
&& rm -rf /var/lib/apt/lists/*
# Requirements are installed here to ensure they will be cached.
COPY ./requirements /requirements
RUN pip install -r /requirements/local.txt
COPY ./compose/production/django/entrypoint /entrypoint
RUN sed -i 's/\r$//g' /entrypoint
RUN chmod +x /entrypoint
COPY ./compose/local/django/start /start
RUN sed -i 's/\r$//g' /start
RUN chmod +x /start
COPY ./compose/local/django/celery/worker/start /start-celeryworker
RUN sed -i 's/\r$//g' /start-celeryworker
RUN chmod +x /start-celeryworker
COPY ./compose/local/django/celery/beat/start /start-celerybeat
RUN sed -i 's/\r$//g' /start-celerybeat
RUN chmod +x /start-celerybeat
COPY ./compose/local/django/celery/flower/start /start-flower
RUN sed -i 's/\r$//g' /start-flower
RUN chmod +x /start-flower
WORKDIR /app
ENTRYPOINT ["/entrypoint"]
\end{verbatim}
\end{listing}

View File

@ -0,0 +1,437 @@
\chapter{Travailler en isolation}
Nous allons aborder la gestion et l'isolation des dépendances. Cette
section est aussi utile pour une personne travaillant seule, que pour
transmettre les connaissances à un nouveau membre de l'équipe ou pour
déployer l'application elle-même.
Il en était déjà question au deuxième point des 12 facteurs: même dans
le cas de petits projets, il est déconseillé de s'en passer. Cela évite
les déploiements effectués à l'arrache à grand renfort de \texttt{sudo}
et d'installation globale de dépendances, pouvant potentiellement
occasioner des conflits entre les applications déployées:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Il est tout à fait envisagable que deux applications différentes
soient déployées sur un même hôte, et nécessitent chacune deux
versions différentes d'une même dépendance.
\item
Pour la reproductibilité d'un environnement spécifique, cela évite
notamment les réponses type "Ca juste marche chez moi", puisque la
construction d'un nouvel environnement fait partie intégrante du
processus de construction et de la documentation du projet; grâce à
elle, nous avons la possibilité de construire un environnement sain et
d'appliquer des dépendances identiques, quelle que soit la machine
hôte.
\end{enumerate}
\includegraphics{images/it-works-on-my-machine.jpg}
\section{Environnements virtuels}
\begin{figure}
\centering
\includegraphics{images/xkcd-1987.png}
\caption{\url{https://xkcd.com/1987}}
\end{figure}
Un des reproches que l'on peut faire au langage concerne sa versatilité: il est possible de réaliser beaucoup de choses, mais celles-ci ne sont
pas toujours simples ou directes.
Pour quelqu'un qui débarquererait, la quantité d'options différentes peut paraître rebutante. Nous pensons notamment aux environnements virtuels: ils sont géniaux à utiliser, mais on est passé par virtualenv (l'ancêtre), virtualenvwrapper (sa version améliorée et plus ergonomique), \texttt{venv} (la version intégrée depuis la version 3.3 de l'interpréteur, et \href{https://docs.python.org/3/library/venv.html}{la manière recommandée} de créer un environnement depuis la 3.5).
Pour créer un nouvel environnement, vous aurez donc besoin:
\begin{enumerate}
\item
D'une installation de Python - \url{https://www.python.org/}
\item
D'un terminal - voir le point \href{../environment/_index.xml\#un-terminal}{Un terminal}
\end{enumerate}
Il existe plusieurs autres modules permettant d'arriver au même résultat, avec quelques avantages et inconvénients pour chacun d'entre eux.
Le plus prometteur d'entre eux est \href{https://python-poetry.org/}{Poetry}, qui dispose d'une interface
en ligne de commande plus propre et plus moderne que ce que PIP propose.
\subsection{Poetry}
Poetry se propose de gérer le projet au travers d'un fichier pyproject.toml.
TOML (du nom de son géniteur, Tom Preston-Werner, légèrement CEO de GitHub à ses heures), se place comme alternative aux formats comme JSON, YAML ou INI.
\begin{verbatim}
$ poetry new django-gecko
$ tree django-gecko/
django-gecko/
django_gecko
__init__.py
pyproject.toml
README.rst
tests
__init__.py
test_django_gecko.py
2 directories, 5 files
\end{verbatim}
\section{Un système de virtualisation}
Par "\emph{système de virtualisation}", nous entendons n'importe quel application, système d'exploitation, système de containeurisation, ... qui permette de créer ou recréer un environnement de
développement aussi proche que celui en production.
Les solutions sont nombreuses:
\begin{itemize}
\item
\href{https://www.virtualbox.org/}{VirtualBox}
\item
\href{https://www.vagrantup.com/}{Vagrant}
\item
\href{https://www.docker.com/}{Docker}
\item
\href{https://linuxcontainers.org/lxc/}{Linux Containers (LXC)}
\item
\href{https://docs.microsoft.com/fr-fr/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v}{Hyper-V}
\end{itemize}
Ces quelques propositions se situent un cran plus loin que la "simple" isolation d'un environnement, puisqu'elles vous permettront de construire un environnement complet.
Elles constituent donc une étape supplémentaires dans la configuration de votre espace de travail, mais en amélioreront la qualité.
Dans la suite, nous détaillerons Vagrant et Docker, qui constituent deux solutions automatisables et multiplateformes, dont la configuration peut faire partie intégrante de vos sources.
\subsection{Vagrant}
Vagrant consiste en un outil de création et de gestion d'environnements virtualisés, en respectant toujours une même manière de travailler, indépendamment des choix techniques et de l'infrastructure que vous pourriez sélectionner.
\begin{quote}
Vagrant is a tool for building and managing virtual machine environments
in a single workflow. With an easy-to-use workflow and focus on
automation, Vagrant lowers development environment setup time, increases
production parity, and makes the "works on my machine" excuse a relic of
the past. \footnote{\url{https://www.vagrantup.com/intro}}
\end{quote}
La partie la plus importante de la configuration de Vagrant pour votre
projet consiste à placer un fichier \texttt{Vagrantfile} - \emph{a
priori} à la racine de votre projet - et qui contiendra les information
suivantes:
\begin{itemize}
\item
Le choix du \emph{fournisseur} (\textbf{provider}) de virtualisation
(Virtualbox, Hyper-V et Docker sont natifs; il est également possible
de passer par VMWare, AWS, etc.)
\item
Une \emph{box}, qui consiste à lui indiquer le type et la version
attendue du système virtualisé (Debian 10, Ubuntu 20.04, etc. - et
\href{https://app.vagrantup.com/boxes/search}{il y a du choix}).
\item
La manière dont la fourniture (\textbf{provisioning}) de
l'environnement doit être réalisée: scripts Shell, fichiers, Ansible,
Puppet, Chef, \ldots\hspace{0pt} Choisissez votre favori :-) même s'il
est toujours possible de passer par une installation et une
maintenance manuelle, après s'être connecté sur la machine.
\item
Si un espace de stockage doit être partagé entre la machine virtuelle
et l'hôte
\item
Les ports qui doivent être transmis de la machine virtuelle vers
l'hôte.
\end{itemize}
La syntaxe de ce fichier \texttt{Vagrantfile} est en \href{https://www.ruby-lang.org/en/}{Ruby}.
Vous trouverez ci-dessous un exemple, généré (et nettoyé) après avoir exécuté la commande \texttt{vagrant\ init}:
\begin{listing}[H]
\begin{minted}{ruby}
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip:
"127.0.0.1"
config.vm.provider "virtualbox" do |vb|
vb.gui = true
vb.memory = "1024"
end
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y nginx
SHELL
end
\end{minted}
\end{listing}
Dans le fichier ci-dessus, nous créons:
\begin{itemize}
\item
Une nouvelle machine virtuelle (ie. \emph{invitée}) sous Ubuntu Bionic Beaver, en x64
\item
Avec une correspondance du port \texttt{80} de la machine vers le port \texttt{8080} de l'hôte, en limitant l'accès à celui-ci - accédez à \texttt{localhost:8080} et vous accéderez au port \texttt{80} de la machine virtuelle.
\item
En utilisant Virtualbox comme backend - la mémoire vive allouée sera limitée à 1Go de RAM et nous ne voulons pas voir l'interface graphique au démarrage
\item
Et pour finir, nous voulons appliquer un script de mise à jour \texttt{apt-get\ update} et installer le paquet \texttt{nginx}
\end{itemize}
Par défaut, le répertoire courant (ie. le répertoire dans lequel notre fichier \texttt{Vagrantfile} se trouve) sera synchronisé dans le répertoire \texttt{/vagrant} sur la machine invitée.
\subsection{Docker}
\includegraphics{images/docker.jpg}
(copié/collé de cookie-cutter)
\begin{listing}[H]
\begin{verbatim}
version: '3'
volumes:
local_postgres_data: {}
local_postgres_data_backups: {}
services:
<< description des services >>
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
django: &django
build:
context: .
dockerfile: ./compose/local/django/Dockerfile
image: khana_local_django
container_name: django
depends_on:
- postgres
volumes:
- .:/app:z
env_file:
- ./.envs/.local/.django
- ./.envs/.local/.postgres
ports:
- "8000:8000"
command: /start
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
postgres:
build:
context: .
dockerfile: ./compose/production/postgres/Dockerfile
image: khana_production_postgres
container_name: postgres
volumes:
- local_postgres_data:/var/lib/postgresql/data:Z
- local_postgres_data_backups:/backups:z
env_file:
- ./.envs/.local/.postgres
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
docs:
image: khana_local_docs
container_name: docs
build:
context: .
dockerfile: ./compose/local/docs/Dockerfile
env_file:
- ./.envs/.local/.django
volumes:
- ./docs:/docs:z
- ./config:/app/config:z
- ./khana:/app/khana:z
ports:
- "7000:7000"
command: /start-docs
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
redis:
image: redis:5.0
container_name: redis
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
celeryworker:
<<: *django
image: khana_local_celeryworker
container_name: celeryworker
depends_on:
- redis
- postgres
ports: []
command: /start-celeryworker
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
celerybeat:
<<: *django
image: khana_local_celerybeat
container_name: celerybeat
depends_on:
- redis
- postgres
ports: []
command: /start-celerybeat
\end{verbatim}
\end{listing}
\begin{listing}[H]
\begin{verbatim}
flower:
<<: *django
image: khana_local_flower
container_name: flower
ports:
- "5555:5555"
command: /start-flower
\end{verbatim}
\end{listing}
\subsection{Docker-compose}
\begin{listing}[H]
\begin{verbatim}
# docker-compose.yml
version: '3.8'
services:
web:
build: .
command: python /code/manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- 8000:8000
depends_on:
- slqserver
slqserver:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
- "ACCEPT_EULA=Y"
- "SA_PASSWORD=sqklgjqihagrtdgqk12§!"
ports:
- 1433:1433
volumes:
- ../sqlserver/data:/var/opt/mssql/data
- ../sqlserver/log:/var/opt/mssql/log
- ../sqlserver/secrets:/var/opt/mssql/secrets
\end{verbatim}
\end{listing}
\subsection{Dockerfile}
\begin{listing}[H]
\begin{verbatim}
FROM python:3.8-slim-buster
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
RUN apt-get update \
# dependencies for building Python packages
&& apt-get install -y build-essential \
# psycopg2 dependencies
&& apt-get install -y libpq-dev \
# Translations dependencies
&& apt-get install -y gettext \
# cleaning up unused files
&& apt-get purge -y --auto-remove -o
APT::AutoRemove::RecommendsImportant=false \
&& rm -rf /var/lib/apt/lists/*
# Requirements are installed here to ensure they will be cached.
COPY ./requirements /requirements
RUN pip install -r /requirements/local.txt
COPY ./compose/production/django/entrypoint /entrypoint
RUN sed -i 's/\r$//g' /entrypoint
RUN chmod +x /entrypoint
COPY ./compose/local/django/start /start
RUN sed -i 's/\r$//g' /start
RUN chmod +x /start
COPY ./compose/local/django/celery/worker/start /start-celeryworker
RUN sed -i 's/\r$//g' /start-celeryworker
RUN chmod +x /start-celeryworker
COPY ./compose/local/django/celery/beat/start /start-celerybeat
RUN sed -i 's/\r$//g' /start-celerybeat
RUN chmod +x /start-celerybeat
COPY ./compose/local/django/celery/flower/start /start-flower
RUN sed -i 's/\r$//g' /start-flower
RUN chmod +x /start-flower
WORKDIR /app
ENTRYPOINT ["/entrypoint"]
\end{verbatim}
\end{listing}
\section{Docker \& Dockerfile}
\begin{listing}
\begin{verbatim}
# Dockerfile
# Pull base image
FROM python:3.8-slim-buster
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV DEBIAN_FRONTEND noninteractive
ENV ACCEPT_EULA=Y
# install Microsoft SQL Server requirements.
ENV ACCEPT_EULA=Y
RUN apt-get update -y && apt-get update \
&& apt-get install -y --no-install-recommends curl gcc g++ gnupg
# Add SQL Server ODBC Driver 17
RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
RUN curl https://packages.microsoft.com/config/debian/10/prod.list > /etc/apt/sources.list.d/mssql-release.list
RUN apt-get update \
&& apt-get install -y msodbcsql17 unixodbc-dev
# clean the install.
RUN apt-get -y clean
# Set work directory
WORKDIR /code
# Install dependencies
COPY ./requirements/base.txt /code/requirements/
RUN pip install --upgrade pip
RUN pip install -r ./requirements/base.txt
# Copy project
COPY . /code/
\end{verbatim}
\caption{Un exemple de Dockerfile}
\end{listing}

BIN
images/docker.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -52,6 +52,8 @@
\include{chapters/tools.tex}
\include{chapters/working-in-isolation.tex}
\include{chapters/python.tex}
\include{chapters/new-project.tex}