Kubernetes 部署
本小节,讲解如何将前端 + 后端项目,使用 Kubernetes(K8s),部署到一个已有的 K8s 集群中。
后端采用最小化的部署,包括 gateway-server 网关、system-server 服务、infra-server 服务。整体链路如下:
浏览器 ──NodePort 30080──> yudao-ui-admin(Nginx + 前端静态文件)
│ /admin-api/、/app-api/、/infra/ws
↓ Service:yudao-gateway:48080
yudao-gateway(Deployment)
│ 从 Nacos 获取服务实例(Pod IP),负载均衡转发
↓
yudao-system(Deployment) <──Feign──> yudao-infra(Deployment)
│ │
└──> 外部 MySQL + 外部 Redis
以上 3 个后端服务,都使用 Pod IP 注册到外部 Nacos(8848 / 9848)
注意:服务器的 IP 地址。
- K8s 节点 IP:192.168.0.213(用于 NodePort 访问)
- MySQL、Redis、Nacos 所在服务器的内网 IP:192.168.0.100
下属所有涉及到 IP 的配置,需要替换成你自己的。
友情提示:
- 本文是入门级的最小部署示例:每个服务 1 个副本,MySQL、Redis、Nacos 使用已就绪的实例,不在本文中部署有状态的中间件,也不涉及高可用。
- 本文的后端镜像、Spring 环境变量名,与仓库
script/docker-compose目录(《Docker Compose 部署》)保持一致,建议先用它在单机上跑通。不同的是,K8s 中的 MySQL、Redis、Nacos 是外部实例,地址、账号密码、命名空间请按实际填写。
# 1. 环境准备
# 1.1 前置条件
| 项目 | 要求 |
|---|---|
| K8s 集群 | 已就绪,并且可以使用 kubectl 命令管理。Pod 之间可以通过 Pod IP 互相访问(K8s 默认如此,如果配置了 NetworkPolicy 需要放行) |
| 镜像仓库 | Harbor、阿里云 ACR 等任一镜像仓库。构建机可以 docker push,K8s 节点可以拉取 |
| MySQL | 已创建 ruoyi-vue-pro 库,并导入项目的 sql/mysql/ruoyi-vue-pro.sql;有一个拥有该库权限的账号(下文以 yudao 为例);K8s 的 Pod 可以访问它的 3306 端口 |
| Redis | K8s 的 Pod 可以访问它的 6379 端口;有密码时记下密码 |
| Nacos | K8s 的 Pod 可以访问它的 8848 和 9848 两个端口;已创建 ID 为 dev 的命名空间(见「1.3 准备 Nacos」);开启鉴权时记下账号密码 |
| 构建机 | 安装了 JDK、Maven、Node、Docker,用于编译项目、构建并推送镜像 |
注意:MySQL、Redis、Nacos 的地址,不能填 `127.0.0.1` 或 `localhost`!
Pod 中的 127.0.0.1 指向的是 Pod 自己,而不是 K8s 节点或你的电脑。需要填写 Pod 可以访问到的内网 IP 或域名。
数据库的初始化,可以参考 《Docker 部署》 使用 Navicat 导入,也可以在后端项目根目录下,使用有建库权限的账号执行如下命令:
## ① 创建数据库
mysql -h 192.168.0.100 -P 3306 -uroot -p -e 'CREATE DATABASE IF NOT EXISTS `ruoyi-vue-pro` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'
## ② 导入项目的表结构与初始数据
mysql -h 192.168.0.100 -P 3306 -uroot -p ruoyi-vue-pro < sql/mysql/ruoyi-vue-pro.sql
## ③(可选)创建应用账号 yudao,只授权 ruoyi-vue-pro 库。已有账号时跳过这一步
mysql -h 192.168.0.100 -P 3306 -uroot -p --default-character-set=utf8mb4 <<'SQL'
CREATE USER 'yudao'@'%' IDENTIFIED BY '改成你的 MySQL 密码';
GRANT ALL PRIVILEGES ON `ruoyi-vue-pro`.* TO 'yudao'@'%';
SQL
- 第 ③ 步的密码,就是后文 Secret 中填写的 MySQL 密码。如果报
ERROR 1396错误,说明yudao账号已存在:请直接使用它的实际密码,不要重置。
# 1.2 确认后端分支与 JDK 版本
后端项目有 3 个分支,JDK 版本、运行镜像、Redis 配置前缀、Nacos 客户端版本都不同,必须一一对应,不能混用:
| 后端分支 | JDK | Spring Boot | 各服务 Dockerfile 的运行镜像 | Redis 环境变量 | Nacos 客户端 |
|---|---|---|---|---|---|
master | 8 | 2.7 | eclipse-temurin:8-jre | SPRING_REDIS_HOST、SPRING_REDIS_PORT、SPRING_REDIS_DATABASE | 2.x |
master-jdk17 | 17 / 21 | 3.5 | eclipse-temurin:17-jre | SPRING_DATA_REDIS_HOST、SPRING_DATA_REDIS_PORT、SPRING_DATA_REDIS_DATABASE | 3.x |
master-jdk25 | 25 | 4.x | eclipse-temurin:25-jre | 同 master-jdk17 | 3.x |
下文以 master-jdk17 分支为完整示例。如果你使用 master 分支(JDK 8),需要把下文 ConfigMap、Secret 中的 SPRING_DATA_REDIS_*,改成 SPRING_REDIS_*。运行镜像直接使用对应分支各服务自带的 Dockerfile 即可。
# 1.3 准备 Nacos
① Nacos 的安装,可参考 《Docker 部署》 的「1.5 安装 Nacos」小节。master-jdk17、master-jdk25 分支使用的是 Nacos 3.x 客户端(master-jdk17 为 3.0.3),建议使用 Nacos 3.x 服务端。
注意,script/docker-compose 中的内置 Nacos 没有开启鉴权,端口也不对外开放,只供 Compose 网络内使用,不要直接给 K8s 集群使用。
② 在 Nacos 控制台的 [命名空间] 菜单下,新建一个命名空间。注意!它的“命名空间ID”、“命名空间名”都要是 dev 噢!!!(Compose 的内置 Nacos 默认使用 public 命名空间;K8s 使用外部 Nacos,这里沿用其它部署文档的 dev。)
注意:这个命名空间,只给 K8s 中的服务使用!
服务注册到 Nacos 的是 Pod IP(例如说 10.244.1.23),它只能在 K8s 集群内部访问。
如果本地 IDE 启动的服务,也注册到了同一个 dev 命名空间,网关可能把请求转发到你电脑的 IP,或者集群外的服务被转发到 Pod IP,都会访问失败。此时,请新建一个独立的命名空间(例如说 k8s),并同步修改下文 ConfigMap 中的 SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE、SPRING_CLOUD_NACOS_CONFIG_NAMESPACE。
③ Nacos 的 9848 端口(gRPC 端口,等于 8848 + 1000)必须对 Pod 开放。只开放 8848 时,服务会启动失败或无法注册。如果 Nacos 部署在 K8s 集群内,它的 Service 也需要同时暴露 8848、9848 两个端口;集群模式的部署,可参考 《Nacos 集群部署说明》 (opens new window) 文档。
# 2. 构建并推送镜像
注意:镜像地址的替换!!!
下文的镜像地址,统一写作 registry.example.com/yudao/xxx:1.0.0,它是一个占位符,需要全部替换成你的镜像仓库地址。例如说:
- Harbor:
harbor.your-company.com/yudao - 阿里云 ACR:
registry.cn-hangzhou.aliyuncs.com/你的命名空间
每次发布,都使用新的版本号(例如说 1.0.1),不要使用 latest 标签,否则 K8s 不会感知到镜像变化,也不方便回滚。
先执行 docker login registry.example.com 命令,登录你的镜像仓库。
# 2.1 后端镜像
在后端项目的根目录下,执行如下命令:
## ① 编译后端,构建出各服务 target 目录下的 Jar 包
mvn clean package -Dmaven.test.skip=true
## ② 构建镜像:构建上下文是各服务的目录,使用该目录下项目自带的 Dockerfile
docker build --platform linux/amd64 -t registry.example.com/yudao/yudao-gateway:1.0.0 yudao-gateway
docker build --platform linux/amd64 -t registry.example.com/yudao/yudao-module-system-server:1.0.0 yudao-module-system/yudao-module-system-server
docker build --platform linux/amd64 -t registry.example.com/yudao/yudao-module-infra-server:1.0.0 yudao-module-infra/yudao-module-infra-server
## ③ 推送镜像
docker push registry.example.com/yudao/yudao-gateway:1.0.0
docker push registry.example.com/yudao/yudao-module-system-server:1.0.0
docker push registry.example.com/yudao/yudao-module-infra-server:1.0.0
- 项目自带的
Dockerfile中,执行的是COPY ./target/xxx.jar app.jar,所以构建上下文必须是各服务的目录。这与script/docker-compose/docker-compose.yml中各服务的build.context是同一批 Dockerfile。
友情提示:`--platform linux/amd64` 参数
如果你在 Apple 芯片的 Mac 上构建镜像,默认构建出 arm64 架构的镜像,在 amd64 的 K8s 节点上会报 exec format error 错误。请根据 K8s 节点的 CPU 架构,设置该参数。
# 2.2 前端镜像
和 Docker 部署时把前端文件放到宿主机目录不同,K8s 中推荐把前端构建产物,直接打包进 Nginx 镜像。
# 第一步,构建前端
前端的打包方式,和 《Linux 部署》 的「3. 部署前端」小节一致。不同的是,前端的“后端地址”要填写 K8s 中 Nginx 的访问地址,即 http://192.168.0.213:30080:
| 前端项目 | 配置文件 | 修改的配置项 | 打包命令 => 产物目录 |
|---|---|---|---|
yudao-ui-admin-vue3 | .env.prod | VITE_BASE_URL='http://192.168.0.213:30080',VITE_API_URL=/admin-api 保持不变 | pnpm build:prod => dist-prod |
yudao-ui-admin-vben | apps/web-antd/.env.production | VITE_BASE_URL=http://192.168.0.213:30080,VITE_GLOB_API_URL=/admin-api | pnpm build:antd => apps/web-antd/dist |
- 前端的
VITE_APP_CAPTCHA_ENABLE,要与下文 ConfigMap 中的YUDAO_CAPTCHA_ENABLE一致,默认都是true。 yudao-ui-admin-vue2同理,把VUE_APP_BASE_API改成 Nginx 的访问地址即可。
# 第二步,构建镜像
① 新建一个 yudao-ui-admin-image 目录,将前端构建产物目录复制为它的 dist 子目录(dist 下直接是 index.html 等文件)。
② 在 yudao-ui-admin-image 目录下,新建 Dockerfile 文件,内容如下:
FROM nginx:1.28-alpine
## 将前端构建产物,复制到 Nginx 的默认站点目录
COPY dist/ /usr/share/nginx/html/
③ 执行如下命令,构建并推送镜像:
cd yudao-ui-admin-image
docker build --platform linux/amd64 -t registry.example.com/yudao/yudao-ui-admin:1.0.0 .
docker push registry.example.com/yudao/yudao-ui-admin:1.0.0
Nginx 的配置文件,不打进镜像,而是在下文通过 ConfigMap 挂载,方便修改。
# 3. 编写 K8s 清单
在构建机上,新建一个 k8s 目录,在其中创建如下 6 个文件。文件名的数字前缀,决定了 kubectl apply -f k8s/ 时的执行顺序。
k8s
├── 00-namespace.yaml # 命名空间
├── 01-config.yaml # ConfigMap + Secret:3 个后端服务共用的配置
├── 02-yudao-system.yaml # system-server:Deployment
├── 03-yudao-infra.yaml # infra-server:Deployment
├── 04-yudao-gateway.yaml # gateway-server:Deployment + Service
└── 05-yudao-ui-admin.yaml # 前端:Nginx 配置 ConfigMap + Deployment + Service
重要!!!K8s Service 不能替代 Nacos
网关转发 /admin-api/system/** 时,路由使用的是 grayLb://system-server(项目自定义的灰度负载均衡);网关校验访问令牌时,通过带负载均衡的 WebClient 调用 http://system-server/...;system-server 与 infra-server 之间的 Feign 调用,使用的也是 system-server、infra-server 这些 Nacos 服务名(即 spring.application.name)。它们都是从 Nacos 获取实例的 Pod IP 后直接访问,不经过 K8s Service。
所以:
system-server、infra-server不需要创建 Service。即使创建了,网关和 Feign 也不会使用它。- 只有
yudao-gateway需要 Service,给 Nginx 访问;以及yudao-ui-admin需要 Service,给浏览器访问。 - 另外,K8s 的命名空间
yudao,和 Nacos 的命名空间dev,是两个不同的概念,不要混淆。
# 3.1 00-namespace.yaml
所有资源,都部署在 yudao 命名空间下:
apiVersion: v1
kind: Namespace
metadata:
name: yudao
# 3.2 01-config.yaml
各服务通过环境变量,覆盖 Jar 包中 application-dev.yaml 的 Nacos、MySQL、Redis 配置。非敏感配置放在 ConfigMap,密码等敏感配置放在 Secret。3 个服务共用这一份配置,网关会忽略它用不到的 MySQL、Redis 配置:
apiVersion: v1
kind: ConfigMap
metadata:
name: yudao-cloud-config
namespace: yudao
data:
TZ: "Asia/Shanghai"
SPRING_PROFILES_ACTIVE: "dev"
JAVA_OPTS: "-Xms512m -Xmx512m"
# ① Nacos:注册中心 + 配置中心。NAMESPACE 填写的是命名空间的 ID
SPRING_CLOUD_NACOS_SERVER_ADDR: "192.168.0.100:8848" # 只需要填写 8848,客户端会自动使用 9848 gRPC 端口
SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE: "dev"
SPRING_CLOUD_NACOS_CONFIG_NAMESPACE: "dev"
SPRING_CLOUD_NACOS_DISCOVERY_GROUP: "DEFAULT_GROUP"
SPRING_CLOUD_NACOS_CONFIG_GROUP: "DEFAULT_GROUP"
# ② MySQL:master 主库 + slave 从库。application-dev.yaml 中的 slave 是“模拟从库”,没有真正的从库时,也指向主库
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: "jdbc:mysql://192.168.0.100:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true"
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_SLAVE_URL: "jdbc:mysql://192.168.0.100:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true"
# ③ Redis:master-jdk17、master-jdk25 分支使用 SPRING_DATA_REDIS_*;master(JDK 8)分支改成 SPRING_REDIS_*
SPRING_DATA_REDIS_HOST: "192.168.0.100"
SPRING_DATA_REDIS_PORT: "6379"
SPRING_DATA_REDIS_DATABASE: "1"
# ④ XXL-Job:没有部署 XXL-Job 调度中心时,关闭执行器,避免反复注册失败
XXL_JOB_ENABLED: "false"
# ⑤ 其它:与 script/docker-compose 保持一致
YUDAO_CAPTCHA_ENABLE: "true" # 需与前端打包时的 VITE_APP_CAPTCHA_ENABLE 一致
YUDAO_DEMO: "false" # 重要!application-dev.yaml 默认开启演示模式(禁止写操作),这里关闭
---
apiVersion: v1
kind: Secret
metadata:
name: yudao-cloud-secret
namespace: yudao
type: Opaque
stringData:
# 重要!!!以下账号密码,按外部 MySQL、Redis、Nacos 的实际情况填写
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: "yudao"
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: "改成你的 MySQL 密码"
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_SLAVE_USERNAME: "yudao"
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_SLAVE_PASSWORD: "改成你的 MySQL 密码"
# Redis 密码(master 分支为 SPRING_REDIS_PASSWORD)。Redis 未设置密码时,删除这一行
SPRING_DATA_REDIS_PASSWORD: "改成你的 Redis 密码"
# Nacos 开启鉴权时,取消下面两行的注释
# SPRING_CLOUD_NACOS_USERNAME: "nacos"
# SPRING_CLOUD_NACOS_PASSWORD: "改成你的 Nacos 密码"
友情提示:配置的优先级
每个服务的配置,按照优先级从高到低,来自如下 4 个地方:
- ① Pod 的环境变量,即本文的 ConfigMap、Secret
- ② Nacos 配置中心:
dev命名空间、DEFAULT_GROUP分组下,Data ID为${spring.application.name}-dev.yaml的配置,例如说system-server-dev.yaml。不存在时会忽略 - ③ Jar 包中的
application-dev.yaml - ④ Jar 包中的
application.yaml
也就是说,Nacos 中的配置,会覆盖 Jar 包中的配置;而本文的环境变量,又会覆盖 Nacos 中的配置。如果你希望在 Nacos 中统一管理 MySQL、Redis 等配置,需要删除 ConfigMap、Secret 中对应的 Key,否则 Nacos 中的修改不会生效。配置中心的使用,可见 《配置中心 Nacos》 文档。
友情提示:
- ConfigMap、Secret 中的 Key,就是 Spring Boot 的环境变量名。例如说,
SPRING_CLOUD_NACOS_SERVER_ADDR对应spring.cloud.nacos.server-addr配置项。 SPRING_DATA_REDIS_DATABASE是 Redis 的库索引。如果多个环境共用一个 Redis,建议使用不同的库索引,避免缓存的 Key 互相覆盖。- 包含密码的
01-config.yaml,不要提交到 Git 仓库。
# 3.3 02-yudao-system.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: yudao-system
namespace: yudao
spec:
replicas: 1
selector:
matchLabels:
app: yudao-system
template:
metadata:
labels:
app: yudao-system
spec:
# imagePullSecrets: # 私有镜像仓库时,取消注释,见「4. 部署与验证」的第二步
# - name: yudao-registry
containers:
- name: yudao-system
image: registry.example.com/yudao/yudao-module-system-server:1.0.0 # 重要!!!替换成你的镜像地址
ports:
- containerPort: 48081
envFrom:
- configMapRef:
name: yudao-cloud-config
- secretRef:
name: yudao-cloud-secret
resources:
requests:
cpu: 500m
memory: 768Mi
limits:
memory: 1Gi
# 探针:使用 TCP 方式,只检查 48081 端口是否已经监听
startupProbe: # 启动探针:最多等待 30 x 10 = 300 秒启动完成
tcpSocket:
port: 48081
periodSeconds: 10
failureThreshold: 30
readinessProbe:
tcpSocket:
port: 48081
periodSeconds: 10
livenessProbe: # 存活探针:连续失败时,重启容器
tcpSocket:
port: 48081
periodSeconds: 20
failureThreshold: 3
疑问:为什么探针使用 TCP,而不是 HTTP?
项目的接口默认需要登录,/actuator 等监控端点也不应该依赖它来做存活判断。所以这里使用 TCP 探针,它只能说明端口已经监听,不代表 MySQL、Redis 可用,也不代表已经注册到 Nacos,后者需要通过「4. 部署与验证」中的步骤确认。
另外,system-server、infra-server 的流量来自 Nacos 中的 Pod IP,不经过 Service,所以它们的 readinessProbe 不会影响网关的转发,只用于 kubectl rollout status 判断部署进度。
探针的更多说明,可见 K8s 官方的 《Configure Liveness, Readiness and Startup Probes》 (opens new window) 文档。
# 3.4 03-yudao-infra.yaml
整体和 02-yudao-system.yaml 是一致的,只是名字、镜像、端口不同。怕你写错了,所以提供了下~
apiVersion: apps/v1
kind: Deployment
metadata:
name: yudao-infra
namespace: yudao
spec:
replicas: 1
selector:
matchLabels:
app: yudao-infra
template:
metadata:
labels:
app: yudao-infra
spec:
# imagePullSecrets:
# - name: yudao-registry
containers:
- name: yudao-infra
image: registry.example.com/yudao/yudao-module-infra-server:1.0.0 # 重要!!!替换成你的镜像地址
ports:
- containerPort: 48082
envFrom:
- configMapRef:
name: yudao-cloud-config
- secretRef:
name: yudao-cloud-secret
resources:
requests:
cpu: 500m
memory: 768Mi
limits:
memory: 1Gi
startupProbe:
tcpSocket:
port: 48082
periodSeconds: 10
failureThreshold: 30
readinessProbe:
tcpSocket:
port: 48082
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 48082
periodSeconds: 20
failureThreshold: 3
# 3.5 04-yudao-gateway.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: yudao-gateway
namespace: yudao
spec:
replicas: 1
selector:
matchLabels:
app: yudao-gateway
template:
metadata:
labels:
app: yudao-gateway
spec:
# imagePullSecrets:
# - name: yudao-registry
containers:
- name: yudao-gateway
image: registry.example.com/yudao/yudao-gateway:1.0.0 # 重要!!!替换成你的镜像地址
ports:
- containerPort: 48080
envFrom:
- configMapRef:
name: yudao-cloud-config
- secretRef:
name: yudao-cloud-secret
resources:
requests:
cpu: 500m
memory: 768Mi
limits:
memory: 1Gi
startupProbe:
tcpSocket:
port: 48080
periodSeconds: 10
failureThreshold: 30
readinessProbe: # 就绪探针:未就绪时,Service 不会把 Nginx 的流量转发给它
tcpSocket:
port: 48080
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 48080
periodSeconds: 20
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: yudao-gateway
namespace: yudao
spec:
selector:
app: yudao-gateway
ports:
- port: 48080
targetPort: 48080
# 3.6 05-yudao-ui-admin.yaml
Nginx 通过 K8s Service 的名字 yudao-gateway,访问网关:
apiVersion: v1
kind: ConfigMap
metadata:
name: yudao-ui-admin-nginx
namespace: yudao
data:
default.conf: |
server {
listen 80;
server_name _;
client_max_body_size 32m; ## 与后端 spring.servlet.multipart.max-request-size 保持一致
location / { ## 前端项目
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /admin-api/ { ## 后端项目 - 管理后台
proxy_pass http://yudao-gateway:48080/admin-api/; ## yudao-gateway 是网关 Service 的名字
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off; ## 流式响应(SSE)不缓冲
proxy_read_timeout 300s;
}
location /app-api/ { ## 后端项目 - 用户 App
proxy_pass http://yudao-gateway:48080/app-api/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
proxy_read_timeout 300s;
}
location /infra/ws { ## 后端项目 - WebSocket(网关会转发给 infra-server)
proxy_pass http://yudao-gateway:48080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_read_timeout 600s;
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: yudao-ui-admin
namespace: yudao
spec:
replicas: 1
selector:
matchLabels:
app: yudao-ui-admin
template:
metadata:
labels:
app: yudao-ui-admin
spec:
# imagePullSecrets:
# - name: yudao-registry
containers:
- name: nginx
image: registry.example.com/yudao/yudao-ui-admin:1.0.0 # 重要!!!替换成你的镜像地址
ports:
- containerPort: 80
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d # 替换镜像自带的 default.conf
readinessProbe:
tcpSocket:
port: 80
periodSeconds: 10
volumes:
- name: nginx-conf
configMap:
name: yudao-ui-admin-nginx
---
apiVersion: v1
kind: Service
metadata:
name: yudao-ui-admin
namespace: yudao
spec:
type: NodePort
selector:
app: yudao-ui-admin
ports:
- port: 80
targetPort: 80
nodePort: 30080 # 通过 http://节点IP:30080 访问
友情提示:
- Nginx 启动时,需要能解析
yudao-gateway这个 Service 名,所以04-yudao-gateway.yaml要先于它创建(文件名前缀已经保证了顺序)。 - 本文为了简单,使用 NodePort 对外暴露。如果你的集群有 Ingress Controller 或 LoadBalancer,也可以改用它们,此时前端构建时的后端地址,要改成对应的域名。
# 4. 部署与验证
# 第一步,创建命名空间
kubectl apply -f k8s/00-namespace.yaml
# 第二步,配置镜像拉取凭证(可选)
如果你的镜像仓库是私有的,执行如下命令创建凭证,并取消 02 ~ 05 文件中 imagePullSecrets 的注释:
kubectl -n yudao create secret docker-registry yudao-registry \
--docker-server=registry.example.com \
--docker-username=你的账号 \
--docker-password=你的密码
# 第三步,检查 Pod 到 MySQL、Redis、Nacos 的连通性
在部署后端之前,先使用临时 Pod,从 K8s 集群内部,检查 MySQL、Redis、Nacos 是否可以访问:
## ① 检查 MySQL:返回 system_users 表的记录数,说明地址、密码、SQL 初始化都正确
kubectl -n yudao run mysql-check --rm -it --restart=Never --image=mysql:8.4 \
--env="MYSQL_PWD=改成你的 MySQL 密码" -- \
mysql -h 192.168.0.100 -P 3306 -uyudao ruoyi-vue-pro -e 'SELECT COUNT(*) FROM system_users;'
## ② 检查 Redis:返回 PONG。Redis 未设置密码时,去掉 --env 这一行
kubectl -n yudao run redis-check --rm -it --restart=Never --image=redis:7.4-alpine \
--env="REDISCLI_AUTH=改成你的 Redis 密码" -- \
redis-cli -h 192.168.0.100 -p 6379 -n 1 ping
## ③ 检查 Nacos 的 8848、9848 端口:都返回 ok。这里借用 mysql 镜像中的 bash,只做 TCP 端口级的检查
kubectl -n yudao run nacos-check --rm -it --restart=Never --image=mysql:8.4 -- \
bash -c 'for p in 8848 9848; do timeout 3 bash -c "</dev/tcp/192.168.0.100/$p" && echo "$p ok" || echo "$p 不通"; done'
- 密码通过
MYSQL_PWD、REDISCLI_AUTH环境变量传入,避免出现在命令参数中。账号、密码与01-config.yaml的 Secret 保持一致。
# 第四步,部署
kubectl apply -f k8s/
## 等待后端、前端部署完成
kubectl -n yudao rollout status deploy/yudao-system --timeout=600s
kubectl -n yudao rollout status deploy/yudao-infra --timeout=600s
kubectl -n yudao rollout status deploy/yudao-gateway --timeout=600s
kubectl -n yudao rollout status deploy/yudao-ui-admin
# 第五步,验证
① 执行 kubectl -n yudao get pods -o wide 命令,查看到 Pod 的状态都是 Running,READY 都是 1/1,并记下各 Pod 的 IP 列。各服务的日志中,分别可以看到 Started GatewayServerApplication、Started SystemServerApplication、Started InfraServerApplication 的启动成功提示。
② 执行如下命令,确认服务使用 Pod IP 注册到了 Nacos:
kubectl -n yudao logs deploy/yudao-system | grep "register finished"
看到类似如下的内容,并且 IP 与上一步 yudao-system 的 Pod IP 一致:
nacos registry, DEFAULT_GROUP system-server 10.244.1.23:48081 register finished
yudao-gateway、yudao-infra 同理。也可以打开 Nacos 控制台,在 dev 命名空间的 [服务管理 -> 服务列表] 菜单下,看到 gateway-server、system-server、infra-server 三个服务,实例 IP 都是 Pod IP。
③ 执行如下命令,验证 Nginx => 网关 Service => 网关 => Nacos => system-server / infra-server 的链路:
## 返回 {"code":0,"data":1,"msg":""},说明 system-server 可以从 MySQL 查询到默认租户
curl -G --data-urlencode "name=芋道源码" http://192.168.0.213:30080/admin-api/system/tenant/get-id-by-name
## 返回 {"code":401,"data":null,"msg":"账号未登录"},这是 infra-server 返回的未登录提示
curl http://192.168.0.213:30080/admin-api/infra/
友情提示:
- 服务刚启动时,网关的负载均衡会缓存实例列表,可能需要等待几十秒才会生效。如果持续返回 503,参见「6. 常见问题」。
- 如果 NodePort 访问不通,可以先跳过 Nginx,直接验证网关:执行
kubectl -n yudao port-forward svc/yudao-gateway 48080:48080命令,然后在另一个终端把上面的地址换成http://127.0.0.1:48080执行。
④ 请求 http://192.168.0.213:30080 (opens new window) 地址,成功访问前端项目。使用默认账号 admin、密码 admin123 登录。
⑤ 登录成功后,验证服务之间的 Feign 调用:
system-server=>infra-server:system-server的访问日志,是通过 Feign 调用infra-server写入数据库的。执行如下命令,可以看到application_name为system-server的记录(例如说登录接口/admin-api/system/auth/login):
kubectl -n yudao run mysql-check --rm -it --restart=Never --image=mysql:8.4 \
--env="MYSQL_PWD=改成你的 MySQL 密码" -- \
mysql -h 192.168.0.100 -P 3306 -uyudao ruoyi-vue-pro \
-e 'SELECT application_name, request_url, create_time FROM infra_api_access_log ORDER BY id DESC LIMIT 5;'
infra-server=>system-server:在前端打开 [基础设施 -> 文件管理] 等infra-server的菜单,能正常加载数据。因为infra-server需要通过 Feign 调用system-server,进行权限校验。
⑥ 执行如下命令,验证 后端 => Redis 正常,可以看到登录的访问令牌:
kubectl -n yudao run redis-check --rm -it --restart=Never --image=redis:7.4-alpine \
--env="REDISCLI_AUTH=改成你的 Redis 密码" -- \
redis-cli -h 192.168.0.100 -p 6379 -n 1 --scan --pattern 'oauth2_access_token:*'
# 5. 日常运维
# 5.1 更新
① 后端:重新编译,构建并推送新版本号的镜像(例如说 1.0.1),然后执行如下命令滚动更新。以 system-server 为例:
kubectl -n yudao set image deploy/yudao-system yudao-system=registry.example.com/yudao/yudao-module-system-server:1.0.1
kubectl -n yudao rollout status deploy/yudao-system --timeout=600s
滚动更新时,旧 Pod 会从 Nacos 注销,但网关的实例缓存刷新有延迟,可能出现短暂的请求失败。本文是入门示例,不展开处理。
② 前端:同理,更新 yudao-ui-admin 镜像即可。
③ 配置:修改 01-config.yaml 后,执行 kubectl apply -f k8s/01-config.yaml,再执行 kubectl -n yudao rollout restart deploy/yudao-system deploy/yudao-infra deploy/yudao-gateway 让 Pod 重新读取环境变量。
④ 回滚:执行 kubectl -n yudao rollout undo deploy/yudao-system 命令,回滚到上一个版本。
建议同步修改 k8s 目录下 YAML 中的镜像版本号,保证清单与集群一致。
# 5.2 扩展其它服务
如果需要部署 bpm-server、pay-server 等其它服务,参考 02-yudao-system.yaml 新增一个 Deployment 即可:
- ① 构建并推送对应模块
yudao-module-xxx/yudao-module-xxx-server的镜像,修改name、image、端口(以该模块application.yaml中的server.port为准)。 - ② 同样使用
yudao-cloud-config、yudao-cloud-secret配置,不需要创建 Service。 - ③ 网关中已经配置了各服务的路由(
yudao-gateway的application.yaml),服务注册到 Nacos 后,即可通过网关访问。 - ④ 部分模块有额外的 SQL 或中间件依赖,请先阅读对应模块的“功能开启”文档。
# 5.3 查看日志
kubectl -n yudao logs -f deploy/yudao-system --tail=200 ## 服务日志
kubectl -n yudao logs deploy/yudao-system --previous ## 容器重启后,查看上一次崩溃的日志
kubectl -n yudao describe pod -l app=yudao-system ## 查看 Pod 事件:拉取镜像、探针失败等
容器内的日志文件(/root/logs/system-server.log 等)会随 Pod 删除而丢失。如果需要长期保存,可以接入集群的日志采集方案。
# 5.4 扩容
服务改成多副本前,需要注意:WebSocket 的消息发送,默认是 local 本地模式(yudao.websocket.sender-type),infra-server 多副本时需要改成 redis 等模式。具体可见 《WebSocket 实时通信》 文档。
# 5.5 删除
kubectl delete namespace yudao
它会删除本文创建的所有 K8s 资源,Pod 退出时会从 Nacos 注销。MySQL、Redis、Nacos 在本文清单之外,不受影响。
# 6. 常见问题
| 现象 | 可能的原因 |
|---|---|
Pod 状态为 ImagePullBackOff | 镜像地址占位符没有替换;私有仓库没有配置 imagePullSecrets;节点无法访问镜像仓库 |
日志报 exec format error | 镜像的 CPU 架构与节点不一致,参见「2.1 后端镜像」的 --platform 参数 |
Pod 状态为 CrashLoopBackOff | 执行 kubectl -n yudao logs deploy/yudao-system --previous 查看原因。常见的是 MySQL / Redis / Nacos 地址填了 127.0.0.1、密码错误、Nacos 的 9848 端口不通、Redis 环境变量与分支不匹配 |
接口返回 503 Service Unavailable | 网关在 Nacos 中找不到可用实例:服务没有注册到 dev 命名空间;命名空间中混入了集群外的实例;服务刚启动,等待几十秒再试 |
| 接口超时或连接被拒绝 | Nacos 中的实例 IP 不是 Pod IP(例如说本地 IDE 的 IP),或 NetworkPolicy 禁止了 Pod 之间的访问 |
| Nacos 中的配置修改没有生效 | ConfigMap、Secret 中存在同名的 Key(环境变量优先级更高),删除后重启 Pod;或者 Data ID、命名空间、分组与服务不匹配。参见「3.2 01-config.yaml」中配置的优先级 |
| 访问页面报 502 | 网关还没有就绪,执行 kubectl -n yudao get endpoints yudao-gateway 确认有可用的 Pod IP |
前端请求跑到了 localhost:48080 等地址 | 前端构建时,没有把后端地址改成 http://节点IP:30080,参见「2.2 前端镜像」 |
# 666. 更多说明
① 如果你只有一台服务器,更推荐使用 《Docker Compose 部署》。
② 如果需要 HTTPS 访问,建议在 Ingress 或集群外的负载均衡上配置证书,可参考 《HTTPS 证书》 文档申请证书。