J'essaie de conteneuriser une application Web frontale et j'ai du mal à comprendre comment passer des variables d'environnement. L'application est une application angulaire, elle est donc 100% côté client.
Dans un service dorsal typique, le passage des variables d’environnement est facile, car tout est exécuté sur le même hôte, de sorte que les variables d’environnement peuvent être facilement sélectionnées par le service principal. Cependant, dans une application frontale, cela est différent: l'application s'exécute dans le navigateur du client.
Je souhaite configurer mon application via des variables d'environnement, car cela facilite beaucoup le déploiement. Toute la configuration peut être faite en docker-compose.yml
et il n’est pas nécessaire de gérer plusieurs images, une pour chaque environnement possible. Il n'y a qu'une seule image immuable. Ceci est conforme à la philosophie d’application à 12 facteurs, comme on peut le trouver sur https://12factor.net/config .
Je construis mon image d'application comme suit:
FROM node:Alpine as builder
COPY package.json ./
RUN npm i && mkdir /app && cp -R ./node_modules ./app
WORKDIR /app
COPY . .
RUN $(npm bin)/ng build
FROM nginx:Alpine
COPY nginx/default.conf /etc/nginx/conf.d/
RUN rm -rf /usr/share/nginx/html/*
COPY --from=builder /app/dist /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
Dans app/config.ts
, j'ai:
export const config = {
REST_API_URL: 'http://default-url-to-my-backend-rest-api'
};
Idéalement, je veux faire quelque chose comme ceci dans mon docker-compose.yml
:
backend:
image: ...
frontend:
image: my-frontend-app
environment:
- REST_API_URL=http://backend:8080/api
Je pense donc que je devrais modifier ce app/config.ts
pour remplacer REST_API_URL
par la variable d'environnement. Comme je préfère une image Docker immuable (donc je ne veux pas faire cela remplacer pendant la construction), je suis assez perplexe de savoir comment progresser ici. Je crois que je devrais aider à modifier le app/config.ts
au moment de l'exécution avant que le proxy nginx ne soit démarré. Cependant, le fait que ce fichier soit minifié et groupé avec Webpack rend cela plus difficile.
Des idées comment aborder cela?
La façon dont j'ai résolu ceci est la suivante:
1.Définissez la valeur dans enviroment.prod.ts avec une chaîne unique et identifiable:
export const environment = {
production: true,
REST_API_URL: 'REST_API_URL_REPLACE',
};
2.Créez un entryPoint.sh, cet entryPoint sera exécuté chaque fois que vous effectuez une exécution du menu fixe dans le menu fixe.
#!/bin/bash
set -xe
: "${REST_API_URL_REPLACE?Need an api url}"
sed -i "s/REST_API_URL_REPLACE/$REST_API_URL_REPLACE/g" /usr/share/nginx/html/main*bundle.js
exec "$@"
Comme vous pouvez le constater, ce point d'entrée récupère l'argument 'REST_API_URL_REPLACE' et le remplace (dans ce cas) dans le fichier * bundle.js principal par la valeur de la variable var.
3.Ajouter le entrypoint.sh dans le fichier de dock avant le CMD (il a besoin d'autorisations d'exécution):
FROM node:Alpine as builder
COPY package.json ./
RUN npm i && mkdir /app && cp -R ./node_modules ./app
WORKDIR /app
COPY . .
RUN $(npm bin)/ng build --prod
FROM nginx:Alpine
COPY nginx/default.conf /etc/nginx/conf.d/
RUN rm -rf /usr/share/nginx/html/*
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy the EntryPoint
COPY ./entryPoint.sh /
RUN chmod +x entryPoint.sh
ENTRYPOINT ["/entryPoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
4. Lancez l'image avec env ou utilisez docker-compose (la barre oblique doit être échappée):
docker run -e REST_API_URL_REPLACE='http:\/\/backend:8080\/api'-p 80:80 image:tag
Il existe probablement une meilleure solution qui n’a pas besoin d’utiliser une expression régulière dans le fichier minifié, mais cela fonctionne bien.
Mettez vos variables d'environnement dans le index.html
!!
Croyez-moi, je sais d'où vous venez! Intégrer des variables propres à l'environnement dans la phase de création de mon application Angular va à l'encontre de tout ce que j'ai appris sur la portabilité et la séparation des problèmes.
Mais attendez! Examinez de près un index.html
angulaire commun:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>mysite</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="https://assets.mysite.com/styles.3ff695c00d717f2d2a11.css">
<script>
env = {
api: 'https://api.mysite.com/'
}
</script>
</head>
<body>
<app-root></app-root>
<script type="text/javascript" src="https://assets.mysite.com/runtime.ec2944dd8b20ec099bf3.js"></script>
<script type="text/javascript" src="https://assets.mysite.com/polyfills.20ab2d163684112c2aba.js"></script>
<script type="text/javascript" src="https://assets.mysite.com/main.b55345327c564b0c305e.js"></script>
</body>
</html>
C'est toute la configuration !!!
C'est comme le fichier docker-compose.yml que vous utilisez pour gérer vos applications Docker:
runtime
est comme votre image de base que vous modifiez rarement.}polyfills
sont les éléments dont vous avez besoin et qui ne figuraient pas dans l'image de base dont vous avez besoin.} _main
est votre application actuelle qui change à peu près chaque version.} _Vous pouvez faire la même chose avec votre application frontend qu'avec votre application Docker!
Comment??
Il suffit de pointer le /src/environments/environment.prod.ts
puant sur l'objet window
.
export const environment = (window as any).env;
// or be a rebel and just use window.env directly in your components
et ajoutez un script à votre index.html avec la variable d'environnement WHERE THEY BELONG!:
<script>
env = { api: 'https://api.myapp.com' }
</script>
Je suis tellement convaincu par cette approche que j'ai créé un site Web dédié à celle-ci: https://immutablewebapps.org . Je pense que vous constaterez qu'il y a beaucoup d'autres avantages!
~~~
À présent, j'ai réussi à utiliser cette méthode avec deux AWS S3 Buckets: un pour les actifs statiques versionnés et un pour le index.html
(le routage est extrêmement simple: servir index.html
pour chaque chemin). Je ne l'ai pas fait avec des conteneurs comme vous le proposez. Si je devais utiliser des conteneurs, je voudrais faire une séparation nette entre le bâtiment et la publication de nouveaux actifs, et la publication d'un nouveau index.html
. Peut-être que je rendrais index.html
à la volée à partir d'un modèle avec les variables d'environnement du conteneur.
Si vous choisissez cette approche, j'aimerais savoir comment cela se passe!
Je me débattais avec le même problème, mais je devais aussi passer les valeurs de configuration de docker-compose à Angular, ce que je n’ai pas trouvé aussi simple.
Fondamentalement, j'ai adopté une approche similaire et est venu avec la solution suivante:
docker-compose.yml
à Dockerfile
à l'aide du compose ARGS . Donc, dans docker-compose.yml
j'ai:
magicsword.core.web:
build:
args:
- AUTH_SERVER_URL=http://${EXTERNAL_DNS_NAME_OR_IP}:55888/
- GAME_SERVER_URL=http://${EXTERNAL_DNS_NAME_OR_IP}:55889/
- GUI_SERVER_URL=http://${EXTERNAL_DNS_NAME_OR_IP}:55890/ # =self
Dockerfile
en tant que variables:
ARG AUTH_SERVER_URL
ARG GAME_SERVER_URL
ARG GUI_SERVER_URL
RUN apt-get update && apt-get install -y gettext
RUN envsubst < ./src/environments/environment.ts > ./src/environments/environment.ts.tmp && mv ./src/environments/environment.ts.tmp ./src/environments/environment.ts
Le environment.ts
avant la substitution, pour référence:
export const environment = {
production: true,
GAME_SERVER_URL: "$GAME_SERVER_URL",
GUI_SERVER_URL: "$GUI_SERVER_URL",
AUTH_SERVER_URL: "$AUTH_SERVER_URL"
};
Voila. J'espère que ça aide quelqu'un :)
Ma solution: au moment de l'exécution, utilisez des volumes de menu fixe pour monter un fichier de configuration js spécifique sous le nom env.js.
J'ai un fichier docker composer pour dev et prod.
J'ai dev.env.js et prod.env.js.
Mon fichier html fait référence à env.js.
Dans le volume docker-compose.yml, montez le fichier env sous la forme env.js.
Par exemple. mon dev compose:
web:
image: nginx
ports:
- 80:80
volumes:
- ../frontend:/usr/share/nginx/html
- ../frontend/dev.env.js:/usr/share/nginx/html/env.js
Et ma prod compose:
web:
image: nginx
ports:
- 80:80
volumes:
- ../frontend:/usr/share/nginx/html
- ../frontend/prod.env.js:/usr/share/nginx/html/env.js