一、核心背景
在不新增服务器、沿用现有 16G 开发机前提下,将原本运行在 Docker Compose 上的微服务架构(MySQL、Redis、2个 Java 服务、Nginx)迁移至 K3s,同时保留 Milvus、Ollama、FTP 等 AI 相关中间件继续由 Docker Compose 托管。
硬件环境:单机 16G 内存,已有 Docker Compose 全套服务稳定运行中。
迁移目标:
-
K3s 托管 Java 应用 + Nginx + MySQL + Redis
-
完全离线导入镜像(服务器无外网)
-
K3s Pod 访问宿主机 Docker 中间件(Milvus、Ollama)
-
迁移期间业务尽量不中断
二、最终落地架构
| 组件 | 版本 | 运行环境 | 状态 |
|---|---|---|---|
| MySQL | 8.0 | K3s(离线导入镜像) | ✅ |
| Redis | 7.x(latest) | K3s(离线导入镜像) | ✅ |
| Admin(Java) | 1.0.0 | K3s(离线导入镜像) | ✅ |
| API(Java) | 1.0.0 | K3s(离线导入镜像) | ⚠️ jar包待修复 |
| Nginx | latest | K3s(离线导入镜像 + ConfigMap) | ✅ |
| Milvus | 2.3.0 | Docker Compose(保留) | ✅ |
| Ollama | latest | Docker Compose(保留) | ✅ |
| FTP | 3.0.5 | 宿主机原生服务 | ✅ |
通信方式 :K3s Pod 通过宿主机 IP 192.168.18.100 访问 Docker 中间件。
三、踩坑实录与根因分析
坑 1:镜像导入明明成功,K3s 却说找不到
现象:
bash
bash
ctr -n k8s.io images import mysql.tar # 显示成功
ctr -n k8s.io images ls # 能看到镜像
k3s kubectl apply -f mysql.yaml # Pod 报 ErrImageNeverPull
根因 :K3s 使用独立的 containerd(socket:/run/k3s/containerd/containerd.sock),系统 ctr 操作的是默认 containerd(/run/containerd/containerd.sock),两个实例完全隔离。
解决 :所有镜像操作必须用 k3s ctr:
bash
bash
sudo k3s ctr -n k8s.io images import mysql.tar
sudo k3s ctr -n k8s.io images ls
坑 2:imagePullPolicy: IfNotPresent 仍然尝试外网拉取
现象 :离线环境下,即便镜像已导入,kubelet 仍报 ErrImagePull。
根因 :镜像名包含 docker.io 域名时,kubelet 会尝试访问该域名进行元数据校验。
解决 :强制使用 imagePullPolicy: Never,且镜像名必须与 k3s ctr images ls 输出完全一致:
yaml
bash
image: docker.io/library/mysql:8.0
imagePullPolicy: Never
坑 3:HostPath 挂载 jar 包失败
现象 :CreateContainerConfigError,describe 显示 failed to prepare subPath for volumeMount
根因 :K3s(containerd)不支持用 subPath 挂载单个文件。
解决:挂载整个目录,command 中指定完整路径:
yaml
bash
volumeMounts:
- name: jar
mountPath: /app
volumes:
- name: jar
hostPath:
path: /home/xyy/k3s-shangzhuhui/jar
type: Directory
command: ["java", "-jar", "/app/shangzhuhui-admin-1.0.0.jar"]
坑 4:Nginx 配置文件挂载同样踩坑
现象 :同样的 failed to prepare subPath。
解决:改用 ConfigMap 挂载单文件:
bash
bash
kubectl create configmap nginx-config -n biz-shangzhuhui --from-file=nginx.conf
yaml
bash
volumes:
- name: nginx-config
configMap:
name: nginx-config
坑 5:Nginx 启动报 host not found
现象 :host not found in upstream "shangzhuhui-api"
根因 :Nginx 启动时会解析 proxy_pass 中的域名,后端 Service 不存在则拒绝启动。
解决:注释掉不存在的后端配置,或先部署后端正则。
坑 6:Nginx 重定向丢失端口
现象 :访问 http://IP:30080/admin 返回 301,Location: http://IP/admin(丢端口)
根因 :try_files $uri $uri/ /admin/index.html 中的 $uri/ 触发内部重定向,Nginx 默认用 $host 构造 Location,不带端口。
解决 :添加 port_in_redirect on;,或前端直接使用 /admin/login 等具体路径绕过。
坑 7:数据库名三处不一致
现象 :应用报 Unknown database 'shangzhuhui'
根因:
-
MySQL YAML 中
MYSQL_DATABASE: shangzhuhui_v2 -
SQL 脚本中
CREATE DATABASE shanzhuhui(注意字母差异) -
应用连接串中
shangzhuhui
解决 :统一改为 shangzhuhui,手动导入 SQL:
bash
bash
kubectl exec -n biz-shangzhuhui mysql-xxx -- mysql -p111111 shangzhuhui < schema.sql
坑 8:K3s Pod 无法访问宿主机 Docker 中间件
现象 :Java 应用启动报 DEADLINE_EXCEEDED,无法连接 Milvus。
排障过程:
-
先用
curl测试网络:kubectl exec -it pod -- curl http://192.168.18.100:19530/api/v1/health返回 404 -
404 是 Milvus 健康检查接口路径问题,非网络不通------说明网络层正常
-
确认问题不在网络,而在应用 jar 包本身
-
健康检查接口最终返回
milvus: "OK",确认网络连接正常
结论:网络层无问题,隔离正常。
四、迁移前后资源对比(16G 服务器)
对比数据
| 指标 | Docker Compose | K3s | 变化 |
|---|---|---|---|
| 系统总内存占用 | 4.1G / 15G | 4.4G / 15G | +0.3G |
| 系统可用内存 | 11G | 10G | -1G |
| K3s 控制平面 | --- | ~1G | +1G |
| MySQL | 424M | 389M | -35M |
| Redis | 33M | 5M | -28M |
| Nginx | 4.8M | 2M | -2.8M |
| Admin(Java) | 493M | 391M | -102M |
核心结论
-
K3s 控制平面开销约 1G 内存(kubelet + containerd + flannel + apiserver 等)
-
业务容器内存占用显著降低:Java 服务降低 20%,Redis 降低 85%
-
净增仅 300MB 内存,换来完整 Kubernetes 编排能力
-
资源代价极低,适合资源敏感型单机迁移场景
五、固化铁律(K3s 离线部署)
-
镜像导入 :一律用
sudo k3s ctr -n k8s.io images import,不用系统ctr -
imagePullPolicy :离线环境必须
Never,镜像名与k3s ctr images ls完全一致 -
HostPath 挂载 :挂目录不挂单文件,避免
subPath坑 -
Nginx 配置:用 ConfigMap 挂载,不用 hostPath 单文件
-
Nginx 重定向 :添加
port_in_redirect on,前端尽量使用具体路径 -
后端依赖 :Nginx 中
proxy_pass的后端 Service 必须先存在,否则注释 -
数据库:SQL、初始化环境变量、应用连接串,三处库名必须一致
-
排障优先级 :先验镜像是否导入正确(
k3s ctr images ls),再看 Pod 日志
六、常用命令速查
bash
bash
# 导入镜像到 K3s 的 containerd
sudo k3s ctr -n k8s.io images import <tar包>
# 查看已导入的镜像
sudo k3s ctr -n k8s.io images ls
# 重启 K3s 服务
sudo systemctl restart k3s
# 查看命名空间下所有资源
kubectl get all -n <namespace>
# 实时查看 Pod 日志(按标签筛选)
kubectl logs -n <namespace> -l app=<label> -f
# 创建 ConfigMap 从文件
kubectl create configmap <名称> -n <ns> --from-file=<文件>
# 删除 ConfigMap
kubectl delete configmap <名称> -n <ns>
# 端口转发:本地工具连接 K3s 内 MySQL(临时调试用)
kubectl port-forward -n <ns> svc/mysql 3306:3306
# 查看 Pod 资源占用(CPU/内存)
kubectl top pods -n <namespace>
# 进入容器调试
kubectl exec -it -n <namespace> <pod名> -- bash
七、底稿收尾
本文是《技术底稿》系列第 36 篇,记录 Docker Compose 微服务迁移 K3s 过程中的完整踩坑、排障、资源对比与规范固化。
核心价值:
-
8 条可复用的 K3s 离线部署铁律
-
迁移前后真实资源对比数据
-
单机混合架构(K3s + Docker Compose)通信方案
适合小团队单机迁移、无外网环境 K3s 落地的参考范本。