一、核心背景在不新增服务器、沿用现有 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迁移期间业务尽量不中断二、最终落地架构组件版本运行环境状态MySQL8.0K3s离线导入镜像✅Redis7.xlatestK3s离线导入镜像✅AdminJava1.0.0K3s离线导入镜像✅APIJava1.0.0K3s离线导入镜像⚠️ jar包待修复NginxlatestK3s离线导入镜像 ConfigMap✅Milvus2.3.0Docker Compose保留✅OllamalatestDocker Compose保留✅FTP3.0.5宿主机原生服务✅通信方式K3s Pod 通过宿主机 IP192.168.18.100访问 Docker 中间件。三、踩坑实录与根因分析坑 1镜像导入明明成功K3s 却说找不到现象bashctr -n k8s.io images import mysql.tar # 显示成功 ctr -n k8s.io images ls # 能看到镜像 k3s kubectl apply -f mysql.yaml # Pod 报 ErrImageNeverPull根因K3s 使用独立的 containerdsocket/run/k3s/containerd/containerd.sock系统ctr操作的是默认 containerd/run/containerd/containerd.sock两个实例完全隔离。解决所有镜像操作必须用k3s ctrbashsudo k3s ctr -n k8s.io images import mysql.tar sudo k3s ctr -n k8s.io images ls坑 2imagePullPolicy: IfNotPresent 仍然尝试外网拉取现象离线环境下即便镜像已导入kubelet 仍报ErrImagePull。根因镜像名包含docker.io域名时kubelet 会尝试访问该域名进行元数据校验。解决强制使用imagePullPolicy: Never且镜像名必须与k3s ctr images ls输出完全一致yamlimage: docker.io/library/mysql:8.0 imagePullPolicy: Never坑 3HostPath 挂载 jar 包失败现象CreateContainerConfigErrordescribe 显示failed to prepare subPath for volumeMount根因K3scontainerd不支持用subPath挂载单个文件。解决挂载整个目录command 中指定完整路径yamlvolumeMounts: - 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]坑 4Nginx 配置文件挂载同样踩坑现象同样的failed to prepare subPath。解决改用 ConfigMap 挂载单文件bashkubectl create configmap nginx-config -n biz-shangzhuhui --from-filenginx.confyamlvolumes: - name: nginx-config configMap: name: nginx-config坑 5Nginx 启动报 host not found现象host not found in upstream shangzhuhui-api根因Nginx 启动时会解析proxy_pass中的域名后端 Service 不存在则拒绝启动。解决注释掉不存在的后端配置或先部署后端正则。坑 6Nginx 重定向丢失端口现象访问http://IP:30080/admin返回 301Location: 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_v2SQL 脚本中CREATE DATABASE shanzhuhui注意字母差异应用连接串中shangzhuhui解决统一改为shangzhuhui手动导入 SQLbashkubectl exec -n biz-shangzhuhui mysql-xxx -- mysql -p111111 shangzhuhui schema.sql坑 8K3s Pod 无法访问宿主机 Docker 中间件现象Java 应用启动报DEADLINE_EXCEEDED无法连接 Milvus。排障过程先用curl测试网络kubectl exec -it pod -- curl http://192.168.18.100:19530/api/v1/health返回 404404 是 Milvus 健康检查接口路径问题非网络不通——说明网络层正常确认问题不在网络而在应用 jar 包本身健康检查接口最终返回milvus: OK确认网络连接正常结论网络层无问题隔离正常。四、迁移前后资源对比16G 服务器对比数据指标Docker ComposeK3s变化系统总内存占用4.1G / 15G4.4G / 15G0.3G系统可用内存11G10G-1GK3s 控制平面—~1G1GMySQL424M389M-35MRedis33M5M-28MNginx4.8M2M-2.8MAdminJava493M391M-102M核心结论K3s 控制平面开销约 1G 内存kubelet containerd flannel apiserver 等业务容器内存占用显著降低Java 服务降低 20%Redis 降低 85%净增仅 300MB 内存换来完整 Kubernetes 编排能力资源代价极低适合资源敏感型单机迁移场景五、固化铁律K3s 离线部署镜像导入一律用sudo k3s ctr -n k8s.io images import不用系统ctrimagePullPolicy离线环境必须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# 导入镜像到 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 applabel -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 落地的参考范本。