Динамическое развертывание Angular.Один образ докера, многократное развертывание - PullRequest
0 голосов
/ 01 февраля 2019

Мне нужно найти способ динамического развертывания приложения Angular, чтобы один и тот же образ докера позволил мне выполнить несколько развертываний (разработка, подготовка, производство), в которых изменен URL-адрес доступа.

IПодаю заявку на Angular 7 с использованием Angular-Cli 6.4.1.Для развертывания я создаю многоступенчатый контейнер Docker, в котором я создаю образ и настраиваю его с помощью nginx.

Проблема заключается в том, что для развертывания мы используем частный реестр, в котором мы используем прокси-сервер, который обрабатывает перенаправления, поэтомучто мое приложение будет развернуто по следующей схеме: {SERVER_HOST}: {PORT} / {SERVER_LOCATION}

Когда я пытаюсь получить доступ к этому URL, загружается только index.html, так как остальные ресурсысвязанный с базовым путем "/".

Angular предоставляет аргумент в сборке (--base-href), который позволяет изменить путь всего приложения, однако это не помогает, так как мне нужнотот же образ Docker, позволяющий мне выполнять различные развертывания, так что параметр {SERVER_LOCATION} не всегда одинаков.

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

В качестве обходного пути я решил создать в индексе функцию, которая выполняет запрос ajax, который собирает параметры конфигурации для загрузки остальных ресурсов, однако мне это не нравится, поскольку он ломаетсяОперация Angular.

<-- This is working fine, but is not dynamic: -->
npm run build -- --prod --base-href https://myHost.net:8080/path/app/ --configuration=$configuration


<-- And this is working but is not Angular friendly -->

/** assets/data/appConfig.json */
{
    "SERVER_HOST": "https://myHost.net:8080",
    "SERVER_PATH": "/path/app/"
}

/** index.html */
<script>
  (function() {
    if (window.onerror) {
      loadConfig();
    }

    function loadConfig() {
      var xhttp = new XMLHttpRequest();
      var url = 'assets/data/appConfig.json';
      xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          var data = JSON.parse(this.response);
          window.appBaseHref = data.SERVER_PATH;
          document.getElementById("base").href = data.SERVER_PATH;
        }
      };
      xhttp.open('GET', url);
      xhttp.send();
    }
  })()
</script>

Мне сказали, что есть опция для перехода через прокси, но я не могу найти способ сделать это, я не знаю, как это настроить.Я думаю, что, возможно, в файле конфигурации nginx можно выполнить какие-то настройки, чтобы приложение «считывало» из предоставленного URL-адреса, а базовый ресурс приложения всегда можно было сохранить как "/".

В настоящее время мой файл nginx.conf:

server {
    listen 80;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html =404;
    }

    location /documentation {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /documentation/index.html =404;
    }
}

Где путь к местоположению '/' - это мое угловое приложение, а путь к местоположению '/ document' - это зарезервированный путь для документации проекта.

Любая помощь будет приветствоваться.

<--- Редактировать --->

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

Я настроил Nginx для добавления свойства env $ uri + $ basepath, но я понимаю, что отношения как раз противоположны.Мне нужен мой index.html для отправки запросов на маршрут прокси вместо пути '/', потому что из-за этого запрос даже не входит в журнал (прокси не перенаправляет меня, потому что у него нет префикса моегоapp)

Я приведу пример: мое приложение размещено по адресу: myHost: 8080 / myapp / front. Когда я ввожу этот URL-адрес (myHost: 8080 / myapp / front), приложение загружает индекс,но связанные ресурсы (стили, время выполнения, полифилы, сценарии и main) не загружаются, поскольку они отправляют запрос к: myHost: 8080 / {resourcePath} вместо myHost: 8080 / myapp / front / {resourcePath} enter image description here

На данный момент я не знаю, куда идти.Я мог бы получить результат, который я хочу поставить флаг --base-href / myapp / front, но я хочу, чтобы этот маршрут был динамическим и зависел от моей переменной среды системы VIRTUAL_HOST, которая, конечно, я не знаюво время сборки приложения

Далее я вставляю свой dockerfile и файл конфигурации nginx.

Dockerfile (Строки с комментариями - это предложения, которые не работают ожидаемым образом).

### STAGE 0: Based on Node.js, to build and compile Angular ###
FROM node:alpine as node

# Create app directory
WORKDIR /app

# Copy the dependencies to install once and let Docker use the cache for the next builds
COPY package*.json /app/

# Install all dependencies
RUN npm install

# Copy all the project into the image
COPY ./ /app/

# Argument to build the image according to the environment
ARG configuration=production

# Compiles our project
RUN npm run build -- --prod --configuration=$configuration

### STAGE 1: Based on Nginx, to have only the compiled app, ready for production with Nginx ###
FROM nginx:1.13.3-alpine

## Remove default nginx website
RUN rm -rf /usr/share/nginx/html/*


## From 'builder' stage copy over the artifacts in dist folder to default nginx public folder
COPY --from=node /app/dist/dsa-frontend /usr/share/nginx/html

# Add directive
# COPY nginx-custom.conf.template /etc/nginx/conf.d/default.conf.template

COPY nginx-custom.conf /etc/nginx/conf.d/default.conf

# CMD /bin/bash -c "envsubst '\$VIRTUAL_SERVICE_LOCATION' < nginx-custom.conf > /etc/nginx/conf.d/default.conf"

CMD ["nginx", "-g", "daemon off;"]

nginx-custom.conf

server {
    listen 80;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /myapp/front$uri /myapp/front$uri/;
        # try_files $uri $uri/ $uri$VIRTUAL_SERVICE_LOCATION $uri$VIRTUAL_SERVICE_LOCATION/ /index.html;
    }

    location /documentation {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /documentation/index.html =404;
    }
}

Но, похоже, ничего из этого не работает.

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

А теперь все работает нормально.

1 Ответ

0 голосов
/ 04 июля 2019

Я думаю, что мне удалось получить комплексное (хотя, вероятно, довольно хрупкое - не стесняйтесь рекомендовать улучшения!) Решение точно такой же проблемы: повторное использование одного образа Angular / NGINX Docker для нескольких сред (следовательно, для нескольких базовых)маршруты).

Кроме того, я хотел решить проблему доступа к внешним API, поэтому все, что запрашивает приложение, будет проходить через NGINX в контейнере, поэтому вам не нужно думать о CORS .В моем примере у меня есть две вещи:

  • Конечная точка сервера изображений
  • Конечная точка Socket.IO (WebSocket)

То, что я до сих пор делал не попробуй (но собираюсь):

  • Маршрутизация на стороне клиента (я ожидаю, что здесь могут появиться некоторые проблемы, но я думаю, что они решаемы, если вы используете конфигурацию разумным способом)
  • HTTPS (вероятно, не так сильно изменится из-за этого)

Конечный результат выглядит следующим образом:


Сборка и запуск контейнера локально (когда у вас все на месте)

ROOT_PATH - это параметр, который вы искали.

DNS_IP необходим из-за NGINX proxy_pass, вы, вероятно, захотите замените Google DNS на что-то другое .

docker build . -t fancy-web-app && \
docker run -it --rm \
    --name fancy-web-app-configured \
    --publish target=80,published=80 \
    --env ROOT_PATH='staging' \
    --env WEBSOCKET_PATH='socket.io' \
    --env IMAGE_PATH='image' \
    --env API_HOST='api.server.example.com' \
    --env API_PATH='api/path/on/api/server' \
    --env DNS_IP='8.8.8.8 8.8.4.4' \
    fancy-web-app

Я перечислю все, что вам нужно для ясности:

NGNIX

  1. nginx.conf

    Изменено во время запуска контейнера, в противном случае его нельзя использовать, так как оно имеет ссылки на enпеременные vironment.

Докер

docker-entrypoint.sh <- * * * Вот где происходит <em>magic .* * *

Dockerfile

Многоступенчатый , имеет docker-entrypoint.sh по умолчанию точка входа для этапа выполнения.

Угловой

index.html

С конфигурацией разработки по умолчанию, измененной во время запуска контейнера, так что она превращается в "конфигурацию выполнения".

app.conf.ts

Конфигурация, позволяющая абстрагировать конфигурацию в глобальную переменную и не вносить это уродство в ваши услуги

app.module.ts

Внедрение угловой зависимости, позволяющее ссылаться на конфигурацию в любом сервисе

some-angular.service.ts

Здесь вы используете введенную конфигурацию.

Угловые бонусные баллы

proxy.conf.json

Для работы на локальном хосте без контейнеров (эквивалент nginx.conf для разработки, будет использоваться WebPack / ng serve)

angular.json

Здесь вы можете указать конфигурацию прокси один раз и использовать ее везде по умолчанию.


Если есть какой-то интерес, я могу настроить репозиторий GitHub со всеми необходимыми частями на месте (пожалуйста, прокомментируйте / оцените, чтобы я мог видеть, что это необходимо).


NGINX

1.nginx.conf

server {
  listen 80;

  # Serve static files (HTML, CSS, JS)
  location /${ROOT_PATH} {
    # Using `alias` insead of `root` directive, so ${ROOT_PATH}
    # will be discarded and static file will be fetched straight
    # from the specified folder
    alias /usr/share/nginx/html;
    try_files $uri $uri/ /index.html =404;
  }

  # Re-route calls to external APIs
  location  ~ ^/${ROOT_PATH}/(${WEBSOCKET_PATH}|${IMAGE_PATH})/(.*) {
    resolver ${DNS_IP};
    proxy_pass http://${API_HOST}/${API_PATH}/$1/$2$is_args$args;
    proxy_http_version 1.1;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header Host ${API_HOST};
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
  }

}

Докер

2.docker-entrypoint.sh

#!/usr/bin/env sh
set -eu

# Inject environment variables into NGINX configuration
# List all variables to be substituted to avoid clashing with
# NGINX own variables: https://serverfault.com/questions/577370
envsubst \
    '${API_HOST} \
    ${API_PATH} \
    ${ROOT_PATH} \
    ${WEBSOCKET_PATH} \
    ${IMAGE_PATH} \
    ${DNS_IP}' \
    < /etc/nginx/conf.d/default.conf.template \
    > /etc/nginx/conf.d/default.conf
cat /etc/nginx/conf.d/default.conf

# Set correct HTML base tage, so static resources are fetched
# from the right path instead of the root path.
# NOTE: Trailing and leading slashes in base href are important!
# Using `~` separator to avoid problems with forward slashes
sed --in-place \
  's~<base href="/">~<base href="/'$ROOT_PATH'/">~' \
  /usr/share/nginx/html/index.html

# Set WebSocket API endpoint
# Using `~` separator to avoid problems with forward slashes
sed --in-place \
  "s~webSocketPath.*,~webSocketPath: \`/$ROOT_PATH/$WEBSOCKET_PATH\`,~" \
  /usr/share/nginx/html/index.html

# Set image API endpoint
# Using `~` separator to avoid problems with forward slashes
sed --in-place \
  's~imageBaseUrl.*~imageBaseUrl: `${window.location}'$IMAGE_PATH'`~' \
  /usr/share/nginx/html/index.html

cat /usr/share/nginx/html/index.html

exec "$@"

3.Dockerfile

# Produce static files
FROM node:10.15.3-alpine
WORKDIR /app
COPY ./package.json ./package.json
COPY ./package-lock.json ./package-lock.json
RUN npm set progress=false && \
    npm install --silent
COPY . /app
RUN npm run ng build -- --prod --output-path=dist

# Use NGINX to serve static files and re-route requests
FROM nginx:1.15.10-alpine
RUN rm -rf /usr/share/nginx/html/*
COPY --from=0 /app/dist/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf.template
COPY docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD [ "nginx", "-g", "daemon off;" ]
EXPOSE 80

Угловой

4.index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Fancy web app</title>
  <base href="/">
  <script>
    // These default values make sense when you `npm start`
    // They will be substituted during container startup
    // Using global variable on the window object here.
    // Is there a better way?
    window['app-config'] = {
      webSocketUrl: `${window.location.host}`,
      webSocketPath: `/socket.io`,
      imageBaseUrl: `${window.location}image`
    };
    console.log(window['app-config']);
  </script>

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <pdd-root></pdd-root>
</body>
</html>

5.app.conf.ts

import { Injectable } from '@angular/core';

@Injectable()
export class Configuration {
  webSocketUrl: string;
  webSocketPath: string;
  imageBaseUrl: string;
}

// Reading configuration out, so we have it Angular world 
export const AppConfiguration: Configuration = window['app-config'];

6.app.module.ts (вычеркнуты не интересные детали)

import <--snip-->

@NgModule({
  declarations: [
    <--snip-->
  ],
  imports: [
    <--snip-->
  ],
  providers: [
    <---snip-->
    SomeAngularService,
    { provide: Configuration, useValue: AppConfiguration },
    <---snip--->
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

7.some-angular.service.ts (вычеркнуты не интересные детали)

<--snip-->
export class BackendService {

  constructor(private configuration: Configuration) {

    const client = io(
      // Do not append route to the hostname
      // otherwise Socket.IO will treat it as `namespace`
      this.configuration.webSocketUrl,
      {
        // Do not auto-connect to exclude racing
        // between setup and connection
        autoConnect: false,
        // You have to specify route here
        path: configuration.webSocketPath
      });
      <--snip-->

Угловые бонусные баллы

8.proxy.conf.json

{
  "/socket.io/*": {
    "target": "http://localhost:3000/socket.io/",
    "ws": true,
    "secure": false,
    "logLevel": "debug",
    "pathRewrite": { "^/socket.io" : "" }
  },
  "/image/*": {
    "target": "http://localhost:3000/image/",
    "secure": false,
    "logLevel": "debug",
    "pathRewrite": { "^/image" : "" }
  }
}

9.angular.json -> см. последнюю строку перед фрагментом ниже

<--snip-->
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "fancy-web-app:build",
            "proxyConfig": "proxy.conf.json"
<--snip-->
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...