在当今的云原生生态中,将应用迁移至 Kubernetes 已不再是选择题,而是提升系统弹性、可伸缩性和运维效率的必经之路。然而,将一个包含数据库、缓存、后端服务和前端界面的全栈应用,遵循最佳实践部署到 K8s 集群,对许多开发者而言仍是一项挑战。

本篇教程旨在提供一个详尽且可复现的蓝图,我们将以现代化的高性能项目 RuoYi-Vue3-FastAPI 为例,完成一次端到端的 Kubernetes 部署实战。在此,首先要向该项目的作者 insistence 及其所有贡献者表示诚挚的感谢,他们卓越的工作为社区提供了一个优秀的实践范本。

本文不仅会详细阐述每一步的操作,更会深入剖析其背后的原理。我们提供的所有 DockerfileKubernetes YAML 配置文件,都经过精心设计和验证,可以作为您在自己项目中部署类似应用的坚实基础和起点。

无论您是希望系统学习 K8s 应用部署的开发者,还是寻求标准化部署流程的运维工程师,本指南都将为您提供清晰的路线图和可直接使用的代码资产。下面,让我们共同开启这段通往专业化云原生部署的旅程。

本次部署目标

  • 项目源码: RuoYi-Vue3-FastAPI

  • 部署环境: 一个基础的 K8s 集群

    • master101 (主节点): 10.0.0.101

    • worker102 (工作节点): 10.0.0.102

  • 最终效果: 通过浏览器访问 http://<worker节点IP>:<NodePort>,即可打开若依系统登录页面。

篇章一:环境准备

在 K8s 上部署自定义应用,首先要解决镜像存储问题。为了方便快捷,我们将在主节点上搭建一个私有的 Docker Registry,用于存放我们后续构建的应用镜像。

步骤 1.1: 搭建本地 Docker Registry (在 master101 操作)

# 1. 如果节点未安装 Docker,请先安装
sudo apt-get update
sudo apt-get install -y docker.io 
sudo systemctl enable docker --now

# 2. 运行一个 Docker Registry 容器,监听在 5000 端口
docker run -d -p 5000:5000 --restart=always --name registry registry:2

步骤 1.2: 配置集群信任本地 Registry (所有节点操作)

💡 核心原理: K8s 的节点(Kubelet)默认只信任安全的 HTTPS Registry。我们搭建的 Registry 是 HTTP 的,因此需要明确告知所有节点上的 Docker/Containerd 进程,10.0.0.101:5000 这个地址是可信的,无需使用 HTTPS。

master101worker102 两个节点上都执行以下操作:

# 创建或修改 Docker 配置文件
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "insecure-registries" : ["10.0.0.101:5000"]
}
EOF

# 重启 Docker 使配置生效
sudo systemctl daemon-reload
sudo systemctl restart docker

⚠️ 注意:如果你的 K8s 集群使用 Containerd 作为容器运行时,请参考文末的【实战排错】章节进行配置。

篇章二:应用容器化

接下来,我们需要为若依的后端(FastAPI)和前端(Vue)分别制作 Docker 镜像。

步骤 2.1: 后端 Dockerfile (在 master101 操作)

cd 到后端项目目录 /root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/,创建 Dockerfile:

# /root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/Dockerfile

# 使用轻量的 Python 官方镜像
FROM python:3.10-slim

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY ./requirements.txt /app/requirements.txt

# 使用国内镜像源加速依赖安装
RUN pip install --no-cache-dir --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple && \
    pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

# 复制所有项目代码
COPY . /app

# 暴露应用端口
EXPOSE 8888

# 启动命令,监听所有网络接口
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8888"]

步骤 2.2: 前端 Dockerfile 与 Nginx 配置 (在 master101 操作)

前端项目需要先编译成静态文件,再由 Nginx 提供服务。这里我们采用高效的 多阶段构建 策略。

cd 到前端项目目录 /root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-frontend/。

首先,创建 Nginx 配置文件 nginx.conf:

# /root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-frontend/nginx.conf

server {
    listen       80;
    server_name  localhost;

    # 托管编译后的静态文件
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    # 代理 API 请求
    # /prod-api/ 是若依前端配置的 API 前缀
    location /prod-api/ {
        # 关键!这里指向后端 K8s Service 的 DNS 名称
        # K8s 内部 DNS 会将 ruoyi-backend-service 解析到正确的 Pod IP
        proxy_pass http://ruoyi-backend-service:8888/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

然后,创建前端的Dockerfile:

# /root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-frontend/Dockerfile

# --- 构建阶段 (Builder) ---
# 使用 Node.js 18 镜像进行编译
FROM node:18 as builder
WORKDIR /app
COPY package*.json ./
# 使用国内镜像源加速
RUN npm install --registry=https://registry.npmmirror.com
COPY . .
# 执行生产环境构建
RUN npm run build:prod

# --- 生产阶段 (Production) ---
# 使用轻量的 Nginx 镜像
FROM nginx:alpine
# 从构建阶段复制编译好的静态文件
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制自定义的 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露 80 端口
EXPOSE 80
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

篇章三:镜像构建与推送

现在,我们将刚才定义的 Dockerfile 构建成镜像,并推送到我们的私有仓库。

步骤 3.1: 构建并推送后端镜像

cd /root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/
docker build -t 10.0.0.101:5000/ruoyi-backend:v1.0 .
docker push 10.0.0.101:5000/ruoyi-backend:v1.0

步骤 3.2: 构建并推送前端镜像

cd /root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-frontend/
docker build -t 10.0.0.101:5000/ruoyi-frontend:v1.0 .
docker push 10.0.0.101:5000/ruoyi-frontend:v1.0

篇章四:K8s 资源编排

在 master101 上创建一个工作目录:

mkdir -p /root/ruoyi-k8s-deploy
cd /root/ruoyi-k8s-deploy

我们将所有资源清单(YAML文件)都放在这个目录中。

1. 命名空间 (01-namespace.yaml)

为若依系统创建一个专属的命名空间,便于管理和隔离。

# 01-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: ruoyi-ns

2. 持久化存储 (02-mysql-pv-pvc.yaml)

MySQL 的数据必须持久化。我们使用 hostPath 将数据存储在 worker102 节点上。

⚠️ 提示hostPath 适用于测试和单节点场景。在生产环境中,强烈建议使用更可靠的存储方案,如 NFS, Ceph 或云厂商提供的持久化存储。

先在 worker102 节点上创建数据目录:

# 在 worker102 上执行
sudo mkdir -p /data/mysql
sudo chmod 777 /data/mysql

然后回到 master101,创建 PV 和 PVC 的 YAML 文件:

# 02-mysql-pv-pvc.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  hostPath:
    path: "/data/mysql"
  nodeAffinity: # 节点亲和性,强制 PV 绑定到 worker102
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - worker102
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
  namespace: ruoyi-ns # 确保在 ruoyi-ns 命名空间下
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  resources:
    requests:
      storage: 5Gi

3. 核心配置 (03-config.yaml)

使用 Secret 存储敏感数据(如数据库密码),ConfigMap 存储非敏感配置。

# 03-config.yaml
# 用于存储 MySQL root 密码的 Secret
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: ruoyi-ns
type: Opaque
stringData:
  # 强烈建议修改为你的强密码!
  MYSQL_ROOT_PASSWORD: "YourStrongPassword123!" 
# 此处 ConfigMap 已被后端 Deployment 中的环境变量替代,为保持简洁,此处省略。
# 在更复杂的场景中,可以将一些不常变的配置放入 ConfigMap。

4. 依赖服务:MySQL 和 Redis

MySQL (04-mysql-deployment.yaml)

# 04-mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-deployment
  namespace: ruoyi-ns
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: MYSQL_ROOT_PASSWORD
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-storage
        persistentVolumeClaim:
          claimName: mysql-pvc # 引用之前创建的 PVC
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-service # Service 名称,供其他应用在集群内部访问
  namespace: ruoyi-ns
spec:
  selector:
    app: mysql
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

5. 若依应用:后端与前端

后端 (06-ruoyi-backend-deployment.yaml)

# 06-ruoyi-backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ruoyi-backend-deployment
  namespace: ruoyi-ns
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ruoyi-backend
  template:
    metadata:
      labels:
        app: ruoyi-backend
    spec:
      containers:
      - name: ruoyi-backend
        image: 10.0.0.101:5000/ruoyi-backend:v1.0
        imagePullPolicy: Always
        ports:
        - containerPort: 8888
        env:
          # --- 设置应用运行环境,避免加载本地 .env 文件 ---
          - name: APP_ENV
            value: "prod"
          # --- 数据库配置 ---
          - name: DB_TYPE
            value: "mysql"
          - name: DB_HOST
            value: "mysql-service" # 使用 K8s Service DNS 名称
          - name: DB_PORT
            value: "3306"
          - name: DB_USERNAME
            value: "root"
          - name: DB_DATABASE
            value: "ry-fast" # 数据库名
          - name: DB_PASSWORD
            valueFrom:
              secretKeyRef:
                name: mysql-secret
                key: MYSQL_ROOT_PASSWORD
          - name: DB_ECHO
            value: "False"
          # --- Redis 配置 ---
          - name: REDIS_HOST
            value: "redis-service" # 使用 K8s Service DNS 名称
          - name: REDIS_PORT
            value: "6379"
          - name: REDIS_PASSWORD
            value: ""
          - name: REDIS_DATABASE
            value: "0"
          # --- 应用配置 ---
          - name: APP_PORT
            value: "8888"
          - name: APP_RELOAD
            value: "False"
---
apiVersion: v1
kind: Service
metadata:
  name: ruoyi-backend-service # 必须与前端 Nginx 配置中的 proxy_pass 地址一致
  namespace: ruoyi-ns
spec:
  selector:
    app: ruoyi-backend
  ports:
  - protocol: TCP
    port: 8888
    targetPort: 8888

前端 (07-ruoyi-frontend-deployment.yaml)

使用 NodePort 类型的 Service,将前端服务暴露到集群外部,方便我们访问。

# 07-ruoyi-frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ruoyi-frontend-deployment
  namespace: ruoyi-ns
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ruoyi-frontend
  template:
    metadata:
      labels:
        app: ruoyi-frontend
    spec:
      containers:
      - name: ruoyi-frontend
        image: 10.0.0.101:5000/ruoyi-frontend:v1.0
        imagePullPolicy: Always
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: ruoyi-frontend-service
  namespace: ruoyi-ns
spec:
  type: NodePort # 使用 NodePort 类型暴露服务
  selector:
    app: ruoyi-frontend
  ports:
  - protocol: TCP
    port: 80       # Service 内部端口
    targetPort: 80 # Pod 容器端口
    nodePort: 30080 # 指定一个固定的外部端口

篇章五:部署与验证

cd /root/ruoyi-k8s-deploy

# 按顺序一次性应用所有配置
kubectl apply -f 01-namespace.yaml
kubectl apply -f 02-mysql-pv-pvc.yaml
kubectl apply -f 03-config.yaml
kubectl apply -f 04-mysql-deployment.yaml
kubectl apply -f 05-redis-deployment.yaml
kubectl apply -f 06-ruoyi-backend-deployment.yaml
kubectl apply -f 07-ruoyi-frontend-deployment.yaml

查看部署状态:

kubectl get pods -n ruoyi-ns -o wide --watch

篇章六:实战排错

❌ 错误 1: 镜像拉取失败 (ImagePullBackOff)

现象: kubectl describe pod ... 显示 server gave HTTP response to HTTPS client。

原因: 这个问题我们在准备阶段就预料到了。Pod 被调度到了 worker102 节点,但该节点上的容器运行时(Docker/Containerd)没有被配置信任我们自建的 HTTP Registry。

解决方案:

  1. 确认 worker102 的容器运行时:

    • ssh root@10.0.0.102 "ps aux | grep dockerd" (有输出则为 Docker)

    • ssh root@10.0.0.102 "ps aux | grep containerd" (有输出则为 Containerd)

  2. 根据运行时进行配置:

    • 如果是 Docker: 按照【篇章一】的方法,在 worker102 上配置 /etc/docker/daemon.json 并重启 Docker。

    • 如果是 Containerd: 编辑 worker102 上的 /etc/containerd/config.toml,在末尾添加:

[plugins."io.containerd.grpc.v1.cri".registry.mirrors."10.0.0.101:5000"]
  endpoint = ["http://10.0.0.101:5000"]

然后重启 Containerd: systemctl restart containerd。

重新触发拉取: 删除出问题的 Pod,K8s 会自动重建,此时它将能成功拉取镜像。

kubectl delete pod <pod-name> -n ruoyi-ns

❌ 错误 2: 后端应用启动失败 (CrashLoopBackOff)

现象: kubectl logs <ruoyi-backend-pod-name> -n ruoyi-ns 显示 OperationalError: (2003, "Can't connect to MySQL server on '127.0.0.1' ...")。

原因: 应用程序默认尝试连接本地(127.0.0.1)的 MySQL,但我们已经通过环境变量注入了正确的 mysql-service 地址。这通常意味着环境变量没有被正确读取。

解决方案:
请仔细检查 06-ruoyi-backend-deployment.yaml 文件中的 env 部分。确保 DB_HOST 的值是 "mysql-service",并且 APP_ENV 设置为 "prod"。本教程提供的 YAML 已经修正了此问题。如果修改了 YAML,记得重新 apply。

❌ 错误 3: SQL 导入问题 (功能异常)

现象: 登录或操作某些功能时报错,日志中可能出现数据类型相关的 SQL 错误。

原因: 官方 ruoyi-fastapi.sql 文件中,某些字段(如 sys_menu 表的 component 字段)的长度可能不足以存储前端路由组件的路径(例如 system/user/index)。

解决方案:

  1. 修改 SQL 文件:
    在 master101 上,编辑 /root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql 文件。找到 sys_menu 表的定义(约635行),将 component 字段的长度从 varchar(100) 增加到 varchar(255)。

-- 修改前
`component`  varchar(100) default null comment '组件路径',
-- 修改后
`component`  varchar(255) default null comment '组件路径',

篇章七:收尾工作 —— 数据初始化与最终访问

在所有 Pod 正常运行后,我们需要将 SQL 文件导入到 MySQL 容器中。

步骤 7.1: 初始化数据库

# 在 master101 上执行

# --- 定义一些方便使用的变量 ---
SQL_FILE_PATH="/root/RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql"
MYSQL_POD_NAME=$(kubectl get pods -n ruoyi-ns -l app=mysql -o jsonpath='{.items[0].metadata.name}')
MYSQL_ROOT_PASSWORD=$(kubectl get secret mysql-secret -n ruoyi-ns -o jsonpath='{.data.MYSQL_ROOT_PASSWORD}' | base64 --decode)

# 1. 将修改后的 SQL 文件复制到 MySQL Pod 中
kubectl cp ${SQL_FILE_PATH} ${MYSQL_POD_NAME}:/tmp/ruoyi.sql -n ruoyi-ns

# 2. 登录 Pod,执行数据库导入命令 (删除旧库,创建新库,导入数据)
kubectl exec -it ${MYSQL_POD_NAME} -n ruoyi-ns -- \
bash -c "mysql -u root -p'${MYSQL_ROOT_PASSWORD}' --default-character-set=utf8mb4 -e 'DROP DATABASE IF EXISTS \`ry-fast\`; CREATE DATABASE \`ry-fast\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE \`ry-fast\`; SOURCE /tmp/ruoyi.sql;'"

# 3. 重启后端服务,确保它加载最新的数据库结构和数据
kubectl rollout restart deployment/ruoyi-backend-deployment -n ruoyi-ns

步骤 7.2: 访问系统

http://10.0.0.102:30080