我把上市公司的微前端部署发布流程实现了?记一次组内技术分享 (附代码)

3,671 阅读9分钟

预计时间:45分钟。

会议名称:基于Qiankun的微前端部署发布流程

最终目标:了解公司微前端中台的前后端部署流程(重点关注过程)。

tips:为了搞清楚公司的前后端部署原理,我花了一个早上狠狠看了下k8s相关知识,并用单机版的minikube启动了第一个k8s前端应用。最终认真研究了公司jenkins配置,逐行代码逐个功能的研究,最终理解了整个中台应用前后端是如何部署并上线的。这是2023年初做的组内技术分享,今天看到了正好记下备忘 emmm。

一、相关技术

搭建整个流程,需技术掌握程度:了解使用。

  • 前端:QianKun、TypeScript、React、Webpack5
  • 后端:Mysql8、NestJs
  • 运维:Nginx、Docker、K8s

(注:受限于篇幅,中间各类技术只会提个大概帮助了解过程,具体实现需单独查阅资料)

二、访问流程(关键)

主要分三个步骤:

  • 页面请求。当发起一个请求到服务器,服务器会通过nginx进行转发。
  • nginx转发。nginx根据域名来转发到私有gitlab、私有docker镜像源和k8s集群(用来部署应用容器)。
  • k8s内部转发

k8s集群内部部署了两类应用:gateway(网关)和 service(服务):

(1)gateway内部部署一个nginx,负责将k8s外的请求转发到k8s内部的服务

(2)服务是k8s的一个节点,每个节点里面部署一个子应用

这里有几个名词:nginx、k8s、docker。

nginx是什么?一个高性能的Web服务器,可以做正向代理、反向代理。

k8s是什么?一个可以自动部署、管理容器的编排系统。

docker是什么?一个开源的应用容器引擎。

三、流程图分析

1. 页面请求

页面地址请求类型分为这几种:

1.1. 为什么子应用地址和主应用前缀都是/console?

主应用这样做(/console),是没问题的,直接请求到主应用的前端网页。

子应用这样做(/console/app),是为实现在主应用页面(/console)下,去发起请求子应用(/app),使得主/子应用可以共存

1.2. 如果子应用前缀和主应用不一致会怎样?

如果前缀不一致,nginx会直接转发到子应用或主应用,主/子应用就不能一起出现。

2. Nginx转发

2.1. nginx介绍

Nginx (engine x) 是一个高性能的HTTP的web服务器,可以做正向代理、反向代理,同时也提供了IMAP/POP3/SMTP服务。

2.2. 正向代理/反向代理

正向代理:代理客户端,例如:Vpn。

反向代理:代理服务端,例如:负载均衡。

2.3. nginx 转发配置(关键)

根据不同域名转发到不同的端口服务。

(后面 k8s 的 gateway 是根据前缀转发到不同的子应用)

# 完整nginx配置文档参考地址:
# https://zhuanlan.zhihu.com/p/619165119 

server {
	listen 80;  # 监听80端口
	server_name www.demo.com; #监听域名

	location / { # 代理路径。匹配前缀为 / 的地址
        proxy_pass http://192.168.49.2:30001/; # 转发到 k8s 的 GateWay 服务
	}
}

server {
	listen 80;
	server_name git.demo.com;

	location / {
        proxy_pass http://www.demo.com:9000/;
	}
}

server {
	listen 80;
	server_name jenkins.demo.com;

	location / {
        proxy_pass http://www.demo.com:8080/;
	}
}

server {
	listen 80;
	server_name registry.demo.com;
	client_max_body_size 0; # 设置不限制请求内容大小,因为docker镜像很大。

	location / {
        proxy_pass http://www.demo.com:5000/;
	}
}

3. k8s内部部署技术点分析

我们这里使用k8s + docker来实现容器管理。

k8s内部的gateway服务负责集群内外的沟通。

(除gateway外其余服务在k8s外是不可见的)

3.1. docker 介绍

Docker是一个开源的应用容器引擎,基于Go语言。

它能够在各种不同的操作系统上运行你的应用,而无需配置运行环境。

我们这里使用它来部署:前端应用服务、后端Api服务。

3.1.1. docker组成

docker通过镜像来创建一个又一个的容器。

每个容器里面会运行n个应用,同时容器可以映射到宿主机端口、文件/目录。

3.1.2. docker构建镜像

docker build -t [镜像名称] .。

3.1.3. docker从镜像创建容器

docker run -itd --name [容器名称] -p [本机端口]:[容器端口] -v [本机目录]:[容器目录] [镜像名称]

  • -it // 创建容器后会进入容器,退出会关闭(如果单独使用-i 或 -t,则不能完整的在容器内输入和打印结果。-i表示输入源打开,-t表示分配控制台)
  • -d // 创建容器后运行在后台。
3.1.4. docker进入容器

docker exec -it [容器名称] bash

或者

docker attach [容器名称]

区别:使用exec进入容器后退出,容器不会关闭。使用attach,容器会关闭

3.1.5. docker创建一个tag

docker tag [源镜像] [目标镜像]

3.1.6. docker数据持久化

通过 -v [本机目录]:[容器目录]的形式,让容器的指定目录同步挂载到宿主机。

这样删除容器再重新创建,数据不会丢失。

3.2. k8s介绍

(说明:限于篇幅,此处只介绍必要的k8s组件,还有更多的k8s组成单元可参考官网文档。)

k8s是一个容器管理编排系统。简单理解就是管理多个应用启动、访问的。

k8s通常会搭建一个集群,一个集群里面有多台主机,一台主机就是一个Node。

Node 是一个服务器节点,跑了一个K8S应用,里面运行了多个pod。

Pod 是k8s中最小的计算单元,里面部署有多个docker容器。

Service 用来代理网络服务,负责集群内的网络通信。

(还有Deployment可用来管理pod副本,支持版本回滚,错误重启等一系列强大的功能,感兴趣可以查看官网文档)

3.2.1. k8s 获取所有应用状态

(按照官网文档安装好k8s,k会被设置为 kubectl 的别名)

k get all

3.2.2. k8s 查看pod运行状态(查询报错时用到)

k logs [pod名称]

3.2.3. k8s 进入某个pod

k exec -it [pod名称] bash

3.2.4. k8s 删除指定服务

k delete all --all # 删除全部

k delete pod [pod名称]

k delete service [service名称]

...

3.3. k8s 内部部署一个容器并暴露到外界(实战)

这里,我们在k8s内部部署一个nginx应用,并在外界访问30002端口映射到容器内部80端口。

我们通过yaml文件来部署容器:

  • 创建一个Service。代理从宿主机访问到Pod。
  • 创建一个Pod。用来部署容器。

(注意:k8s yaml文件内部不允许使用下划线)

# port:集群内部访问service端口
# containerPort: pod内部容器端口
# nodePort: 外部访问k8s集群内部service的端口
# targetPod: pod的端口

apiVersion: v1				# api版本号 - v1
kind: Service				# 类型 - service
metadata:
  name: i-nginx-service
  labels:
    app: i-nginx-service
spec:
  type: NodePort			# 指定service类型。nodePort:暴露端口到集群外。默认类型仅集群内访问。
  ports:
  - port: 80			# 集群内部service的端口,通过 i-nginx-service:80 访问
    targetPort: 80		        # 匹配pod的端口
    nodePort: 30002	        # 暴露到集群外部端口(宿主机端口)
  selector:			# 匹配到pod
    app: i-nginx
---
apiVersion: v1
kind: Pod
metadata:
  name: i-nginx-pod
  labels:
    app: i-nginx
spec:
  containers:
  - name: i-nginx
    image: nginx
    ports:
    - containerPort: 80		# 容器内访问端口

我们可以执行: kubectl apply -f k8s.yaml 来运行这个容器,

测试是否成功,我们可以在宿主机内(即虚拟机内)执行:curl [当前k8s - ip地址]:30002,会打印nginx首页消息。

也可以在宿主机nginx配置转发,再访问 www.demo.com:30002即可。

server {
	listen 30002;

  location / {
      proxy_pass http://[当前k8s - ip地址]:30002;
  }
}

我们只需要知道,这里的一系列操作只是为了:在外面能访问k8s里面的容器

4. 部署上线流程

4.1. 前端部署

前端服务部署的流程:

  • 打包代码到dist
  • 使用Dockerfile创建一个docker镜像
  • 镜像内部会使用 nginx 启动一个服务器,用来代理 dist 文件夹
  • 上传镜像到私有docker镜像源
  • jenkins上传yaml文件到远程服务器
  • 在远程服务器使用 kubectl 执行yaml文件去重新创建pod、service(kubectl是k8s的命令行工具)
  • 这时,k8s执行yaml文件时会去私有docker镜像源拉取镜像。
  • 通过 [service名称]:80 访问 pod中页面。

4.2. 后端部署

后端服务和前端不一样,是依靠启动 node 服务的形式来提供接口服务的(需要安装依赖才能运行)

后端服务部署的流程:

  • 打包到dist
  • 使用Dockerfile创建一个docker镜像
  • (在创建容器运行时,安装packages.json中的全部依赖,并执行npm命令启动node服务)

// 后面步骤和前端服务就一样的了。

  • 上传镜像到私有docker镜像源
  • jenkins上传yaml文件到远程服务器
  • 在远程服务器使用 kubectl 执行yaml文件去重新创建pod
  • 这时,k8s执行yaml文件时会去私有docker镜像源拉取镜像。
  • 通过 [service名称]:80 访问 pod中页面。

4.3. gateway网关转发

这个服务的作用:用来转发宿主机发送到k8s内部的请求。

因此,这里不涉及到任何代码。

仅仅在k8s中创建了一个service和一个pod,在里面运行nginx来负责代理转发。

# 网关 - 负责转发到子应用
# 每次新增插件,都要在这里注册代理地址,并启动一次构建

APP=micro-gateway		        # 应用名称
K8S_APP=$APP			# K8S应用名称
K8S_NAMESPACE=micro-k8s-front	# K8S命名空间(用于资源隔离,常用作区分测试、正式环境)
K8S_YAML=gateway.yaml		# K8S的yaml文件
K8S_DIR=/data/k8s			# 服务器存放k8s的yaml文件目录

DOCKER_TAG=${BUILD_VERSION}		# docker镜像标签
DOCKER_NAME=registry.tjh.com/$APP	        # docker镜像名称(如果前面有地址,则推送到该地址,而非官方docker hub)
DOCKER_IMAGE=$DOCKER_NAME:$DOCKER_TAG      # 最终的完整docker镜像标识

# nginx配置文件创建
cat << EOF > default.conf
server {
	listen 80;
    
    # 前端
    location =/ {
		proxy_pass http://micro-console-front-service:80/;
    }
    
    location /console {
		proxy_pass http://micro-console-front-service:80/;
	}

	location /app {
		proxy_pass http://micro-app-front-service:80/;
	}
    
    location /app-react {
		proxy_pass http://micro-app-react-front-service:80/;
	}
    
    location /gunboss {
		proxy_pass http://micro-gunboss-front-service:80/;
	}

	# 后端
    location /home/api/ {
    	proxy_pass http://api-back-service/;
	}
}
EOF

# 新建gateway docker镜像
cat << EOF > Dockerfile
FROM nginx
COPY default.conf /etc/nginx/conf.d/
EXPOSE 80
EOF

docker build -t ${DOCKER_IMAGE} .
docker push ${DOCKER_IMAGE}

# 执行k8s构建网关
cat << EOF > $K8S_YAML

apiVersion: v1
kind: Service
metadata:
  name: $K8S_APP-service
  namespace: $K8S_NAMESPACE
  labels:
    app: $K8S_APP-service
spec:
  type: NodePort # 方便集群外访问
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30001 # 对外暴露的PORT
  selector:
    app: $K8S_APP
---
apiVersion: v1
kind: Pod
metadata:
  name: $K8S_APP
  namespace: $K8S_NAMESPACE
  labels:
    app: $K8S_APP
spec:
  containers:
  - name: $K8S_APP
    image: $DOCKER_IMAGE
    ports:
    - containerPort: 80
EOF


# 远程执行 k8s
ssh root@172.16.231.133 mkdir -p $K8S_DIR
scp $K8S_YAML 172.16.231.133:$K8S_DIR
ssh root@172.16.231.133 kubectl apply -f $K8S_DIR/$K8S_YAML

4.4. QianKun为什么子应用打包的publicPath要加子应用前缀?

为了 gateway 能正确代理到子应用的资源。

(子应用不止一个,如果不加前缀就会都代理到同一个目录 / 去了)。

/app -> 代理到子应用 index.html

/app/main.js -> 代理到子应用的 main.js

/app/assets/xxx.icon -> 代理到子应用的 assets目录下

四、快速部署上线一个子应用(实战)

这里,我演示开发一个前端应用,并实现快速部署到服务器,并将在中台(gunboss)开启这个应用。

... (此处为屏幕演示操作,具体步骤

只需要下面几步:

  • 推送一个前端项目git代码
  • jenkins 新建前端 service 流水线。
  • jenkins 修改 gateway 中转发。
  • gunboss 网站注册应用/配置菜单。
  • 打开网站。

五、相关资料

完整实现代码(前后端)