根据各环境的不同,前端部署的镜像可能会有多个,但只是请求API的不同而已,怎么通过一个镜像来适配多个环境的代码
目前,我们的前端应用部署镜像,都是根据不同的环境,生成不同的镜像,即我们的前端应用环境,在编译时就已经生成.一般应用上没有问题,但是不同的环境镜像导致的问题是,无法很好地做迁移,即同一个镜像,无法应用到不同的环境中去.比如一个最常见的场景就是,我们的请求API要根据不同的环境来变化.
要解决这个问题,我们就要把我们的运行环境变量,传入到前端应用中去. 但是,我们的前端应用是跑在浏览器端的,如何把变量传递进去呢? 办法是有的,其一就是在服务器端就把前端应用的变量先改好,其二是把环境变量也一起传给浏览器,挂在window对象下面,其三是利用nginx.conf配置nginx进行接口转发.
一 先来说说在服务端修改前端应用变量这个方法
我们先来看看前端应用打包,一般地,我们前端应用都是这样构建:
// package.json
scripts:{
"xxx-stage": "cross-env BUILD_TYPE=stage MOCK=none umi build",
}
以上可见,环境变量stage
是在构建过程中就传入了项目中,在使用时,我们可以通过种方法去使用我们的环境变量:
const BaseApi= {
dev: {
env: 'dev',
loginUrl: '//xxx-common-app.dev.xxx.com',
},
test: {
env: 'test',
loginUrl: '//xxx-common-app.test.xxx.com',
},
stage: {
env: '(stage)',
loginUrl: 'https://xxx-common-app.stage.xxx.com',
},
prod: {
env: '',
loginUrl: 'https://xxx-common-app.xxx.com',
}
};
export defaut BaseApi[BUILD_TYPE]
实际上,构建完成后,这个BUILD_TYPE
就是字符串的值 stage
,简单说,就是写死了.在api地址里使用的,实际就是这个对象:
{
env: '(stage)',
loginUrl: 'https://xxx-common-app.stage.xxx.com',
}
好了,在说我们现在这个方法,之前,先说一下docker的环境变量.docker可以通过启动时,传入环境变量,比如:docker run -e NODE_ENV='stage' -e BASEAPI='``[stage.xxx.com](http://stage.xxx.com)``' -p 8808:80 vuedocker
这样就把NODE_ENV和BASEAPI这两个环境变量传给了容器. 看上去比较令人激动....然而...有什么用呢?WEB应用的代码都构建完成了,这两个是在服务端的,又不是客户端.
这里就回到我们的方法了. linux提供了一个命令: envsubst
通过这个命令,可以修改容器里的文件. 好,我们简单梳理一下逻辑:
- 前端构建,生成dist文件夹及前端文件,比如index.html / .js / .css等文件;假设api地址目前是
api.dev.xxx.com
- 构建docker镜像,目前api地址还是
api.dev.xxx.com
- 启动镜像,并在启动后执行脚本,(这步可以在dockfile文件里配置
ENTRYPOINT [ "sh", "/nginx-entrypoint.sh" ]
),脚本根据传入的环境变量stage
,暴力地遍历docker镜像里的所有js文件,把api.dev.xxx.com
修改为api.stage.xxx.com
- 用户访问时,拿到的请求的api就是
api.stage.xxx.com
了.
这个暴力修改的方法是在功能上是可行的,不过涉及到修改构建后的文件,感觉不太放心,所以可以发展为第二个方法
二 把变量挂在window对象下
这个方法也用到上面讲的一个命令,就是envsubst
不同的是,我们不用遍历所有的js,我们只需要修改我们的配置文件即可.以vue项目为例,说一下步骤:
- 在public文件夹下,创建一个uri.config.template.js文件,这个文件以用来线上替换用的模板,内容如下:
window.my_app_name_env= {
NODE_ENV: '${NODE_ENV}',
BASEAPI: '${BASEAPI}',
};
看到上面的这两个${NODE_ENV}
${BASEAPI}
了吧,就是在docker里使用envsubst
来替换环境变量的.
此外,我们因为需要本地开发,也需要一个配置文件uri.config.js,内容:
window.my_app_name_env= {
NODE_ENV: 'dev',
BASEAPI: '//api.dev.xxx.com',
};
在模板文件 index.html
文件里引入上面的uri.config.js
:
<script src="./uri.config.js"></script>
这个时候,在本地开发时,已经可以通过window对象拿到环境变量及api地址了.我们接着来实现线上的替换.
- 创建一个docker启动后的运行脚本:
nginx-entrypoint.sh
,内容如下:
WWW_DIR=/usr/share/nginx/html
CONFIGJSON_FILE_SRC="${WWW_DIR}/uri.config.template.js"
CONFIGJSON_FILE_DST="${WWW_DIR}/uri.config.js"
envsubst < "${CONFIGJSON_FILE_SRC}" > "${CONFIGJSON_FILE_DST}"
[ -z "$@" ] && nginx -g 'daemon off;' || $@
配置dockerfile
:
FROM registry.xxx.com:8500/library/nginx:alpine-v1 AS server
WORKDIR /usr/share/nginx/html
COPY dist ./
RUN apk add --no-cache gettext
COPY nginx-entrypoint.sh /
ENTRYPOINT [ "sh", "/nginx-entrypoint.sh" ]
好了,文件基本配齐了.
3.1 本地构建应用,即yarn build
生成dist文件夹及文件,里面应该包含了uri.config.template.js和uri.config.template.js这两个文件;
3.2 构建镜像,把dist打包成镜像;
3.3 部署后启动镜像,把环境变量传进去:docker run -e NODE_ENV='stage' -e BASEAPI='stage.xxx.com' -p 8808:80 vuedocker
,根据脚本 nginx-entrypoint.sh
里的内容,会替换掉这两个${NODE_ENV}
${BASEAPI}
为实际的值,并生成到文件 uri.config.js
中去,这些文件会一般传到客户端的浏览器,然后挂在window对象下,这样就可以拿到值了.
三 利用nginx.conf配置nginx进行接口转发
原理和上面的方法二差不多,要根据环境变量修改的,就不是uri.confg.js这个文件,而是nginx.conf这个文件.stage环境的简单配置如下:
# proxy_params
index index.html index.htm;
proxy_connect_timeout 3000s;
proxy_send_timeout 3000s;
proxy_read_timeout 3000s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
client_max_body_size 100m;
# nginx.conf
server
{
## 监听80端口
listen 80;
## 访问域名 WEB_DOMAIN为具体的域名,如:www.baidu.com
server_name WEB_DOMAIN;
index index.html index.htm;
## 匹配 路径中包含 /api/v1 的请求,并且/api/v1/后存在runtime或者workbench,将这些请求转发到具体的proxy_pass中
location ~ ^/api/ {
## 引入proxy_params文件的配置信息
include proxy_params;
## 代理转发地址,具体的 host 和 post 自己指定
proxy_pass https://api.stage.xxx.com;
## 重写,转发前,将url中的某些参数进行过滤
rewrite ^/api/(.*)$ /$1 break;
}
## 日志
access_log logs/*.log;
}
根据不同的环境,修改proxy_pass的值即可.
最后,我们的服务器集群是k8s管理的,这样操作起来就更简单的,以上的方法二和方法三,我们不需要在docker启动后运行修改的脚本,k8s可以在不同的环境中直接配置相应的文件,问题解决!