Как передать переменные среды во внешнее веб-приложение?

Я пытаюсь контейнеризовать внешнее веб-приложение, и у меня возникают проблемы, чтобы выяснить, как передавать переменные среды. Приложение является угловым приложением, поэтому оно на 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 на переменную среды. Поскольку я предпочитаю неизменяемое изображение Докера (поэтому я не хочу, чтобы это заменялось во время сборки), я очень озадачен тем, как продвигаться здесь. Я считаю, что должен поддерживать изменение app/config.ts во время выполнения до запуска прокси-сервера nginx. Тем не менее, тот факт, что этот файл миниатюрный и пакетный пакет, делает это более сложным.

Любые идеи, как справиться с этим?

Ответы

Ответ 1

Способ, которым я решил это, заключается в следующем:

1. Задайте значение в enviroment.prod.ts с уникальной и идентифицируемой строкой:

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

2. Создайте элемент entryPoint.sh, этот 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 "[email protected]"

Как вы можете видеть, эта точка входа получает аргумент "REST_API_URL_REPLACE" и заменяет его (в данном случае) в основном файле * bundle.js для значения var.

3. Добавьте файл entrypoint.sh в файл докеров перед CMD (ему нужны разрешения на выполнение):

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.Lauch изображение с env или использование docker-compose (слэш должен быть экранирован):

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

Вероятно, существует лучшее решение, которое не требует регулярного выражения в мини файле, но это отлично работает.

Ответ 2

Поместите переменные окружения в 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-compose.yml, который вы используете для поддержки ваших приложений Docker:

  • версионные неизменные активы
  • переменные среды
  • привязка приложения
  • метаданные среды
  • даже разные связки ощущаются как слои изображения докера, не так ли?
    • runtime похож на ваш базовый образ, который вы редко меняете.
    • polyfills - это те вещи, которые вам нужны, и которые не включены в базовый образ, который вам нужен.
    • main - ваше настоящее приложение, которое в значительной степени меняется с каждым выпуском.

С вашим приложением-интерфейсом вы можете сделать то же самое, что и с приложением Docker!

  1. Сборка, версия и публикация неизменных ресурсов (js bundles/Docker image)
  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

и добавьте скрипт в ваш index.html с переменной среды ГДЕ ОНИ БЫЛИ!:

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

Я так сильно чувствую этот подход, что создал сайт, посвященный этому: https://immutablewebapps.org. Я думаю, вы найдете много других преимуществ!

~~~

Теперь я сделал это успешно, используя два блока AWS S3: один для версионных статических ресурсов, а другой только для index.html (это делает маршрутизацию очень простой: подача index.html для каждого пути). Я не сделал это, запустив контейнеры, как вы предлагаете. Если бы я использовал контейнеры, я бы хотел провести четкое разделение между зданием и публикацией новых активов и выпуском нового index.html. Возможно, я бы визуализировал index.html на лету из шаблона с переменными среды контейнера.

Если вы выберете этот подход, я хотел бы знать, как это получается!

Ответ 3

Я боролся с одной и той же проблемой, но мне также нужно было передать значения конфигурации с уровня докеры до углового, что я не нашел в этом просто.

В принципе, я сделал аналогичный подход и пришел со следующим решением:

  1. Я передал желаемые значения из docker-compose.yml в Dockerfile используя состав ARGS. Так что в docker-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. Теперь они должны быть отмечены в Dockerfile как переменные:

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

Вуаля. Надеюсь, это поможет кому-то :)

Ответ 4

У меня была похожая проблема для статического HTML файла, и вот что я хотел решить:

  • количество переменных env может масштабироваться
  • env vars может быть установлен во время запуска, а не во время сборки
  • не заботясь о сохранении формата замены переменных, чтобы он не конфликтовал с другим текстом

Я пробовал другие ответы, но кажется, что они не соответствовали вышеупомянутому. Это то, чем я в итоге воспользовался 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;"]

replaceEnvVars.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
    ...

Ответ 5

Мое решение: во время выполнения используйте докеры для монтирования определенного конфигурационного файла js как env.js.

У меня есть файл для докеры для dev и prod.

У меня есть 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

И мой prod сочиняет:

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