Как передать переменные среды во внешнее веб-приложение?
Я пытаюсь контейнеризовать внешнее веб-приложение, и у меня возникают проблемы, чтобы выяснить, как передавать переменные среды. Приложение является угловым приложением, поэтому оно на 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!
- Сборка, версия и публикация неизменных ресурсов (js bundles/Docker image)
- Опубликуйте манифест развертывания в промежуточной версии (index.html/docker-compose.yml)
- Тест в постановке
- Опубликуйте манифест развертывания в рабочей среде, ссылаясь на те же ресурсы, которые вы только что протестировали! Мгновенно! Атомно!
Как??
Просто наведите вонючий /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
Я боролся с одной и той же проблемой, но мне также нужно было передать значения конфигурации с уровня докеры до углового, что я не нашел в этом просто.
В принципе, я сделал аналогичный подход и пришел со следующим решением:
- Я передал желаемые значения из
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
- Теперь они должны быть отмечены в
Dockerfile
как переменные:
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
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