Avant, j'utilisais souvent l'étiquette :latest
pour mes images de base dans les fichiers Docker, ce qui impliquait un versioning implicite.
Cette pratique semblait pratique pour s'assurer d'avoir la dernière version d'une image sans avoir à spécifier manuellement une version à chaque fois.
Cependant, cette approche présentait plusieurs inconvénients significatifs.
- Elle compromettait la reproductibilité des environnements de développement et de production. Deux builds successifs pouvaient aboutir à des environnements différents si une nouvelle version de l'image était publiée entre-temps.
- Ca introduisait une incohérence et une opacité dans le contrôle des versions utilisées, rendant difficile la détection et la correction des problèmes liés à des mises à jour incompatibles ou bug.
- Ca pouvait conduire à des problèmes de sécurité si la dernière version contenait des vulnérabilités non identifiées au moment du déploiement.
Maintenant, je spécifie explicitement le versioning de chaque image Docker en fixant une version spécifique, comme :12
, par exemple.
Cette approche a plusieurs impacts positifs:
- elle assure une reproductibilité cohérente des environnements en éliminant les variations inattendues dues à des mises à jour automatiques.
- ça facilite également le diagnostic et la résolution de problèmes en permettant à tous les membres de l'équipe de travailler avec des versions identiques des dépendances.
- ça améliore la sécurité en permettant une évaluation des vulnérabilités connues pour chaque version utilisée
L'utilisation d'outils comme Snyk ou Renovate-bot permet de simplifier la gestion des mises à jour en détectant automatique les nouvelles versions disponibles et propose des mises à jour via des MR/PR, facilitant le passage d'un versioning implicite à un versioning explicite sans coût supplémentaire.
Cela permet non seulement un meilleur contrôle sur ce qui est déployé en production mais aussi d'intégrer ces mises à jour de manière contrôlée et sécurisée.
Nous autorisions les appels HTTP des clients même s'ils ne précisaient pas de numéro de version. Par défaut la version était donc implicite, à savoir la dernière version publiée (latest).
Notre API refuse maintenant les appels d'API lorsque le numéro de version (MAJOR.MINOR) n'est pas précisé. Nos consommateurs doivent donc explicitement préciser la version. Soit via un header (Accepter: application/vnd.maboite.vMAJOR.MINOR+json), soit via le chemin (e.g. /vMAJOR.MINOR), soit via un query parameter (?v=MAJOR.MINOR).
Les dépendances du projet était en "*" (a-k-a "latest" a-k-a "utilise la dernière version publiée de cette dépendance"). D'une construction à l'autre, le projet pouvait ne plus être livrable.
La gestion des dépendances du projet est maintenant explicite, la version de chaque dépendance est explicitement spécifiée en vX.Y.Z. L'équipe utilise un fichier de lock par projet (c.f. javascript, rust, ruby, php) pour assurer la reproductibilité des constructions.
Pull d'une image de container (e.g. docker) sans préciser de tag. Cela revient à préciser le tag "latest", à savoir la dernière version publiée de l'image. Cet anti-pattern peut s'observer dans les fichiers docker-compose.yml, yaml Kubernetes ou encore pour la construction d'une image depuis un Dockerfile (FROM image).
L'équipe précise maintenant explicitement le tag de chaque image dont son projet dépend et n'emploie plus le tag "latest".
Configuration écrite en dur, cachée dans le code source du programme (e.g. identifiant d'accès à la base de données).
Configuration via des variables d'environnements requises au démarrage du programme
Voila ce qu'il se passe quand un standard (ASN.1 RFC2631) laisse passer l'implicite.
Et vous, quelles sont vos retours d'expérience concernant l'application de ce principe ?