3台机器30分钟,我用K3s撸了一套生产可用的K8s集群(附4个致命坑)
手头3台CentOS虚机,本来准备跑Kafka测试集群的,结果领导临时说要验证一下容器化部署方案。正经用kubeadm搭K8s?光etcd和各种证书问题就够折腾半天的。果断上K3s——一个二进制文件解决战斗。
但真跑起来才发现,"轻量"不等于"没坑"。从节点加入报x509证书错误、重启服务直接卡死、镜像拉取超时……记录一下完整的部署过程和4个差点让我砸键盘的问题。
环境说明
| 节点 | 主机名 | IP | 角色 |
|---|---|---|---|
| Master | kafka-node1 | 192.168.198.201 | Server(控制平面) |
| Worker 1 | kafka-node2 | 192.168.198.202 | Agent |
| Worker 2 | kafka-node3 | 192.168.198.203 | Agent |
操作系统是 CentOS/Rocky Linux 系列,K3s版本 v1.36.3+k3s1。
部署Master节点
K3s的安装体验确实丝滑。在kafka-node1上一行搞定:
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \
INSTALL_K3S_MIRROR=cn \
RANCHER_MIRROR_URL="https://rancher-mirror.rancher.cn/k3s" \
sh -
这里用的是Rancher的国内镜像源,直接从GitHub拉脚本的话大概率超时。装完之后 systemctl status k3s 看一眼是active就行。
拿Token,后面Worker加入要用:
cat /var/lib/rancher/k3s/server/node-token
输出一长串字符,复制好备用。顺便说一句,这个token文件权限是600,root才能读,安全性上还行。
部署Worker节点
Worker节点加入集群理论上也是一行命令的事,但国内网络环境下我建议先手动下载K3s二进制文件,不然安装脚本去GitHub下载binary能卡到天荒地老。
手动放置二进制文件
在kafka-node2和kafka-node3上都执行:
mkdir -p /usr/local/bin/
curl -Lo /usr/local/bin/k3s https://rancher-mirror.rancher.cn/k3s/v1.36.3+k3s1/k3s
chmod +x /usr/local/bin/k3s
验证一下:
k3s --version
# 输出: k3s version v1.36.3+k3s1
注册加入集群
二进制文件到位后,跑安装脚本时它会自动识别本地已有的k3s文件,跳过下载直接配置systemd服务:
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \
K3S_URL=https://192.168.198.201:6443 \
K3S_TOKEN=<你的Node-Token> \
sh -
注意:K3S_URL 一定要带,不带的话安装脚本会把这台机器当Server启动——这就是我踩的第一个坑,下面会讲。
打标签验证
回到Master节点,给Worker打上角色标签:
kubectl label node kafka-node2 node-role.kubernetes.io/worker=worker
kubectl label node kafka-node3 node-role.kubernetes.io/worker=worker
不打标签其实不影响调度,但 kubectl get nodes 的时候ROLES那一列会显示 <none>,看着不舒服:
kubectl get nodes
正常输出应该三个节点全部Ready:
NAME STATUS ROLES AGE VERSION
kafka-node1 Ready control-plane,master 10m v1.36.3+k3s1
kafka-node2 Ready worker 5m v1.36.3+k3s1
kafka-node3 Ready worker 5m v1.36.3+k3s1
到这里集群就跑起来了。但实际操作中,我在kafka-node2和node3加入的过程中连续撞了4个问题。
踩坑记录:4个差点让人放弃的故障
坑1:Worker装成了Server,6444端口冲突
现象:kafka-node2执行完安装脚本后,systemctl status k3s 一看进程是 /usr/local/bin/k3s server——不对啊,这台应该是agent才对。日志里一堆报错:
listen tcp 127.0.0.1:6444: bind: address already in use
原因:第一次安装时手抖漏了 K3S_URL 环境变量。安装脚本的逻辑是这样的——检测到没有 K3S_URL 就认为你要启动一个独立的Server节点。
修复:
# 先卸载干净
/usr/local/bin/k3s-uninstall.sh
# 杀残留进程,释放端口
pkill -9 -f k3s
fuser -k 6444/tcp
# 确认端口释放了
ss -tlnp | grep 6444
然后重新跑带 K3S_URL 的安装命令。这里有个细节:k3s-uninstall.sh会把 /usr/local/bin/k3s 也删掉,所以还得重新下载一遍binary。我第二次就学聪明了,先下binary再跑安装脚本。
坑2:x509证书时间校验失败
现象:Agent启动后一直连不上Server,journalctl -u k3s-agent 里反复刷这个错:
CA cert validation failed: x509: certificate is not valid before [某个未来时间]
current time [Worker当前时间] is before [证书签发时间]
原因:这破虚机的时间不知道什么时候飘了,比Master节点慢了好几分钟。K3s的Server在安装时会自动生成CA证书,证书的 Not Before 字段是Master的当前时间。Worker的系统时间落后于这个值,TLS握手直接就拒绝了。
说白了就是时钟不同步。
修复:
关键点在于,当时间偏差比较大的时候,chronyd默认不会自动跳跃纠正(它只做缓慢调整)。得手动强制同步:
# 停掉chronyd避免冲突
systemctl stop chronyd
# 强行对齐Master节点的时间
chronyd -q 'server kafka-node1 iburst'
# 验证一下时间
date
# 重新启动时间同步
systemctl start chronyd
# 重启agent
systemctl restart k3s-agent
如果你的环境里几台机器都没配NTP源,建议在Master上先搞好时间,然后所有Worker都以Master为NTP server同步。或者更粗暴:
# 直接用ntpdate一步到位(如果装了的话)
ntpdate kafka-node1
预防措施:部署前先在所有节点跑一遍 timedatectl 确认时间一致,差个几秒没事,差几分钟就会出这个问题。
坑3:镜像拉取失败,Pod一直ContainerCreating
现象:节点Ready了,deploy也创建了,但Pod卡在 ContainerCreating 状态不动。kubectl describe pod 一看Events:
Failed to pull image "rancher/mirrored-pause:3.10.2":
connect: connection refused
连基础的pause镜像都拉不下来。
原因:K3s用的容器运行时是内置的containerd,不是Docker。containerd的镜像拉取走的是自己的配置,跟你系统里装的Docker daemon mirrors完全无关。默认去 docker.io 拉镜像,国内裸连基本没戏。
修复:
在所有节点创建 /etc/rancher/k3s/registries.yaml:
mirrors:
"docker.io":
endpoint:
- "https://docker.m.daocloud.io"
- "https://docker.nju.edu.cn"
然后重启对应的服务:
# Master节点
systemctl restart k3s
# Worker节点
systemctl restart k3s-agent
如果已经有Pod卡住了,可以用crictl手动拉一下pause镜像再打tag:
crictl pull docker.m.daocloud.io/rancher/mirrored-pause:3.10.2
ctr -n k8s.io image tag \
docker.m.daocloud.io/rancher/mirrored-pause:3.10.2 \
docker.io/rancher/mirrored-pause:3.10.2
打完tag之后那些卡住的Pod会自动恢复。
补充:DaoCloud的镜像源现在有白名单限制了,不是所有镜像都能加速。如果碰到某些业务镜像还是拉不到,考虑自建Harbor或者用阿里云ACR做中转。
坑4:systemctl restart k3s-agent 直接卡死
现象:执行重启命令后终端完全没反应,Ctrl+C都中断不了,只能新开终端窗口。
原因:之前的Agent进程可能处于死锁状态或者卡在网络重试循环里,systemd在等待它优雅退出(默认90秒超时)。如果进程完全不响应SIGTERM,就会一直卡住。
修复:
新开一个终端:
# 暴力杀进程
pkill -9 -f k3s
# 清理Agent的缓存数据(可选但推荐)
rm -rf /var/lib/rancher/k3s/agent/data
# 重新启动
systemctl start k3s-agent
清理 /var/lib/rancher/k3s/agent/data 目录会让Agent重新下载一些运行时数据,启动会慢几秒但能解决很多奇怪的状态残留问题。
改进建议:可以修改systemd的stop超时时间,免得每次都卡90秒。编辑 /etc/systemd/system/k3s-agent.service(或者创建override):
[Service]
TimeoutStopSec=30
然后 systemctl daemon-reload。30秒杀不死就直接SIGKILL了,不至于干等。
服务暴露:让外面能访问到集群里的应用
集群跑起来了,部署个nginx测试一下外部访问。先创建一个Pod:
kubectl run test-nginx --image=nginx:alpine --port=80
NodePort方式
最简单直接,开一个节点端口映射:
kubectl expose pod test-nginx --type=NodePort --port=80 --target-port=80 --name=nginx-nodeport
kubectl patch svc nginx-nodeport --type='json' \
-p='[{"op": "replace", "path": "/spec/ports/0/nodePort", "value":30080}]'
访问 http://192.168.198.201:30080 就能看到nginx欢迎页。端口范围30000-32767,适合快速验证和非HTTP类服务。
LoadBalancer方式
K3s自带了Klipper LoadBalancer(ServiceLB),不需要额外装MetalLB:
kubectl expose pod test-nginx --type=LoadBalancer --port=80 --target-port=80 --name=nginx-lb
创建完之后 kubectl get svc 看EXTERNAL-IP会显示节点IP。直接访问 http://192.168.198.201 或任意节点IP的80端口就行。
注意:Klipper的原理是在每个节点上启动一个daemonset做端口转发,所以会占用宿主机的80端口。如果你的节点上跑着其他web服务占了80,会冲突。
Traefik Ingress(推荐)
生产环境还是Ingress最合适,K3s默认就装了Traefik,直接写Ingress规则:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
rules:
- host: nginx.k3s.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: test-nginx-svc
port:
number: 80
本地hosts文件加一条 192.168.198.201 nginx.k3s.local,然后浏览器访问 http://nginx.k3s.local 就通了。多个服务按域名或路径分发,干净利落。
排障速查表
| 故障现象 | 排查方向 | 修复命令 |
|---|---|---|
| Worker启动为Server | 检查是否传了K3S_URL | k3s-uninstall.sh → 重新安装 |
| x509证书未生效 | 检查节点间时间差 | chronyd -q 'server <master> iburst' |
| 镜像拉取超时/refused | 检查registries.yaml | 配置国内mirror + 重启服务 |
| restart命令卡死 | Agent进程死锁 | pkill -9 -f k3s → 清缓存 → 重启 |
| Pod一直Pending | 检查节点资源和taint | kubectl describe node 看Conditions |
| 节点NotReady | 检查agent日志 | journalctl -u k3s-agent -f |
写在后面
K3s确实是目前搭建小规模K8s集群最省事的方案,一个binary包含了containerd、Flannel、CoreDNS、Traefik、metrics-server全家桶,512MB内存的Agent节点都能跑。对于开发测试环境、边缘计算、IoT网关这些场景,比kubeadm那套东西舒服太多。
但"轻量"带来的代价是排障时能找到的社区资料相对少,很多问题的错误信息不太直观。特别是时钟同步和镜像源这两个国内环境的"特色"问题,官方文档基本不会提,全靠自己踩。
希望这篇能帮你少走几步弯路。集群搭好之后下一步该考虑的是持久化存储(local-path还是NFS)和监控告警(Prometheus全家桶),后面有机会再写。
我是「运维躬行录」,专注分享云计算和运维的生产实践经验。如果这篇文章对你有帮助,帮忙点个赞、转发一下。
关注公众号:耕云躬行录
个人博客:躬行笔记