sat*_*nik 7 base-path docker kubernetes angular
我知道这个问题已经以多种不同的形式被提出。但我已经阅读了我能找到的关于此事的所有帖子和文章,但无法根据我们面临的先决条件解决我的问题。因此,我想首先描述我们的问题并给出我们的动机,然后描述我们已经尝试过或正在使用的不同方法,尽管它们并不令人满意。
我们的设置可以说是有些复杂。我们的 Angular 应用程序部署在多个 Kubernetes 集群上,并通过不同的路径提供服务。
集群本身位于代理后面,并且 Kubernetes 本身添加了一个名为 Ingress 的代理。因此,我们的常见设置可能如下所示:
当地的
http://本地主机:4200/
本地集群
https://kubernetes.local/angular-app
发展
https://ourcompany.com/proxy-dev/kubernetes/angular-app
分期
https://ourcompany.com/proxy-staging/kubernetes/angular-app
顾客
https://kubernetes.customer.com/angular-app
应用程序始终以不同的基本路径提供服务。由于这是 Kubernetes,我们的应用程序是使用 Docker 容器部署的。
通常,人们会使用ng build
转译 Angular 应用程序,然后使用如下所示的 Dockerfile:
FROM nginx:1.17.10-alpine
EXPOSE 80
COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY dist/ /usr/share/nginx/html/
Run Code Online (Sandbox Code Playgroud)
要告诉 CLI 如何设置 base-href 以及从何处提供资源,可以使用Angular CLI 的--base-href
和选项。--deploy-url
不幸的是,这意味着我们需要为每个要部署应用程序的环境构建一个 Docker 容器。
此外,我们必须注意在这个预构建过程中设置其他环境变量,例如在environments[.prod].ts
每个部署环境中。
我们当前的解决方案包括在部署期间构建应用程序。这是通过使用另一个 Docker 容器作为所谓的initContainer来实现的,该容器在 nginx 容器启动之前运行。
该 Dockerfile 如下所示:
FROM node:13.10.1-alpine
# set working directory
WORKDIR /app
# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install and cache app dependencies
COPY package*.json ./
RUN npm install
COPY / /app/
ENV PROTOCOL http
ENV HOST localhost
ENV CONTEXT_PATH /
ENV PORT 8080
ENV ENDPOINT localhost/endpoint
VOLUME /app/dist
# prebuild ES5 modules
RUN ng build --prod --output-path=build
CMD \
sed -i -E "s@(endpoint: ['|\"])[^'\"]+@\1${ENDPOINT}@g" src/environments/environment.prod.ts \
&& \
ng build \
--prod \
--base-href=${PROTOCOL}://${HOST}:${PORT}${CONTEXT_PATH} \
--deploy-url=${PROTOCOL}://${HOST}:${PORT}${CONTEXT_PATH} \
--output-path=build \
&& \
rm -rf /app/dist/* && \
mv -v build/* /app/dist/
Run Code Online (Sandbox Code Playgroud)
将此容器作为initContainer合并到 Kubernetes 部署中,允许使用正确的 base-href、deployment-url 构建应用程序,并根据此应用程序部署的环境替换特定变量。
这种方法工作得很好,除了它产生的一些问题:
ng build
当然,这总是运行命令。ng build
为了防止每次容器已经运行前面RUN
指令中的命令来缓存这些模块时,它都会构建 ES 模块。由于这两个问题,我们决定将逻辑从 Kubernetes/Docker 直接移至应用程序本身。
经过一番研究,我们偶然发现了APP_BASE_HREF
InjectionToken。因此,我们尝试遵循网络上的不同指南,根据应用程序部署的环境来动态设置它。具体首先要做的是:
config.json
命名的文件/src/assets/config.json
{
"basePath": "/angular-app",
"endpoint": "https://kubernetes.local/endpoint"
}
Run Code Online (Sandbox Code Playgroud)
main.ts
文件中添加了代码,通过父历史记录递归地探测当前window.location.pathname
以查找config.json
.FROM nginx:1.17.10-alpine
EXPOSE 80
COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY dist/ /usr/share/nginx/html/
Run Code Online (Sandbox Code Playgroud)
app.module.ts
theAPP_BASE_HREF
被初始化为APP_CONFIG
FROM node:13.10.1-alpine
# set working directory
WORKDIR /app
# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install and cache app dependencies
COPY package*.json ./
RUN npm install
COPY / /app/
ENV PROTOCOL http
ENV HOST localhost
ENV CONTEXT_PATH /
ENV PORT 8080
ENV ENDPOINT localhost/endpoint
VOLUME /app/dist
# prebuild ES5 modules
RUN ng build --prod --output-path=build
CMD \
sed -i -E "s@(endpoint: ['|\"])[^'\"]+@\1${ENDPOINT}@g" src/environments/environment.prod.ts \
&& \
ng build \
--prod \
--base-href=${PROTOCOL}://${HOST}:${PORT}${CONTEXT_PATH} \
--deploy-url=${PROTOCOL}://${HOST}:${PORT}${CONTEXT_PATH} \
--output-path=build \
&& \
rm -rf /app/dist/* && \
mv -v build/* /app/dist/
Run Code Online (Sandbox Code Playgroud)
重要提示:我们使用这种方法而不是使用 the APP_INITIALIZER
,因为当我们尝试时, the 的提供程序APP_BASE_HREF
总是在 the 的提供程序之前运行APP_INITIALIZER
,因此 theAPP_BASE_HREF
始终是未定义的。
不幸的是,这仅在本地有效,在应用程序被代理时不起作用。我们在这里观察到的问题是,当应用程序最初由网络服务器提供服务而没有指定base-href和deploy-url时,应用程序显然会尝试从“/”(根)加载所有内容。
但这也意味着它尝试从那里获取角度脚本(又称为main.js
、vendor.js
)runtime.js
以及所有其他资产,因此我们的代码实际上都没有运行。
为了解决这个问题,我们稍微调整了代码。
我们没有让 Angular 探测服务器,而是config.json
直接将代码放在里面index.html
并内联它。
像这样,我们可以找到基本路径,并将 html 中的所有链接替换为前缀链接,以至少加载脚本和其他资源。这看起来如下:
{
"basePath": "/angular-app",
"endpoint": "https://kubernetes.local/endpoint"
}
Run Code Online (Sandbox Code Playgroud)
此外,我们必须调整提供程序内部的代码,APP_BASE_HREF
如下所示:
export type AppConfig = {
basePath: string;
endpoint: string;
};
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
export async function fetchConfig(): Promise<AppConfig> {
const pathName = window.location.pathname.split('/');
for (const index of pathName.slice().reverse().keys()) {
const path = pathName.slice(0, pathName.length - index).join('/');
const url = stripSlashes(`${window.location.origin}/${path}/assets/config.json`);
const promise = fetch(url);
const [response, error] = await handle(promise) as [Response, any];
if (!error && response.ok && response.headers.get('content-type') === 'application/json') {
return await response.json();
}
}
return null;
}
fetchConfig().then((config: AppConfig) => {
platformBrowserDynamic([{ provide: APP_CONFIG, useValue: config }])
.bootstrapModule(AppModule)
.catch(err => console.error('An unexpected error occured: ', err));
});
Run Code Online (Sandbox Code Playgroud)
现在发生的事情是,它加载页面,将源 URL 替换为前缀 URL,加载脚本,加载应用程序并设置APP_BASE_HREF
.
路由似乎可以工作,但所有其他逻辑(例如加载语言文件、降价文件和其他资产)都不再工作。
我认为该--base-href
选项实际上设置了APP_BASE_HREF
但该--deploy-url
选项的作用我无法找到。
大多数文章和帖子都指出,指定 base-href 就足够了,资产也可以工作,但情况似乎并非如此。
考虑到所有这些,我的问题是如何设计一个 Angular 应用程序,使其绝对能够设置其 base-href 和 deploy-url,以便所有 Angular 功能(如路由、翻译、导入()等)都像我一样工作是通过 Angular CLI 设置的吗?
我不确定我是否提供了足够的信息来完全理解我们的问题是什么以及我们的期望,但如果没有,我会尽可能提供。
我们花了一些时间,但最终设法找到一个足够简单的解决方案来涵盖我们在问题中提出的所有要点。
该解决方案与最初的方法非常接近,即离线构建 Angular 应用程序并将内容复制到nginx
容器内,并与之前在 init 容器中使用的构建过程混合在一起。
我们的解决方案如下所示。我们首先离线构建应用程序,并在 thebase-href
和 thedeploy-url
应该在后面的位置注入一些可识别的字符串。
ng build --prod --base-href=http://recognisable-host-name/ --deploy-url=http://recognisable-host-name/
Run Code Online (Sandbox Code Playgroud)
生成的runtime*.js
文件index.html
将在代码中包含这些内容,并作为要加载的资源的基础。
然后在第二阶段,标准 Angular 应用程序的 Dockerfile 改编自
FROM nginx:1.17.10-alpine
EXPOSE 80
COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY dist/ /usr/share/nginx/html/
Run Code Online (Sandbox Code Playgroud)
像这样的新版本
FROM nginx:1.17.10-alpine
EXPOSE 80
COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY dist/ /usr/share/nginx/html/
ENV CONTEXT_PATH http://localhost:80
ENV ENDPOINT http://localhost/endpoint
ENV VARIABLE foobar
CMD \
mainFiles=$(ls /usr/share/nginx/html/main*.js) \
&& \
for f in ${mainFiles}; do \
envsubst '${ENDPOINT},${VARIABLE}' < "$f" > "${f}.tmp" && mv "${f}.tmp" "$f"; \
done \
&& \
runtimeFiles=$(grep -lr "recognisable-host-name" /usr/share/nginx/html) \
&& \
for f in ${runtimeFiles}; do sed -i -E "s@http://recognisable-host-name/?@${CONTEXT_PATH}@g" "$f"; done \
&& \
nginx -g 'daemon off;'
Run Code Online (Sandbox Code Playgroud)
首先,我们传递我们想要进行的替换。首先CONTEXT_PATH
是完整的基本路径,它将替换http://recognisable-host-name
我们之前注入的字符串,首先找到包含该字符串的所有文件,然后用命令替换它sed
。
envsubst
也可以通过利用和替换文件中所有提到的这些变量来替换可能特定于环境的其他变量main*.js
。因此,environments.prod.ts
准备如下:
FROM nginx:1.17.10-alpine
EXPOSE 80
COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY dist/ /usr/share/nginx/html/
Run Code Online (Sandbox Code Playgroud)
这照顾了我比赛的所有要点,即:
我希望它对其他人有帮助,特别是因为当我研究这个主题时,我发现了几十篇文章,但没有一篇满足所有标准,或者需要大量复杂、低效或难以维护的自定义代码。
编辑:
如果有人感兴趣,我建立了一个演示项目,可以在其中测试此解决方案:
https:
//gitlab.com/satanik-angular/base-path-problem
归档时间: |
|
查看次数: |
3398 次 |
最近记录: |