Jarrod  Douglas

Jarrod Douglas

1660532820

Quelques Bonnes Pratiques Docker Pour Les Développeurs Python

Cet article examine quelques bonnes pratiques à suivre lors de l'écriture de Dockerfiles et de l'utilisation de Docker en général. Alors que la plupart des pratiques répertoriées s'appliquent à tous les développeurs, quel que soit le langage, quelques-unes s'appliquent uniquement à ceux qui développent des applications basées sur Python.

Fichiers Docker

Utiliser des builds en plusieurs étapes

Tirez parti des versions en plusieurs étapes pour créer des images Docker plus légères et plus sécurisées.

Les builds Docker en plusieurs étapes vous permettent de diviser vos Dockerfiles en plusieurs étapes. Par exemple, vous pouvez avoir une étape pour compiler et créer votre application, qui peut ensuite être copiée dans les étapes suivantes. Étant donné que seule la dernière étape est utilisée pour créer l'image, les dépendances et les outils associés à la création de votre application sont ignorés, laissant une image légère et modulaire prête pour la production.

Exemple de développement Web :

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt


# final stage
FROM python:3.9-slim

WORKDIR /app

COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

RUN pip install --no-cache /wheels/*

Dans cet exemple, le compilateur GCC est requis pour l'installation de certains packages Python. Nous avons donc ajouté une étape temporaire de construction pour gérer la phase de construction. Étant donné que l'image d'exécution finale ne contient pas de GCC, elle est beaucoup plus légère et plus sécurisée.

Comparaison de taille :

REPOSITORY                 TAG                    IMAGE ID       CREATED          SIZE
docker-single              latest                 8d6b6a4d7fb6   16 seconds ago   259MB
docker-multi               latest                 813c2fa9b114   3 minutes ago    156MB

Exemple de science des données :

# temp stage
FROM python:3.9 as builder

RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels jupyter pandas


# final stage
FROM python:3.9-slim

WORKDIR /notebooks

COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/*

Comparaison de taille :

REPOSITORY                  TAG                   IMAGE ID       CREATED         SIZE
ds-multi                    latest                b4195deac742   2 minutes ago   357MB
ds-single                   latest                7c23c43aeda6   6 minutes ago   969MB

En résumé, les builds en plusieurs étapes peuvent réduire la taille de vos images de production, ce qui vous permet d'économiser du temps et de l'argent. De plus, cela simplifiera vos conteneurs de production. De plus, en raison de la petite taille et de la simplicité, il y a potentiellement une surface d'attaque plus petite.

Commandez les commandes Dockerfile de manière appropriée

Portez une attention particulière à l'ordre de vos commandes Dockerfile pour tirer parti de la mise en cache des couches.

Docker met en cache chaque étape (ou couche) dans un Dockerfile particulier pour accélérer les générations suivantes. Lorsqu'une étape change, le cache est invalidé non seulement pour cette étape particulière, mais pour toutes les étapes suivantes.

Exemple:

FROM python:3.9-slim

WORKDIR /app

COPY sample.py .

COPY requirements.txt .

RUN pip install -r /requirements.txt

Dans ce Dockerfile, nous avons copié le code de l'application avant d'installer les exigences. Maintenant, chaque fois que nous modifions sample.py , la construction réinstallera les packages. Ceci est très inefficace, en particulier lorsque vous utilisez un conteneur Docker comme environnement de développement. Par conséquent, il est crucial de conserver les fichiers qui changent fréquemment vers la fin du Dockerfile.

Vous pouvez également empêcher les invalidations de cache indésirables en utilisant un fichier .dockerignore pour exclure l'ajout de fichiers inutiles au contexte de construction Docker et à l'image finale. Plus d'informations ici sous peu.

Ainsi, dans le Dockerfile ci-dessus, vous devez déplacer la COPY sample.py .commande vers le bas :

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r /requirements.txt

COPY sample.py .

Remarques:

  1. Placez toujours les couches susceptibles de changer aussi bas que possible dans le Dockerfile.
  2. Combiner RUN apt-get updateet RUN apt-get installcommandes. (Cela permet également de réduire la taille de l'image. Nous y reviendrons sous peu.)
  3. Si vous souhaitez désactiver la mise en cache pour une version Docker particulière, ajoutez l' --no-cache=Trueindicateur.

Utiliser de petites images de base Docker

Les images Docker plus petites sont plus modulaires et sécurisées.

Construire, pousser et extraire des images est plus rapide avec des images plus petites. Ils ont également tendance à être plus sécurisés car ils n'incluent que les bibliothèques et les dépendances système nécessaires à l'exécution de votre application.

Quelle image de base Docker devez-vous utiliser ?

Malheureusement, cela dépend.

Voici une comparaison de taille de différentes images de base Docker pour Python :

REPOSITORY   TAG                 IMAGE ID       CREATED      SIZE
python       3.9.6-alpine3.14    f773016f760e   3 days ago   45.1MB
python       3.9.6-slim          907fc13ca8e7   3 days ago   115MB
python       3.9.6-slim-buster   907fc13ca8e7   3 days ago   115MB
python       3.9.6               cba42c28d9b8   3 days ago   886MB
python       3.9.6-buster        cba42c28d9b8   3 days ago   886MB

Bien que la version Alpine, basée sur Alpine Linux , soit la plus petite, elle peut souvent entraîner une augmentation des temps de construction si vous ne trouvez pas de binaires compilés qui fonctionnent avec. Par conséquent, vous devrez peut-être construire vous-même les fichiers binaires, ce qui peut augmenter la taille de l'image (en fonction des dépendances requises au niveau du système) et les temps de construction (en raison de la nécessité de compiler à partir de la source).

Reportez-vous à La meilleure image de base Docker pour votre application Python et L'utilisation d'Alpine peut ralentir les constructions de Python Docker 50× pour en savoir plus sur les raisons pour lesquelles il est préférable d'éviter d'utiliser des images de base basées sur Alpine.

Au final, tout est une question d'équilibre. En cas de doute, commencez par une *-slimsaveur, en particulier en mode développement, pendant que vous construisez votre application. Vous souhaitez éviter d'avoir à mettre à jour en permanence le Dockerfile pour installer les dépendances nécessaires au niveau du système lorsque vous ajoutez un nouveau package Python. Au fur et à mesure que vous renforcez votre application et vos Dockerfile(s) pour la production, vous souhaiterez peut-être explorer l'utilisation d'Alpine pour l'image finale à partir d'une construction en plusieurs étapes.

N'oubliez pas non plus de mettre à jour régulièrement vos images de base pour améliorer la sécurité et augmenter les performances. Lorsqu'une nouvelle version d'une image de base est publiée - c'est-à-dire 3.9.6-slim-> 3.9.7-slim- vous devez extraire la nouvelle image et mettre à jour vos conteneurs en cours d'exécution pour obtenir tous les derniers correctifs de sécurité.

Minimiser le nombre de couches

C'est une bonne idée de combiner autant que possible les commandes , et , car elles créent des RUNcalques COPY. ADDChaque calque augmente la taille de l'image car ils sont mis en cache. Par conséquent, à mesure que le nombre de couches augmente, la taille augmente également.

Vous pouvez tester cela avec la docker historycommande :

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
dockerfile   latest    180f98132d02   51 seconds ago   259MB

$ docker history 180f98132d02

IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
180f98132d02   58 seconds ago       COPY . . # buildkit                             6.71kB    buildkit.dockerfile.v0
<missing>      58 seconds ago       RUN /bin/sh -c pip install -r requirements.t…   35.5MB    buildkit.dockerfile.v0
<missing>      About a minute ago   COPY requirements.txt . # buildkit              58B       buildkit.dockerfile.v0
<missing>      About a minute ago   WORKDIR /app
...

Attention aux tailles. Seules les commandes RUN, COPYet ADDajoutent de la taille à l'image. Vous pouvez réduire la taille de l'image en combinant des commandes dans la mesure du possible. Par exemple:

RUN apt-get update
RUN apt-get install -y netcat

Peut être combiné en une seule RUNcommande :

RUN apt-get update && apt-get install -y netcat

Ainsi, on crée un seul calque au lieu de deux, ce qui réduit la taille de l'image finale.

Bien que ce soit une bonne idée de réduire le nombre de calques, il est beaucoup plus important que cela soit moins un objectif en soi et plus un effet secondaire de la réduction de la taille de l'image et des temps de construction. En d'autres termes, concentrez-vous davantage sur les trois pratiques précédentes - les constructions en plusieurs étapes, l'ordre de vos commandes Dockerfile et l'utilisation d'une petite image de base - plutôt que d'essayer d'optimiser chaque commande.

Remarques:

  1. RUN, COPY, et ADDcréent chacun des calques.
  2. Chaque couche contient les différences par rapport à la couche précédente.
  3. Les calques augmentent la taille de l'image finale.

Des astuces:

  1. Combinez les commandes associées.
  2. Supprimez les fichiers inutiles dans le même RUN stepqui les a créés.
  3. Réduisez le nombre d' apt-get upgradeexécutions car il met à niveau tous les packages vers la dernière version.
  4. Avec les builds en plusieurs étapes, ne vous inquiétez pas trop de l'optimisation excessive des commandes dans les étapes temporaires.

Enfin, pour plus de lisibilité, c'est une bonne idée de trier les arguments multi-lignes par ordre alphanumérique :

RUN apt-get update && apt-get install -y \
    git \
    gcc \
    matplotlib \
    pillow  \
    && rm -rf /var/lib/apt/lists/*

Utiliser des conteneurs non privilégiés

Par défaut, Docker exécute les processus de conteneur en tant que racine à l'intérieur d'un conteneur. Cependant, il s'agit d'une mauvaise pratique car un processus exécuté en tant que root à l'intérieur du conteneur s'exécute en tant que root dans l'hôte Docker. Ainsi, si un attaquant accède à votre conteneur, il a accès à tous les privilèges root et peut effectuer plusieurs attaques contre l'hôte Docker, comme-

  1. copier des informations sensibles du système de fichiers de l'hôte vers le conteneur
  2. exécuter des commandes à distance

Pour éviter cela, assurez-vous d'exécuter les processus de conteneur avec un utilisateur non root :

RUN addgroup --system app && adduser --system --group app

USER app

Vous pouvez aller plus loin et supprimer l'accès au shell et vous assurer qu'il n'y a pas non plus de répertoire personnel :

RUN addgroup --gid 1001 --system app && \
    adduser --no-create-home --shell /bin/false --disabled-password --uid 1001 --system --group app

USER app

Vérifier:

$ docker run -i sample id

uid=1001(app) gid=1001(app) groups=1001(app)

Ici, l'application dans le conteneur s'exécute sous un utilisateur non root. Cependant, gardez à l'esprit que le démon Docker et le conteneur lui-même fonctionnent toujours avec les privilèges root. Assurez-vous de consulter Exécuter le démon Docker en tant qu'utilisateur non root pour obtenir de l'aide sur l'exécution du démon et des conteneurs en tant qu'utilisateur non root.

Préférez COPIER à AJOUTER

À utiliser COPYsauf si vous êtes certain d'avoir besoin des fonctionnalités supplémentaires fournies avec ADD.

Quelle est la différence entre COPYet ADD?

Les deux commandes vous permettent de copier des fichiers d'un emplacement spécifique dans une image Docker :

ADD <src> <dest>
COPY <src> <dest>

Bien qu'ils semblent servir le même objectif, ils ADDont quelques fonctionnalités supplémentaires :

  • COPYest utilisé pour copier des fichiers ou des répertoires locaux de l'hôte Docker vers l'image.
  • ADDpeut être utilisé pour la même chose ainsi que pour télécharger des fichiers externes. De plus, si vous utilisez un fichier compressé (tar, gzip, bzip2, etc.) comme <src>paramètre, ADDdécompressera automatiquement le contenu à l'emplacement donné.
# copy local files on the host  to the destination
COPY /source/path  /destination/path
ADD /source/path  /destination/path

# download external file and copy to the destination
ADD http://external.file/url  /destination/path

# copy and extract local compresses files
ADD source.file.tar.gz /destination/path

Mettre en cache les packages Python sur l'hôte Docker

Lorsqu'un fichier requirements est modifié, l'image doit être reconstruite pour installer les nouveaux packages. Les étapes précédentes seront mises en cache, comme indiqué dans Réduire le nombre de couches . Le téléchargement de tous les packages lors de la reconstruction de l'image peut entraîner une activité réseau importante et prendre beaucoup de temps. Chaque reconstruction prend le même temps pour télécharger des packages communs à travers les versions.

Vous pouvez éviter cela en mappant le répertoire du cache pip à un répertoire sur la machine hôte. Ainsi, pour chaque reconstruction, les versions mises en cache persistent et peuvent améliorer la vitesse de construction.

Ajoutez un volume au docker exécuté en tant que -v $HOME/.cache/pip-docker/:/root/.cache/pipou en tant que mappage dans le fichier Docker Compose.

Le répertoire présenté ci-dessus est uniquement à titre de référence. Assurez-vous de mapper le répertoire de cache et non les packages de site (où résident les packages construits).

Le déplacement du cache de l'image Docker vers l'hôte peut vous faire gagner de l'espace dans l'image finale.

Si vous utilisez Docker BuildKit , utilisez les montages de cache BuildKit pour gérer le cache :

# syntax = docker/dockerfile:1.2

...

COPY requirements.txt .

RUN --mount=type=cache,target=/root/.cache/pip \
        pip install -r requirements.txt

...

Exécutez un seul processus par conteneur

Pourquoi est-il recommandé de n'exécuter qu'un seul processus par conteneur ?

Supposons que votre pile d'applications se compose de deux serveurs Web et d'une base de données. Bien que vous puissiez facilement exécuter les trois à partir d'un seul conteneur, vous devez exécuter chacun dans un conteneur séparé pour faciliter la réutilisation et la mise à l'échelle de chacun des services individuels.

  1. Mise à l' échelle - Avec chaque service dans un conteneur séparé, vous pouvez mettre à l'échelle l'un de vos serveurs Web horizontalement selon vos besoins pour gérer plus de trafic.
  2. Réutilisabilité - Peut-être avez-vous un autre service qui a besoin d'une base de données conteneurisée. Vous pouvez simplement réutiliser le même conteneur de base de données sans apporter deux services inutiles avec lui.
  3. Journalisation - Le couplage des conteneurs rend la journalisation beaucoup plus complexe. Nous aborderons cela plus en détail plus loin dans cet article.
  4. Portabilité et prévisibilité - Il est beaucoup plus facile de créer des correctifs de sécurité ou de déboguer un problème lorsqu'il y a moins de surface avec laquelle travailler.

Préférer la syntaxe de tableau à la chaîne

Vous pouvez écrire les commandes CMDet ENTRYPOINTdans vos fichiers Docker dans les formats tableau (exec) ou chaîne (shell) :

# array (exec)
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "main:app"]

# string (shell)
CMD "gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app"

Les deux sont corrects et réalisent presque la même chose ; cependant, vous devez utiliser le format exec chaque fois que possible. De la documentation Docker :

  1. Assurez-vous que vous utilisez le formulaire exec de CMDet ENTRYPOINTdans votre Dockerfile.
  2. Par exemple, n'utilisez ["program", "arg1", "arg2"]pas "program arg1 arg2". L'utilisation de la forme de chaîne oblige Docker à exécuter votre processus à l'aide de bash, qui ne gère pas correctement les signaux. Compose utilise toujours le formulaire JSON, donc ne vous inquiétez pas si vous remplacez la commande ou le point d'entrée dans votre fichier Compose.

Ainsi, puisque la plupart des shells ne traitent pas les signaux vers les processus enfants, si vous utilisez le format shell, CTRL-C(qui génère un SIGTERM) peut ne pas arrêter un processus enfant.

Exemple:

FROM ubuntu:18.04

# BAD: shell format
ENTRYPOINT top -d

# GOOD: exec format
ENTRYPOINT ["top", "-d"]

Essayez les deux. Notez qu'avec la saveur du format shell, CTRL-Ccela ne tuera pas le processus. Au lieu de cela, vous verrez ^C^C^C^C^C^C^C^C^C^C^C.

Une autre mise en garde est que le format shell porte le PID du shell, pas le processus lui-même.

# array format
root@18d8fd3fd4d2:/app# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 python manage.py runserver 0.0.0.0:8000
    7 ?        Sl     0:02 /usr/local/bin/python manage.py runserver 0.0.0.0:8000
   25 pts/0    Ss     0:00 bash
  356 pts/0    R+     0:00 ps ax


# string format
root@ede24a5ef536:/app# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /bin/sh -c python manage.py runserver 0.0.0.0:8000
    8 ?        S      0:00 python manage.py runserver 0.0.0.0:8000
    9 ?        Sl     0:01 /usr/local/bin/python manage.py runserver 0.0.0.0:8000
   13 pts/0    Ss     0:00 bash
  342 pts/0    R+     0:00 ps ax

Comprendre la différence entre ENTRYPOINT et CMD

Dois-je utiliser ENTRYPOINT ou CMD pour exécuter des processus de conteneur ?

Il existe deux manières d'exécuter des commandes dans un conteneur :

CMD ["gunicorn", "config.wsgi", "-b", "0.0.0.0:8000"]

# and

ENTRYPOINT ["gunicorn", "config.wsgi", "-b", "0.0.0.0:8000"]

Les deux font essentiellement la même chose : démarrez l'application sur config.wsgiavec un serveur Gunicorn et liez-la à 0.0.0.0:8000.

Le CMDest facilement contournable. Si vous exécutez docker run <image_name> uvicorn config.asgi, le CMD ci-dessus est remplacé par les nouveaux arguments - par exemple, uvicorn config.asgi. Alors que pour remplacer la ENTRYPOINTcommande, il faut spécifier l' --entrypointoption :

docker run --entrypoint uvicorn config.asgi <image_name>

Ici, il est clair que nous remplaçons le point d'entrée. Il est donc recommandé d'utiliser ENTRYPOINTover CMDpour éviter de remplacer accidentellement la commande.

Ils peuvent également être utilisés ensemble.

Par exemple:

ENTRYPOINT ["gunicorn", "config.wsgi", "-w"]
CMD ["4"]

Lorsqu'ils sont utilisés ensemble comme ceci, la commande qui est exécutée pour démarrer le conteneur est :

gunicorn config.wsgi -w 4

Comme indiqué ci-dessus, CMDest facilement contournable. Ainsi, CMDpeut être utilisé pour passer des arguments à la ENTRYPOINTcommande. Le nombre de travailleurs peut être facilement modifié comme suit :

docker run <image_name> 6

Cela démarrera le conteneur avec six travailleurs Gunicorn plutôt que quatre.

Inclure une instruction HEALTHCHECK

Utilisez a HEALTHCHECKpour déterminer si le processus en cours d'exécution dans le conteneur est non seulement opérationnel, mais également "sain".

Docker expose une API pour vérifier l'état du processus en cours d'exécution dans le conteneur, qui fournit bien plus d'informations que simplement si le processus est "en cours d'exécution" ou non puisque "en cours d'exécution" couvre "il est opérationnel", "en cours de lancement", et même "coincé dans un état d'erreur de boucle infinie". Vous pouvez interagir avec cette API via l' instruction HEALTHCHECK .

Par exemple, si vous diffusez une application Web, vous pouvez utiliser les éléments suivants pour déterminer si le point de /terminaison est opérationnel et peut gérer les requêtes :

HEALTHCHECK CMD curl --fail http://localhost:8000 || exit 1

Si vous exécutez docker ps, vous pouvez voir l'état du HEALTHCHECK.

Exemple sain :

CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS                            PORTS                                       NAMES
09c2eb4970d4   healthcheck   "python manage.py ru…"   10 seconds ago   Up 8 seconds (health: starting)   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   xenodochial_clarke

Exemple malsain :

CONTAINER ID   IMAGE         COMMAND                  CREATED              STATUS                          PORTS                                       NAMES
09c2eb4970d4   healthcheck   "python manage.py ru…"   About a minute ago   Up About a minute (unhealthy)   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   xenodochial_clarke

Vous pouvez aller plus loin et configurer un point de terminaison personnalisé utilisé uniquement pour les vérifications de l'état, puis configurer le HEALTHCHECKpour tester les données renvoyées. Par exemple, si le point de terminaison renvoie une réponse JSON de {"ping": "pong"}, vous pouvez demander au HEALTHCHECKde valider le corps de la réponse.

Voici comment afficher l'état de la vérification de l'état à l'aide dedocker inspect :

❯ docker inspect --format "{{json .State.Health }}" ab94f2ac7889
{
  "Status": "healthy",
  "FailingStreak": 0,
  "Log": [
    {
      "Start": "2021-09-28T15:22:57.5764644Z",
      "End": "2021-09-28T15:22:57.7825527Z",
      "ExitCode": 0,
      "Output": "..."

Ici, la sortie est coupée car elle contient toute la sortie HTML.

Vous pouvez également ajouter une vérification d'état à un fichier Docker Compose :

version: "3.8"

services:
  web:
    build: .
    ports:
      - '8000:8000'
    healthcheck:
      test: curl --fail http://localhost:8000 || exit 1
      interval: 10s
      timeout: 10s
      start_period: 10s
      retries: 3

Option :

  • test: La commande à tester.
  • interval: L'intervalle à tester -- c'est-à-dire, testez chaque xunité de temps.
  • timeout: Temps maximum d'attente de la réponse.
  • start_period: Quand commencer le bilan de santé. Il peut être utilisé lorsque des tâches supplémentaires sont effectuées avant que les conteneurs ne soient prêts, comme l'exécution de migrations.
  • retries: nombre maximal de tentatives avant de désigner un test comme failed.

Si vous utilisez un outil d'orchestration autre que Docker Swarm, c'est-à-dire Kubernetes ou AWS ECS, il est fort probable que l'outil dispose de son propre système interne pour gérer les vérifications de l'état. Reportez-vous à la documentation de l'outil particulier avant d'ajouter l' HEALTHCHECKinstruction.

Images

Version des images Docker

Dans la mesure du possible, évitez d'utiliser la latestbalise.

Si vous vous fiez à la latestbalise (qui n'est pas vraiment une "balise" puisqu'elle est appliquée par défaut lorsqu'une image n'est pas explicitement balisée), vous ne pouvez pas savoir quelle version de votre code s'exécute en fonction de la balise d'image. Cela complique les restaurations et facilite leur écrasement (accidentellement ou par malveillance). Les balises, comme votre infrastructure et vos déploiements, doivent être immuables .

Quelle que soit la façon dont vous traitez vos images internes, vous ne devez jamais utiliser la latestbalise pour les images de base, car vous pourriez déployer par inadvertance une nouvelle version avec des modifications radicales en production.

Pour les images internes, utilisez des balises descriptives pour identifier plus facilement la version du code en cours d'exécution, gérer les restaurations et éviter les collisions de noms.

Par exemple, vous pouvez utiliser les descripteurs suivants pour créer une balise :

  1. Horodatages
  2. ID d'image Docker
  3. Git commit hachages
  4. Version sémantique

Pour plus d'options, consultez cette réponse de la question Stack Overflow "Version correcte des images Docker".

Par exemple:

docker build -t web-prod-a072c4e5d94b5a769225f621f08af3d4bf820a07-0.1.4 .

Ici, nous avons utilisé ce qui suit pour former la balise :

  1. Nom du projet:web
  2. Nom de l'environnement :prod
  3. Hachage de validation Git :a072c4e5d94b5a769225f621f08af3d4bf820a07
  4. Version sémantique :0.1.4

Il est essentiel de choisir un schéma de marquage et d'être cohérent avec celui-ci. Étant donné que les hachages de commit permettent de lier rapidement une balise d'image au code, il est fortement recommandé de les inclure dans votre schéma de balisage.

Ne stockez pas de secrets dans les images

Les secrets sont des informations sensibles telles que les mots de passe, les informations d'identification de la base de données, les clés SSH, les jetons et les certificats TLS, pour n'en nommer que quelques-uns. Ceux-ci ne doivent pas être intégrés à vos images sans être cryptés, car les utilisateurs non autorisés qui accèdent à l'image peuvent simplement examiner les couches pour en extraire les secrets.

N'ajoutez pas de secrets à vos Dockerfiles en clair, surtout si vous poussez les images vers un registre public comme Docker Hub :

FROM python:3.9-slim

ENV DATABASE_PASSWORD "SuperSecretSauce"

Au lieu de cela, ils doivent être injectés via :

  1. Variables d'environnement (au moment de l'exécution)
  2. Arguments au moment de la construction (au moment de la construction)
  3. Un outil d'orchestration comme Docker Swarm (via les secrets Docker) ou Kubernetes (via les secrets Kubernetes)

En outre, vous pouvez aider à empêcher les fuites de secrets en ajoutant des fichiers et des dossiers secrets communs à votre fichier .dockerignore :

**/.env
**/.aws
**/.ssh

Enfin, soyez explicite sur les fichiers qui sont copiés sur l'image plutôt que de copier tous les fichiers de manière récursive :

# BAD
COPY . .

# GOOD
copy ./app.py .

Être explicite aide également à limiter le contournement du cache.

Variables d'environnement

Vous pouvez transmettre des secrets via des variables d'environnement, mais ils seront visibles dans tous les processus enfants, conteneurs liés et journaux, ainsi que via docker inspect. Il est également difficile de les mettre à jour.

$ docker run --detach --env "DATABASE_PASSWORD=SuperSecretSauce" python:3.9-slim

d92cf5cf870eb0fdbf03c666e7fcf18f9664314b79ad58bc7618ea3445e39239


$ docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' d92cf5cf870eb0fdbf03c666e7fcf18f9664314b79ad58bc7618ea3445e39239

DATABASE_PASSWORD=SuperSecretSauce
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
PYTHON_VERSION=3.9.7
PYTHON_PIP_VERSION=21.2.4
PYTHON_SETUPTOOLS_VERSION=57.5.0
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/c20b0cfd643cd4a19246ccf204e2997af70f6b21/public/get-pip.py
PYTHON_GET_PIP_SHA256=fa6f3fb93cce234cd4e8dd2beb54a51ab9c247653b52855a48dd44e6b21ff28b

Il s'agit de l'approche la plus simple de la gestion des secrets. Bien que ce ne soit pas le plus sûr, il gardera les gens honnêtes honnêtes car il fournit une fine couche de protection, aidant à garder les secrets cachés des yeux curieux et errants.

Transmettre des secrets en utilisant un volume partagé est une meilleure solution, mais ils doivent être chiffrés, via Vault ou AWS Key Management Service (KMS), car ils sont enregistrés sur disque.

Arguments de construction

Vous pouvez transmettre des secrets au moment de la construction en utilisant des arguments de construction , mais ils seront visibles pour ceux qui ont accès à l'image via docker history.

Exemple:

FROM python:3.9-slim

ARG DATABASE_PASSWORD

Construire:

$ docker build --build-arg "DATABASE_PASSWORD=SuperSecretSauce" .

Si vous n'avez besoin d'utiliser les secrets que temporairement dans le cadre de la construction, c'est-à-dire des clés SSH pour cloner un référentiel privé ou télécharger un package privé, vous devez utiliser une construction en plusieurs étapes, car l'historique du générateur est ignoré pour les étapes temporaires :

# temp stage
FROM python:3.9-slim as builder

# secret
ARG SSH_PRIVATE_KEY

# install git
RUN apt-get update && \
    apt-get install -y --no-install-recommends git

# use ssh key to clone repo
RUN mkdir -p /root/.ssh/ && \
    echo "${PRIVATE_SSH_KEY}" > /root/.ssh/id_rsa
RUN touch /root/.ssh/known_hosts &&
    ssh-keyscan bitbucket.org >> /root/.ssh/known_hosts
RUN git clone git@github.com:testdrivenio/not-real.git


# final stage
FROM python:3.9-slim

WORKDIR /app

# copy the repository from the temp image
COPY --from=builder /your-repo /app/your-repo

# use the repo for something!

La construction en plusieurs étapes ne conserve l'historique que pour l'image finale. Gardez à l'esprit que vous pouvez utiliser cette fonctionnalité pour les secrets permanents dont vous avez besoin pour votre application, comme un identifiant de base de données.

Vous pouvez également utiliser la nouvelle --secretoption dans Docker build pour transmettre des secrets aux images Docker qui ne sont pas stockées dans les images.

# "docker_is_awesome" > secrets.txt

FROM alpine

# shows secret from default secret location:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

Cela montera le secret à partir du secrets.txtfichier.

Construisez l'image :

docker build --no-cache --progress=plain --secret id=mysecret,src=secrets.txt .

# output
...
#4 [1/2] FROM docker.io/library/alpine
#4 sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7
#4 CACHED

#5 [2/2] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#5 sha256:75601a522ebe80ada66dedd9dd86772ca932d30d7e1b11bba94c04aa55c237de
#5 0.635 docker_is_awesome#5 DONE 0.7s

#6 exporting to image

Enfin, vérifiez l'historique pour voir si le secret fuit :

❯ docker history 49574a19241c
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
49574a19241c   5 minutes ago   CMD ["/bin/sh"]                                 0B        buildkit.dockerfile.v0
<missing>      5 minutes ago   RUN /bin/sh -c cat /run/secrets/mysecret # b…   0B        buildkit.dockerfile.v0
<missing>      4 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      4 weeks ago     /bin/sh -c #(nop) ADD file:aad4290d27580cc1a…   5.6MB

Pour en savoir plus sur les secrets de construction, consultez Ne divulguez pas les secrets de construction de votre image Docker .

Les secrets de Docker

Si vous utilisez Docker Swarm , vous pouvez gérer les secrets avec Docker secrets .

Par exemple, initialisez le mode Docker Swarm :

$ docker swarm init

Créez un secret Docker :

$ echo "supersecretpassword" | docker secret create postgres_password -
qdqmbpizeef0lfhyttxqfbty0

$ docker secret ls
ID                          NAME                DRIVER    CREATED         UPDATED
qdqmbpizeef0lfhyttxqfbty0   postgres_password             4 seconds ago   4 seconds ago

Lorsqu'un conteneur a accès au secret ci-dessus, il se monte sur /run/secrets/postgres_password. Ce fichier contiendra la valeur réelle du secret en clair.

Vous utilisez un autre outil d'orhestration ?

  1. AWS EKS - Utilisation des secrets AWS Secrets Manager avec Kubernetes
  2. DigitalOcean Kubernetes - Étapes recommandées pour sécuriser un cluster DigitalOcean Kubernetes
  3. Google Kubernetes Engine – Utilisation de Secret Manager avec d'autres produits
  4. Nomad - Intégration Vault et récupération des secrets dynamiques

Utiliser un fichier .dockerignore

Nous avons déjà mentionné plusieurs fois l'utilisation d'un fichier .dockerignore . Ce fichier est utilisé pour spécifier les fichiers et dossiers que vous ne souhaitez pas ajouter au contexte de construction initial envoyé au démon Docker, qui construira ensuite votre image. En d'autres termes, vous pouvez l'utiliser pour définir le contexte de génération dont vous avez besoin.

Lorsqu'une image Docker est construite, l'intégralité du contexte Docker, c'est-à-dire la racine de votre projet, est envoyée au démon Docker avant que les commandes COPYou ADDne soient évaluées. Cela peut être assez coûteux, surtout si vous avez de nombreuses dépendances, des fichiers de données volumineux ou des artefacts de construction dans votre projet. De plus, la CLI Docker et le démon peuvent ne pas être sur la même machine. Ainsi, si le démon est exécuté sur une machine distante, vous devez être encore plus attentif à la taille du contexte de construction.

Que devez-vous ajouter au fichier .dockerignore ?

  1. Fichiers et dossiers temporaires
  2. Construire des journaux
  3. Secrets locaux
  4. Fichiers de développement locaux comme docker-compose.yml
  5. Dossiers de contrôle de version tels que ".git", ".hg" et ".svn"

Exemple:

**/.git
**/.gitignore
**/.vscode
**/coverage
**/.env
**/.aws
**/.ssh
Dockerfile
README.md
docker-compose.yml
**/.DS_Store
**/venv
**/env

En résumé, un .dockerignore correctement structuré peut aider :

  1. Diminuer la taille de l'image Docker
  2. Accélérez le processus de construction
  3. Empêcher l'invalidation inutile du cache
  4. Empêcher les fuites de secrets

Lint et scanne vos Dockerfiles et images

Le peluchage est le processus de vérification de votre code source pour les erreurs programmatiques et stylistiques et les mauvaises pratiques qui pourraient conduire à des défauts potentiels. Tout comme avec les langages de programmation, les fichiers statiques peuvent également être pelucheux. Avec vos Dockerfiles en particulier, les linters peuvent vous aider à vous assurer qu'ils sont maintenables, à éviter une syntaxe obsolète et à respecter les meilleures pratiques. Le peluchage de vos images devrait faire partie intégrante de vos pipelines CI.

Hadolint est le linter Dockerfile le plus populaire :

$ hadolint Dockerfile

Dockerfile:1 DL3006 warning: Always tag the version of an image explicitly
Dockerfile:7 DL3042 warning: Avoid the use of cache directory with pip. Use `pip install --no-cache-dir <package>`
Dockerfile:9 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.
Dockerfile:17 DL3025 warning: Use arguments JSON notation for CMD and ENTRYPOINT arguments

Vous pouvez le voir en action en ligne sur https://hadolint.github.io/hadolint/ . Il existe également une extension de code VS.

Vous pouvez coupler vos Dockerfiles avec l'analyse d'images et de conteneurs pour les vulnérabilités.

Quelques options :

  1. Snyk est le fournisseur exclusif d'analyse de vulnérabilité native pour Docker. Vous pouvez utiliser la docker scancommande CLI pour numériser des images.
  2. Trivy peut être utilisé pour analyser des images de conteneurs, des systèmes de fichiers, des référentiels git et d'autres fichiers de configuration.
  3. Clair est un projet open source utilisé pour l'analyse statique des vulnérabilités dans les conteneurs d'applications.
  4. Anchore est un projet open source qui fournit un service centralisé pour l'inspection, l'analyse et la certification des images de conteneurs.

En résumé, peluchez et analysez vos Dockerfiles et images pour faire apparaître tout problème potentiel qui s'écarte des meilleures pratiques.

Signer et vérifier les images

Comment savez-vous que les images utilisées pour exécuter votre code de production n'ont pas été altérées ?

La falsification peut passer par le fil via des attaques de l' homme du milieu (MITM) ou du fait que le registre est complètement compromis.

Docker Content Trust (DCT) permet la signature et la vérification des images Docker à partir de registres distants.

Pour vérifier l'intégrité et l'authenticité d'une image, définissez la variable d'environnement suivante :

DOCKER_CONTENT_TRUST=1

Maintenant, si vous essayez d'extraire une image qui n'a pas été signée, vous recevrez l'erreur suivante :

Error: remote trust data does not exist for docker.io/namespace/unsigned-image:
notary.docker.io does not have trust data for docker.io/namespace/unsigned-image

Vous pouvez en savoir plus sur la signature d'images dans la documentation Signature d'images avec Docker Content Trust .

Lorsque vous téléchargez des images depuis Docker Hub, assurez-vous d'utiliser des images officielles ou des images vérifiées provenant de sources fiables. Les équipes plus importantes devraient envisager d'utiliser leur propre registre de conteneurs privé interne .

Conseils bonus

Utilisation des environnements virtuels Python

Devriez-vous utiliser un environnement virtuel à l'intérieur d'un conteneur ?

Dans la plupart des cas, les environnements virtuels sont inutiles tant que vous vous en tenez à n'exécuter qu'un seul processus par conteneur. Étant donné que le conteneur lui-même fournit une isolation, les packages peuvent être installés à l'échelle du système. Cela dit, vous souhaiterez peut-être utiliser un environnement virtuel dans une construction en plusieurs étapes plutôt que de créer des fichiers de roue.

Exemple avec roues :

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt


# final stage
FROM python:3.9-slim

WORKDIR /app

COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

RUN pip install --no-cache /wheels/*

Exemple avec virtualenv :

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

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


# final stage
FROM python:3.9-slim

COPY --from=builder /opt/venv /opt/venv

WORKDIR /app

ENV PATH="/opt/venv/bin:$PATH"

Définir les limites de la mémoire et du processeur

It's a good idea to limit the memory usage of your Docker containers, especially if you're running multiple containers on a single machine. This can prevent any of the containers from using all available memory and thereby crippling the rest.

The easiest way to limit memory usage is to use --memory and --cpu options in the Docker cli:

$ docker run --cpus=2 -m 512m nginx

The above command limits the container usage to 2 CPUs and 512 megabytes of main memory.

You can do the same in a Docker Compose file like so:

version: "3.9"
services:
  redis:
    image: redis:alpine
    deploy:
      resources:
        limits:
          cpus: 2
          memory: 512M
        reservations:
          cpus: 1
          memory: 256M

Take note of the reservations field. It's used to set a soft limit, which takes priority when the host machine has low memory or CPU resources.

Additional resources:

  1. Runtime options with Memory, CPUs, and GPUs
  2. Docker Compose resouce constraints

Log to stdout or stderr

Les applications exécutées dans vos conteneurs Docker doivent écrire des messages de journal sur la sortie standard (stdout) et l'erreur standard (stderr) plutôt que sur un fichier.

Vous pouvez ensuite configurer le démon Docker pour envoyer vos messages de journal à une solution de journalisation centralisée (comme CloudWatch Logs ou Papertrail ).

Pour en savoir plus, consultez Traiter les journaux comme des flux d'événements à partir de l'application Twelve-Factor et Configurer les pilotes de journalisation à partir de la documentation Docker.

Utiliser une monture de mémoire partagée pour Gunicorn Heartbeat

Gunicorn utilise un système de pulsation basé sur des fichiers pour s'assurer que tous les processus de travail forkés sont actifs.

Dans la plupart des cas, les fichiers heartbeat se trouvent dans "/tmp", qui est souvent en mémoire via tmpfs . Étant donné que Docker n'utilise pas tmpfs par défaut, les fichiers seront stockés sur un système de fichiers sauvegardé sur disque. Cela peut causer des problèmes , comme des gels aléatoires puisque le système de pulsation utilise os.fchmod, ce qui peut bloquer un travailleur si le répertoire se trouve en fait sur un système de fichiers sauvegardé sur disque.

Heureusement, il existe une solution simple : remplacez le répertoire heartbeat par un répertoire mappé en mémoire via le --worker-tmp-dirdrapeau.

gunicorn --worker-tmp-dir /dev/shm config.wsgi -b 0.0.0.0:8000

Conclusion

Cet article a examiné plusieurs bonnes pratiques pour rendre vos Dockerfiles et images plus propres, plus légers et plus sécurisés.

Source :  https://testdrive.io

#python #docker 

What is GEEK

Buddha Community

Quelques Bonnes Pratiques Docker Pour Les Développeurs Python
Shardul Bhatt

Shardul Bhatt

1626775355

Why use Python for Software Development

No programming language is pretty much as diverse as Python. It enables building cutting edge applications effortlessly. Developers are as yet investigating the full capability of end-to-end Python development services in various areas. 

By areas, we mean FinTech, HealthTech, InsureTech, Cybersecurity, and that's just the beginning. These are New Economy areas, and Python has the ability to serve every one of them. The vast majority of them require massive computational abilities. Python's code is dynamic and powerful - equipped for taking care of the heavy traffic and substantial algorithmic capacities. 

Programming advancement is multidimensional today. Endeavor programming requires an intelligent application with AI and ML capacities. Shopper based applications require information examination to convey a superior client experience. Netflix, Trello, and Amazon are genuine instances of such applications. Python assists with building them effortlessly. 

5 Reasons to Utilize Python for Programming Web Apps 

Python can do such numerous things that developers can't discover enough reasons to admire it. Python application development isn't restricted to web and enterprise applications. It is exceptionally adaptable and superb for a wide range of uses.

Robust frameworks 

Python is known for its tools and frameworks. There's a structure for everything. Django is helpful for building web applications, venture applications, logical applications, and mathematical processing. Flask is another web improvement framework with no conditions. 

Web2Py, CherryPy, and Falcon offer incredible capabilities to customize Python development services. A large portion of them are open-source frameworks that allow quick turn of events. 

Simple to read and compose 

Python has an improved sentence structure - one that is like the English language. New engineers for Python can undoubtedly understand where they stand in the development process. The simplicity of composing allows quick application building. 

The motivation behind building Python, as said by its maker Guido Van Rossum, was to empower even beginner engineers to comprehend the programming language. The simple coding likewise permits developers to roll out speedy improvements without getting confused by pointless subtleties. 

Utilized by the best 

Alright - Python isn't simply one more programming language. It should have something, which is the reason the business giants use it. Furthermore, that too for different purposes. Developers at Google use Python to assemble framework organization systems, parallel information pusher, code audit, testing and QA, and substantially more. Netflix utilizes Python web development services for its recommendation algorithm and media player. 

Massive community support 

Python has a steadily developing community that offers enormous help. From amateurs to specialists, there's everybody. There are a lot of instructional exercises, documentation, and guides accessible for Python web development solutions. 

Today, numerous universities start with Python, adding to the quantity of individuals in the community. Frequently, Python designers team up on various tasks and help each other with algorithmic, utilitarian, and application critical thinking. 

Progressive applications 

Python is the greatest supporter of data science, Machine Learning, and Artificial Intelligence at any enterprise software development company. Its utilization cases in cutting edge applications are the most compelling motivation for its prosperity. Python is the second most well known tool after R for data analytics.

The simplicity of getting sorted out, overseeing, and visualizing information through unique libraries makes it ideal for data based applications. TensorFlow for neural networks and OpenCV for computer vision are two of Python's most well known use cases for Machine learning applications.

Summary

Thinking about the advances in programming and innovation, Python is a YES for an assorted scope of utilizations. Game development, web application development services, GUI advancement, ML and AI improvement, Enterprise and customer applications - every one of them uses Python to its full potential. 

The disadvantages of Python web improvement arrangements are regularly disregarded by developers and organizations because of the advantages it gives. They focus on quality over speed and performance over blunders. That is the reason it's a good idea to utilize Python for building the applications of the future.

#python development services #python development company #python app development #python development #python in web development #python software development

Jarrod  Douglas

Jarrod Douglas

1660532820

Quelques Bonnes Pratiques Docker Pour Les Développeurs Python

Cet article examine quelques bonnes pratiques à suivre lors de l'écriture de Dockerfiles et de l'utilisation de Docker en général. Alors que la plupart des pratiques répertoriées s'appliquent à tous les développeurs, quel que soit le langage, quelques-unes s'appliquent uniquement à ceux qui développent des applications basées sur Python.

Fichiers Docker

Utiliser des builds en plusieurs étapes

Tirez parti des versions en plusieurs étapes pour créer des images Docker plus légères et plus sécurisées.

Les builds Docker en plusieurs étapes vous permettent de diviser vos Dockerfiles en plusieurs étapes. Par exemple, vous pouvez avoir une étape pour compiler et créer votre application, qui peut ensuite être copiée dans les étapes suivantes. Étant donné que seule la dernière étape est utilisée pour créer l'image, les dépendances et les outils associés à la création de votre application sont ignorés, laissant une image légère et modulaire prête pour la production.

Exemple de développement Web :

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt


# final stage
FROM python:3.9-slim

WORKDIR /app

COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

RUN pip install --no-cache /wheels/*

Dans cet exemple, le compilateur GCC est requis pour l'installation de certains packages Python. Nous avons donc ajouté une étape temporaire de construction pour gérer la phase de construction. Étant donné que l'image d'exécution finale ne contient pas de GCC, elle est beaucoup plus légère et plus sécurisée.

Comparaison de taille :

REPOSITORY                 TAG                    IMAGE ID       CREATED          SIZE
docker-single              latest                 8d6b6a4d7fb6   16 seconds ago   259MB
docker-multi               latest                 813c2fa9b114   3 minutes ago    156MB

Exemple de science des données :

# temp stage
FROM python:3.9 as builder

RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels jupyter pandas


# final stage
FROM python:3.9-slim

WORKDIR /notebooks

COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/*

Comparaison de taille :

REPOSITORY                  TAG                   IMAGE ID       CREATED         SIZE
ds-multi                    latest                b4195deac742   2 minutes ago   357MB
ds-single                   latest                7c23c43aeda6   6 minutes ago   969MB

En résumé, les builds en plusieurs étapes peuvent réduire la taille de vos images de production, ce qui vous permet d'économiser du temps et de l'argent. De plus, cela simplifiera vos conteneurs de production. De plus, en raison de la petite taille et de la simplicité, il y a potentiellement une surface d'attaque plus petite.

Commandez les commandes Dockerfile de manière appropriée

Portez une attention particulière à l'ordre de vos commandes Dockerfile pour tirer parti de la mise en cache des couches.

Docker met en cache chaque étape (ou couche) dans un Dockerfile particulier pour accélérer les générations suivantes. Lorsqu'une étape change, le cache est invalidé non seulement pour cette étape particulière, mais pour toutes les étapes suivantes.

Exemple:

FROM python:3.9-slim

WORKDIR /app

COPY sample.py .

COPY requirements.txt .

RUN pip install -r /requirements.txt

Dans ce Dockerfile, nous avons copié le code de l'application avant d'installer les exigences. Maintenant, chaque fois que nous modifions sample.py , la construction réinstallera les packages. Ceci est très inefficace, en particulier lorsque vous utilisez un conteneur Docker comme environnement de développement. Par conséquent, il est crucial de conserver les fichiers qui changent fréquemment vers la fin du Dockerfile.

Vous pouvez également empêcher les invalidations de cache indésirables en utilisant un fichier .dockerignore pour exclure l'ajout de fichiers inutiles au contexte de construction Docker et à l'image finale. Plus d'informations ici sous peu.

Ainsi, dans le Dockerfile ci-dessus, vous devez déplacer la COPY sample.py .commande vers le bas :

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r /requirements.txt

COPY sample.py .

Remarques:

  1. Placez toujours les couches susceptibles de changer aussi bas que possible dans le Dockerfile.
  2. Combiner RUN apt-get updateet RUN apt-get installcommandes. (Cela permet également de réduire la taille de l'image. Nous y reviendrons sous peu.)
  3. Si vous souhaitez désactiver la mise en cache pour une version Docker particulière, ajoutez l' --no-cache=Trueindicateur.

Utiliser de petites images de base Docker

Les images Docker plus petites sont plus modulaires et sécurisées.

Construire, pousser et extraire des images est plus rapide avec des images plus petites. Ils ont également tendance à être plus sécurisés car ils n'incluent que les bibliothèques et les dépendances système nécessaires à l'exécution de votre application.

Quelle image de base Docker devez-vous utiliser ?

Malheureusement, cela dépend.

Voici une comparaison de taille de différentes images de base Docker pour Python :

REPOSITORY   TAG                 IMAGE ID       CREATED      SIZE
python       3.9.6-alpine3.14    f773016f760e   3 days ago   45.1MB
python       3.9.6-slim          907fc13ca8e7   3 days ago   115MB
python       3.9.6-slim-buster   907fc13ca8e7   3 days ago   115MB
python       3.9.6               cba42c28d9b8   3 days ago   886MB
python       3.9.6-buster        cba42c28d9b8   3 days ago   886MB

Bien que la version Alpine, basée sur Alpine Linux , soit la plus petite, elle peut souvent entraîner une augmentation des temps de construction si vous ne trouvez pas de binaires compilés qui fonctionnent avec. Par conséquent, vous devrez peut-être construire vous-même les fichiers binaires, ce qui peut augmenter la taille de l'image (en fonction des dépendances requises au niveau du système) et les temps de construction (en raison de la nécessité de compiler à partir de la source).

Reportez-vous à La meilleure image de base Docker pour votre application Python et L'utilisation d'Alpine peut ralentir les constructions de Python Docker 50× pour en savoir plus sur les raisons pour lesquelles il est préférable d'éviter d'utiliser des images de base basées sur Alpine.

Au final, tout est une question d'équilibre. En cas de doute, commencez par une *-slimsaveur, en particulier en mode développement, pendant que vous construisez votre application. Vous souhaitez éviter d'avoir à mettre à jour en permanence le Dockerfile pour installer les dépendances nécessaires au niveau du système lorsque vous ajoutez un nouveau package Python. Au fur et à mesure que vous renforcez votre application et vos Dockerfile(s) pour la production, vous souhaiterez peut-être explorer l'utilisation d'Alpine pour l'image finale à partir d'une construction en plusieurs étapes.

N'oubliez pas non plus de mettre à jour régulièrement vos images de base pour améliorer la sécurité et augmenter les performances. Lorsqu'une nouvelle version d'une image de base est publiée - c'est-à-dire 3.9.6-slim-> 3.9.7-slim- vous devez extraire la nouvelle image et mettre à jour vos conteneurs en cours d'exécution pour obtenir tous les derniers correctifs de sécurité.

Minimiser le nombre de couches

C'est une bonne idée de combiner autant que possible les commandes , et , car elles créent des RUNcalques COPY. ADDChaque calque augmente la taille de l'image car ils sont mis en cache. Par conséquent, à mesure que le nombre de couches augmente, la taille augmente également.

Vous pouvez tester cela avec la docker historycommande :

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
dockerfile   latest    180f98132d02   51 seconds ago   259MB

$ docker history 180f98132d02

IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
180f98132d02   58 seconds ago       COPY . . # buildkit                             6.71kB    buildkit.dockerfile.v0
<missing>      58 seconds ago       RUN /bin/sh -c pip install -r requirements.t…   35.5MB    buildkit.dockerfile.v0
<missing>      About a minute ago   COPY requirements.txt . # buildkit              58B       buildkit.dockerfile.v0
<missing>      About a minute ago   WORKDIR /app
...

Attention aux tailles. Seules les commandes RUN, COPYet ADDajoutent de la taille à l'image. Vous pouvez réduire la taille de l'image en combinant des commandes dans la mesure du possible. Par exemple:

RUN apt-get update
RUN apt-get install -y netcat

Peut être combiné en une seule RUNcommande :

RUN apt-get update && apt-get install -y netcat

Ainsi, on crée un seul calque au lieu de deux, ce qui réduit la taille de l'image finale.

Bien que ce soit une bonne idée de réduire le nombre de calques, il est beaucoup plus important que cela soit moins un objectif en soi et plus un effet secondaire de la réduction de la taille de l'image et des temps de construction. En d'autres termes, concentrez-vous davantage sur les trois pratiques précédentes - les constructions en plusieurs étapes, l'ordre de vos commandes Dockerfile et l'utilisation d'une petite image de base - plutôt que d'essayer d'optimiser chaque commande.

Remarques:

  1. RUN, COPY, et ADDcréent chacun des calques.
  2. Chaque couche contient les différences par rapport à la couche précédente.
  3. Les calques augmentent la taille de l'image finale.

Des astuces:

  1. Combinez les commandes associées.
  2. Supprimez les fichiers inutiles dans le même RUN stepqui les a créés.
  3. Réduisez le nombre d' apt-get upgradeexécutions car il met à niveau tous les packages vers la dernière version.
  4. Avec les builds en plusieurs étapes, ne vous inquiétez pas trop de l'optimisation excessive des commandes dans les étapes temporaires.

Enfin, pour plus de lisibilité, c'est une bonne idée de trier les arguments multi-lignes par ordre alphanumérique :

RUN apt-get update && apt-get install -y \
    git \
    gcc \
    matplotlib \
    pillow  \
    && rm -rf /var/lib/apt/lists/*

Utiliser des conteneurs non privilégiés

Par défaut, Docker exécute les processus de conteneur en tant que racine à l'intérieur d'un conteneur. Cependant, il s'agit d'une mauvaise pratique car un processus exécuté en tant que root à l'intérieur du conteneur s'exécute en tant que root dans l'hôte Docker. Ainsi, si un attaquant accède à votre conteneur, il a accès à tous les privilèges root et peut effectuer plusieurs attaques contre l'hôte Docker, comme-

  1. copier des informations sensibles du système de fichiers de l'hôte vers le conteneur
  2. exécuter des commandes à distance

Pour éviter cela, assurez-vous d'exécuter les processus de conteneur avec un utilisateur non root :

RUN addgroup --system app && adduser --system --group app

USER app

Vous pouvez aller plus loin et supprimer l'accès au shell et vous assurer qu'il n'y a pas non plus de répertoire personnel :

RUN addgroup --gid 1001 --system app && \
    adduser --no-create-home --shell /bin/false --disabled-password --uid 1001 --system --group app

USER app

Vérifier:

$ docker run -i sample id

uid=1001(app) gid=1001(app) groups=1001(app)

Ici, l'application dans le conteneur s'exécute sous un utilisateur non root. Cependant, gardez à l'esprit que le démon Docker et le conteneur lui-même fonctionnent toujours avec les privilèges root. Assurez-vous de consulter Exécuter le démon Docker en tant qu'utilisateur non root pour obtenir de l'aide sur l'exécution du démon et des conteneurs en tant qu'utilisateur non root.

Préférez COPIER à AJOUTER

À utiliser COPYsauf si vous êtes certain d'avoir besoin des fonctionnalités supplémentaires fournies avec ADD.

Quelle est la différence entre COPYet ADD?

Les deux commandes vous permettent de copier des fichiers d'un emplacement spécifique dans une image Docker :

ADD <src> <dest>
COPY <src> <dest>

Bien qu'ils semblent servir le même objectif, ils ADDont quelques fonctionnalités supplémentaires :

  • COPYest utilisé pour copier des fichiers ou des répertoires locaux de l'hôte Docker vers l'image.
  • ADDpeut être utilisé pour la même chose ainsi que pour télécharger des fichiers externes. De plus, si vous utilisez un fichier compressé (tar, gzip, bzip2, etc.) comme <src>paramètre, ADDdécompressera automatiquement le contenu à l'emplacement donné.
# copy local files on the host  to the destination
COPY /source/path  /destination/path
ADD /source/path  /destination/path

# download external file and copy to the destination
ADD http://external.file/url  /destination/path

# copy and extract local compresses files
ADD source.file.tar.gz /destination/path

Mettre en cache les packages Python sur l'hôte Docker

Lorsqu'un fichier requirements est modifié, l'image doit être reconstruite pour installer les nouveaux packages. Les étapes précédentes seront mises en cache, comme indiqué dans Réduire le nombre de couches . Le téléchargement de tous les packages lors de la reconstruction de l'image peut entraîner une activité réseau importante et prendre beaucoup de temps. Chaque reconstruction prend le même temps pour télécharger des packages communs à travers les versions.

Vous pouvez éviter cela en mappant le répertoire du cache pip à un répertoire sur la machine hôte. Ainsi, pour chaque reconstruction, les versions mises en cache persistent et peuvent améliorer la vitesse de construction.

Ajoutez un volume au docker exécuté en tant que -v $HOME/.cache/pip-docker/:/root/.cache/pipou en tant que mappage dans le fichier Docker Compose.

Le répertoire présenté ci-dessus est uniquement à titre de référence. Assurez-vous de mapper le répertoire de cache et non les packages de site (où résident les packages construits).

Le déplacement du cache de l'image Docker vers l'hôte peut vous faire gagner de l'espace dans l'image finale.

Si vous utilisez Docker BuildKit , utilisez les montages de cache BuildKit pour gérer le cache :

# syntax = docker/dockerfile:1.2

...

COPY requirements.txt .

RUN --mount=type=cache,target=/root/.cache/pip \
        pip install -r requirements.txt

...

Exécutez un seul processus par conteneur

Pourquoi est-il recommandé de n'exécuter qu'un seul processus par conteneur ?

Supposons que votre pile d'applications se compose de deux serveurs Web et d'une base de données. Bien que vous puissiez facilement exécuter les trois à partir d'un seul conteneur, vous devez exécuter chacun dans un conteneur séparé pour faciliter la réutilisation et la mise à l'échelle de chacun des services individuels.

  1. Mise à l' échelle - Avec chaque service dans un conteneur séparé, vous pouvez mettre à l'échelle l'un de vos serveurs Web horizontalement selon vos besoins pour gérer plus de trafic.
  2. Réutilisabilité - Peut-être avez-vous un autre service qui a besoin d'une base de données conteneurisée. Vous pouvez simplement réutiliser le même conteneur de base de données sans apporter deux services inutiles avec lui.
  3. Journalisation - Le couplage des conteneurs rend la journalisation beaucoup plus complexe. Nous aborderons cela plus en détail plus loin dans cet article.
  4. Portabilité et prévisibilité - Il est beaucoup plus facile de créer des correctifs de sécurité ou de déboguer un problème lorsqu'il y a moins de surface avec laquelle travailler.

Préférer la syntaxe de tableau à la chaîne

Vous pouvez écrire les commandes CMDet ENTRYPOINTdans vos fichiers Docker dans les formats tableau (exec) ou chaîne (shell) :

# array (exec)
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "main:app"]

# string (shell)
CMD "gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app"

Les deux sont corrects et réalisent presque la même chose ; cependant, vous devez utiliser le format exec chaque fois que possible. De la documentation Docker :

  1. Assurez-vous que vous utilisez le formulaire exec de CMDet ENTRYPOINTdans votre Dockerfile.
  2. Par exemple, n'utilisez ["program", "arg1", "arg2"]pas "program arg1 arg2". L'utilisation de la forme de chaîne oblige Docker à exécuter votre processus à l'aide de bash, qui ne gère pas correctement les signaux. Compose utilise toujours le formulaire JSON, donc ne vous inquiétez pas si vous remplacez la commande ou le point d'entrée dans votre fichier Compose.

Ainsi, puisque la plupart des shells ne traitent pas les signaux vers les processus enfants, si vous utilisez le format shell, CTRL-C(qui génère un SIGTERM) peut ne pas arrêter un processus enfant.

Exemple:

FROM ubuntu:18.04

# BAD: shell format
ENTRYPOINT top -d

# GOOD: exec format
ENTRYPOINT ["top", "-d"]

Essayez les deux. Notez qu'avec la saveur du format shell, CTRL-Ccela ne tuera pas le processus. Au lieu de cela, vous verrez ^C^C^C^C^C^C^C^C^C^C^C.

Une autre mise en garde est que le format shell porte le PID du shell, pas le processus lui-même.

# array format
root@18d8fd3fd4d2:/app# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 python manage.py runserver 0.0.0.0:8000
    7 ?        Sl     0:02 /usr/local/bin/python manage.py runserver 0.0.0.0:8000
   25 pts/0    Ss     0:00 bash
  356 pts/0    R+     0:00 ps ax


# string format
root@ede24a5ef536:/app# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /bin/sh -c python manage.py runserver 0.0.0.0:8000
    8 ?        S      0:00 python manage.py runserver 0.0.0.0:8000
    9 ?        Sl     0:01 /usr/local/bin/python manage.py runserver 0.0.0.0:8000
   13 pts/0    Ss     0:00 bash
  342 pts/0    R+     0:00 ps ax

Comprendre la différence entre ENTRYPOINT et CMD

Dois-je utiliser ENTRYPOINT ou CMD pour exécuter des processus de conteneur ?

Il existe deux manières d'exécuter des commandes dans un conteneur :

CMD ["gunicorn", "config.wsgi", "-b", "0.0.0.0:8000"]

# and

ENTRYPOINT ["gunicorn", "config.wsgi", "-b", "0.0.0.0:8000"]

Les deux font essentiellement la même chose : démarrez l'application sur config.wsgiavec un serveur Gunicorn et liez-la à 0.0.0.0:8000.

Le CMDest facilement contournable. Si vous exécutez docker run <image_name> uvicorn config.asgi, le CMD ci-dessus est remplacé par les nouveaux arguments - par exemple, uvicorn config.asgi. Alors que pour remplacer la ENTRYPOINTcommande, il faut spécifier l' --entrypointoption :

docker run --entrypoint uvicorn config.asgi <image_name>

Ici, il est clair que nous remplaçons le point d'entrée. Il est donc recommandé d'utiliser ENTRYPOINTover CMDpour éviter de remplacer accidentellement la commande.

Ils peuvent également être utilisés ensemble.

Par exemple:

ENTRYPOINT ["gunicorn", "config.wsgi", "-w"]
CMD ["4"]

Lorsqu'ils sont utilisés ensemble comme ceci, la commande qui est exécutée pour démarrer le conteneur est :

gunicorn config.wsgi -w 4

Comme indiqué ci-dessus, CMDest facilement contournable. Ainsi, CMDpeut être utilisé pour passer des arguments à la ENTRYPOINTcommande. Le nombre de travailleurs peut être facilement modifié comme suit :

docker run <image_name> 6

Cela démarrera le conteneur avec six travailleurs Gunicorn plutôt que quatre.

Inclure une instruction HEALTHCHECK

Utilisez a HEALTHCHECKpour déterminer si le processus en cours d'exécution dans le conteneur est non seulement opérationnel, mais également "sain".

Docker expose une API pour vérifier l'état du processus en cours d'exécution dans le conteneur, qui fournit bien plus d'informations que simplement si le processus est "en cours d'exécution" ou non puisque "en cours d'exécution" couvre "il est opérationnel", "en cours de lancement", et même "coincé dans un état d'erreur de boucle infinie". Vous pouvez interagir avec cette API via l' instruction HEALTHCHECK .

Par exemple, si vous diffusez une application Web, vous pouvez utiliser les éléments suivants pour déterminer si le point de /terminaison est opérationnel et peut gérer les requêtes :

HEALTHCHECK CMD curl --fail http://localhost:8000 || exit 1

Si vous exécutez docker ps, vous pouvez voir l'état du HEALTHCHECK.

Exemple sain :

CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS                            PORTS                                       NAMES
09c2eb4970d4   healthcheck   "python manage.py ru…"   10 seconds ago   Up 8 seconds (health: starting)   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   xenodochial_clarke

Exemple malsain :

CONTAINER ID   IMAGE         COMMAND                  CREATED              STATUS                          PORTS                                       NAMES
09c2eb4970d4   healthcheck   "python manage.py ru…"   About a minute ago   Up About a minute (unhealthy)   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   xenodochial_clarke

Vous pouvez aller plus loin et configurer un point de terminaison personnalisé utilisé uniquement pour les vérifications de l'état, puis configurer le HEALTHCHECKpour tester les données renvoyées. Par exemple, si le point de terminaison renvoie une réponse JSON de {"ping": "pong"}, vous pouvez demander au HEALTHCHECKde valider le corps de la réponse.

Voici comment afficher l'état de la vérification de l'état à l'aide dedocker inspect :

❯ docker inspect --format "{{json .State.Health }}" ab94f2ac7889
{
  "Status": "healthy",
  "FailingStreak": 0,
  "Log": [
    {
      "Start": "2021-09-28T15:22:57.5764644Z",
      "End": "2021-09-28T15:22:57.7825527Z",
      "ExitCode": 0,
      "Output": "..."

Ici, la sortie est coupée car elle contient toute la sortie HTML.

Vous pouvez également ajouter une vérification d'état à un fichier Docker Compose :

version: "3.8"

services:
  web:
    build: .
    ports:
      - '8000:8000'
    healthcheck:
      test: curl --fail http://localhost:8000 || exit 1
      interval: 10s
      timeout: 10s
      start_period: 10s
      retries: 3

Option :

  • test: La commande à tester.
  • interval: L'intervalle à tester -- c'est-à-dire, testez chaque xunité de temps.
  • timeout: Temps maximum d'attente de la réponse.
  • start_period: Quand commencer le bilan de santé. Il peut être utilisé lorsque des tâches supplémentaires sont effectuées avant que les conteneurs ne soient prêts, comme l'exécution de migrations.
  • retries: nombre maximal de tentatives avant de désigner un test comme failed.

Si vous utilisez un outil d'orchestration autre que Docker Swarm, c'est-à-dire Kubernetes ou AWS ECS, il est fort probable que l'outil dispose de son propre système interne pour gérer les vérifications de l'état. Reportez-vous à la documentation de l'outil particulier avant d'ajouter l' HEALTHCHECKinstruction.

Images

Version des images Docker

Dans la mesure du possible, évitez d'utiliser la latestbalise.

Si vous vous fiez à la latestbalise (qui n'est pas vraiment une "balise" puisqu'elle est appliquée par défaut lorsqu'une image n'est pas explicitement balisée), vous ne pouvez pas savoir quelle version de votre code s'exécute en fonction de la balise d'image. Cela complique les restaurations et facilite leur écrasement (accidentellement ou par malveillance). Les balises, comme votre infrastructure et vos déploiements, doivent être immuables .

Quelle que soit la façon dont vous traitez vos images internes, vous ne devez jamais utiliser la latestbalise pour les images de base, car vous pourriez déployer par inadvertance une nouvelle version avec des modifications radicales en production.

Pour les images internes, utilisez des balises descriptives pour identifier plus facilement la version du code en cours d'exécution, gérer les restaurations et éviter les collisions de noms.

Par exemple, vous pouvez utiliser les descripteurs suivants pour créer une balise :

  1. Horodatages
  2. ID d'image Docker
  3. Git commit hachages
  4. Version sémantique

Pour plus d'options, consultez cette réponse de la question Stack Overflow "Version correcte des images Docker".

Par exemple:

docker build -t web-prod-a072c4e5d94b5a769225f621f08af3d4bf820a07-0.1.4 .

Ici, nous avons utilisé ce qui suit pour former la balise :

  1. Nom du projet:web
  2. Nom de l'environnement :prod
  3. Hachage de validation Git :a072c4e5d94b5a769225f621f08af3d4bf820a07
  4. Version sémantique :0.1.4

Il est essentiel de choisir un schéma de marquage et d'être cohérent avec celui-ci. Étant donné que les hachages de commit permettent de lier rapidement une balise d'image au code, il est fortement recommandé de les inclure dans votre schéma de balisage.

Ne stockez pas de secrets dans les images

Les secrets sont des informations sensibles telles que les mots de passe, les informations d'identification de la base de données, les clés SSH, les jetons et les certificats TLS, pour n'en nommer que quelques-uns. Ceux-ci ne doivent pas être intégrés à vos images sans être cryptés, car les utilisateurs non autorisés qui accèdent à l'image peuvent simplement examiner les couches pour en extraire les secrets.

N'ajoutez pas de secrets à vos Dockerfiles en clair, surtout si vous poussez les images vers un registre public comme Docker Hub :

FROM python:3.9-slim

ENV DATABASE_PASSWORD "SuperSecretSauce"

Au lieu de cela, ils doivent être injectés via :

  1. Variables d'environnement (au moment de l'exécution)
  2. Arguments au moment de la construction (au moment de la construction)
  3. Un outil d'orchestration comme Docker Swarm (via les secrets Docker) ou Kubernetes (via les secrets Kubernetes)

En outre, vous pouvez aider à empêcher les fuites de secrets en ajoutant des fichiers et des dossiers secrets communs à votre fichier .dockerignore :

**/.env
**/.aws
**/.ssh

Enfin, soyez explicite sur les fichiers qui sont copiés sur l'image plutôt que de copier tous les fichiers de manière récursive :

# BAD
COPY . .

# GOOD
copy ./app.py .

Être explicite aide également à limiter le contournement du cache.

Variables d'environnement

Vous pouvez transmettre des secrets via des variables d'environnement, mais ils seront visibles dans tous les processus enfants, conteneurs liés et journaux, ainsi que via docker inspect. Il est également difficile de les mettre à jour.

$ docker run --detach --env "DATABASE_PASSWORD=SuperSecretSauce" python:3.9-slim

d92cf5cf870eb0fdbf03c666e7fcf18f9664314b79ad58bc7618ea3445e39239


$ docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' d92cf5cf870eb0fdbf03c666e7fcf18f9664314b79ad58bc7618ea3445e39239

DATABASE_PASSWORD=SuperSecretSauce
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
PYTHON_VERSION=3.9.7
PYTHON_PIP_VERSION=21.2.4
PYTHON_SETUPTOOLS_VERSION=57.5.0
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/c20b0cfd643cd4a19246ccf204e2997af70f6b21/public/get-pip.py
PYTHON_GET_PIP_SHA256=fa6f3fb93cce234cd4e8dd2beb54a51ab9c247653b52855a48dd44e6b21ff28b

Il s'agit de l'approche la plus simple de la gestion des secrets. Bien que ce ne soit pas le plus sûr, il gardera les gens honnêtes honnêtes car il fournit une fine couche de protection, aidant à garder les secrets cachés des yeux curieux et errants.

Transmettre des secrets en utilisant un volume partagé est une meilleure solution, mais ils doivent être chiffrés, via Vault ou AWS Key Management Service (KMS), car ils sont enregistrés sur disque.

Arguments de construction

Vous pouvez transmettre des secrets au moment de la construction en utilisant des arguments de construction , mais ils seront visibles pour ceux qui ont accès à l'image via docker history.

Exemple:

FROM python:3.9-slim

ARG DATABASE_PASSWORD

Construire:

$ docker build --build-arg "DATABASE_PASSWORD=SuperSecretSauce" .

Si vous n'avez besoin d'utiliser les secrets que temporairement dans le cadre de la construction, c'est-à-dire des clés SSH pour cloner un référentiel privé ou télécharger un package privé, vous devez utiliser une construction en plusieurs étapes, car l'historique du générateur est ignoré pour les étapes temporaires :

# temp stage
FROM python:3.9-slim as builder

# secret
ARG SSH_PRIVATE_KEY

# install git
RUN apt-get update && \
    apt-get install -y --no-install-recommends git

# use ssh key to clone repo
RUN mkdir -p /root/.ssh/ && \
    echo "${PRIVATE_SSH_KEY}" > /root/.ssh/id_rsa
RUN touch /root/.ssh/known_hosts &&
    ssh-keyscan bitbucket.org >> /root/.ssh/known_hosts
RUN git clone git@github.com:testdrivenio/not-real.git


# final stage
FROM python:3.9-slim

WORKDIR /app

# copy the repository from the temp image
COPY --from=builder /your-repo /app/your-repo

# use the repo for something!

La construction en plusieurs étapes ne conserve l'historique que pour l'image finale. Gardez à l'esprit que vous pouvez utiliser cette fonctionnalité pour les secrets permanents dont vous avez besoin pour votre application, comme un identifiant de base de données.

Vous pouvez également utiliser la nouvelle --secretoption dans Docker build pour transmettre des secrets aux images Docker qui ne sont pas stockées dans les images.

# "docker_is_awesome" > secrets.txt

FROM alpine

# shows secret from default secret location:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

Cela montera le secret à partir du secrets.txtfichier.

Construisez l'image :

docker build --no-cache --progress=plain --secret id=mysecret,src=secrets.txt .

# output
...
#4 [1/2] FROM docker.io/library/alpine
#4 sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7
#4 CACHED

#5 [2/2] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#5 sha256:75601a522ebe80ada66dedd9dd86772ca932d30d7e1b11bba94c04aa55c237de
#5 0.635 docker_is_awesome#5 DONE 0.7s

#6 exporting to image

Enfin, vérifiez l'historique pour voir si le secret fuit :

❯ docker history 49574a19241c
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
49574a19241c   5 minutes ago   CMD ["/bin/sh"]                                 0B        buildkit.dockerfile.v0
<missing>      5 minutes ago   RUN /bin/sh -c cat /run/secrets/mysecret # b…   0B        buildkit.dockerfile.v0
<missing>      4 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      4 weeks ago     /bin/sh -c #(nop) ADD file:aad4290d27580cc1a…   5.6MB

Pour en savoir plus sur les secrets de construction, consultez Ne divulguez pas les secrets de construction de votre image Docker .

Les secrets de Docker

Si vous utilisez Docker Swarm , vous pouvez gérer les secrets avec Docker secrets .

Par exemple, initialisez le mode Docker Swarm :

$ docker swarm init

Créez un secret Docker :

$ echo "supersecretpassword" | docker secret create postgres_password -
qdqmbpizeef0lfhyttxqfbty0

$ docker secret ls
ID                          NAME                DRIVER    CREATED         UPDATED
qdqmbpizeef0lfhyttxqfbty0   postgres_password             4 seconds ago   4 seconds ago

Lorsqu'un conteneur a accès au secret ci-dessus, il se monte sur /run/secrets/postgres_password. Ce fichier contiendra la valeur réelle du secret en clair.

Vous utilisez un autre outil d'orhestration ?

  1. AWS EKS - Utilisation des secrets AWS Secrets Manager avec Kubernetes
  2. DigitalOcean Kubernetes - Étapes recommandées pour sécuriser un cluster DigitalOcean Kubernetes
  3. Google Kubernetes Engine – Utilisation de Secret Manager avec d'autres produits
  4. Nomad - Intégration Vault et récupération des secrets dynamiques

Utiliser un fichier .dockerignore

Nous avons déjà mentionné plusieurs fois l'utilisation d'un fichier .dockerignore . Ce fichier est utilisé pour spécifier les fichiers et dossiers que vous ne souhaitez pas ajouter au contexte de construction initial envoyé au démon Docker, qui construira ensuite votre image. En d'autres termes, vous pouvez l'utiliser pour définir le contexte de génération dont vous avez besoin.

Lorsqu'une image Docker est construite, l'intégralité du contexte Docker, c'est-à-dire la racine de votre projet, est envoyée au démon Docker avant que les commandes COPYou ADDne soient évaluées. Cela peut être assez coûteux, surtout si vous avez de nombreuses dépendances, des fichiers de données volumineux ou des artefacts de construction dans votre projet. De plus, la CLI Docker et le démon peuvent ne pas être sur la même machine. Ainsi, si le démon est exécuté sur une machine distante, vous devez être encore plus attentif à la taille du contexte de construction.

Que devez-vous ajouter au fichier .dockerignore ?

  1. Fichiers et dossiers temporaires
  2. Construire des journaux
  3. Secrets locaux
  4. Fichiers de développement locaux comme docker-compose.yml
  5. Dossiers de contrôle de version tels que ".git", ".hg" et ".svn"

Exemple:

**/.git
**/.gitignore
**/.vscode
**/coverage
**/.env
**/.aws
**/.ssh
Dockerfile
README.md
docker-compose.yml
**/.DS_Store
**/venv
**/env

En résumé, un .dockerignore correctement structuré peut aider :

  1. Diminuer la taille de l'image Docker
  2. Accélérez le processus de construction
  3. Empêcher l'invalidation inutile du cache
  4. Empêcher les fuites de secrets

Lint et scanne vos Dockerfiles et images

Le peluchage est le processus de vérification de votre code source pour les erreurs programmatiques et stylistiques et les mauvaises pratiques qui pourraient conduire à des défauts potentiels. Tout comme avec les langages de programmation, les fichiers statiques peuvent également être pelucheux. Avec vos Dockerfiles en particulier, les linters peuvent vous aider à vous assurer qu'ils sont maintenables, à éviter une syntaxe obsolète et à respecter les meilleures pratiques. Le peluchage de vos images devrait faire partie intégrante de vos pipelines CI.

Hadolint est le linter Dockerfile le plus populaire :

$ hadolint Dockerfile

Dockerfile:1 DL3006 warning: Always tag the version of an image explicitly
Dockerfile:7 DL3042 warning: Avoid the use of cache directory with pip. Use `pip install --no-cache-dir <package>`
Dockerfile:9 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.
Dockerfile:17 DL3025 warning: Use arguments JSON notation for CMD and ENTRYPOINT arguments

Vous pouvez le voir en action en ligne sur https://hadolint.github.io/hadolint/ . Il existe également une extension de code VS.

Vous pouvez coupler vos Dockerfiles avec l'analyse d'images et de conteneurs pour les vulnérabilités.

Quelques options :

  1. Snyk est le fournisseur exclusif d'analyse de vulnérabilité native pour Docker. Vous pouvez utiliser la docker scancommande CLI pour numériser des images.
  2. Trivy peut être utilisé pour analyser des images de conteneurs, des systèmes de fichiers, des référentiels git et d'autres fichiers de configuration.
  3. Clair est un projet open source utilisé pour l'analyse statique des vulnérabilités dans les conteneurs d'applications.
  4. Anchore est un projet open source qui fournit un service centralisé pour l'inspection, l'analyse et la certification des images de conteneurs.

En résumé, peluchez et analysez vos Dockerfiles et images pour faire apparaître tout problème potentiel qui s'écarte des meilleures pratiques.

Signer et vérifier les images

Comment savez-vous que les images utilisées pour exécuter votre code de production n'ont pas été altérées ?

La falsification peut passer par le fil via des attaques de l' homme du milieu (MITM) ou du fait que le registre est complètement compromis.

Docker Content Trust (DCT) permet la signature et la vérification des images Docker à partir de registres distants.

Pour vérifier l'intégrité et l'authenticité d'une image, définissez la variable d'environnement suivante :

DOCKER_CONTENT_TRUST=1

Maintenant, si vous essayez d'extraire une image qui n'a pas été signée, vous recevrez l'erreur suivante :

Error: remote trust data does not exist for docker.io/namespace/unsigned-image:
notary.docker.io does not have trust data for docker.io/namespace/unsigned-image

Vous pouvez en savoir plus sur la signature d'images dans la documentation Signature d'images avec Docker Content Trust .

Lorsque vous téléchargez des images depuis Docker Hub, assurez-vous d'utiliser des images officielles ou des images vérifiées provenant de sources fiables. Les équipes plus importantes devraient envisager d'utiliser leur propre registre de conteneurs privé interne .

Conseils bonus

Utilisation des environnements virtuels Python

Devriez-vous utiliser un environnement virtuel à l'intérieur d'un conteneur ?

Dans la plupart des cas, les environnements virtuels sont inutiles tant que vous vous en tenez à n'exécuter qu'un seul processus par conteneur. Étant donné que le conteneur lui-même fournit une isolation, les packages peuvent être installés à l'échelle du système. Cela dit, vous souhaiterez peut-être utiliser un environnement virtuel dans une construction en plusieurs étapes plutôt que de créer des fichiers de roue.

Exemple avec roues :

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt


# final stage
FROM python:3.9-slim

WORKDIR /app

COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

RUN pip install --no-cache /wheels/*

Exemple avec virtualenv :

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

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


# final stage
FROM python:3.9-slim

COPY --from=builder /opt/venv /opt/venv

WORKDIR /app

ENV PATH="/opt/venv/bin:$PATH"

Définir les limites de la mémoire et du processeur

It's a good idea to limit the memory usage of your Docker containers, especially if you're running multiple containers on a single machine. This can prevent any of the containers from using all available memory and thereby crippling the rest.

The easiest way to limit memory usage is to use --memory and --cpu options in the Docker cli:

$ docker run --cpus=2 -m 512m nginx

The above command limits the container usage to 2 CPUs and 512 megabytes of main memory.

You can do the same in a Docker Compose file like so:

version: "3.9"
services:
  redis:
    image: redis:alpine
    deploy:
      resources:
        limits:
          cpus: 2
          memory: 512M
        reservations:
          cpus: 1
          memory: 256M

Take note of the reservations field. It's used to set a soft limit, which takes priority when the host machine has low memory or CPU resources.

Additional resources:

  1. Runtime options with Memory, CPUs, and GPUs
  2. Docker Compose resouce constraints

Log to stdout or stderr

Les applications exécutées dans vos conteneurs Docker doivent écrire des messages de journal sur la sortie standard (stdout) et l'erreur standard (stderr) plutôt que sur un fichier.

Vous pouvez ensuite configurer le démon Docker pour envoyer vos messages de journal à une solution de journalisation centralisée (comme CloudWatch Logs ou Papertrail ).

Pour en savoir plus, consultez Traiter les journaux comme des flux d'événements à partir de l'application Twelve-Factor et Configurer les pilotes de journalisation à partir de la documentation Docker.

Utiliser une monture de mémoire partagée pour Gunicorn Heartbeat

Gunicorn utilise un système de pulsation basé sur des fichiers pour s'assurer que tous les processus de travail forkés sont actifs.

Dans la plupart des cas, les fichiers heartbeat se trouvent dans "/tmp", qui est souvent en mémoire via tmpfs . Étant donné que Docker n'utilise pas tmpfs par défaut, les fichiers seront stockés sur un système de fichiers sauvegardé sur disque. Cela peut causer des problèmes , comme des gels aléatoires puisque le système de pulsation utilise os.fchmod, ce qui peut bloquer un travailleur si le répertoire se trouve en fait sur un système de fichiers sauvegardé sur disque.

Heureusement, il existe une solution simple : remplacez le répertoire heartbeat par un répertoire mappé en mémoire via le --worker-tmp-dirdrapeau.

gunicorn --worker-tmp-dir /dev/shm config.wsgi -b 0.0.0.0:8000

Conclusion

Cet article a examiné plusieurs bonnes pratiques pour rendre vos Dockerfiles et images plus propres, plus légers et plus sécurisés.

Source :  https://testdrive.io

#python #docker 

Art  Lind

Art Lind

1602968400

Python Tricks Every Developer Should Know

Python is awesome, it’s one of the easiest languages with simple and intuitive syntax but wait, have you ever thought that there might ways to write your python code simpler?

In this tutorial, you’re going to learn a variety of Python tricks that you can use to write your Python code in a more readable and efficient way like a pro.

Let’s get started

Swapping value in Python

Instead of creating a temporary variable to hold the value of the one while swapping, you can do this instead

>>> FirstName = "kalebu"
>>> LastName = "Jordan"
>>> FirstName, LastName = LastName, FirstName 
>>> print(FirstName, LastName)
('Jordan', 'kalebu')

#python #python-programming #python3 #python-tutorials #learn-python #python-tips #python-skills #python-development

Art  Lind

Art Lind

1602666000

How to Remove all Duplicate Files on your Drive via Python

Today you’re going to learn how to use Python programming in a way that can ultimately save a lot of space on your drive by removing all the duplicates.

Intro

In many situations you may find yourself having duplicates files on your disk and but when it comes to tracking and checking them manually it can tedious.

Heres a solution

Instead of tracking throughout your disk to see if there is a duplicate, you can automate the process using coding, by writing a program to recursively track through the disk and remove all the found duplicates and that’s what this article is about.

But How do we do it?

If we were to read the whole file and then compare it to the rest of the files recursively through the given directory it will take a very long time, then how do we do it?

The answer is hashing, with hashing can generate a given string of letters and numbers which act as the identity of a given file and if we find any other file with the same identity we gonna delete it.

There’s a variety of hashing algorithms out there such as

  • md5
  • sha1
  • sha224, sha256, sha384 and sha512

#python-programming #python-tutorials #learn-python #python-project #python3 #python #python-skills #python-tips

How To Compare Tesla and Ford Company By Using Magic Methods in Python

Magic Methods are the special methods which gives us the ability to access built in syntactical features such as ‘<’, ‘>’, ‘==’, ‘+’ etc…

You must have worked with such methods without knowing them to be as magic methods. Magic methods can be identified with their names which start with __ and ends with __ like init, call, str etc. These methods are also called Dunder Methods, because of their name starting and ending with Double Underscore (Dunder).

Now there are a number of such special methods, which you might have come across too, in Python. We will just be taking an example of a few of them to understand how they work and how we can use them.

1. init

class AnyClass:
    def __init__():
        print("Init called on its own")
obj = AnyClass()

The first example is _init, _and as the name suggests, it is used for initializing objects. Init method is called on its own, ie. whenever an object is created for the class, the init method is called on its own.

The output of the above code will be given below. Note how we did not call the init method and it got invoked as we created an object for class AnyClass.

Init called on its own

2. add

Let’s move to some other example, add gives us the ability to access the built in syntax feature of the character +. Let’s see how,

class AnyClass:
    def __init__(self, var):
        self.some_var = var
    def __add__(self, other_obj):
        print("Calling the add method")
        return self.some_var + other_obj.some_var
obj1 = AnyClass(5)
obj2 = AnyClass(6)
obj1 + obj2

#python3 #python #python-programming #python-web-development #python-tutorials #python-top-story #python-tips #learn-python