Я думаю, что мне удалось получить комплексное (хотя, вероятно, довольно хрупкое - не стесняйтесь рекомендовать улучшения!) Решение точно такой же проблемы: повторное использование одного образа 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
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-->