Variables d’environnement et fichiers de configuration

The Twelve-Factor App est un ensemble de recommandations à l’usage des développeurs d’applications modernes. Elles supposent que ces applications sont délivrées sous la forme de services Web ou assimilés, qu’elles soient destinées à servir une application en ligne ou non.

Bien que je sois globalement en ligne avec ces recommandations, le troisième point, concernant la gestion de la configuration, m’interpelle. On entend par configuration « tout ce qui est susceptible de varier entre des déploiements (validation, production, environnement de développement, etc.) ».

Twelve Factor App commence par préconiser la séparation de la configuration du code. Par exemple, vous n’avez pas envie d’avoir à repackager et redéployer votre application lorsqu’un paramètre de connection à la base de données doit être modifié. De plus, il n’est pas de la responsabilité des développeurs de décider de ce genre de configuration. Il faut donc que l’application considère cette configuration comme un élément variable du système, dont la valeur est « injectée » d’une manière ou d’une autre lors de son déploiement.

Variables d’environnement

Twelve-Factor App recommande l’utilisation de variables d’environnement pour spécifier cette configuration: ces variables sont définies lors du déploiement, peuvent être facilement modifiées après coup sans avoir à modifier à l’application elle-même, et son de simples paires de clef-valeur de dimensions orthogonales, ce qui facilite leur gestion, y compris à grande échelle.

L’utilisation de variables d’environnement est d’ailleurs facilitée par plusieurs outils couramment utilisés dans le cadre du développement et du déploiement d’applications modernes. Docker permet de spécifier des variables d’environnement propres à un conteneur lors de l’instanciation d’une image. Ce cloisonnement autorise l’association de configurations différentes à différentes instances de la même application, et facilite donc un passage à l’échelle horizontal ainsi que l’exécution de différentes variantes de l’application au sein d’un même hôte.

Kubernetes permet d’intégrer la spécification des variables d’environnement dans la gestion du cycle de vie de l’application. Il est ainsi possible de faire en sorte qu’un pod se lance systématiquement avec un jeu prédéfini de variables d’environnement. Le système de template de Helm apporte davantage de souplesse à cette approche, par exemple en permettant de spécifier des valeurs différentes aux différentes variables selon l’environnement de déploiement.

Il est même possible d’utiliser une solution telle que Vault afin de dissocier la définition et le stockage de variables (potentiellement secrètes, comme le login et le mot de passe permettant de se connecter à un service) des scripts de déploiement. Lorsqu’on combine Kubernetes et Vault, les variables d’environnement sont définies et préservées soigneusement dans Vault. Kubernetes peut se connecter à Vault, récupérer les variables d’environnement propres au pod en cours de déploiement, et traiter ces variables comme des secrets (qui seront en fin de compte elles-mêmes visibles par l’application sous la forme de variables d’environnement).

Fichiers de configuration

Les applications se sont pendant longtemps basées sur des fichiers de configuration pour apporter une souplesse à leur exécution. Par exemple, les fichiers .ini ont été inventés en 1985 afin de permettre le paramétrage des logiciels sous Windows 1.0. La syntaxe et la sémantique de ces fichiers, très simples, ont facilité leur adoption par de nombreux développeurs, si bien que le format INI s’est généralisé au-delà des systèmes Windows.

Ce format n’apporte cependant pas grand-chose par rapport aux variables d’environnement (pour autant qu’on puisse en limiter la portée à une application particulière), et l’usage d’une base de données centralisée (la base de registre dans le cas de Windows) n’arrange pas les choses lorsqu’il s’agit de déployer de nombreuses applications aux configurations parfois contradictoires: cette approche ne passe tout simplement pas à l’échelle.

Néanmoins, il s’avère qu’en pratique la structure très plate des variables d’environnement devient un inconvénient dès que la configuration applicative se complexifie. De nombreux paramètres sont en effet fortement liés entre eux. Par exemple, la connexion à une base de données peut être définie par l’adresse IP du serveur auquel se connecter, le port sur lequel il écoute, le login et mot de passe devant être utilisés pour s’authentifier, etc. Il devient alors utile de pouvoir exprimer des regroupements de paramètres (ce que permettait déjà, dans une certaine mesure, le format INI!) ou, plus généralement, d’utiliser des structures plus complexes qu’une simple collection de clef-valeur.

Le nom de la clef est parfois utilisé pour supporter une structure hiérarchique implicite. Par exemple, on utilisera DB_HOST pour indiquer l’hôte de la base de données, et DB_PORT pour son port d’écoute. Il s’agit à mon avis d’une mauvaise approche, car elle impose une discipline stricte tant aux développeurs qu’aux responsables du déploiement. Sa sémantique implicite la pénalise. Elle ne permet de plus que l’expression de structures hiérarchiques.

L’utilisation de formats plus avancés (JSON, par exemple, ou encore HOCON), permet cette structuration plus complexe. Elle apporte également d’autres avantages. Ces formats proposent une syntaxe pour exprimer systématiquement des structures hiérarchiques, des listes, des dictionnaires, etc. Ces structures peuvent être décrites formellement (par exemple grâce à un schéma JSON), ce qui facilite leur documentation et leur validation. Leur définition explicite permet par ailleurs une meilleure exploitation au niveau du code, par exemple en générant automatiquement des classes correspondant aux structures, et la levée d’exception si le parsing de la configuration échoue.

La définition de structures (potentiellement complexes) pour définir des aspects de la configuration application permet leur réutilisation. C’est un élément intéressant, car la configuration applicative est souvent répétitive. Tous les composants de votre application devant se connecter à une base de données peuvent par exemple exploiter la même définition d’une connection, réutilisant donc sa structure, son parseur, sa gestion des erreurs ou des valeurs optionnelles, et sa documentation.

Ces structures sont de plus composables. La configuration d’une connexion à une base de données peut avoir, en tant que composant facultatif, la description du protocole de chiffrement utilisé, qui elle-même pourrait contenir un identifiant de protocole et un fichier de certificat. Inversement, la configuration de connexion à la base de données pourrait être incluse dans la description plus générale d’une des sources de données exploitées par l’applicatif.

En combinant la composabilité et la réutilisabilité, les éléments de configuration deviennent des briques descriptives modulaires. On peut donc s’attendre à des avantages similaires que ceux présentés lorsqu’un développeur passe de l’usage de variables globales à la programmation orientée objet.

Impact du passage à des fichiers de configuration

Remarquons que l’utilisation de fichiers de configuration plutôt que de variables d’environnement n’empêche pas l’usage des pratiques répandues dans le développement d’applications modernes. Il est toujours possible d’utiliser un moteur de template afin de créer des fichiers de configuration propres à chaque environnement de déploiement. Les éléments secrets d’une configuration peuvent toujours être stockés dans un dépôt sécurisé pour être injectés dans un fichier de configuration lors du déploiement. Si cela est souhaitable, un fichier de configuration peut toujours être modifié après coup, savoir avoir à recompiler ou à redéployer l’application.

Bien que n’étant pas un expert en sécurité, je pense qu’il n’y a pas grande différence entre l’usage de variables d’environnement et celui de fichiers de configuration: une personne malveillante ayant accès au système exécutant l’application pourra lire aussi bien les premières que les seconds. Les mécanismes de virtualisation et de cloisonnement actuels interdisent (ou en tout cas limitent) leur visibilité de l’extérieur du système et assurent la destruction des éléments de la configuration dès la fin de l’exécution de l’application.

Le principal changement sera donc applicatif, avec le besoin de lire un fichier (à un emplacement connu d’avance, par exemple grâce à une variable d’environnement) plutôt qu’un ensemble de variables d’environnement. Passé ce changement, l’application bénéficiera des avantages d’une structuration forte et formelle décrits plus haut.

Conclusion

Twelve-Factor App est un recueil de bonnes pratiques résultant de l’expérience issue d’un grand nombre de projets. Il est donc intéressant de considérer leur application lorsqu’on commence un nouveau projet ou qu’on considère l’amélioration de projets existants.

Cependant, la simplicité des variables d’environnement me semble souvent limitative pour la configuration d’une application. La séparation de la configuration et du code est clairement présente. Le besoin d’agnosticisme, avancé par Twelve-Factor App, me semble satisfait par les multiples formats de données disponibles dont l’usage est généralisés (y compris certains qui sont utilisés dans pratiquement tous les langages de programmation). Le risque de voir un élément de configuration divulgué n’est pas plus élevé avec un fichier qu’avec une variable d’environnement, pour peu que les mécanismes de template et d’injection de secret, typiquement déjà présents dans un système de déploiement moderne, continuent à être exploité.

Je ne vois donc pas d’inconvénient majeur à l’utilisation de fichiers de configuration, et pense que les avantages qu’ils comportent méritent qu’on s’y intéresse.

Laisser un commentaire