đŹđ§ Compiler un front Angular variabilisĂ© comme un chef
Posté le 11/08/2020 par Florent Jaby
DerniĂšre mise Ă jour
Cet article vous a-t-il été utile ?
Posté le 11/08/2020 par Florent Jaby
DerniĂšre mise Ă jour
Cet article vous a-t-il été utile ?
Si vous vivez dans le prĂ©sent, voire un peu dans le passĂ©, vous avez sĂ»rement une application de type SPA rĂ©alisĂ©e avec le framework Angular. Vu que vous vivez dans le prĂ©sent, vous avez sĂ»rement envie de suivre un processus de dĂ©veloppement et de livraison sain, avec promotion dâune mĂȘme version dâun artefact Ă travers plusieurs environnements (test, intĂ©gration, recette, prĂ©production, production, etc.)
Seulement voilĂ , Angular, Ă lâinstar de beaucoup dâautres frameworks front-end, vous inflige de construire/compiler votre application une fois par environnement en collant la configuration associĂ©e dans un fichier qui porte le nom de lâenvironnement cible.
Cela pousse en général aux défauts suivants :
Des secrets ajoutés au dépÎt de code dans ces fichiers de configuration
Un livrable différent par environnement, avec potentiellement des comportements différents
Aucune injection possible de configuration sans ajouter au code
Pas possible dâimaginer dâautres environnements sans ajouter au code, par exemple des review apps pour votre application
Si vous voulez plus dâinformations sur pourquoi ces points sont des dĂ©fauts, je vous invite Ă lire les chapitres 3, 5 et 10 de twelve-factor apps (la rĂ©fĂ©rence en ce qui concerne les applications Cloud Ready / Native).
Nous souhaitons les caractéristiques suivantes pour notre application :
Constuire un seul artefact pour toutes nos cibles de déploiement
Injecter la configuration spĂ©cifique par les variables dâenvironnement
Adapter le comportement de lâapplication, typiquement la balise <base href="...">
en fonction dâune configuration
Un dĂ©ploiement identique Ă nâimporte quel autre type dâapplication
Sur mon projet, tous nos applicatifs sont packagĂ©s avec Docker et deployĂ©s soit sur des VMs avec Ansible soit dans un cluster Kubernetes. On a donc cherchĂ© Ă packager notre application Angular de la mĂȘme maniĂšre.
Nous avons choisi nginx:alpine
comme image de base. Dans notre SI, tous les applicatifs parlent HTTP. Pour faire rentrer notre application web dans le moule, il fallait donc que son image suive les mĂȘmes principes que nos briques dâAPI par exemple :
JâĂ©coute sur un PORT
connu (ici 80
car la terminaison TLS est faite soit par ingress soit par un F5)
Je sais sur quel prĂ©fixe dâURI je suis exposĂ© via la configuration BASE_URL
(par exemple : https://www.domaine.fr/mon-appli
)
Voici donc Ă quoi ressemble notre Dockerfile
:
Ce Dockerfile
dĂ©crit un Multi Stage Build. Câest une mĂ©canisme proposĂ© pour utiliser des images de bases diffĂ©rentes pour la phase de construction de lâimage Ă proprement parler et la phase dâexecution. On utilise node:slim
pour construire lâapplication Angular car nous avons besoin des executables node
, npm
et npx
. Ensuite, nous utilisons lâimage de base nginx:alpine
pour disposer dâun serveur HTTP statique plutĂŽt lĂ©ger. nous utilisons une configuration spĂ©ciale nginx.front.conf
qui vient de notre base de code. Elle ne fait rien dâintĂ©ressant Ă part le strict minimum, ce qui nâest pas forcĂ©ment le cas de la configuration de base. Elle sert Ă©galement Ă servir le mĂȘme index.html
mĂȘme pour les routes enfants afin que ce soit bien notre application quoi soit chargĂ©e Ă chaque fois et prenne en charge le routage cĂŽtĂ© navigateur.
Nous pouvons faire ceci car lâoutput de la commande ng build
se trouve entiĂšrement dans le dossier /app/dist/
et ne nécessite aucune execution par le serveur. (pas besoin non plus de nettoyer les dépendances avec npm prune --production
)
La derniĂšre ligne CMD ["nginx", "-g", "daemon off;"]
quant Ă elle indique simplement quâon dĂ©marre le serveur Nginx au premier plan avec la configuration que nous lui avons copiĂ©e. Câest le dossier dist/
qui sera servi statiquement pour toutes les requĂȘtes entrantes sur le port 80.
docker-entrypoint
Je nâai pas encore parlĂ© de la curieuse ligne RUN npm ci && npx ng build --prod --base-href '\${BASE_URL}'
. Attention ici, lâobjectif nâest pas de faire la substitution de valeur au moment du docker build
mais bien dâinscrire dans le fichier gĂ©nĂ©rĂ© le placeholder ${BASE_URL}
. Il est donc important de respecter lâechappement. Lâoption --base-href '\${BASE_URL}'
aura pour conséquence de génerer un index.html
qui a cette tĂȘte lĂ .
On peut remarquer la ligne <base href="${BASE_URL}">
. Câest vraiment cette version avec un placeholder qui est stockĂ©e dans lâimage. En revanche, ce ne sera jamais cette version qui sera servie. En effet, dans notre Dockerfile
nous avons précisé aussi :
Pour Docker, lâentrypoint
est lâexecutable qui sera lancĂ©e dans le conteneur une fois lâimage lancĂ©e. Ses arguments seront le contenu de CMD
. En pratique, nous lâutilisons pour exĂ©cuter du code avant de lancer rĂ©ellement Nginx. Voici le contenu de notre entrypoint
envsubst
est un petit programme, installé sur à peu prÚs toutes les distributions *nix, qui se charge de remplacer les chaßnes de caractÚres de la forme ${MA_VARIABLE}
par la valeur prĂ©sente dans lâenvironnement. On change donc le contenu de notre index.html
juste avant de lancer Nginx pour modifier cette ligne
en cette ligne
Ici nous bĂ©nĂ©ficions du fait que notre application nâest exposĂ©e que sur un seul prĂ©fixe dâURL ce qui nâest pas forcĂ©ment le cas pour vos applications. Dans un cas pareil, vous pourrez soit dĂ©ployer 2 fois la mĂȘme image avec 2 configurations diffĂ©rentes (complexitĂ© dans le dĂ©ploiement), ou alors vous orienter vers la solutions de Server-Side Rendering la plus adaptĂ©e Ă votre framework (complexitĂ© dans lâapplicatif)
config.js
En ce qui concerne la configuration spécifique à notre application (API_BASE_URL
, AUTH_BASE_URL
, etc.), nous utilisons un service Angular ConfigService
Ce service cherche dans lâespace global (window
) un objet nommé 'config'
et en copie toutes les clés. Cet objet est initialisé par notre fichier assets/config.js
importé depuis notre index.html
. En revanche, lorsque nous construisons lâimage, câest seulement template.config.js
que nous incorporons dans notre container. Voici Ă quoi il ressemble :
Nous pouvons modifier notre docker-entrypoint.sh
pour utiliser ce template :
Et zou !
Maintenant que nous avons fait tout ça, vérifions donc que nos critÚres sont bien remplis :
Notre artefact est une image Docker que lâon peut balader sur plusieurs environnements, facilement transmissible.
Nous pouvons configurer notre application grĂące aux variables dâenvironnements que lâon donne au dĂ©marrage de cette image afin dâadapter le comportement de lâapplication Ă son dĂ©ploiement.
Nous déployons un service HTTP tout simple, il suffit donc de diriger les connexions vers notre container.
Aucune incidence pour travailler localement avec ng serve
Cette approche nous permet Ă©galement de faire plus facilement certaines choses :
Utiliser un registre docker pour la promotion dâune version de notre application avec les tags
Faire une configuration aux petits oignons de notre exposition HTTP, notamment en ce qui concerne les stratégies de cache ou des redirections
Rajouter une protection de type BasicAuth
Ă notre application si on veut limiter la population qui y a accĂšs
GĂ©rer la terminaison TLS directement dans nginx pour un chiffrement en profondeur
Cette maniĂšre de packager et livrer nos applications Angular nous a bien aidĂ©, en particulier en rentrant dans le moule de tous nos processus de livraison pour la gestion de la configuration et lâexposition. Je vous recommande dâessayer, en particulier si vous entrez dans une logique de review apps et que vous souhaitez livrer exactement ce que vous avez validĂ© !
Jâimagine que cette approche est transposable Ă dâautres frameworks front. Comment cela se passe-t-il pour vous avec Vue, React ou Ember ?
Source : https://blog.octo.com/compiler-un-front-angular-variabilise-comme-un-chef/
Pour avoir plus dâinformations sur cette commande, tapez man envsubst
dans votre terminal préféré