REX AWS Elastic Beanstalk: Limite des variables d'environnement

Rédigé par Didier NANDIEGOU le 22/03/2023
Temps de lecture: 9 minutes

Introduction

Elastic Beanstalk est un service PaaS (Platform as a Service) sur AWS qui permet de déployer rapidement une application web complète sans se préoccuper des détails liés à la gestion du réseau ou des instances EC2. Le service gère automatiquement l’allocation des ressources, le déploiement, le monitoring, le loadbalancing et même les versions de l’application, ce qui permet aux utilisatrices et utilisateurs de se concentrer sur leur code. Elastic Beanstalk peut également se charger de provisionner des bases de données SQL sur AWS RDS ce qui renforce l’aspect ready to use du service. Grâce à la prise en charge native de nombreux runtimes tels que Go, PHP, Python, Ruby, Node.js et Java, ainsi que des images Docker, les possibilités sont quasi infinies. Pour déployer une application sur EB, il faut donc simplement packager son application, fournir les variables d’environnement et ajuster quelques paramètres pour être très rapidement online. C’est sur ce point que je me suis heurté à une limitation d’AWS Beanstalk sur la taille des variables d’environnement.

1. La gestion des variables d’environnement sur ElasticBeanstalk

Les variables d’environnement dans Elastic Beanstalk, sont des informations de configuration clé-valeur (mots de passe, endpoint API…) plus ou moins sensibles qui vont définir le fonctionnement de l’application. Elles sont configurables via l’outil utilisé pour le déploiement, soit le code IAC (Infrastrucure as Code) ou la console AWS.

Variables d’environnement sur la console AWS
Variables d'environnement sur la console AWS. Source:https://docs.aws.amazon.com/

Ces variables sont ensuite accessibles à l’application via une fonction dépendamment du runtime. En Node.js ce sera, par exemple process.env.ENV_VARIABLE :

1var endpoint = process.env.API_ENDPOINT

Les variables d’environnement sont stockées dans le fichier /opt/elasticbeanstalk/deployment/env sur les instances EC2 et peuvent être consultées directement.

Extensions d’Elastic Beanstalk sur les plateformes Linux

Le service offre la possibilité d’étendre les fonctionnalités en ajoutant des paramètres ou des commandes qui s’exécuteront au démarrage des instances et/ou à chaque nouveau déploiement. Restons sur les variables d’environnement pour parler des fichiers .ebextensions et des Platform hooks.

Les fichiers .ebextensions

Ce sont des fichiers de configuration au format JSON ou YAML avec une extension .config qui sont lus lors des mises à jour de l’application. Ils doivent être placés dans le dossier .ebextensions à la racine du projet.

1~/my-app/
2|-- .ebextensions/
3|   |-- options.config

Exemple de contenu du fichier:

1option_settings:
2  aws:elasticbeanstalk:environment:
3    DEBUG: "true"

Dans l’exemple ci-dessus, la variable d’environnement DEBUG est définie directement sans avoir besoin de mettre à jour la configuration via la console AWS ou l’IAC. Tout l’intérêt ici réside dans le fait que les développeuses et développeurs n’ont pas à toucher au code d’infrastrucure pour effectuer des changements.

Les platform hooks

Les plateform hooks sont des scripts custom qui vont s’exécuter à différentes étapes de déploiement et de provisionnement de l’application Beanstalk. Cette fonctionnalité est disponible sur les instances Amazon Linux 2 et pas sur les précédentes. Ce sont donc des scripts qui peuvent être utilisés pour initialiser des variables d’environnement. Pour configuer ces platform hooks il faut créer l’arborescence suivante à la racine du projet:

 1~/my-app/
 2`-- .platform/
 3    |-- hooks/
 4    |   |-- prebuild/
 5    |   |-- predeploy/
 6    |   `-- postdeploy/
 7    `-- confighooks/
 8        |-- prebuild/
 9        |-- predeploy/
10        `-- postdeploy/
  • .platform/hooks contient les scripts exécutés au déploiement d’une nouvelle version de l’application
  • .platform/confighooks stocke les scripts qui, eux, s’exécutent aux changements de configuration

Ensuite Elastic Beanstalk lance une séquence qui est la même dans les deux cas:

  • Exécution des scripts prebuild avant d’appliquer la mise à jour
  • Exécution des scripts predeploy pendant la configuration de l’application
  • Exécution des scripts postdeploy après le déploiement

2. Alors, cette limitation?

Au fur et à mesure que votre application évolue, il est fort probable qu’elle ait besoin de plus en plus de variables d’environnement sans compter les éventuels tentatives de résolution de problèmes qui conduisent à ajouter des variables de debug. Vous aurez alors de plus en plus de chance de tomber sur cette erreur lors d’un énième déploiement:

The combined size of all environment properties cannot exceed 4,096 bytes when stored as strings with the format key=value.

J’ai passé du temps à chercher d’où venait cette limite et surtout quel paramètre d’EB permettait de la modifier… Mais l’explication est à la fois simple et frustrante. En réalité, quelque soit la façon dont le déploiement de l’application est fait sur Beanstalk (Pipeline de CI/CD, via la console AWS ou un script), c’est AWS CloudFormation qui se charge de provisionner l’environnement. Et c’est de là que vient la limite de 4KB qui correspond à la taille par défaut d’une page mémoire dans les kernels Linux sur l’architecture x86. Pour résumer, c’est hard-codé et ne peut pas être modifié.

3. Trouver des chemins de traverse

Dans l’exploration des possibles solutions, j’ai pris le parti de trouver un moyen de récupérer les variables d’environnement depuis un autre service: Parameter Store. Parameter Store est un “sous service” d’AWS System Manager (SSM) qui permet de stocker des paramètres sous forme de clé-valeur avec une gestion des versions des paramètres et offre la possibilité de chiffrer les informations. L’idée ici est d’aller chercher ces variables dans Parameter store pendant les déploiements. Plusieurs solutions existent pour ajouter des variables d’environnement à une application Elastic Beanstalk, mais est ce qu’elle permettent de se défaire de la limite liée à CloudFormation?

Option 1: Prise en charge native de Parameter Store

Elastic Beanstalk, comme d’autres services AWS, prend en charge nativevement le mapping de Parameter Store ce qui arrange bien nos affaires. La méthode se base sur les ebextensions pour configurer une variable et la mapper directement depuis Parameter Store.

1OptionSettings:
2  aws:elasticbeanstalk:application:environment:
3    EXAMPLE_API_KEY: {{resolve:ssm:/my-app/dev/env_vars/EXAMPLE_API_KEY:3}}

/my-app/dev/env_vars/EXAMPLE_API_KEY correspond au chemin du paramètre à récupérer et :3 désigne la version. La solution est simple, pratique et reproductible à l’infini…

The combined size of all environment properties cannot exceed 4,096 bytes when stored as strings with the format key=value.

Je retire ce que j’ai dis. Les ebextentions sont aussi appliquées par CloudFormation et je retombe sur la même erreur.

Celà reste une excellente solution qui donne une vraie autonomie aux équipes de développement qui n’ont plus besoin de se reposer sur les Ops pour les changements de variables d’environnement. L’autre problème de cette méthode est que pour chaque mise à jour de variable il faudrait relancer un déploiement pour récupérer les bonnes valeurs et ne surtout pas se tromper de versions. Ce qui augmente les possibilités de commettre des erreurs.

Option 2:Utiliser les platform hooks et le fichier env

Puisque la première solution n’a pas fonctionné comme je l’espérais, je me suis tourné vers une autre caractéristique d’Elastic Beanstalk en terme de gestion de variables d’environnement: le fichier /opt/elasticbeanstalk/deployment/env. En effet, en creusant un peu, je trouve ce fichier qui contient toutes les variables d’environnement en clair. Ce n’est pas l’idéal, mais ça se tente. Ici, je vais ajouter un script aux platform hooks pour récupérer les valeurs depuis Parameter Store et les insérer dans le fichier env. Il faut avant tout que les variables que l’on souhaite récupérer soient sous le même chemin dans Parameter store. Le script va alors récupérer tous les paramètres sous ce chemin et les ajouter au fichier.

Variables d’environnement dans Parameter store
Variables d'environnement dans Parameter store

Ensuite, j’ajoute mon script à la fois dans les hooks et les confighooks pour qu’il s’exécute lors des déploiements d’une nouvelle version de l’application, mais aussi en cas de mise à jour de configuration. Le choix d’utiliser le postdeploy n’est pas anodin non plus. Lors de mes tests, j’ai réalisé que les variables d’environnement dans le fichier env étaient réinitialisées entre chaque étape du déploiement. Le plus sur est donc de mettre le script à la toute fin.

1~/my-app/
2`-- .platform/
3    |-- hooks/
4    |   |-- postdeploy/
5    |   |   |-- map_parameters_to_env_vars.sh
6    |-- confighooks/ 
7    |   |-- postdeploy/
8    |   |   |-- map_parameters_to_env_vars.sh
 1#!/usr/bin/env bash
 2
 3echo "Running script to fetch parameter store values and add them to /opt/elasticbeanstalk/deployment/env file."
 4
 5readarray eb_env_vars < /opt/elasticbeanstalk/deployment/env
 6
 7parameter_store_path="/my-app/dev/env_vars/"
 8
 9TOKEN=`curl -X PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds:21600"`
10AWS_DEFAULT_REGION=`curl -H "X-aws-ec2-metadata-token:$TOKEN" -v http://169.254.169.254/latest/meta-data/placement/region`
11
12export AWS_DEFAULT_REGION
13
14#Create a copy of the environment variable file.
15cp /opt/elasticbeanstalk/deployment/env /opt/elasticbeanstalk/deployment/custom_env_var
16
17jq_actions=$(echo -e ".Parameters | .[] | [.Name, .Value] | \042\(.[0])=\(.[1])\042 | sub(\042${parameter_store_path}/\042; \042\042)")
18
19aws ssm get-parameters-by-path \
20--path $parameter_store_path \
21--with-decryption \
22--region eu-west-3 \
23| jq -r "$jq_actions" >> /opt/elasticbeanstalk/deployment/custom_env_var
24
25cp /opt/elasticbeanstalk/deployment/custom_env_var /opt/elasticbeanstalk/deployment/env
26
27#Remove temporary working file.
28rm -f /opt/elasticbeanstalk/deployment/custom_env_var
29
30#Remove duplicate files upon deployment.
31rm -f /opt/elasticbeanstalk/deployment/*.bak

Enfin, il faut bien penser à mettre à jour le rôle IAM associé à l’application Beanstalk pour qu’elle ait accès à Parameter Store. La policy “AmazonSSMReadOnlyAccess suffit largement.

eb env
Variables d'environnement dans Beanstalk

Le script a bien fonctionné et les variables sont bien mises à jour dans la configuration d’Elastic Beanstalk. Seul bémol, en rajoutant suffisament de variables, j’obtiens la même erreur, encore.

Alt Text

On a également le même problème que la solution précédente concernant le redéploiement à chaque changement de valeur d’un paramètre.

Option 3:Récupérer les variables via le sdk AWS

Après avoir passé pas mal de temps à explorer les solutions coté déploiement, j’ai compris que quelque soit la méthode utilisée, pour le moment la limitation à 4KB pour les variables d’environnement ne peut pas être contournée côté Elastic Beanstalk. CloudFormation intervient toujours dans l’ajout des variables d’environnement, même ajoutées directement dans le fichier env. La soution la plus simple dans ces cas est de se tourner vers le code de notre application et d’aller chercher les valeurs de nos variables dans Parameter Store ou Secret Manager. En Node.js par exemple, le SDK AWS peut être utilisé pour accéder aux valeurs dans Parameter Store.

 1const AWS = require('aws-sdk')
 2
 3const ssmClient = new AWS.SSM({
 4  apiVersion: '2014-11-06',
 5  region: 'eu-west-3'
 6});
 7
 8const environment = 'dev'
 9
10ssmClient.getParametersByPath({
11  Path: `/my-app/${environment}/env_vars/`,
12  Recursive: true,
13  WithDecryption: true
14}, (err, data) => {
15  if (data?.Parameters) {
16    console.log(data.Parameters)
17  }
18});

Cela nécessite une adaptation de l’application, mais au moins, la limite CloudFormation ne pose plus de problème. Bien sur, en fonction des use case plusieurs de ces solutions peuvent être utilisées en même temps. Un autre point fort de cette méthode est de pouvoir relire les valeurs dans Parameter Store à certains endroits du code pour éviter des redéploiements à chaque modification d’un paramètre.

Conclusion

Elastic Beanstalk, comme solution PaaS peut être bien pratique pour déployer son application sans se soucier du reste, du fait de la rapidité de mise en œuvre et de la simplicité de cette option pour des équipes de développement sans grande expérience dans la gestion des infrastructures. Cependant, les options natives peuvent vite se révéler être un casse-tête dès que le besoin augmente, comme c’est le cas pour cette limitation. Heureusement, des solutions de contournement existent. J’espère d’ailleurs qu’AWS va traiter ce problème rapidement. Vous l’aurez compris, le plus simple pour récupérer les variables d’environnement c’est d’utiliser le SDK AWS.