My Blog · RSS

k3s 体验:从 docker-compose 到轻量级集群

2025-12-08 00:00

k3s 体验:从 docker-compose 到轻量级集群

项目背景

我现在有七八台机器,原来全部用 docker-compose 来跑服务。 虽然 docker-compose 已经比直接敲 docker run 舒服很多,但当机器数量一多,问题就来了:

  • 每台机器一份 docker-compose.yaml
  • 想改一个环境变量,要登上所有机器改一圈
  • 想知道整个系统“现在到底跑了什么”,需要挨台登录

我给自己定了一个目标:用同一套方式、集中管理所有机器上的容器

对我来说,k3s 满足了两个核心诉求:

  1. 用一套 kubectl 命令管理所有容器
  2. 自身足够轻量,适合我这种“穷人多机小集群”

k3s 是什么?

官方一句话:k3s 是通过 CNCF 一致性认证的轻量级 Kubernetes 发行版,专门为边缘计算和 IoT 场景设计。

可以简单理解为:

k3s = “缩小版、瘦身版的 Kubernetes”

特点:

  • 单一二进制,安装简单(基本就是一条脚本)

  • 默认集成了:

    • containerd 作为容器运行时
    • Flannel 作为默认 CNI(容器网络)
    • Local Path Provisioner 等实用组件(存储)
  • 资源占用比完整 k8s 小很多,非常适合:

    • 家用小集群
    • 边缘节点、树莓派
    • 个人学习 / 轻量生产环境

这里就不展开安装命令了,直接搜 “k3s install” 或者让 LLM 按你的系统写安装脚本即可。

原理图


k3s 与 k8s:叫法和架构上的一点区别

在「标准 Kubernetes」里,我们习惯说:

  • Master(控制平面)
  • Worker(工作节点)

在 k3s 里叫法略微不同,但角色是一样的:

  • K3s Server ≈ Master 节点(控制平面)

    • 负责整个集群的“脑子”:调度、存储状态、响应 kubectl 请求
    • k3s 把原来 kube-apiserver / kube-scheduler 等多个进程,打包成了一个精简进程
  • K3s Agent ≈ Worker 节点(工作节点)

    • 真正跑你的 Pod 的地方
    • 向 Server 注册、汇报状态,接受调度

最大的不同在于: k3s 把多进程的控制平面合成了“一个进程”,从而节省了不少资源。

Flannel 是干嘛的?

k3s 默认会启用一个 CNI 插件,常见就是 Flannel

  • 核心作用:给每个 Pod 分一个 IP,建立一个“虚拟内网”(Overlay Network)

  • 常见端口:

    • VXLAN 模式:UDP 8472
    • 如果换成 WireGuard 后端,则会用 UDP 51820

你可以简单理解为:

Flannel = “让所有节点上的 Pod 像在同一网段一样互相访问”的网络层。


k3s 与 Docker:造砖 vs 盖楼

一句话总结:

  • Docker / docker-compose:某台机器上做容器编排(单机)
  • K3s / Kubernetes:一堆机器上做容器编排(集群)

Docker 的典型架构

Docker Client (命令行)
Docker Daemon (docker 服务)
containerd
容器

K3s 的典型架构

kubectl
k3s Server(控制平面)
k3s Agent(各个节点)
containerd
Pod / 容器

注意:k3s 默认就带了 containerd,不需要再装 Docker。

K3s vs Docker 核心对比表(保留原意,稍微整理一下)

对比维度Docker / docker-compose(容器引擎)K3s(容器编排系统)
核心比喻“制造砖块”:打包、分发、跑应用容器“盖楼房”:管理一堆机器上的容器,调度、扩容、自愈
扩展性 (Scaling)单机为主:多起几个副本 = 多敲几次 docker-compose up 或改 scale跨机器需要自己折腾 IP / 负载均衡集群自动扩缩容:改一下 replicas: 5,调度器自动分配到多台机器上
故障自愈 (Healing)容器级:restart: always 能救的是容器进程崩溃机器挂了 = 上面的容器都没了集群级:某个节点宕机,K3s 发现后会把 Pod 自动调度到其他节点重建
网络 (Networking)主要靠端口映射:-p 8080:80跨机器通信依赖宿主机 IPPod 间在同一个虚拟网段,通过 CNI(默认 Flannel)互通用 Service / Ingress 做负载均衡和流量入口
存储 (Storage)多为本地挂载:./data:/var/lib/...容器一旦迁移到另一台机器,数据就不在那了用 PersistentVolume / PersistentVolumeClaim(PV/PVC)抽象存储可以对接分布式存储,Pod 飘到哪数据都跟着
底层关系包含 Docker CLI / Daemon / containerd / runck3s Server/Agent + containerd + runc(默认不依赖 Docker)
典型场景本地开发、单机部署、小工具微服务、边缘集群、多机资源池、对高可用有要求的场景

集群安装与网络规划(简略版)

拓扑设定(例子)

假设:

  • Master / Server 节点: 47.1.1.1
  • Worker / Agent 节点: 48.1.1.1(可能在 NAT 后)

实际用的时候替换成你自己的 IP 即可。

所有节点目前都带公网 IP,后面计划用 Tailscale 组内网,这里先按公网来。

安全组 / firewalld 放行建议

这里只是实践经验,不是“唯一正确答案”,具体还要结合你公司 / 云厂商安全策略。

Master(47.1.1.1):

  • TCP 6443:对 Worker 开放(可以只放行 Worker IP + 你自己的管理 IP)

    • 用途:kube-apiserver,Worker 要来“找组织”
  • UDP 8472UDP 51820:对 Worker 开放

    • 用途:Flannel / WireGuard 的 Pod 网络
  • TCP 10250:只允许集群内相关节点访问

    • 用途:kubelet API,日志 / 监控
  • TCP 80 / 443:对外开放(如果你打算通过 Master 暴露业务)

  • TCP 22:只允许你自己的管理 IP,SSH 登录

Worker(48.1.1.1 / NAT 后):

  • 如果 Worker 在家里 / NAT 后,通常不需要对外开放端口,只要能主动访问 Master 即可。

  • 如果 Worker 也是云服务器,建议:

    • UDP 8472 / 51820:允许 Master IP
    • TCP 10250:允许 Master IP
    • TCP 80 / 443:如果你计划让流量直接打到 Worker 才需要开放

安装 kubectl 并连接集群

kubectl 是你和集群交互的核心工具,可以理解为:

kubectl = “k8s / k3s 的 git 命令行”

大致流程:

  1. Windows / Linux 上安装 kubectl
  2. 从 k3s Server 拿到 kubeconfig 文件(通常在 /etc/rancher/k3s/k3s.yaml
  3. 把该文件复制到本机 ~/.kube/config,或者通过 KUBECONFIG 指定路径
  4. kubectl get nodes 能看到集群节点,说明联通成功

具体安装命令每个平台都不太一样,这里不细写,可以直接问 LLM:

「在 Windows(或 Linux)上怎么安装 kubectl,并连接到已有的 k3s 集群?」


从 docker-compose 迁移到 k3s

当集群搭好之后,就可以开始做核心工作:把原来一堆 docker-compose.yaml 迁移到 K3s 上。

1. 核心概念映射

先建立心智对照表:

Docker ComposeKubernetes / K3s说明
services: 一项Deployment / StatefulSet控制容器副本数、镜像版本、滚动更新策略
ports:Service把 Pod 暴露出来,提供稳定的访问入口
volumes:PV + PVChostPath存储抽象是迁移里最容易踩坑的
environment:ConfigMap / Secret配置与镜像解耦,敏感信息用 Secret
depends_on:InitContainer / 健康检查 + 重试逻辑k8s 没有 depends_on,要自己设计“就绪检测”
healthcheck:Liveness / Readiness Probes存活探针 + 就绪探针,更细粒度的健康管理

2. 最大的坑:存储(Volumes)

在 docker-compose 里,你可能经常这样写:

volumes:
  - ./mysql-data:/var/lib/mysql

在单机上没问题,但在 k3s 集群 中有一个致命问题:

  • Pod 可能今天被调度在节点 A,明天在节点 B
  • 你的 ./mysql-data 只在某一台机器上存在
  • Pod 飘过去之后,要么找不到数据,要么重新创建空目录,相当于“丢数据”

解决方案:

  1. Local Path Provisioner(推荐)

    • k3s 自带的 StorageClass
    • 你在 YAML 里只需要写 PVC,真正的目录位置由 Provisioner 在对应节点上自动创建
    • 适合无状态 + 一般数据,也可以用于轻量数据库等
  2. HostPath(我目前在用,但要非常小心)

    • 直接把宿主机目录暴露给 Pod

    • 必须配合:

      • nodeSelector / nodeAffinity
      • 保证 Pod 永远只在这台机器上跑
    • 一旦 Pod 被调度到别的节点,就会找不到这条路径了

我目前在某些需要“直接读本机磁盘”的场景下,用的就是 hostPath + nodeSelector,下面的例子也是这种写法。


示例:以 komari 为例的迁移 Demo

原来的 docker-compose 配置

version: '3.8'
services:
  komari:
    image: ghcr.io/komari-monitor/komari:latest
    container_name: komari
    ports:
      - "25774:25774"
    volumes:
      - ./data:/app/data
    environment:
      ADMIN_USERNAME: username
      ADMIN_PASSWORD: pass
      KOMARI_ENABLE_CLOUDFLARED: "true"
      KOMARI_CLOUDFLARED_TOKEN: 123
    restart: unless-stopped

迁移到 k3s:Deployment + Secret + hostPath

下面这个 Deployment 大部分是由 LLM 帮我生成,然后我再手工调整的。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: komari
  labels:
    app: komari
spec:
  replicas: 1
  selector:
    matchLabels:
      app: komari
  template:
    metadata:
      labels:
        app: komari
    spec:
      # [策略] 钉在特定节点(例如 master)上,方便读本地数据
      nodeSelector:
        kubernetes.io/hostname: hosteons
      
      containers:
      - name: komari
        image: ghcr.io/komari-monitor/komari:latest
        ports:
        - containerPort: 25774

        # 管理账号,从 Secret 里取
        env:
        - name: ADMIN_USERNAME
          valueFrom:
            secretKeyRef:
              name: komari-secret
              key: ADMIN_USERNAME 
      
        - name: ADMIN_PASSWORD
          valueFrom:
            secretKeyRef:
              name: komari-secret
              key: ADMIN_PASSWORD 
        
        # 开启内置 Tunnel
        - name: KOMARI_ENABLE_CLOUDFLARED
          value: "true"
        
        # Cloudflare Token,同样放在 Secret 里
        - name: KOMARI_CLOUDFLARED_TOKEN
          valueFrom:
            secretKeyRef:
              name: komari-secret
              key: KOMARI_CLOUDFLARED_TOKEN

        volumeMounts:
        - name: data-vol
          mountPath: /app/data
      
      volumes:
      - name: data-vol
        hostPath:
          # 宿主机路径(对应原来的 ./data)
          path: /opt/k3s-data/komari
          type: DirectoryOrCreate

如果你希望像 25774:25774 那样对外暴露端口,可以再加一个 Service,例如 NodePort 或 LoadBalancer。这里就不展开了,保留原有思路。


善用 LLM 做迁移

我现在的做法很简单粗暴:直接丢给 LLM

“LLM 负责 80% 的体力活,人类只做最后 20% 的审核和取舍”,效率提升非常明显。


用 Git 管理 k3s 配置

现在我所有的 k3s 配置文件都放在一个 Git 仓库里,最简单粗暴但非常管用。

里面混了 sing-box、监控、nodepass 等资源,有点类似“个人版密雪配置仓”。

即便不做 GitOps,只做到这几点也很有价值:

  1. 所有集群配置都有版本记录(改挂了可以回滚)

  2. 多台机器统一从仓库拉配置,减少“我记得这个节点是这么配置的”的错觉

  3. 新增服务只需要:

    • 写好 YAML
    • 提交 Git
    • kubectl apply -f 一下

sealed-secrets:把敏感信息安全地放进 Git

集群配置想放进 Git,但里面又有各种密码、Token,这就是 sealed-secrets 登场的地方。

一句话:

sealed-secrets = 可以放心推到 GitHub 的加密 Secret

基本原理(简化理解):

  • 在集群里安装一个 controller(SealedSecretsController)
  • 在本机安装 kubeseal CLI
  • 你本地写一个普通的 Secret(明文)
  • kubeseal 加密 -> 得到一个 SealedSecret 对象
  • 提交到 Git
  • 集群里的 controller 会用自己的私钥解密,自动生成真正的 Secret

安装 kubeseal(略)

这里同样不写详细命令,可以搜:

  • “sealed-secrets controller install”
  • “kubeseal install”

通常就是一个 kubectl apply -f ... + 安装 CLI 二进制。

加解密流程示例

  1. 写一个明文 Secret 模板 plain.yaml注意:这个文件不要提交到 Git!
apiVersion: v1
kind: Secret
metadata:
  name: komari-secret  # Secret 名字
  namespace: default
type: Opaque
stringData:
  ADMIN_USERNAME: "username"
  ADMIN_PASSWORD: "pass"
  KOMARI_CLOUDFLARED_TOKEN: "eyJhIjoiZGY5ZWExO.....token"
  1. kubeseal 转成密文 sealed-secret.yaml
kubeseal --format=yaml < plain.yaml > sealed-secret.yaml

得到的 sealed-secret.yaml 会是一个 kind: SealedSecret 的对象,里面的值已经被加密,可以安全提交到 Git。

  1. 在 Deployment 里引用这个 Secret(跟普通 Secret 用法一样):
        - name: KOMARI_CLOUDFLARED_TOKEN 
          valueFrom:
            secretKeyRef:
              name: komari-secret          # Secret 名
              key: KOMARI_CLOUDFLARED_TOKEN  # 键名
  1. 提交 sealed-secret.yaml,在集群里执行:
kubectl apply -f sealed-secret.yaml
kubectl apply -f komari-deploy.yaml

当你重新部署应用时,集群会自动从 SealedSecret 还原出真正的 Secret,然后 Pod 就能正常拉到这些敏感配置。


小结 & 一些建议

如果你现在也在用 docker-compose 管 N 台机器,我的整体体验是:

  • 一步到位上完整 k8s 很重,k3s 是一个不错的过渡甚至终点

  • 把配置从“散落在每台机器”进化到“集中 Git 管理”,心态会好很多

  • 迁移时:

    1. 先从无状态服务开始迁移(Stateless)
    2. 存储部分要格外小心,优先了解 PVC / StorageClass
    3. 敏感信息尽量一开始就用 Secret + sealed-secrets
  • 大量重复性 YAML 不必自己手写,大胆交给 LLM 起草,只要你最后能看懂它生成的内容