Docker: faire les images les plus petites avec du NodeJS
Etape 1 : usage des "&&"
Il faut savoir que de base, Docker crée un "layer" pour chaque ligne de commande déclaré dans le fichier Dockerfile.
Prenons le cas suivant:
FROM ubuntu RUN apt-get update RUN apt-get install vim
Nous allons avoir 2 layers de créé: un pour chaque run. De plus, Docker, à chaque fois que vous lancer une commande de compilation, va regarder si les layers ont changé, si oui, il va en créé des nouveaux, sans effecer les anciens. Ce qui peut rapidement prendre de la place.
Et là, nous avons un exemple très simple, mais cela peut être critique quand vous avez de nombreuses lignes.
Vous pouvez remplacer l'exemple suivant par:
FROM ubuntu RUN apt-get update && apt-get install vim
Et si vous voulez le rendre lisible:
FROM ubuntu RUN apt-get update \ && apt-get install vim
Etape 2 : faisons du multi-stage
Imaginons que nous avons une application NodeJS (utilisant Express, ou autre). Nous allons avoir tendance à écrire un fichier Dockerfile comme suit:
FROM node:8 EXPOSE 3000 WORKDIR /app COPY package.json index.js ./ RUN npm install CMD ["npm", "start"]
Où nous exposons le port 3000, nous créons un répertoire de travail "app", nous copions les fichiers nécessaires, nous installons les dépendances NPM et nous démarrons le serveur.
Classique, mais pas optimisé, car nous allons avoir 5 layers.
Essayons l'approche dite "multi-stage", qui consiste à nommer les "FROM" et à les réutiliser:
FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM node:8 COPY --from=build /app / EXPOSE 3000 CMD ["npm", "start"]
Comme nous pouvons le voir, nous avons uns "stage" se nommant "build" qui va regrouper 3 commandes
Nous avons ensuite le stage le plus haut, qui ne devrait changer d'ailleurs, qui accède au stage précédant.
L'avantage de cela c'est que désormais, nous avons plus que trois layers: un pour le stage "build", un pour le "EXPOSE" et un pour le "CMD".
C'est d'autant plus avantageux que ces derniers layers ne vont pas bouger, alors que le layer du stage build lui se mettre à jour et seulement lui.
Etape 3 - version 1 : Distroless
Derrière ce nom barbare se cache un principe tout simple: ne conserver que ce qui est nécessaire pour que cela fonctionne.
Par exemple, quand nous lançons notre image Docker, notre serveur n'a besoin au fond que de NodeJS lui même, plus de NPM, ni d'apt-get, etc ...
Un projet de Google a été créé dans ce sens et qui s'appelle tout simplement Distroless: https://github.com/GoogleCloudPlatform/distroless
Ils nous ont fait des images Docker et nous allons l'utiliser dans l'exemple précédent.
FROM node:8 as build
WORKDIR /app
COPY package.json index.js ./
RUN npm install
FROM gcr.io/distroless/nodejs
COPY --from=build /app /
EXPOSE 3000
CMD ["npm", "start"]
Comme vous pouvez voir, la différence est minime: nous avons changé juste le deuxième "FROM"
Mais l'impact est énorme. Car nous pouvons passer d'une image Docker de 600 Mo à ... 80 Mo !!!!
Par contre, attention: nous ne pouvons plus "débugguer".
Ce que j'entends, c'est que nous ne pouvons plus faire ce genre de chose:
> docker exec -ti dockerID bash
Car bash sera enlevé. Donc nous ne pouvons plus regardé les logs, ou ce qui se passe sur l'instance Docker. La seule chose que nous pourrons faire sera de lancer une commande node.
Après, cela a un bon côté des choses: en production, personne ne pourra alors analyser le système et le modifié, ce qui renforce la sécurité.
Etape 3 - version 2 : Alpine Linux
Alpine Linux est une distribution permettant d'avoir une image très légère de linux ou en étant très sécurisé.
Du coup, nous allons modifier notre script pour utiliser une version Alpine de Node:
FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM node:8-alpine COPY --from=build /app / EXPOSE 3000 CMD ["npm", "start"]
Si nous analysons nos images, nous voyons ... que nous avons une image de 70 Mo ! Et en plus nous avons un shell, contrairement à la version distroless.
Le hic potentiel est que si nous utilisons des dépendances NPM qui ont d'être compilé via node-gyp, nous pouvons avoir des soucis, car Alpine Linux utilise "glibc" pour compiler.
Commentaires
Enregistrer un commentaire