k3s 体验:从 docker-compose 到轻量级集群
2025-12-08 00:00
k3s 体验:从 docker-compose 到轻量级集群
项目背景
我现在有七八台机器,原来全部用 docker-compose 来跑服务。
虽然 docker-compose 已经比直接敲 docker run 舒服很多,但当机器数量一多,问题就来了:
- 每台机器一份
docker-compose.yaml - 想改一个环境变量,要登上所有机器改一圈
- 想知道整个系统“现在到底跑了什么”,需要挨台登录
我给自己定了一个目标:用同一套方式、集中管理所有机器上的容器。
对我来说,k3s 满足了两个核心诉求:
- 用一套
kubectl命令管理所有容器 - 自身足够轻量,适合我这种“穷人多机小集群”
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跨机器通信依赖宿主机 IP | Pod 间在同一个虚拟网段,通过 CNI(默认 Flannel)互通用 Service / Ingress 做负载均衡和流量入口 |
| 存储 (Storage) | 多为本地挂载:./data:/var/lib/...容器一旦迁移到另一台机器,数据就不在那了 | 用 PersistentVolume / PersistentVolumeClaim(PV/PVC)抽象存储可以对接分布式存储,Pod 飘到哪数据都跟着 |
| 底层关系 | 包含 Docker CLI / Daemon / containerd / runc | k3s 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 8472或UDP 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 IPTCP 10250:允许 Master IPTCP 80 / 443:如果你计划让流量直接打到 Worker 才需要开放
安装 kubectl 并连接集群
kubectl 是你和集群交互的核心工具,可以理解为:
kubectl= “k8s / k3s 的 git 命令行”
大致流程:
- 在 Windows / Linux 上安装
kubectl - 从 k3s Server 拿到
kubeconfig文件(通常在/etc/rancher/k3s/k3s.yaml) - 把该文件复制到本机
~/.kube/config,或者通过KUBECONFIG指定路径 kubectl get nodes能看到集群节点,说明联通成功
具体安装命令每个平台都不太一样,这里不细写,可以直接问 LLM:
「在 Windows(或 Linux)上怎么安装 kubectl,并连接到已有的 k3s 集群?」
从 docker-compose 迁移到 k3s
当集群搭好之后,就可以开始做核心工作:把原来一堆 docker-compose.yaml 迁移到 K3s 上。
1. 核心概念映射
先建立心智对照表:
| Docker Compose | Kubernetes / K3s | 说明 |
|---|---|---|
services: 一项 | Deployment / StatefulSet | 控制容器副本数、镜像版本、滚动更新策略 |
ports: | Service | 把 Pod 暴露出来,提供稳定的访问入口 |
volumes: | PV + PVC 或 hostPath | 存储抽象是迁移里最容易踩坑的 |
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 飘过去之后,要么找不到数据,要么重新创建空目录,相当于“丢数据”
解决方案:
Local Path Provisioner(推荐)
- k3s 自带的 StorageClass
- 你在 YAML 里只需要写 PVC,真正的目录位置由 Provisioner 在对应节点上自动创建
- 适合无状态 + 一般数据,也可以用于轻量数据库等
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 仓库里,最简单粗暴但非常管用。
- 📦 Mixup 项目 https://github.com/dyq94310/Mixup/
里面混了 sing-box、监控、nodepass 等资源,有点类似“个人版密雪配置仓”。
即便不做 GitOps,只做到这几点也很有价值:
所有集群配置都有版本记录(改挂了可以回滚)
多台机器统一从仓库拉配置,减少“我记得这个节点是这么配置的”的错觉
新增服务只需要:
- 写好 YAML
- 提交 Git
kubectl apply -f一下
sealed-secrets:把敏感信息安全地放进 Git
集群配置想放进 Git,但里面又有各种密码、Token,这就是 sealed-secrets 登场的地方。
一句话:
sealed-secrets = 可以放心推到 GitHub 的加密 Secret
基本原理(简化理解):
- 在集群里安装一个 controller(SealedSecretsController)
- 在本机安装
kubesealCLI - 你本地写一个普通的 Secret(明文)
- 用
kubeseal加密 -> 得到一个SealedSecret对象 - 提交到 Git
- 集群里的 controller 会用自己的私钥解密,自动生成真正的 Secret
安装 kubeseal(略)
这里同样不写详细命令,可以搜:
- “sealed-secrets controller install”
- “kubeseal install”
通常就是一个 kubectl apply -f ... + 安装 CLI 二进制。
加解密流程示例
- 写一个明文 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"
- 用
kubeseal转成密文sealed-secret.yaml:
kubeseal --format=yaml < plain.yaml > sealed-secret.yaml
得到的 sealed-secret.yaml 会是一个 kind: SealedSecret 的对象,里面的值已经被加密,可以安全提交到 Git。
- 在 Deployment 里引用这个 Secret(跟普通 Secret 用法一样):
- name: KOMARI_CLOUDFLARED_TOKEN
valueFrom:
secretKeyRef:
name: komari-secret # Secret 名
key: KOMARI_CLOUDFLARED_TOKEN # 键名
- 提交
sealed-secret.yaml,在集群里执行:
kubectl apply -f sealed-secret.yaml
kubectl apply -f komari-deploy.yaml
当你重新部署应用时,集群会自动从 SealedSecret 还原出真正的 Secret,然后 Pod 就能正常拉到这些敏感配置。
小结 & 一些建议
如果你现在也在用 docker-compose 管 N 台机器,我的整体体验是:
一步到位上完整 k8s 很重,k3s 是一个不错的过渡甚至终点
把配置从“散落在每台机器”进化到“集中 Git 管理”,心态会好很多
迁移时:
- 先从无状态服务开始迁移(Stateless)
- 存储部分要格外小心,优先了解 PVC / StorageClass
- 敏感信息尽量一开始就用 Secret + sealed-secrets
大量重复性 YAML 不必自己手写,大胆交给 LLM 起草,只要你最后能看懂它生成的内容