diff --git a/asciidoc-to-tex.tex b/asciidoc-to-tex.tex index 3ffbcae..aafd341 100644 --- a/asciidoc-to-tex.tex +++ b/asciidoc-to-tex.tex @@ -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:} diff --git a/chapters/new-project.tex b/chapters/new-project.tex index afff66b..882cb15 100644 --- a/chapters/new-project.tex +++ b/chapters/new-project.tex @@ -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 d’utiliser 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 l’exé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} diff --git a/chapters/python.tex b/chapters/python.tex index dff24da..c306cd3 100644 --- a/chapters/python.tex +++ b/chapters/python.tex @@ -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 + ... diff --git a/chapters/tools.tex b/chapters/tools.tex index f61a675..ff1e45f 100644 --- a/chapters/tools.tex +++ b/chapters/tools.tex @@ -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} diff --git a/chapters/working-in-isolation.tex b/chapters/working-in-isolation.tex new file mode 100644 index 0000000..fb75b58 --- /dev/null +++ b/chapters/working-in-isolation.tex @@ -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} diff --git a/images/docker.jpg b/images/docker.jpg new file mode 100644 index 0000000..a5947e5 Binary files /dev/null and b/images/docker.jpg differ diff --git a/main.tex b/main.tex index c55ffdc..5eddd9a 100644 --- a/main.tex +++ b/main.tex @@ -52,6 +52,8 @@ \include{chapters/tools.tex} +\include{chapters/working-in-isolation.tex} + \include{chapters/python.tex} \include{chapters/new-project.tex}