实验:RuoYi-Cloud 在 高可用的Kubernetes 上的完整安装步骤(国内镜像版)
实验:RuoYi-Cloud 在 高可用的Kubernetes 上的完整安装步骤(国内镜像版)
本文只包含 若依微服务相关安装,不包含 Kubernetes 集群、Prometheus、Grafana 等平台组件的安装。
本文固定使用当前实验环境已经验证的版本和方式,不追求“最新版本”。
1. 最终部署结构
ruoyi 命名空间
├── ruoyi-mysql
│ ├── StatefulSet
│ ├── PVC:5Gi
│ └── ClusterIP:3306
├── ruoyi-redis
│ ├── StatefulSet
│ ├── PVC:2Gi
│ └── ClusterIP:6379
├── ruoyi-nacos
│ ├── StatefulSet
│ ├── PVC:2Gi
│ ├── ClusterIP:8848、9848
│ └── NodePort:30082 → 控制台 8080
├── ruoyi-gateway
│ ├── Deployment
│ └── ClusterIP:8080
├── ruoyi-auth
│ ├── Deployment
│ └── ClusterIP:9200
├── ruoyi-system
│ ├── Deployment
│ └── ClusterIP:9201
└── ruoyi-ui
├── Deployment
└── NodePort:30081 → Nginx 80
浏览器访问:
若依前端:http://192.168.89.104:30081
Nacos控制台:http://192.168.89.104:30082/index.html
也可以把 .104 换成 .105 或 .106。
2. 本次固定版本
RuoYi-Cloud:springboot3 分支
JDK:17
Helm:3.x
MySQL:5.7.44
Redis:7.2.5-alpine
Nacos:v3.0.2
Node:16.20.2-alpine
Nginx:1.27.5-alpine
StorageClass:local-path
国内镜像统一使用:
m.daocloud.io
实验密码:
MySQL root:RuoYiMysql2026@
Redis:RuoYiRedis2026@
Nacos控制台:nacos / nacos
若依系统:admin / admin123
这些密码只适合实验环境,生产环境必须更换,并使用 Vault、External Secrets 或企业密钥管理系统。
第一阶段:前置检查和源码准备
3. 检查集群、Helm 和 StorageClass
以下命令全部在 k8s-master01 执行:
kubectl get nodes -o wide
helm version --short
kubectl get storageclass
必须满足:
6 个节点全部 Ready
Helm 命令可用
local-path 为默认 StorageClass
4. 创建 ruoyi 命名空间
kubectl create namespace ruoyi \
--dry-run=client \
-o yaml \
| kubectl apply -f -
检查:
kubectl get namespace ruoyi
5. 创建工作目录
mkdir -p "$HOME/ruoyi-k8s-lab"/{charts,source,images,artifacts}
mkdir -p \
"$HOME/ruoyi-k8s-lab/charts/mysql" \
"$HOME/ruoyi-k8s-lab/charts/redis" \
"$HOME/ruoyi-k8s-lab/charts/nacos" \
"$HOME/ruoyi-k8s-lab/charts/ruoyi-app"
目录用途:
charts/mysql MySQL Helm Chart
charts/redis Redis Helm Chart
charts/nacos Nacos Helm Chart
charts/ruoyi-app Gateway、Auth、System、UI Helm Chart
source 若依源码
images 镜像构建上下文
artifacts 导出的镜像 tar 包
6. 下载若依 springboot3 源码
sudo apt update
sudo apt install -y git
cd "$HOME/ruoyi-k8s-lab/source"
git clone \
--depth 1 \
--branch springboot3 \
https://gitee.com/y_project/RuoYi-Cloud.git
检查:
cd "$HOME/ruoyi-k8s-lab/source/RuoYi-Cloud"
git branch --show-current
find ./sql -maxdepth 1 -type f -name '*.sql' | sort
应返回分支:
springboot3
本次只导入:
ry_*.sql
ry_config_*.sql
第二阶段:安装 MySQL
7. 创建 MySQL Helm Chart
rm -rf "$HOME/ruoyi-k8s-lab/charts/mysql"
mkdir -p "$HOME/ruoyi-k8s-lab/charts/mysql/templates"
cd "$HOME/ruoyi-k8s-lab/charts/mysql"
7.1 创建 Chart.yaml
cat <<'EOF' > Chart.yaml
apiVersion: v2
name: ruoyi-mysql
description: MySQL for RuoYi Cloud Kubernetes lab
type: application
version: 0.1.0
appVersion: "5.7.44"
EOF
7.2 创建 values-lab.yaml
cat <<'EOF' > values-lab.yaml
fullnameOverride: ruoyi-mysql
image:
repository: m.daocloud.io/docker.io/library/mysql
tag: "5.7.44"
pullPolicy: IfNotPresent
auth:
rootPassword: "RuoYiMysql2026@"
database: "ry-cloud"
service:
port: 3306
persistence:
storageClass: local-path
size: 5Gi
mysql:
innodbBufferPoolSize: 80M
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
EOF
7.3 创建 templates/secret.yaml
cat <<'EOF' > templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
type: Opaque
stringData:
mysql-root-password: {{ .Values.auth.rootPassword | quote }}
EOF
7.4 创建 templates/service.yaml
cat <<'EOF' > templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
ports:
- name: mysql
port: {{ .Values.service.port }}
targetPort: mysql
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.fullnameOverride }}-headless
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
clusterIP: None
selector:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
ports:
- name: mysql
port: {{ .Values.service.port }}
targetPort: mysql
EOF
7.5 创建 templates/statefulset.yaml
cat <<'EOF' > templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceName: {{ .Values.fullnameOverride }}-headless
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
terminationGracePeriodSeconds: 60
containers:
- name: mysql
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- "--innodb-buffer-pool-size={{ .Values.mysql.innodbBufferPoolSize }}"
- "--character-set-server=utf8mb4"
- "--collation-server=utf8mb4_unicode_ci"
- "--default-time-zone=+8:00"
- "--lower-case-table-names=1"
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.fullnameOverride }}
key: mysql-root-password
- name: MYSQL_ROOT_HOST
value: "%"
- name: MYSQL_DATABASE
value: {{ .Values.auth.database | quote }}
- name: TZ
value: Asia/Shanghai
ports:
- name: mysql
containerPort: 3306
protocol: TCP
startupProbe:
exec:
command:
- sh
- -c
- mysqladmin ping -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD" --silent
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
readinessProbe:
exec:
command:
- sh
- -c
- mysqladmin ping -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD" --silent
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
livenessProbe:
exec:
command:
- sh
- -c
- mysqladmin ping -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD" --silent
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 6
resources:
{{ toYaml .Values.resources | indent 12 }}
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: {{ .Values.persistence.storageClass | quote }}
resources:
requests:
storage: {{ .Values.persistence.size }}
EOF
8. 检查并安装 MySQL
cd "$HOME/ruoyi-k8s-lab"
helm lint ./charts/mysql \
-f ./charts/mysql/values-lab.yaml
helm template ruoyi-mysql \
./charts/mysql \
--namespace ruoyi \
-f ./charts/mysql/values-lab.yaml \
> /tmp/ruoyi-mysql-rendered.yaml
grep -n 'image:' /tmp/ruoyi-mysql-rendered.yaml
正式安装:
helm upgrade --install ruoyi-mysql \
./charts/mysql \
--namespace ruoyi \
-f ./charts/mysql/values-lab.yaml \
--wait \
--timeout 15m
检查:
helm list -n ruoyi
kubectl get pod,svc,pvc -n ruoyi -o wide
kubectl logs -n ruoyi ruoyi-mysql-0 --tail=100
9. 导入若依数据库 SQL
MYSQL_ROOT_PASSWORD="$(
kubectl get secret ruoyi-mysql \
-n ruoyi \
-o jsonpath='{.data.mysql-root-password}' \
| base64 -d
)"
验证登录:
kubectl exec \
-n ruoyi \
ruoyi-mysql-0 \
-- env MYSQL_PWD="$MYSQL_ROOT_PASSWORD" \
mysql -uroot \
-e 'SELECT VERSION(); SHOW DATABASES;'
定位 SQL:
cd "$HOME/ruoyi-k8s-lab/source/RuoYi-Cloud"
RY_SQL="$(
find ./sql \
-maxdepth 1 \
-type f \
-name 'ry_*.sql' \
! -name 'ry_config_*.sql' \
! -name 'ry_seata_*.sql' \
| sort \
| tail -n 1
)"
RY_CONFIG_SQL="$(
find ./sql \
-maxdepth 1 \
-type f \
-name 'ry_config_*.sql' \
| sort \
| tail -n 1
)"
echo "若依业务SQL:$RY_SQL"
echo "Nacos配置SQL:$RY_CONFIG_SQL"
创建数据库:
kubectl exec \
-n ruoyi \
ruoyi-mysql-0 \
-- env MYSQL_PWD="$MYSQL_ROOT_PASSWORD" \
mysql -uroot \
-e '
CREATE DATABASE IF NOT EXISTS `ry-cloud`
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `ry-config`
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
'
导入业务 SQL:
kubectl exec -i \
-n ruoyi \
ruoyi-mysql-0 \
-- env MYSQL_PWD="$MYSQL_ROOT_PASSWORD" \
mysql \
-uroot \
--default-character-set=utf8mb4 \
ry-cloud \
< "$RY_SQL"
导入 Nacos 配置 SQL:
kubectl exec -i \
-n ruoyi \
ruoyi-mysql-0 \
-- env MYSQL_PWD="$MYSQL_ROOT_PASSWORD" \
mysql \
-uroot \
--default-character-set=utf8mb4 \
ry-config \
< "$RY_CONFIG_SQL"
验证:
kubectl exec \
-n ruoyi \
ruoyi-mysql-0 \
-- env MYSQL_PWD="$MYSQL_ROOT_PASSWORD" \
mysql -uroot \
-e '
SELECT table_schema, COUNT(*) AS table_count
FROM information_schema.tables
WHERE table_schema IN ("ry-cloud", "ry-config")
GROUP BY table_schema;
'
第三阶段:安装 Redis
10. 创建 Redis Helm Chart
rm -rf "$HOME/ruoyi-k8s-lab/charts/redis"
mkdir -p "$HOME/ruoyi-k8s-lab/charts/redis/templates"
cd "$HOME/ruoyi-k8s-lab/charts/redis"
10.1 创建 Chart.yaml
cat <<'EOF' > Chart.yaml
apiVersion: v2
name: ruoyi-redis
description: Redis for RuoYi Cloud Kubernetes lab
type: application
version: 0.1.0
appVersion: "7.2.5"
EOF
10.2 创建 values-lab.yaml
cat <<'EOF' > values-lab.yaml
fullnameOverride: ruoyi-redis
image:
repository: m.daocloud.io/docker.io/library/redis
tag: "7.2.5-alpine"
pullPolicy: IfNotPresent
auth:
password: "RuoYiRedis2026@"
service:
port: 6379
persistence:
storageClass: local-path
size: 2Gi
redis:
appendonly: "yes"
maxmemory: "128mb"
maxmemoryPolicy: "allkeys-lru"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 300m
memory: 256Mi
EOF
10.3 创建 templates/secret.yaml
cat <<'EOF' > templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
type: Opaque
stringData:
redis-password: {{ .Values.auth.password | quote }}
EOF
10.4 创建 templates/service.yaml
cat <<'EOF' > templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: redis
port: {{ .Values.service.port }}
targetPort: redis
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.fullnameOverride }}-headless
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
clusterIP: None
selector:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: redis
port: {{ .Values.service.port }}
targetPort: redis
EOF
10.5 创建 templates/statefulset.yaml
cat <<'EOF' > templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceName: {{ .Values.fullnameOverride }}-headless
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
terminationGracePeriodSeconds: 30
containers:
- name: redis
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- "--appendonly"
- {{ .Values.redis.appendonly | quote }}
- "--dir"
- "/data"
- "--requirepass"
- "$(REDIS_PASSWORD)"
- "--maxmemory"
- {{ .Values.redis.maxmemory | quote }}
- "--maxmemory-policy"
- {{ .Values.redis.maxmemoryPolicy | quote }}
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.fullnameOverride }}
key: redis-password
ports:
- name: redis
containerPort: 6379
protocol: TCP
startupProbe:
exec:
command:
- sh
- -c
- REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h 127.0.0.1 ping | grep -q PONG
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
readinessProbe:
exec:
command:
- sh
- -c
- REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h 127.0.0.1 ping | grep -q PONG
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
exec:
command:
- sh
- -c
- REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h 127.0.0.1 ping | grep -q PONG
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 6
resources:
{{ toYaml .Values.resources | indent 12 }}
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: {{ .Values.persistence.storageClass | quote }}
resources:
requests:
storage: {{ .Values.persistence.size }}
EOF
11. 检查并安装 Redis
cd "$HOME/ruoyi-k8s-lab"
helm lint ./charts/redis \
-f ./charts/redis/values-lab.yaml
helm template ruoyi-redis \
./charts/redis \
--namespace ruoyi \
-f ./charts/redis/values-lab.yaml \
> /tmp/ruoyi-redis-rendered.yaml
正式安装:
helm upgrade --install ruoyi-redis \
./charts/redis \
--namespace ruoyi \
-f ./charts/redis/values-lab.yaml \
--wait \
--timeout 10m
检查:
kubectl get pod,svc,pvc -n ruoyi -o wide
kubectl logs -n ruoyi ruoyi-redis-0 --tail=100
读取密码并测试:
REDIS_PASSWORD="$(
kubectl get secret ruoyi-redis \
-n ruoyi \
-o jsonpath='{.data.redis-password}' \
| base64 -d
)"
kubectl exec \
-n ruoyi \
ruoyi-redis-0 \
-- env REDISCLI_AUTH="$REDIS_PASSWORD" \
redis-cli ping
应返回:
PONG
写入和读取测试:
kubectl exec \
-n ruoyi \
ruoyi-redis-0 \
-- env REDISCLI_AUTH="$REDIS_PASSWORD" \
redis-cli SET ruoyi:test redis-ok
kubectl exec \
-n ruoyi \
ruoyi-redis-0 \
-- env REDISCLI_AUTH="$REDIS_PASSWORD" \
redis-cli GET ruoyi:test
第四阶段:安装 Nacos
12. 检查 NodePort 30082
kubectl get svc -A \
-o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,NODEPORT:.spec.ports[*].nodePort' \
| grep -w 30082 || true
没有输出再继续。
13. 三个 Worker 预拉取 Nacos 镜像
分别登录 .104、.105、.106,每台执行:
sudo crictl pull \
m.daocloud.io/docker.io/nacos/nacos-server:v3.0.2
检查:
sudo crictl images | grep nacos
14. 创建 Nacos Helm Chart
回到 master01:
rm -rf "$HOME/ruoyi-k8s-lab/charts/nacos"
mkdir -p "$HOME/ruoyi-k8s-lab/charts/nacos/templates"
cd "$HOME/ruoyi-k8s-lab/charts/nacos"
14.1 创建 Chart.yaml
cat <<'EOF' > Chart.yaml
apiVersion: v2
name: ruoyi-nacos
description: Nacos for RuoYi Cloud Kubernetes lab
type: application
version: 0.1.0
appVersion: "3.0.2"
EOF
14.2 创建 values-lab.yaml
cat <<'EOF' > values-lab.yaml
fullnameOverride: ruoyi-nacos
image:
repository: m.daocloud.io/docker.io/nacos/nacos-server
tag: "v3.0.2"
pullPolicy: IfNotPresent
mysql:
host: ruoyi-mysql
port: 3306
database: ry-config
user: root
existingSecret: ruoyi-mysql
passwordKey: mysql-root-password
service:
serverPort: 8848
consolePort: 8080
grpcClientPort: 9848
console:
type: NodePort
nodePort: 30082
persistence:
enabled: true
storageClass: local-path
size: 2Gi
nacos:
mode: standalone
functionMode: all
auth:
enabled: true
token: "UnVvWWlOYWNvc0F1dGhUb2tlbjIwMjYtTGFiLUNoYW5nZU1lLTEyMzQ1Njc4OTA="
identityKey: "ruoyi-nacos-server"
identityValue: "ruoyi-nacos-server-2026"
jvm:
xms: 512m
xmx: 512m
xmn: 256m
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: "1"
memory: 1280Mi
EOF
14.3 创建 templates/secret.yaml
cat <<'EOF' > templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
type: Opaque
stringData:
nacos-auth-token: {{ .Values.nacos.auth.token | quote }}
nacos-identity-key: {{ .Values.nacos.auth.identityKey | quote }}
nacos-identity-value: {{ .Values.nacos.auth.identityValue | quote }}
EOF
14.4 创建 templates/service.yaml
cat <<'EOF' > templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: server
port: {{ .Values.service.serverPort }}
targetPort: server
protocol: TCP
- name: grpc-client
port: {{ .Values.service.grpcClientPort }}
targetPort: grpc-client
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.fullnameOverride }}-headless
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
clusterIP: None
selector:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: server
port: {{ .Values.service.serverPort }}
targetPort: server
protocol: TCP
- name: grpc-client
port: {{ .Values.service.grpcClientPort }}
targetPort: grpc-client
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.fullnameOverride }}-console
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: {{ .Values.console.type }}
selector:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: console
port: {{ .Values.service.consolePort }}
targetPort: console
nodePort: {{ .Values.console.nodePort }}
protocol: TCP
EOF
14.5 创建 templates/statefulset.yaml
cat <<'EOF' > templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ .Values.fullnameOverride }}
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceName: {{ .Values.fullnameOverride }}-headless
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.fullnameOverride }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
terminationGracePeriodSeconds: 60
initContainers:
- name: wait-for-mysql
image: m.daocloud.io/docker.io/library/busybox:1.36.1
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- |
echo "等待 MySQL:{{ .Values.mysql.host }}:{{ .Values.mysql.port }}"
until nc -z {{ .Values.mysql.host }} {{ .Values.mysql.port }}; do
echo "MySQL 尚未就绪,3 秒后重试"
sleep 3
done
echo "MySQL 已可以连接"
containers:
- name: nacos
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: MODE
value: {{ .Values.nacos.mode | quote }}
- name: FUNCTION_MODE
value: {{ .Values.nacos.functionMode | quote }}
- name: NACOS_DEPLOYMENT_TYPE
value: "merged"
- name: NACOS_APPLICATION_PORT
value: {{ .Values.service.serverPort | quote }}
- name: NACOS_CONSOLE_PORT
value: {{ .Values.service.consolePort | quote }}
- name: NACOS_CONSOLE_UI_ENABLED
value: "true"
- name: SPRING_DATASOURCE_PLATFORM
value: "mysql"
- name: MYSQL_SERVICE_HOST
value: {{ .Values.mysql.host | quote }}
- name: MYSQL_SERVICE_PORT
value: {{ .Values.mysql.port | quote }}
- name: MYSQL_SERVICE_DB_NAME
value: {{ .Values.mysql.database | quote }}
- name: MYSQL_SERVICE_USER
value: {{ .Values.mysql.user | quote }}
- name: MYSQL_SERVICE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.mysql.existingSecret }}
key: {{ .Values.mysql.passwordKey }}
- name: MYSQL_DATABASE_NUM
value: "1"
- name: MYSQL_SERVICE_DB_PARAM
value: "characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai"
- name: NACOS_AUTH_ENABLE
value: {{ .Values.nacos.auth.enabled | quote }}
- name: NACOS_AUTH_ADMIN_ENABLE
value: "true"
- name: NACOS_AUTH_CONSOLE_ENABLE
value: "true"
- name: NACOS_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: {{ .Values.fullnameOverride }}
key: nacos-auth-token
- name: NACOS_AUTH_IDENTITY_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.fullnameOverride }}
key: nacos-identity-key
- name: NACOS_AUTH_IDENTITY_VALUE
valueFrom:
secretKeyRef:
name: {{ .Values.fullnameOverride }}
key: nacos-identity-value
- name: JVM_XMS
value: {{ .Values.nacos.jvm.xms | quote }}
- name: JVM_XMX
value: {{ .Values.nacos.jvm.xmx | quote }}
- name: JVM_XMN
value: {{ .Values.nacos.jvm.xmn | quote }}
- name: TZ
value: "Asia/Shanghai"
ports:
- name: console
containerPort: 8080
protocol: TCP
- name: server
containerPort: 8848
protocol: TCP
- name: grpc-client
containerPort: 9848
protocol: TCP
startupProbe:
httpGet:
path: /nacos/v3/admin/core/state/liveness
port: server
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 60
readinessProbe:
httpGet:
path: /nacos/v3/admin/core/state/readiness
port: server
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 12
livenessProbe:
httpGet:
path: /nacos/v3/admin/core/state/liveness
port: server
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 6
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- if .Values.persistence.enabled }}
volumeMounts:
- name: data
mountPath: /home/nacos/data
{{- end }}
{{- if .Values.persistence.enabled }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: {{ .Values.persistence.storageClass | quote }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}
EOF
15. 检查并安装 Nacos
cd "$HOME/ruoyi-k8s-lab"
helm lint ./charts/nacos \
-f ./charts/nacos/values-lab.yaml
helm template ruoyi-nacos \
./charts/nacos \
--namespace ruoyi \
-f ./charts/nacos/values-lab.yaml \
> /tmp/ruoyi-nacos-rendered.yaml
安装:
helm upgrade --install ruoyi-nacos \
./charts/nacos \
--namespace ruoyi \
-f ./charts/nacos/values-lab.yaml \
--wait \
--timeout 15m
观察和日志:
kubectl get pod,pvc,svc -n ruoyi -o wide
kubectl logs -n ruoyi ruoyi-nacos-0 --tail=200
浏览器访问:
http://192.168.89.104:30082/index.html
登录:
用户名:nacos
密码:nacos
16. 修改 Nacos 中的 Kubernetes 地址
读取密码:
MYSQL_ROOT_PASSWORD="$(
kubectl get secret ruoyi-mysql \
-n ruoyi \
-o jsonpath='{.data.mysql-root-password}' \
| base64 -d
)"
REDIS_PASSWORD="$(
kubectl get secret ruoyi-redis \
-n ruoyi \
-o jsonpath='{.data.redis-password}' \
| base64 -d
)"
在 Nacos 控制台修改以下配置,其他内容保持不变。
ruoyi-gateway-dev.yml
spring:
data:
redis:
host: ruoyi-redis
port: 6379
password: RuoYiRedis2026@
ruoyi-auth-dev.yml
spring:
data:
redis:
host: ruoyi-redis
port: 6379
password: RuoYiRedis2026@
ruoyi-system-dev.yml
Redis:
spring:
data:
redis:
host: ruoyi-redis
port: 6379
password: RuoYiRedis2026@
MySQL 主库:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ruoyi-mysql:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
username: root
password: RuoYiMysql2026@
每次修改后点击:
发布
第五阶段:编译和制作后端镜像
17. 安装 JDK、Maven 和 Buildah
只在 master01 执行:
sudo apt update
sudo apt install -y \
openjdk-17-jdk \
maven \
buildah
检查:
java -version
mvn -version
sudo buildah version
18. Maven 编译 Gateway、Auth、System
cd "$HOME/ruoyi-k8s-lab/source/RuoYi-Cloud"
export MAVEN_OPTS="-Xms128m -Xmx768m -XX:+UseG1GC"
mvn \
-DskipTests \
-T 1 \
clean package \
-pl ruoyi-gateway,ruoyi-auth,ruoyi-modules/ruoyi-system \
-am
成功时应看到:
BUILD SUCCESS
定位 JAR:
GATEWAY_JAR="$(
find ruoyi-gateway/target \
-maxdepth 1 \
-type f \
-name 'ruoyi-gateway*.jar' \
| sort \
| head -n 1
)"
AUTH_JAR="$(
find ruoyi-auth/target \
-maxdepth 1 \
-type f \
-name 'ruoyi-auth*.jar' \
| sort \
| head -n 1
)"
SYSTEM_JAR="$(
find ruoyi-modules/ruoyi-system/target \
-maxdepth 1 \
-type f \
-name 'ruoyi-modules-system*.jar' \
| sort \
| head -n 1
)"
ls -lh "$GATEWAY_JAR" "$AUTH_JAR" "$SYSTEM_JAR"
19. 准备镜像目录和 JAR
mkdir -p \
"$HOME/ruoyi-k8s-lab/images/gateway" \
"$HOME/ruoyi-k8s-lab/images/auth" \
"$HOME/ruoyi-k8s-lab/images/system" \
"$HOME/ruoyi-k8s-lab/artifacts"
cp "$GATEWAY_JAR" \
"$HOME/ruoyi-k8s-lab/images/gateway/app.jar"
cp "$AUTH_JAR" \
"$HOME/ruoyi-k8s-lab/images/auth/app.jar"
cp "$SYSTEM_JAR" \
"$HOME/ruoyi-k8s-lab/images/system/app.jar"
20. 创建三个 Dockerfile
Gateway:
cat <<'EOF' > "$HOME/ruoyi-k8s-lab/images/gateway/Dockerfile"
FROM m.daocloud.io/docker.io/library/eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY app.jar /app/app.jar
ENV JAVA_OPTS="-Xms128m -Xmx256m -XX:+UseG1GC -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /app/app.jar"]
EOF
Auth:
cat <<'EOF' > "$HOME/ruoyi-k8s-lab/images/auth/Dockerfile"
FROM m.daocloud.io/docker.io/library/eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY app.jar /app/app.jar
ENV JAVA_OPTS="-Xms128m -Xmx256m -XX:+UseG1GC -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
EXPOSE 9200
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /app/app.jar"]
EOF
System:
cat <<'EOF' > "$HOME/ruoyi-k8s-lab/images/system/Dockerfile"
FROM m.daocloud.io/docker.io/library/eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY app.jar /app/app.jar
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
EXPOSE 9201
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /app/app.jar"]
EOF
21. 构建三个后端镜像
sudo buildah bud \
--format docker \
--layers \
-t localhost/ruoyi-gateway:3.6.8-lab \
"$HOME/ruoyi-k8s-lab/images/gateway"
sudo buildah bud \
--format docker \
--layers \
-t localhost/ruoyi-auth:3.6.8-lab \
"$HOME/ruoyi-k8s-lab/images/auth"
sudo buildah bud \
--format docker \
--layers \
-t localhost/ruoyi-system:3.6.8-lab \
"$HOME/ruoyi-k8s-lab/images/system"
检查:
sudo buildah images \
| grep -E 'ruoyi-(gateway|auth|system)'
22. 导出三个镜像
cd "$HOME/ruoyi-k8s-lab"
sudo buildah push \
localhost/ruoyi-gateway:3.6.8-lab \
docker-archive:"$PWD/artifacts/ruoyi-gateway.tar":localhost/ruoyi-gateway:3.6.8-lab
sudo buildah push \
localhost/ruoyi-auth:3.6.8-lab \
docker-archive:"$PWD/artifacts/ruoyi-auth.tar":localhost/ruoyi-auth:3.6.8-lab
sudo buildah push \
localhost/ruoyi-system:3.6.8-lab \
docker-archive:"$PWD/artifacts/ruoyi-system.tar":localhost/ruoyi-system:3.6.8-lab
sudo chown leecurry:leecurry \
"$HOME/ruoyi-k8s-lab/artifacts/"*.tar
23. 发送并导入三个 Worker
master01 发送:
for ip in 104 105 106; do
echo "发送到 192.168.89.${ip}"
scp \
"$HOME/ruoyi-k8s-lab/artifacts/"*.tar \
"leecurry@192.168.89.${ip}:/tmp/"
done
三个 Worker 每台执行:
for image_file in \
/tmp/ruoyi-gateway.tar \
/tmp/ruoyi-auth.tar \
/tmp/ruoyi-system.tar
do
sudo ctr \
-n k8s.io \
images import \
"$image_file"
done
检查:
sudo crictl images \
| grep -E 'ruoyi-(gateway|auth|system)'
每台都必须看到三个镜像。
第六阶段:部署 Gateway、Auth、System
24. 创建 ruoyi-app Helm Chart
回到 master01:
rm -rf "$HOME/ruoyi-k8s-lab/charts/ruoyi-app"
mkdir -p "$HOME/ruoyi-k8s-lab/charts/ruoyi-app/templates"
cd "$HOME/ruoyi-k8s-lab/charts/ruoyi-app"
24.1 创建 Chart.yaml
cat <<'EOF' > Chart.yaml
apiVersion: v2
name: ruoyi-app
description: RuoYi Cloud core services for Kubernetes lab
type: application
version: 0.1.0
appVersion: "3.6.8"
EOF
24.2 创建 values-lab.yaml
cat <<'EOF' > values-lab.yaml
global:
imagePullPolicy: IfNotPresent
profile: dev
busyboxImage:
repository: m.daocloud.io/docker.io/library/busybox
tag: "1.36.1"
nacos:
serverAddr: ruoyi-nacos:8848
username: nacos
password: nacos
gateway:
name: ruoyi-gateway
replicas: 1
image:
repository: localhost/ruoyi-gateway
tag: "3.6.8-lab"
port: 8080
javaOpts: >-
-Xms128m
-Xmx256m
-XX:+UseG1GC
-Dfile.encoding=UTF-8
-Duser.timezone=Asia/Shanghai
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
auth:
name: ruoyi-auth
replicas: 1
image:
repository: localhost/ruoyi-auth
tag: "3.6.8-lab"
port: 9200
javaOpts: >-
-Xms128m
-Xmx256m
-XX:+UseG1GC
-Dfile.encoding=UTF-8
-Duser.timezone=Asia/Shanghai
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
system:
name: ruoyi-system
replicas: 1
image:
repository: localhost/ruoyi-system
tag: "3.6.8-lab"
port: 9201
javaOpts: >-
-Xms256m
-Xmx512m
-XX:+UseG1GC
-Dfile.encoding=UTF-8
-Duser.timezone=Asia/Shanghai
resources:
requests:
cpu: 150m
memory: 384Mi
limits:
cpu: 700m
memory: 768Mi
EOF
24.3 创建 templates/secret.yaml
cat <<'EOF' > templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: ruoyi-app-nacos
labels:
app.kubernetes.io/name: ruoyi-app
app.kubernetes.io/instance: {{ .Release.Name }}
type: Opaque
stringData:
username: {{ .Values.global.nacos.username | quote }}
password: {{ .Values.global.nacos.password | quote }}
EOF
25. 创建 Gateway Deployment
cat <<'EOF' > templates/gateway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.gateway.name }}
labels:
app.kubernetes.io/name: {{ .Values.gateway.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.gateway.replicas }}
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.gateway.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.gateway.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
initContainers:
- name: wait-for-nacos
image: "{{ .Values.global.busyboxImage.repository }}:{{ .Values.global.busyboxImage.tag }}"
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "until nc -z ruoyi-nacos 8848; do echo 等待Nacos; sleep 3; done"]
- name: wait-for-redis
image: "{{ .Values.global.busyboxImage.repository }}:{{ .Values.global.busyboxImage.tag }}"
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "until nc -z ruoyi-redis 6379; do echo 等待Redis; sleep 3; done"]
containers:
- name: gateway
image: "{{ .Values.gateway.image.repository }}:{{ .Values.gateway.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: JAVA_OPTS
value: {{ .Values.gateway.javaOpts | quote }}
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.global.profile | quote }}
- name: SPRING_CLOUD_NACOS_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
- name: SPRING_CLOUD_NACOS_CONFIG_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_CONFIG_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
- name: SPRING_CLOUD_NACOS_DISCOVERY_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
ports:
- name: http
containerPort: {{ .Values.gateway.port }}
startupProbe:
tcpSocket:
port: http
periodSeconds: 5
failureThreshold: 60
readinessProbe:
tcpSocket:
port: http
periodSeconds: 10
failureThreshold: 6
livenessProbe:
tcpSocket:
port: http
periodSeconds: 20
failureThreshold: 6
resources:
{{ toYaml .Values.gateway.resources | indent 12 }}
EOF
26. 创建 Auth Deployment
cat <<'EOF' > templates/auth-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.auth.name }}
labels:
app.kubernetes.io/name: {{ .Values.auth.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.auth.replicas }}
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.auth.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.auth.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
initContainers:
- name: wait-for-nacos
image: "{{ .Values.global.busyboxImage.repository }}:{{ .Values.global.busyboxImage.tag }}"
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "until nc -z ruoyi-nacos 8848; do echo 等待Nacos; sleep 3; done"]
- name: wait-for-redis
image: "{{ .Values.global.busyboxImage.repository }}:{{ .Values.global.busyboxImage.tag }}"
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "until nc -z ruoyi-redis 6379; do echo 等待Redis; sleep 3; done"]
containers:
- name: auth
image: "{{ .Values.auth.image.repository }}:{{ .Values.auth.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: JAVA_OPTS
value: {{ .Values.auth.javaOpts | quote }}
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.global.profile | quote }}
- name: SPRING_CLOUD_NACOS_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
- name: SPRING_CLOUD_NACOS_CONFIG_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_CONFIG_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
- name: SPRING_CLOUD_NACOS_DISCOVERY_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
ports:
- name: http
containerPort: {{ .Values.auth.port }}
startupProbe:
tcpSocket:
port: http
periodSeconds: 5
failureThreshold: 60
readinessProbe:
tcpSocket:
port: http
periodSeconds: 10
failureThreshold: 6
livenessProbe:
tcpSocket:
port: http
periodSeconds: 20
failureThreshold: 6
resources:
{{ toYaml .Values.auth.resources | indent 12 }}
EOF
27. 创建 System Deployment
cat <<'EOF' > templates/system-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.system.name }}
labels:
app.kubernetes.io/name: {{ .Values.system.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.system.replicas }}
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.system.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.system.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
initContainers:
- name: wait-for-nacos
image: "{{ .Values.global.busyboxImage.repository }}:{{ .Values.global.busyboxImage.tag }}"
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "until nc -z ruoyi-nacos 8848; do echo 等待Nacos; sleep 3; done"]
- name: wait-for-redis
image: "{{ .Values.global.busyboxImage.repository }}:{{ .Values.global.busyboxImage.tag }}"
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "until nc -z ruoyi-redis 6379; do echo 等待Redis; sleep 3; done"]
- name: wait-for-mysql
image: "{{ .Values.global.busyboxImage.repository }}:{{ .Values.global.busyboxImage.tag }}"
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "until nc -z ruoyi-mysql 3306; do echo 等待MySQL; sleep 3; done"]
containers:
- name: system
image: "{{ .Values.system.image.repository }}:{{ .Values.system.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
env:
- name: JAVA_OPTS
value: {{ .Values.system.javaOpts | quote }}
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.global.profile | quote }}
- name: SPRING_CLOUD_NACOS_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR
value: {{ .Values.global.nacos.serverAddr | quote }}
- name: SPRING_CLOUD_NACOS_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
- name: SPRING_CLOUD_NACOS_CONFIG_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_CONFIG_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
- name: SPRING_CLOUD_NACOS_DISCOVERY_USERNAME
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: username
- name: SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD
valueFrom:
secretKeyRef:
name: ruoyi-app-nacos
key: password
ports:
- name: http
containerPort: {{ .Values.system.port }}
startupProbe:
tcpSocket:
port: http
periodSeconds: 5
failureThreshold: 60
readinessProbe:
tcpSocket:
port: http
periodSeconds: 10
failureThreshold: 6
livenessProbe:
tcpSocket:
port: http
periodSeconds: 20
failureThreshold: 6
resources:
{{ toYaml .Values.system.resources | indent 12 }}
EOF
28. 创建三个 ClusterIP Service
cat <<'EOF' > templates/services.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.gateway.name }}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ .Values.gateway.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: {{ .Values.gateway.port }}
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.auth.name }}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ .Values.auth.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: {{ .Values.auth.port }}
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.system.name }}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ .Values.system.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: {{ .Values.system.port }}
targetPort: http
EOF
29. 检查并安装后端服务
cd "$HOME/ruoyi-k8s-lab"
helm lint \
./charts/ruoyi-app \
-f ./charts/ruoyi-app/values-lab.yaml
helm template ruoyi-app \
./charts/ruoyi-app \
--namespace ruoyi \
-f ./charts/ruoyi-app/values-lab.yaml \
> /tmp/ruoyi-app-rendered.yaml
安装:
helm upgrade --install ruoyi-app \
./charts/ruoyi-app \
--namespace ruoyi \
-f ./charts/ruoyi-app/values-lab.yaml \
--wait \
--timeout 15m
检查:
kubectl get pods -n ruoyi -o wide
kubectl get deployment -n ruoyi
kubectl get service -n ruoyi
查看日志:
kubectl logs -n ruoyi deployment/ruoyi-gateway --tail=200
kubectl logs -n ruoyi deployment/ruoyi-auth --tail=200
kubectl logs -n ruoyi deployment/ruoyi-system --tail=200
Nacos 服务列表应出现:
ruoyi-gateway
ruoyi-auth
ruoyi-system
测试 Gateway:
kubectl run ruoyi-gateway-test \
-n ruoyi \
--rm \
-i \
--restart=Never \
--image=m.daocloud.io/docker.io/library/busybox:1.36.1 \
-- wget -S -O- http://ruoyi-gateway:8080/code
第七阶段:构建和部署 UI
30. 检查 NodePort 30081
kubectl get svc -A \
-o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,NODEPORT:.spec.ports[*].nodePort' \
| grep -w 30081 || true
没有输出再继续。
31. 修改前端生产接口前缀
cd "$HOME/ruoyi-k8s-lab/source/RuoYi-Cloud/ruoyi-ui"
if grep -q '^[[:space:]]*VUE_APP_BASE_API' .env.production; then
sed -ri \
"s#^[[:space:]]*VUE_APP_BASE_API[[:space:]]*=.*#VUE_APP_BASE_API = '/prod-api'#" \
.env.production
else
echo "VUE_APP_BASE_API = '/prod-api'" >> .env.production
fi
检查:
grep -n 'VUE_APP_BASE_API' .env.production
32. 准备 UI 构建目录
UI_SOURCE="$HOME/ruoyi-k8s-lab/source/RuoYi-Cloud/ruoyi-ui"
UI_CONTEXT="$HOME/ruoyi-k8s-lab/images/ui"
rm -rf "$UI_CONTEXT"
mkdir -p "$UI_CONTEXT"
cp -a "$UI_SOURCE"/. "$UI_CONTEXT"/
rm -rf \
"$UI_CONTEXT/node_modules" \
"$UI_CONTEXT/dist"
33. 创建 Nginx 配置
cat <<'EOF' > "$UI_CONTEXT/nginx.conf"
server {
listen 80;
server_name _;
charset utf-8;
root /usr/share/nginx/html;
index index.html;
client_max_body_size 100m;
location / {
try_files $uri $uri/ /index.html;
}
location /prod-api/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_pass http://ruoyi-gateway:8080/;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf)$ {
expires 7d;
add_header Cache-Control "public";
try_files $uri =404;
}
}
EOF
34. 创建 UI Dockerfile
cat <<'EOF' > "$UI_CONTEXT/Dockerfile"
FROM m.daocloud.io/docker.io/library/node:16.20.2-alpine AS builder
WORKDIR /build
COPY package*.json ./
RUN npm config set registry https://registry.npmmirror.com \
&& npm install --legacy-peer-deps
COPY . .
RUN npm run build:prod
FROM m.daocloud.io/docker.io/library/nginx:1.27.5-alpine
RUN rm -f /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /build/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
EOF
35. 构建 UI 镜像
cd "$HOME/ruoyi-k8s-lab/source/RuoYi-Cloud/ruoyi-ui"
UI_VERSION="$(
python3 - <<'PY'
import json
with open("package.json", encoding="utf-8") as f:
print(json.load(f)["version"])
PY
)"
UI_TAG="${UI_VERSION}-lab"
UI_CONTEXT="$HOME/ruoyi-k8s-lab/images/ui"
echo "$UI_TAG"
构建:
sudo buildah bud \
--network=host \
--format docker \
--layers \
-t "localhost/ruoyi-ui:${UI_TAG}" \
"$UI_CONTEXT"
检查:
sudo buildah images | grep ruoyi-ui
36. 导出并发送 UI 镜像
rm -f "$HOME/ruoyi-k8s-lab/artifacts/ruoyi-ui.tar"
sudo buildah push \
"localhost/ruoyi-ui:${UI_TAG}" \
"docker-archive:$HOME/ruoyi-k8s-lab/artifacts/ruoyi-ui.tar:localhost/ruoyi-ui:${UI_TAG}"
sudo chown leecurry:leecurry \
"$HOME/ruoyi-k8s-lab/artifacts/ruoyi-ui.tar"
发送:
for ip in 104 105 106; do
scp \
"$HOME/ruoyi-k8s-lab/artifacts/ruoyi-ui.tar" \
"leecurry@192.168.89.${ip}:/tmp/"
done
三个 Worker 每台执行:
sudo ctr \
-n k8s.io \
images import \
/tmp/ruoyi-ui.tar
检查:
sudo crictl images | grep ruoyi-ui
37. 给 values-lab.yaml 添加 UI
回到 master01:
cd "$HOME/ruoyi-k8s-lab/charts/ruoyi-app"
重新获取 UI 版本:
UI_VERSION="$(
python3 - <<'PY'
import json
with open("/home/leecurry/ruoyi-k8s-lab/source/RuoYi-Cloud/ruoyi-ui/package.json", encoding="utf-8") as f:
print(json.load(f)["version"])
PY
)"
UI_TAG="${UI_VERSION}-lab"
如果 values-lab.yaml 中还没有 ui:,执行:
cat <<EOF >> values-lab.yaml
ui:
name: ruoyi-ui
replicas: 1
image:
repository: localhost/ruoyi-ui
tag: "${UI_TAG}"
port: 80
service:
type: NodePort
nodePort: 30081
resources:
requests:
cpu: 20m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
EOF
更新 Chart 版本:
sed -i 's/^version:.*/version: 0.2.0/' Chart.yaml
38. 创建 templates/ui.yaml
cat <<'EOF' > templates/ui.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.ui.name }}
labels:
app.kubernetes.io/name: {{ .Values.ui.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.ui.replicas }}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.ui.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.ui.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: ui
image: "{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag }}"
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- name: http
containerPort: {{ .Values.ui.port }}
protocol: TCP
startupProbe:
httpGet:
path: /
port: http
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
readinessProbe:
httpGet:
path: /
port: http
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
httpGet:
path: /
port: http
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 6
resources:
{{ toYaml .Values.ui.resources | indent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.ui.name }}
labels:
app.kubernetes.io/name: {{ .Values.ui.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: {{ .Values.ui.service.type }}
selector:
app.kubernetes.io/name: {{ .Values.ui.name }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
nodePort: {{ .Values.ui.service.nodePort }}
EOF
39. 升级 ruoyi-app
cd "$HOME/ruoyi-k8s-lab"
helm lint \
./charts/ruoyi-app \
-f ./charts/ruoyi-app/values-lab.yaml
helm template ruoyi-app \
./charts/ruoyi-app \
--namespace ruoyi \
-f ./charts/ruoyi-app/values-lab.yaml \
> /tmp/ruoyi-app-with-ui.yaml
升级:
helm upgrade --install ruoyi-app \
./charts/ruoyi-app \
--namespace ruoyi \
-f ./charts/ruoyi-app/values-lab.yaml \
--atomic \
--wait \
--timeout 10m
检查:
kubectl get pods -n ruoyi -o wide
kubectl get svc ruoyi-ui -n ruoyi
helm history ruoyi-app -n ruoyi
第八阶段:最终验证
40. 检查 Helm Release
helm list -n ruoyi
应看到:
ruoyi-mysql deployed
ruoyi-redis deployed
ruoyi-nacos deployed
ruoyi-app deployed
41. 检查 Pod、Service、PVC
kubectl get pod,svc,pvc -n ruoyi -o wide
所有 Pod 应为:
Running
所有 PVC 应为:
Bound
42. 测试前端和后端
curl -I http://192.168.89.104:30081/
应返回:
HTTP/1.1 200 OK
测试后端代理:
curl -s \
http://192.168.89.104:30081/prod-api/code \
| head -c 500
echo
浏览器登录:
地址:http://192.168.89.104:30081
用户名:admin
密码:admin123
第九阶段:VMware 重启后继续使用
不需要重新执行 Helm 安装、SQL 导入或镜像构建。
启动顺序:
master01
master02
master03
worker01
worker02
worker03
等待 2~5 分钟后执行:
kubectl get nodes
kubectl get pods -n ruoyi -o wide
helm list -n ruoyi
访问:
Nacos:http://192.168.89.104:30082/index.html
若依:http://192.168.89.104:30081
第十阶段:常见排错
43. Pod 异常
kubectl get pods -n ruoyi -o wide
kubectl describe pod <Pod名称> -n ruoyi
44. 查看日志
kubectl logs -n ruoyi ruoyi-mysql-0 --tail=200
kubectl logs -n ruoyi ruoyi-redis-0 --tail=200
kubectl logs -n ruoyi ruoyi-nacos-0 --tail=200
kubectl logs -n ruoyi deployment/ruoyi-gateway --tail=200
kubectl logs -n ruoyi deployment/ruoyi-auth --tail=200
kubectl logs -n ruoyi deployment/ruoyi-system --tail=200
kubectl logs -n ruoyi deployment/ruoyi-ui --tail=200
查看容器上一次日志:
kubectl logs -n ruoyi <Pod名称> --previous --tail=200
45. ImagePullBackOff
先看 Pod 在哪个 Worker:
kubectl get pods -n ruoyi -o wide
登录对应 Worker:
sudo crictl images \
| grep -E 'ruoyi-(gateway|auth|system|ui)'
缺少镜像时重新导入:
sudo ctr -n k8s.io images import /tmp/对应镜像.tar
46. Nacos 注册失败
kubectl logs -n ruoyi deployment/ruoyi-gateway --tail=200
kubectl logs -n ruoyi deployment/ruoyi-auth --tail=200
kubectl logs -n ruoyi deployment/ruoyi-system --tail=200
kubectl get svc ruoyi-nacos -n ruoyi
kubectl get endpointslice -n ruoyi \
-l kubernetes.io/service-name=ruoyi-nacos
47. MySQL 或 Redis 连接失败
MySQL:
MYSQL_ROOT_PASSWORD="$(
kubectl get secret ruoyi-mysql \
-n ruoyi \
-o jsonpath='{.data.mysql-root-password}' \
| base64 -d
)"
kubectl exec \
-n ruoyi \
ruoyi-mysql-0 \
-- env MYSQL_PWD="$MYSQL_ROOT_PASSWORD" \
mysql -uroot -e 'SELECT 1;'
Redis:
REDIS_PASSWORD="$(
kubectl get secret ruoyi-redis \
-n ruoyi \
-o jsonpath='{.data.redis-password}' \
| base64 -d
)"
kubectl exec \
-n ruoyi \
ruoyi-redis-0 \
-- env REDISCLI_AUTH="$REDIS_PASSWORD" \
redis-cli ping
48. UI 返回 502
kubectl get svc ruoyi-gateway -n ruoyi
kubectl get endpointslice -n ruoyi \
-l kubernetes.io/service-name=ruoyi-gateway
从 UI Pod 内测试:
kubectl exec \
-n ruoyi \
deployment/ruoyi-ui \
-- wget -S -O- http://ruoyi-gateway:8080/code
第十一阶段:卸载说明
只卸载业务应用:
helm uninstall ruoyi-app -n ruoyi
卸载 Nacos:
helm uninstall ruoyi-nacos -n ruoyi
卸载 Redis:
helm uninstall ruoyi-redis -n ruoyi
卸载 MySQL:
helm uninstall ruoyi-mysql -n ruoyi
StatefulSet 生成的 PVC 通常不会随 Helm Release 自动删除。
查看:
kubectl get pvc -n ruoyi
只有确认数据不再需要时,才删除:
kubectl delete pvc --all -n ruoyi
删除整个命名空间:
kubectl delete namespace ruoyi
删除命名空间会删除其中所有资源,不要在仍然需要数据时执行。
第十二阶段:当前实验环境限制
当前方案适合学习和实验,但不是完整生产高可用:
MySQL:单副本
Redis:单副本
Nacos:单副本
存储:local-path,本地节点存储
镜像:手工导入三个 Worker
业务服务:初始单副本
后续升级方向:
local-path → Rook-Ceph
手工导入镜像 → Harbor
手工 Buildah 构建 → Jenkins / GitLab CI
手工 Helm 发布 → Argo CD
单副本 → 多副本 + HPA
TCP 探针 → Spring Boot Actuator 健康检查
更多推荐

所有评论(0)