给前端看的Docker部署脚本入门

近期有个项目,是团队一个全栈JS的兄弟写的,最后按要求Docker部署,看了下脚本有很多误解,是由于前后端分离架构后对常见接口配置的误解造成的,这里做个说明,最后给个样例Docker-Compose.yaml

前后端分离后的一般部署架构

开发架构

一般前端开发都知道,我们在开发的时候都可以配置一个叫proxy的选项,来将数据接口转发给开发服务器,这个变量在webpack和vite里面都叫proxy。我们还可以配置一些规则来过滤哪些请求发给后端,例如百家饭OpenAPI的配置片段如下:

      server: {
        proxy: {
          '/aaa': {
            target: 'http://dev.rongapi.cn'
          },
          '^/bbb(?!/new)(.*).json': {
            target: 'http://dev.rongapi.cn'
          },
          '^/ccc/definition/.*': { 
            target: 'http://dev.rongapi.cn'
          },

那请问大家,目前的整个请求逻辑是什么样的?浏览器是向谁发起的请求?是直接发给后台的么?实际情况是这样的:

给前端看的Docker部署脚本入门_第1张图片

 

本地Dev服务器就是我们用yarn dev命令跑起来的服务器,这个服务器根据配置的规则使用代理模式向后端请求部分数据,并转发给前端。

部署架构

因为上面的这个逻辑,我司很多前端(就不说别人了)在部署之后,也认为有个全局可配置后端地址的地方完成了设置后端服务器地址的重任,这个重任大家想当然的认为是axios的baseURL。于是产生了很多围绕BASE_URL的有趣的设计,例如:

给前端看的Docker部署脚本入门_第2张图片

 大家认为,既然开发时设置了分离的后端,部署时也应该有分离的后端,而且明明现在都是前后端分离的架构,当然应该有不同的前后端,这是很容易顺着理解下来的东西。

我们来画一下如果设置不同baseURL的架构图

给前端看的Docker部署脚本入门_第3张图片

浏览器中的axios组件通过设置的不同的baseURL,向一个独立的服务器请求了数据,有没有发现,虽然看起来设置baseURL和proxy的表象一样,但是却产生了不同的数据流向?那直觉上这样的设置还是一样的么?

给前端看的Docker部署脚本入门_第4张图片

在jquery时代,前后端还没有这么分离的时候,架构其实像上图一样简单,著名的RoR框架有设置url结尾(.json/.html/.xml)来区分不同请求的设计,其主要的意图也是从所有的web请求中区分是页面请求还是数据请求。换句话说,一般的架构中,并没有所谓前后端不同请求的存在,都不过是服务器的html/js/css等资源请求。也就是说都是基于baseURL的同域内数据请求而已,在90%以上的情况下,baseURL其实都应该是空或者是类似”/xxx“的前缀。

相对URL和绝对URL

baseURL为空表示什么?例如我们的登录接口如果是/user/login的话。以下代码究竟访问了哪个网址:

axios.post("/user/login",{
   Name:"aaa",
   Password:"bbb"
})

一个完整的域名应该包含协议(http/https),域名,路径等多个部分,上面的/user/login显然并不完整,他不包含域名和协议的部分,我们把完整域名路径叫做绝对URL,而把/user/login这种则称为相对URL,当访问这类地址时,默认其实存在一个变化在里面,axois、js或者浏览器更底层,实际上把这一地址和目前页面地址进行了比较,添加了缺失部分,构建了一个绝对地址。

为什么要讲这个,这个是为什么baseURL在大部分情况下应该是空或者不包含域名的原因,因为前面讲了,所有的前端资源或者后端接口,逻辑上都是一个域下面的东西,相互之间访问,不需要绝对域名,相对域名即可。

将baseURL设置为某个域名,破坏了这种”同域特性“,不仅不直观,而且会使得域名变动等会需要重新修改配置。

错误的部署案例

下面来看我司一个错误的案例,基于上面所说的原理,我司某项目docker-compose.yaml部署规划如下:

给前端看的Docker部署脚本入门_第5张图片

 请注意,上面这个部署逻辑是错的

正确的部署逻辑

我们稍微修正一下

给前端看的Docker部署脚本入门_第6张图片

我们就以此用一个简单的node程序讲解一下正确的配置方法

第一步,准备后端:完成编译

后端需要通过Dockerfile打包,一是一些框架需要build,例如nest等,另外一个是nodejs不像前端最后都打包成文件了,很多node_module在运行时需要获取,如果直接使用node的官方image,需要确保后端部署服务器有下载依赖的条件,但很多部署环境都缺乏互联网连接条件,因此,建议还是打包成image再部署,同时配置一个js文件的volume映射,这样,只要依赖不变,我们是不需要重新打包的,这样除了首次部署需要上传完整的docker image文件之外,其他时候都不需要。

其他选项:

1)使用webpack for node,就是把后端程序也进行打包,但是似乎目前还没有特别完善的方案

2)使用官方node image,同时将node_module和source从部署机上映射进去,这也是种方案,这种方案就可以跳过编译这步了。

编译需要的Dockerfile

在后端目录中增加Dockerfile文件,内容如下

FROM node:last

WORKDIR /app
COPY . .
# 下面两步是关键
RUN npm i
# 如果是需要编译的框架,将自己的编译命令放到下方,第二步是将编译前的源文件删除,建议执行
# RUN npm run build
# RUN rm -rf ./src 

EXPOSE 3000
# 设置启动命令,根据自己的启动命令修改
CMD npm run start:prod

完成文件后,我们就可以通过

docker build -t my-nodejs-app .

命令或者生成对应的后端文件,并通过

docker save my-nodejs-app -o my-nodejs-app.tar

导出为文件,将导出文件上传到部署服务器,执行

docker load -i .\my-nodejs-app.tar

完成服务器导入,导入成功后,执行docker images命令,可以查看到对应的docker

将对应的后端代码也拷贝一份到服务器,命名为backend(从这里开始,拷贝到服务器的文件都应该在同一个目录,包括下面的docker-compose.yaml文件和前端文件)

第二部:前端准备,执行编译

前端编译这里就不多讲了,注意baseURL检查一下,确保设置的不是绝对路径,导出后也拷贝到服务器,放到和后端文件夹同一个父级文件夹里,命名为frontend

第三步:准备docker-compose文件

对应的docker-compose.yaml文件如下,有需要的同学可以直接拷走

version: '3'

services:
  backend: #后台服务
    hostname: app
    image: my-nodejs-app # 这里用nodejs的docker文件,建议先打包后端程序,再使用自己的docker image
    environment:
      NODE_ENV: production 
      MYSQL_HOST: mysqldb #下面mysql service的名称,如果没有数据库依赖,可以去除
    expose:
      - "3000" #内部使用3000端口用于向nginx暴露转发端口
    volumes:
      - ./backend:/home/node/app
    depends_on: #如果没有数据库依赖,可以去除
      - mysqldb
    networks:
      - frontend
      - backend
  mysqldb:
    image: mysql:8.0
    restart: always
    command: --default-authentication-plugin=mysql_native_password
    expose:
      - "3306"
    environment:
      MYSQL_ROOT_PASSWORD: "需要修改的mysql密码"
      MYSQL_DATABASE: "需要修改的mysql数据库名称"
    networks:
      - backend
    volumes:
      # 可以把mysql文件映射出来,也可以采取定义volume等方式
      - ./mysql_data:/var/lib/mysql
  proxy:
    image: nginx:1.21.6-alpine
    ports:
      - "8080:80"
    environment:
      - NGINX_PORT="80"
    networks:
      - frontend
    volumes:
      # 下面设置的是静态文件的路径
      - ./frontend:/usr/share/nginx/html
      # 下面是对应的nginx的配置文件
      - ./default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

 上面的例子中,我们另外会依赖于nginx和mysql的docker image,如果服务器没有自动下载条件需要提前在有条件的机器上通过docker save保存,并在服务器上通过docker load加载,部署时,该路径下除了docker-compose.yaml文件之外,另外需要三个内容:

1)frontend文件夹,前端代码打包生成的静态文件

2)backend文件夹,后端代码

3)default.conf nginx的配置文件

nginx配置

我们通过指定nginx的default配置来达到配置静态文件服务和转发的目的,参考内容如下:

# Complete Nginx Docker reverse proxy config file
server {
  listen 80;
  server_name localhost;

  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
  }

  location /api {
    proxy_pass http://backend:3000/api;
  }

} # End of Docker Nginx reverse proxy example file

上面内容中,/api的部分就是端口转发的部分,有多少个后端接口url就需要配置多少个,如果你的后端接口都是以/api/xxxx形式命名的话,有相同前缀的配置一个即可。

注意,这个地方部署后有可能碰到错误:”host not found in upstream“,通常是由于nginx服务先于后台服务启动造成的,需要检查Docker-compose.yaml文件中proxy段是否依赖后端服务,比如我们的例子里有:

 总结

从上面的文件可以看出,我们实际上最终在服务器,利用nginx的转发功能达到了和本地开发服务器一样的proxy效果。从中,我们希望讲清楚在前后端分离架构下的相对路径的概念,减少前端对于访问后端服务器的URL误解。最后给出了一个docker-compose文件样本,其中nginx的转发规则配置较为重要,希望部署nodejs前后端程序的小伙伴请留意那一部分。

你可能感兴趣的:(Javascript,docker,运维,容器)