手把手,解决Docker容器化部署前端项目的传参问题

2,136 阅读5分钟

根据各环境的不同,前端部署的镜像可能会有多个,但只是请求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 通过这个命令,可以修改容器里的文件. 好,我们简单梳理一下逻辑:

  1. 前端构建,生成dist文件夹及前端文件,比如index.html / .js / .css等文件;假设api地址目前是api.dev.xxx.com
  2. 构建docker镜像,目前api地址还是api.dev.xxx.com
  3. 启动镜像,并在启动后执行脚本,(这步可以在dockfile文件里配置ENTRYPOINT [ "sh", "/nginx-entrypoint.sh" ]),脚本根据传入的环境变量stage,暴力地遍历docker镜像里的所有js文件,把api.dev.xxx.com 修改为 api.stage.xxx.com
  4. 用户访问时,拿到的请求的api就是api.stage.xxx.com 了.

这个暴力修改的方法是在功能上是可行的,不过涉及到修改构建后的文件,感觉不太放心,所以可以发展为第二个方法

二 把变量挂在window对象下

这个方法也用到上面讲的一个命令,就是envsubst 不同的是,我们不用遍历所有的js,我们只需要修改我们的配置文件即可.以vue项目为例,说一下步骤:

  1. 在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地址了.我们接着来实现线上的替换.

  1. 创建一个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可以在不同的环境中直接配置相应的文件,问题解决!