侧边栏壁纸
  • 累计撰写 131 篇文章
  • 累计收到 2 条评论

个人项目从 Docker Compose 迁到 k3s 轻量集群,我踩过的几个坑说清楚

2026-8-5 / 0 评论 / 7 阅读
🤖AI摘要
本文记录了作者将个人项目从Docker Compose迁移到k3s轻量集群过程中遇到的几个关键问题及解决方法。包括国内网络下载镜像、k3s权限设置、端口映射的转换、数据持久化方案选择等。文章建议读者在迁移过程中注意这些问题,并根据自己的需求选择合适的解决方案。

先说结论:折腾完这一趟,我不后悔,但也不会劝所有人都去迁。

我自己的博客加几个小服务,一直跑在 docker-compose 上,单机、够用、省心。2025 年底手贱想试试 k3s,听人说"一条命令装完,Kubernetes 也能用起来",结果从安装到把服务全部跑顺,前前后后花了三个周末。中间好几次想回滚,最后还是咬牙弄完了。这篇把几个真正卡住我的地方记下来,想从 compose 往 k3s 挪的人应该用得上。

安装确实一条命令,但国内网络下得先改镜像

k3s 官方安装就一行:

curl -sfL https://get.k3s.io | sh -

我的服务器在国内,第一次直接跑这条,卡在下载二进制那一步,进度条半天不动,最后超时。后来发现得指定国内镜像源:

curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh -

装完第一件事是权限。k3s 生成的 kubeconfig 在 /etc/rancher/k3s/k3s.yaml,默认权限 644,kubectl 每次都要警告 insecure permissions。chmod 600 一下,再 export KUBECONFIG 指过去,世界就安静了。

最难受的是端口映射,compose 的习惯彻底废了

docker-compose 里写个 ports: "8080:80" 就能访问,这个心智模型在 k3s 里完全不适用。k3s 没有"把容器端口映射到宿主机"这回事,要理解的是三层东西:Deployment 管 Pod 的副本和更新,Service 管集群内部的访问入口,默认 ClusterIP 只在集群里通,对外暴露要么 NodePort、要么 LoadBalancer、要么走 Ingress。

我第一个服务起好以后,习惯性地找"宿主机的 8080 在哪",找半天才发现 Service 配的是 ClusterIP,宿主机上根本没有入口。图省事直接上 LoadBalancer,k3s 默认的 klipper-lb(svclb)会在宿主机开一个端口监听。这个方案小项目够用,但有两个毛病:每个 LoadBalancer 服务都会占一个宿主机端口,服务多了端口列表看着头疼;pod 重建、svclb 重建的时候,手动指定的 nodePort 还可能跟别的服务撞上,报错信息还不直观。

正经做法是统一走 Ingress。k3s 装完自带 Traefik,监听 80/443,写个 Ingress 规则把域名指到对应 Service 就行:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog-ingress
spec:
  ingressClassName: traefik
  rules:
    - host: blog.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: blog
                port:
                  number: 80

这一步想明白以后,所有服务统一走一个入口,比 compose 里各种端口反而清爽。

数据放哪?local-path 不是保险箱

compose 时代我习惯 bind mount,把数据目录直接挂到宿主机路径,-v /data/mysql:/var/lib/mysql,简单粗暴。k3s 里一开始也这么干,hostPath 直接指宿主机目录,跑起来没问题,但心里一直不踏实,后来才意识到问题在哪。

k3s 自带 local-path-provisioner,声明 PVC 它自动在节点上划目录,看起来是零配置持久化。但默认回收策略是 Delete,你把 PVC 一删,数据目录直接清掉,连个确认都没有。我差点在一次"清理测试环境"的操作里把测试库的旧数据全删了,还好当时只是测试数据。

更隐蔽的是单节点还好,哪天加第二个节点,Pod 调度到别的节点,PVC 数据还在第一个节点的本地盘上,Pod 一直 Pending 起不来。要么用 nodeSelector 把有状态的 Pod 钉在固定节点,要么把数据库这类东西放宿主机目录挂载,别指望 local-path 帮你做高可用。

我现在数据库类服务用 hostPath 挂到固定目录,无状态服务直接用 PVC 或者干脆不持久化。想清楚哪些数据丢了无所谓、哪些丢了要命,比纠结用哪种存储方案重要。

containerd 不是 docker,习惯要改

k3s 默认用 containerd 当运行时,没有 docker CLI。我一开始习惯性敲 docker logs、docker exec,全都不存在,得换成:

crictl ps
crictl logs <container-id>
kubectl exec -it <pod-name> -- /bin/sh

这个多敲几次就记住了。真正别扭的是服务发现。compose 里服务之间用 service_name 互相访问,k3s 里变成了 DNS 全名:服务名.命名空间.svc.cluster.local。同命名空间下短名也能用,跨命名空间就得写全。

再一个是镜像。compose 时代我经常本地 build 完直接 docker compose up,k3s 不行,Pod 拉镜像只能从 registry 拉。要么推到 Docker Hub 或私有 registry,要么用 k3s ctr images import 导入本地镜像。我图省事,小项目直接全推 Docker Hub,反正都是公开镜像。

镜像拉取慢,得配 registry mirror

k3s 从 Docker Hub 拉镜像,国内网络基本看运气,大镜像经常拉一半失败。解决办法是配 registry mirror,改 /etc/rancher/k3s/registries.yaml:

mirrors:
  docker.io:
    endpoint:
      - "https://docker.m.daocloud.io"
      - "https://dockerproxy.com"

改完要重启 k3s 服务生效:systemctl restart k3s。这个配置不对或者镜像源不稳定,症状就是 Pod 一直 ImagePullBackOff,kubectl describe 才能看到底层错误。建议配两三个镜像源,一个挂了换下一个。

升级别瞎来,我吃过一次亏

k3s 升级有官方脚本,也可以用 systemd 直接重启服务。小版本内升级一般没事,跨大版本我踩过一次:升级完有个旧 Deployment 一直 CrashLoopBackOff,排查半天发现是 API 版本废弃导致的,Deployment 的 apiVersion 从 apps/v1beta1 换成了 apps/v1。改一下 YAML 重新 apply 就好,但排查过程确实耽误时间。

现在我的习惯是:升级前先把当前版本记下来(k3s --version),升级后逐个服务看一遍状态,别一次升太多版本。

资源限制忘了写,服务器直接 OOM

这个不算 k3s 的坑,是我自己的疏忽。compose 时代我基本不写 mem_limit,k3s 里也不写 requests/limits,结果有一天服务器直接 OOM,kubelet 开始杀进程,连 SSH 都差点连不上。后来给关键服务都补上了资源限制:

resources:
  requests:
    memory: 128Mi
  limits:
    memory: 256Mi

这个一定要写,不写 k3s 不会管你,节点内存被吃光是迟早的事。

现在的状态

折腾完以后,我的博客和几个小服务都跑在 k3s 上,单节点,Traefik 统一入口,证书用 Traefik 自带的 ACME 自动续。说句实话,对纯个人项目来说 compose 完全够用,k3s 带来的复杂度是实实在在的,光"Service 和 Ingress 到底谁管什么"这个概念就要消化一阵子。

但如果服务数量开始多起来,或者想提前熟悉 Kubernetes 生态,k3s 是个不错的入门台阶。它把 etcd 换成了 sqlite(单节点模式),安装一条命令,学习成本比完整 K8s 低不少。我的建议是:先把 compose 里的服务一个个迁过来练手,别一次性全搬,踩坑成本可控。

至于我为什么没后悔,大概是每次 kubectl get pods 看到所有服务整整齐齐 Running 的时候,觉得这几个周末没白花。

评论一下?

OωO
取消