预计时间: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. 页面请求
页面地址请求类型分为这几种:
- 前端页面(主应用):www.demo.com/console/
- 前端页面(子应用):www.demo.com/console/app
- 后端服务:www.demo.com/home/api
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 网站注册应用/配置菜单。
- 打开网站。