Всякий раз, когда я хочу изменить значение переменной среды для моего внешнего приложения, будь то конечная точка API, порт или какой-либо путь, которым я должен управлять, мне нужно отредактировать файл .env, перестройте проект через непрерывный (надеюсь) конвейер и поместите обновленный контейнер в мою сетку приложения.
Поэтому я бы полагался на объект процесса Node, чтобы извлечь значение среды и внедрить его в мой экземпляр axios в виде строкового значения, чтобы получить доступ к моему серверному API или любому другому значению в этом отношении. Но объект процесса буквально не существует в браузере, он использовался во время транспиляции и может быть настроен только во время сборки:
const instance = axios.create({ baseURL: process.env.BACKEND_API_URL, timeout: 1000, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } });
Это особенно болезненно, если у вас есть несколько вышестоящих сервисов или общедоступных сторонних служб, с которыми ваш интерфейс должен взаимодействовать, следовательно, несколько конечных точек API в качестве переменных среды.
Слишком много хлопот для таких простых изменений, так что же вы можете сделать, чтобы обновить переменные среды, такие как BACKEND_API_URL, «динамически», без перестроения и докеризации вашего приложения?
Начиная сверху, точка входа для внедрения переменных среды возможна при запуске нашего контейнера. Итак, как будет выглядеть Dockerfile, который генерирует образ нашего контейнера (частично):
... COPY static.conf /etc/nginx/conf.d/default.conf WORKDIR /usr/share/nginx/html RUN apk add --no-cache bash COPY ./configure-runtime.sh . COPY .env . RUN chmod +x configure-runtime.sh CMD ["/bin/bash", "-c", "/usr/share/nginx/html/configure-runtime.sh && nginx -g \"daemon off;\""]
Важными частями вышеупомянутого Dockerfile являются введение сценария configure-runtime.sh, затем файла .env, содержащего обычные переменные среды, и, в дополнение, static.conf nginx, чтобы настроить структуру прокси, чтобы избежать CORS для конечной точки нашего API, которая может находиться в другом домене. Затем мы указываем контейнеру запустить команду CMD, которая сначала выполняет наш скрипт configure-runtime.sh, а затем запускает nginx для обслуживания нашего внешнего приложения.
Давайте посмотрим на файлы и скрипты один за другим. Начиная с файла static.conf nginx, есть {BACKEND_API_URL}заполнитель, который мы хотим динамически заменить переменными среды Docker:
server { listen 80 default_server; server_name localhost; charset utf-8; root /usr/share/nginx/html; location /api { proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass {BACKEND_API_URL}; } location / { try_files $uri $uri/ /index.html =404; } }
У нас также есть файл .env. Мы будем использовать его как «карту» и запасной вариант на случай, если наш скрипт configure-runtime не сможет найти какие-либо установленные переменные среды Docker, и вместо этого будем использовать значения файла .env:
BACKEND_API_URL=http://localhost
Затем наш скрипт configure-runtime.sh выполняет остальную работу. Он анализирует все переменные среды Docker, которые мы определили, и создает файл javascript (runtime-env.js) с переменными, назначенными в качестве свойств глобального window object
. Именно так наше веб-приложение может получить доступ к этим переменным глобально через браузер и объект окна:
#!/bin/bash # Assign the nginx configuration filename nginx_conf="/etc/nginx/conf.d/default.conf" # Recreate runtime-env config file rm -rf ./runtime-env.js && touch ./runtime-env.js echo "window._env_ = {" >> ./runtime-env.js # Read each line in .env file, each line represents key=value pairs while read -r line || [[ -n "$line" ]]; do # Split env variables by character `=` if printf '%s\n' "$line" | grep -q -e '='; then varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') fi # Read value of current variable if exists as Environment variable value=$(printf '%s\n' "${!varname}") # Otherwise use value from .env file [[ -z $value ]] && value=${varvalue} # Append configuration property to JS file echo " $varname: \"$value\"," >> ./runtime-env.js # Replace the nginx environment variable placeholder if [[ $varname != "" && $value != "" ]]; then sed -i 's|{'$varname'}|'"$value"'|g' $nginx_conf fi done < .env echo "}" >> ./runtime-env.js
Часть вышеприведенного скрипта также является функциональностью sed, ближе к концу, чтобы заменить заполнитель nginx для нашего внутреннего API.
Наконец, мы включаем файл runtime-env.js, сгенерированный на странице index.html нашего приложения, и меняем атрибут baseURL axios непосредственно на наш API, как показано ниже (подсказка: использование прокси-сервера, такого как nginx , вы бы хотели, чтобы это было просто «/api», чтобы избежать запросов, связанных с разными источниками):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <script src="runtime-env.js"></script> </head> <body id="page-top"> <div id="app"></div> </body> </html> const instance = axios.create({ baseURL: window._env_.BACKEND_API_URL, // process.env.BACKEND_API_URL, timeout: 1000, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } });
Действие!
Используя docker-compose файлы yaml и .env, как показано ниже:
# docker-compose.yaml version: "3.2" services: frontend: image: frontend:1.0 ports: - "8001:80" environment: - "BACKEND_API_URL=http://localhost:4567/api" - "DUMMY_VAR_1=some-value-1" - "DUMMY_VAR_2=some-value-2" # .env file BACKEND_API_URL=http://localhost DUMMY_VAR_1=1234 DUMMY_VAR_2=5678
Давайте проверим конфигурацию nginx для нашего работающего образа контейнера, чтобы увидеть, правильно ли заменена наша директива proxy_pass:
Давайте также проверим исходный код нашей веб-страницы, чтобы увидеть набор объектов окна без конечной точки внутреннего API и фиктивных переменных:
Все вышеперечисленные значения доступны в вашем внешнем коде с помощью window._env_.
, за которым следует имя переменной.
Те же идеи можно использовать для Angular, обновив файл environment.ts и получив эти значения с помощью файла runtime-env.js
:
export const environment = { apiUrl: window["_env_"]["BACKEND_API_URL"], ... };
Используя эту настройку, если мы хотим изменить URL-адрес Backend API на другую конечную точку или любую другую переменную в этом отношении, мы просто редактируем значения переменных в нашем файле создания докеров, перезагружаем контейнер, чтобы получить изменения и избежать перестроения с нуля.