31

我正在尝试将前端 Web 应用程序容器化,但在弄清楚如何传递环境变量时遇到了麻烦。该应用程序是一个 Angular 应用程序,因此它是 100% 的客户端。

在典型的后端服务中,传递环境变量很容易,因为一切都在同一台主机上运行,​​因此后端服务可以轻松选择环境变量。但是,在前端应用程序中,情况有所不同:应用程序在客户端的浏览器中运行。

我想通过环境变量配置我的应用程序,因为这使部署更加容易。所有配置都可以在其中完成,docker-compose.yml无需维护多个图像,一个用于每个可能的环境。只有一个不可变的图像。这遵循 12 因素应用理念,可在https://12factor.net/config上找到。

我正在构建我的应用程序映像,如下所示:

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;"]

app/config.ts中,我有:

export const config = {
    REST_API_URL: 'http://default-url-to-my-backend-rest-api'
};

理想情况下,我想在我的docker-compose.yml

backend:
  image: ...
frontend:
  image: my-frontend-app
  environment:
    - REST_API_URL=http://backend:8080/api

所以我相信我应该改变它app/config.ts来替换REST_API_URL环境变量。由于我更喜欢​​不可变的 Docker 映像(所以我不想在构建期间进行此替换),我很困惑如何在这里取得进展。我相信我应该支持在启动 nginx 代理之前更改app/config.ts运行时。然而,这个文件被缩小和 webpack 捆绑的事实使得这更加困难。

任何想法如何解决这个问题?

4

5 回答 5

24

我解决这个问题的方法如下:

1.在 enviroment.prod.ts 中设置一个唯一且可识别的字符串:

export const environment = {
  production: true,
  REST_API_URL: 'REST_API_URL_REPLACE',
};

2.创建一个entryPoint.sh,每次你对容器进行一次docker run时,都会执行这个entryPoint。

#!/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 "$@"

如您所见,此入口点获取 'REST_API_URL_REPLACE' 参数,并在 main*bundle.js 文件中将其(在本例中)替换为 var 的值。

3.在dockerfile中CMD前添加entrypoint.sh(需要执行权限):

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.使用 env 或使用 docker-compose 启动图像(斜线必须转义):

docker run -e REST_API_URL_REPLACE='http:\/\/backend:8080\/api'-p 80:80 image:tag

可能存在一个更好的解决方案,不需要在缩小文件中使用正则表达式,但这工作正常。

于 2018-03-18T16:00:59.013 回答
9

把你的环境变量放在index.html!!

相信我,我知道你来自哪里!将特定于环境的变量烘焙到我的 Angular 应用程序的构建阶段与我所学到的关于可移植性和关注点分离的一切背道而驰。

可是等等!仔细看看一个常见的 Angular index.html

<!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>

这都是配置!!!

就像您用来维护 Docker 应用程序的 docker-compose.yml 一样:

  • 版本化的不可变资产
  • 环境变量
  • 应用程序绑定
  • 环境元数据
  • 甚至不同的捆绑包都感觉像是 docker 图像排序的层,不是吗?
    • runtime就像您很少更改的基本图像。
    • polyfills是您需要的那些未包含在您需要的基本映像中的东西。
    • main是您的实际应用程序,每个版本都会发生很大变化。

您可以对您的前端应用程序执行与您的 Docker 应用程序相同的操作!

  1. 构建、版本化和发布不可变资产(js 包 / Docker 映像)
  2. 将部署清单发布到登台 (index.html / docker-compose.yml)
  3. 分期测试
  4. 将部署清单发布到生产环境......引用您刚刚测试的相同资产!即刻!原子地!

如何??

只需将臭味指向/src/environments/environment.prod.ts物体window即可。

export const environment = (window as any).env;
// or be a rebel and just use window.env directly in your components

并使用环境变量WHERE THEY BELONG将脚本添加到您的 index.html !:

<script>
  env = { api: 'https://api.myapp.com' }
</script>

我对这种方法感觉非常强烈,因此我创建了一个专门针对它的网站:https ://immutablewebapps.org 。我想你会发现还有很多其他的好处!

~~~

现在,我已经使用两个 AWS S3 存储桶成功完成了这项工作:一个用于版本化静态资产,一个仅用于index.html(它使路由变得超级简单:index.html为每条路径提供服务)。我还没有像你提议的那样运行容器。如果我要使用容器,我希望在构建和发布新资产以及发布新的index.html. 也许我会index.html使用容器的环境变量从模板即时渲染。

如果您选择这种方法,我很想知道结果如何!

于 2018-12-05T03:39:33.727 回答
4

对于静态 HTML 文件,我遇到了类似的问题,这就是我想要解决的问题:

  • 环境变量的数量可以扩展
  • 环境变量可以在启动时设置,而不是在构建时设置
  • 不用担心维护变量替换的格式,以免与其他文本发生冲突

我尝试了其他答案,但似乎它们不符合上述要求。所以这就是我最终使用的envsubst

Dockerfile

FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY . /usr/share/nginx/html
EXPOSE 80

# awkwardly replace env variables
COPY ./replaceEnvVars.sh /
RUN chmod +x replaceEnvVars.sh
ENTRYPOINT ["./replaceEnvVars.sh"]
CMD ["nginx", "-g", "daemon off;"]

替换EnvVars.sh

#!/bin/sh

envsubst < /usr/share/nginx/html/index.tmpl.html > /usr/share/nginx/html/index.html && nginx -g 'daemon off;' || cat /usr/share/nginx/html/index.html

index.tmpl.html

<html>
 ...
 <script>
 gtag('config', '${GA_CODE}');
 </script>
 ...
 <a href="${BASE_URL}/login" class="btn btn-primary btn-lg btn-block">Login</a>
</html>

码头工人-compose.yml

version: '3'
services:
  landing:
    build: .
    ...
    environment:
      - BASE_URL=https://dev.example.com
      - GA_CODE=UA-12345678-9
    ...
于 2019-08-15T02:30:04.847 回答
2

我在同样的问题上苦苦挣扎,但还需要将配置值从 docker-compose 级别向下传递到 Angular,我觉得这并不简单。

基本上,我采用了类似的方法并提出了以下解决方案:

  1. 我使用compose ARGS传递了所需的docker-compose.yml值。所以在我有:Dockerfiledocker-compose.yml

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

  1. 这些现在必须在Dockerfileas 变量中标记:

ARG AUTH_SERVER_URL ARG GAME_SERVER_URL ARG GUI_SERVER_URL

  1. 因为,在构建过程中,这些成为正常的环境变量,最后一步是在目标文件中进行实际替换,例如使用一些神奇的单行。我做了如下(只是一个宠物项目,所以不需要是最佳的):

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

environment.ts替换前的,供参考:

export const environment = { production: true, GAME_SERVER_URL: "$GAME_SERVER_URL", GUI_SERVER_URL: "$GUI_SERVER_URL", AUTH_SERVER_URL: "$AUTH_SERVER_URL" };

瞧。希望这可以帮助某人:)

于 2018-09-10T20:10:11.147 回答
1

我的解决方案:在运行时使用 docker 卷将特定的 js 配置文件挂载为 env.js。

我有一个用于 dev 和 prod 的 docker compose 文件。

我有 dev.env.js 和 prod.env.js。

我的 html 文件引用了 env.js。

在 docker-compose.yml 中,我将任一 env 文件卷挂载为 env.js。

例如我的开发人员撰写:

web:
    image: nginx
    ports:
      - 80:80
    volumes:
      - ../frontend:/usr/share/nginx/html
      - ../frontend/dev.env.js:/usr/share/nginx/html/env.js

我的产品组成:

web:
    image: nginx
    ports:
      - 80:80
    volumes:
      - ../frontend:/usr/share/nginx/html
      - ../frontend/prod.env.js:/usr/share/nginx/html/env.js
于 2018-12-02T13:46:19.357 回答