Journal Ça passe crème

Posté par  . Licence CC By‑SA.
13
18
avr.
2020

Quoi

À la recherche d’un CRM pour un petit boulot, je découvre Creme CRM. Selon mon product owner, il y a toutes les fonctionnalités souhaitées. Top!

Ma prod est à base de conteneurs ; il faut « dockeriser » l’outil. Creme est une application Python-Django. Cela ne devrait pas poser de problème particulier, python étant très bien supporté dans Docker.

Comment

Le site officiel renvoi vers le repo git et le README explique la marche à suivre pour lancer l’application.

Il s’avère que Django a une particularité. Il optimise les fichiers statiques (JS, CSS, HTML, images?) avec un outil écrit en java. Il faut java pour faire tourner une application Django avec de bonne performances.

Il s’avère que Django a une deuxième particularité. Il dépend de Pillow, une bibliothèque logicielle de manipulation d’images. Et Pillow doit se compiler sur la machine lors de son installation. Je n’ai pas l’impression que ce soit utilisé par Creme mais dans le doute et pour avoir un packaging Django un peu généraliste, je vais la garder. Il faut un compilateur C et quelques bibliothèques logicielles de développement.

Pour ne pas garder cet embonpoint dans mon image docker finale, je vais faire une construction en deux phases. La première phase fera la génération des médias et la compilation des dépendances, la seconde fera l’image finale, sans java ni compilateur.

Je vais aussi utiliser un « virtual env ». Ce n’est habituellement pas conseillé pour une image docker car le conteneur fait déjà l’isolation de l’environnent applicatif vis-à-vis de l’hôte. Mais dans mon cas, c’est plus pratiques pour transporter les dépendances Python d’une phase à l’autre sans devoir fouiller dans les répertoires système.

Cela donne :

FROM python:3.7-alpine AS django-builder-alpine

RUN apk add --no-cache openjdk11-jre
RUN apk add --no-cache jpeg-dev zlib-dev
RUN apk add --no-cache build-base linux-headers

WORKDIR /app

ENV VIRTUAL_ENV=/app/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

COPY creme/requirements.txt .
RUN pip install -r requirements.txt

COPY . .

# set sqlite backend, else it requires mysql client to be installed
RUN echo "DATABASES={'default':{'ENGINE':'django.db.backends.sqlite3'}}" > creme/local_settings.py

RUN python manage.py generatemedia


FROM python:3.7-alpine

RUN apk add --no-cache redis jpeg zlib

WORKDIR /app

ENV VIRTUAL_ENV=/app/venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

COPY --from=django-builder-alpine /app .

# needed for ingress auto discovery
EXPOSE 8000

CMD redis-server                         & \
    python manage.py migrate             && \
    python manage.py creme_populate      && \
    python manage.py creme_job_manager   & \
    python manage.py runserver 0:8000

Limitations

  • l’image propose le serveur django de développement ; il est certainement moins optimisé qu’un apache+mod_python ou un gunicorn
  • l’image propose le moteur de base de données SQLite ; il n’est pas conseillé pour une utilisation avec plusieurs utilisateurs simultanés. Cela pourrait se changer par simple configuration (fichier local_settings)
  • l’image embarque tous les composants – webapp, db, ordonnanceur, cache – dans le même conteneur ; un déploiement plus élaboré les aurait séparés

Dans la pratique, pour notre petit usage, cela fonctionne parfaitement.

Résultat

L’image de construction pèse 584MB, l’image finale 176MB, une économie de 412MB, environ 70%. La même tentative avec une base Debian-slim donne une image de construction de 508MB et une image finale de 289MB.

EOF

  • # Deux précisions

    Posté par  . Évalué à 9.

    Merci pour ton retour d'expérience !

    Il s’avère que Django a une particularité. Il optimise les fichiers statiques (JS, CSS, HTML, images?) avec un outil écrit en java. Il faut java pour faire tourner une application Django avec de bonne performances.

    Ce n'est pas Django qui fait ça, mais https://pypi.org/project/django-mediagenerator/, une app Django qui a décidé de faire appel à YUI. Et creme utilise cette application. (Qui n'est d'ailleurs pas mise à jour depuis 2015.)

    Il s’avère que Django a une deuxième particularité. Il dépend de Pillow, une bibliothèque logicielle de manipulation d’images. Et Pillow doit se compiler sur la machine lors de son installation.

    C'est une dépendance optionnelle, en effet. Sous Linux, tu n'as besoin de la compiler que si tu utilises musl (c'est ton cas avec Alpine), sinon avec la glibc les wheels manylinux fournies par le projet Pillow s'installent sans compilation.

  • # Java ??

    Posté par  . Évalué à 2.

    J'utilise Django depuis quelques années et je n'avais jamais entendu parler d'un bout de code en java. Rien non plus sur le site officiel.

    Tu aurais un lien qui explique ça ?

    • [^] # Re: Java ??

      Posté par  . Évalué à 4.

      Bon le temps d'écrire ça et j'ai la réponse au-dessus.

  • # Quelques précisions

    Posté par  (site web personnel) . Évalué à 10.

    [je suis le développeur principal de CremeCRM]

    Bonsoir,

    Il s’avère que Django a une particularité. Il optimise les fichiers statiques (JS, CSS, HTML, images?) avec un outil écrit en java. Il faut java pour faire tourner une application Django avec de bonne performances.

    Cette dépendance n'est pas intrinsèque à Django, mais à CremeCRM. Pour info, nous utilisions une app externe 'mediagenerator' bien avant que Django ait sa gestion actuelle des assets, mais comme sont développement a été stoppée (elle n'est plus compatible depuis longtemps avec les versions récentes de Django) j'ai intégré cette app à notre code, j'ai enlevé la plupart des trucs inutiles pour nous, et porté vers les versions récentes de Django puis Python 3.

    Il est tout à fait possible de se passer de Java avec la bonne configuration, mais le JS et le CSS ne sont alors plus minifiés.

    Et oui les images subissent un traitement: les assets finaux sont renommés et contiennent un hash dans leur nom (le nom change donc si ont modifie l'image), ce qui permet de dire au navigateur de garder les images en cache aussi longtemps que possible.

    Quand j'aurai le temps (spoileur: pas une priorité du tout) je regarderai si les minifieurs JS/CSS écrits en Python donnent de bons résultats histoire de pouvoir enlever Java des dépendances de base.

    Il s’avère que Django a une deuxième particularité. Il dépend de Pillow, une bibliothèque logicielle de manipulation d’images. Et Pillow doit se compiler sur la machine lors de son installation.

    Il s'agit là encore d'une dépendance de Creme, pas de Django. On s'en sert de manière très légère certes, mais si on s'en sert (cherche des from PIL).

    J'espère que CremeCRM vous satisfera (même si effectivement on ne conseille pas spécialement SQLite pour autre chose que le développement, voire une utilisation locale mono-utilisateur).

  • # Sans vouloir être méchant

    Posté par  (site web personnel) . Évalué à 6.

    Ma prod est à base de conteneurs
    […]
    l’image propose le serveur django de développement

    => Poubelle. Car comme le dit la sage documentation :

    C’est le moment de noter soigneusement ceci : n’utilisez jamais ce serveur pour quoi que ce soit qui s’approche d’un environnement de production.

    https://docs.djangoproject.com/fr/3.0/intro/tutorial01/#the-development-server

    Adhérer à l'April, ça vous tente ?

    • [^] # Re: Sans vouloir être méchant

      Posté par  . Évalué à 4.

      Sans vouloir être méchant 2 :
      - Plusieurs services dans le même container,
      - aucun chemins absolu,
      - et le tout tourne en root

      Et pour la route, j’étais tombé sur cet article qui explique pourquoi il vaut mieux éviter alpine pour du python :
      https://pythonspeed.com/articles/alpine-docker-python/

      • [^] # Re: Sans vouloir être méchant

        Posté par  . Évalué à 4.

        • Plusieurs services dans le même container,

        Ce n'est pas une très bonne pratique mais ce n'est pas rédhibitoire non plus.

        • aucun chemins absolu,

        pas compris

        • et le tout tourne en root

        Cela se configure au déploiement, par exemple avec un docker run -u 1001

        https://pythonspeed

        Il dit:

        • Make your builds much slower.

        Pour avoir testé slim et alpine, il n'y a pas de différence perceptible de temps de build.

        • Make your images bigger.

        C'est ce que je constate pour la première phase. Mais une fois la deuxième phase effectuée, l'image résultante est plus petite: -113MB.

        • Waste your time.

        Trop tard :)

        • On occassion, introduce obscure runtime bugs.

        Je verrai à l'usage. Je backup/restore tous les jours actuellement au cas où.

        • [^] # Re: Sans vouloir être méchant

          Posté par  . Évalué à 7. Dernière modification le 19 avril 2020 à 09:40.

          (merci pour le journal, c'est sympa d'avoir du contenu technique)

          Ce n'est pas une très bonne pratique mais ce n'est pas rédhibitoire non plus.

          Ça rend le scaling plus compliqué (mais peut-être que ce n'est pas un problème ici, surtout avec le sqlite). Et ça rend le monitoring plus compliqué. Si ton process redis meurt, tu ne vas pas le voir tout de suite.

          « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

      • [^] # Re: Sans vouloir être méchant

        Posté par  (site web personnel, Mastodon) . Évalué à 7. Dernière modification le 19 avril 2020 à 00:47.

        Plusieurs services dans le même container

        Nombre de logiciels sont "dockerises" de cette manière.

        Quel est le problème (outre que ce n'est pas dans la philosophie docker) ?

    • [^] # Re: Sans vouloir être méchant

      Posté par  . Évalué à 3.

      J'ai un reverse proxy HTTPS devant ce qui devrait me garantir un minimum de sécurité.
      J'imagine que si ils ne le recommandent pas, c'est parce qu'il gère sûrement pas très bien les connections simultanées. Ça tombe bien, la base de données que j'ai choisi pour ce petit déploiement n'est pas vraiment faîte pour ça non plus.

      • [^] # Re: Sans vouloir être méchant

        Posté par  . Évalué à 4.

        J'ai un reverse proxy HTTPS devant ce qui devrait me garantir un minimum de sécurité.

        Et il protège contre les fuzzers web ?

        Emacs le fait depuis 30 ans.

        • [^] # Re: Sans vouloir être méchant

          Posté par  . Évalué à 2.

          Pas spécialement pourquoi ? tu conseilles quelque chose ?

          • [^] # Re: Sans vouloir être méchant

            Posté par  . Évalué à 5.

            Pas spécialement pourquoi ?

            Ca me fait toujours tiquer quand je vois un projet disant "oui on a mis en prod l'env de dev qui est pas très sécurisé et carrément déconseillé pour la prod par tout le monde, mais tkt on gère et en plus on a mis un reverse-proxy/firewall/whatever". Ca me fait par contre pas tiquer quand je tombe, quelques temps plus tard, sur une news annoncant un leak massif de données de la plateforme en question, ou que je recois des spams venant de là.
            Penser "sécurité" pendant les process de dev, mais aussi de mise en prod, ce sont des efforts certes, mais qui permettent de grandement réduire la surface d'attaque sur une webapp.

            tu conseilles quelque chose ?

            Pas mettre en prod un env de dev déjà. Si vous avez une DSI qui fait aussi de la sécurité, aller leur demander des conseils, c'est leur métier. Si vous avez un WAF, faire passer les flux accédant à l'appli par là (le WAF servant aussi de reverse-proxy). Si vous en avez pas, bah faudrait penser à en mettre un, au moins pour limiter les XSS et les SQLi. Comme t'as déjà un reverse proxy, tu peux en mettre un dessus : si c'est un nginx tu as naxsi ; si c'est un apache, tu as modsecurity. Et garder un oeil sur les logs aussi, y a toujours pleins de choses intéressantes dedans.

            Emacs le fait depuis 30 ans.

  • # Docker + Django + Apache

    Posté par  (site web personnel) . Évalué à 4. Dernière modification le 18 avril 2020 à 23:26.

    J'ai récemment passé le site openpaper.work dans Docker. C'est aussi du Django. J'avais vu beaucoup de doc et tutoriels de dockerisation qui montraient comment le faire en utilisant aussi le serveur web de développement de Django. Bien que ça peut peut-être faire l'affaire dans un environnement cloud, ça ressemble quand même à une mauvaise idée. Dans mon cas, je n'ai de toute façon qu'un seul conteneur qui tourne. Donc j'ai choisi de continuer à utiliser un serveur Apache dans le conteneur. Ça donne ça: Dockerfile + start.sh + docker.

    Comme c'est la 1ère fois que je dockerise quelque-chose de sérieux, je serais intéressé d'avoir l'avis de gens qui s'y connaissent mieux en Docker.

  • # état de l'art

    Posté par  . Évalué à 2.

    Hello,

    Plusieurs choses qui ne sont pas "état de l'art" dans ton DockerFile (après lecture en diagonale)

    1. USER: ton container run en root, c'est mal pour la sécurité ! Il faut créer un user, lui donner les droits sur les bons dossiers

    2. VOLUMES: tout ce qui est data / qui bouge doit être dans un volume, et tu peux le déclarer dans ton DockerFile. Si ton volume doit avoir déjà du contenu, il faut le créer avant de déclarer le chemin volume (le système de fichier de docker est très pourri en terme de perf). Donc ici il te faut au moins un volume pour la BDD, un autre pour les médias (pour les statics pas besoin, sauf si tu prévois de les faire servir par un autre container nginx)

    3. RUN: (même si je comprends que là c'est en mode test rapide)

    • je pense que c'est pas vraiment bien d'avoir deux commandes en parallèle dans un container. Tu fais plutôt un script qui devient l'entrypoint et qui permet de lancer chaque commande (ok c'est acceptable en première instance)

    • ce n'est pas top non plus de lancer le migrate ou le populate au démarrage, c'est des choses qui sont faite normalement à la livraison, pas au lancement.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.