《k8s权威指南》翻到第五版还在纠结控制平面组件怎么编排的时候,我已经在考虑要不要放弃学习Kubernetes了。说实话,大部分卡在k8s入门阶段的人,不是看不懂概念,而是根本凑不出一套能跑起来的集群环境。标准k8s安装对机器配置、网络环境、依赖版本的要求摆在那里,光是一个kubeadm init失败后的排错就够劝退一批人。
后来换了k3s,十分钟内拿到了一个可用的k8s集群,才真正把精力从“装环境”挪回到“学k8s本身”。这篇文章就把我用k3s搭建k8s集群的完整过程、原理拆解、踩坑记录和实测数据写清楚,给也想快速拿到一套k8s环境的人一条能直接走通的路。
1. 为什么我没直接装标准k8s:资源门槛和依赖复杂度
1.1 标准k8s对硬件和系统的要求,远比文档写的更现实
kubeadm方式部署标准k8s,官方文档写的是2核4G起步,节点数至少是1主2从。但这个配置指的是“能装完”而不是“能用得动”。装完之后,控制平面的etcd、kube-apiserver、kube-controller-manager、kube-scheduler这四件套常驻内存基本吃满2G,剩下的内存分给工作负载后,跑个pod就频繁OOM。实测过在2核4G的机器上装标准k8s,装完系统可用内存只剩800M左右,随便部署个nginx加上监控组件,节点直接进入NotReady状态。
公网云服务器如果选择包年包月,4核8G的实例和2核4G的实例差价接近一倍。为了学一个k8s去专门买高配机器,很多人就是在这步犹豫着犹豫着就放弃了。加上标准k8s安装对系统版本、内核模块、网络插件(Calico或Flannel)、CRI运行时(containerd或CRI-O)都有严格的版本匹配矩阵,任何一个组件版本对不上,报错信息都看不懂。
1.2 k3s到底简化了什么:组件打包、数据库和运行时
k3s做的事情不是砍功能,而是把k8s控制平面的多个组件合并进同一个二进制文件,再替换掉几个重量级依赖。具体来说,最核心的三处改造是:
- 控制平面合并:kube-apiserver、kube-controller-manager、kube-scheduler被封装进同一个k3s server进程,进程数从4个变成1个。
- 存储后端的替代:默认不再单独部署etcd,而是用嵌入式SQLite(单节点模式)或嵌入式etcd(高可用模式)来保存集群状态。
- 运行时简化:默认集成containerd,不需要单独安装和配置容器运行时。
这些改造直接砍掉了标准k8s安装和运维中最复杂的几个环节。装标准k8s时,etcd要单独配置TLS证书、要设置集群成员、要维护备份策略;k3s把这些东西全部包装起来,用户只需要关心一条install脚本。
1.3 k3s是“阉割版”吗:兼容性和生产使用情况
这是很多人听到k3s时的第一反应。实际上k3s是一个通过了CNCF一致性认证的Kubernetes发行版,API层面和标准k8s保持兼容,kubectl、Helm、Prometheus Operator这些工具都能直接用。生产环境里,k3s被大量部署在边缘计算网关、ARM开发板、工业控制设备上,Rancher自家也把它作为轻量集群的标准方案。它和标准k8s的关系,更像是一个预装好依赖和默认配置的发行版,而不是功能打折的替代品。
判断k3s是否适合你,核心标准只有一个:有没有用到k8s的扩展接口和高级特性。如果你的重点是跑常规工作负载、学习核心概念、做CI/CD测试,k3s完全够用;如果项目要求深度定制调度器、自定义API扩展、特殊网络方案,建议还是用标准k8s。
2. 搭建前必须想清楚的三件事:节点角色、操作系统和网络规划
2.1 单节点还是多节点:视角完全不同
k3s支持两种部署模式。单节点模式(一个server节点)适合学习、开发测试、边缘设备运行;高可用模式(多个server节点+多个agent节点)适合生产环境。两种模式的安装复杂度差距很大,单节点基本一条命令搞定,高可用模式需要额外处理server节点的初始化、join参数和数据存储配置。
如果你是想学k8s,第一台机器强烈建议先单节点跑通,把kubectl、Pod、Deployment、Service这些核心概念摸熟,再考虑扩容成高可用。一上来就搭三节点集群,遇到问题定位起来会非常痛苦,排错范围太大。
2.2 server和agent的分工:先分清角色再动手
k3s里server节点等同于标准k8s的控制平面节点,运行apiserver、controller-manager、scheduler和集群数据存储,同时也承担工作负载。agent节点等同于工作节点,只负责运行业务Pod。装之前先把角色分清楚,避免后面加节点时混淆验证逻辑。
2.3 操作系统选型与初始化准备
k3s官方对操作系统的宽容度比标准k8s好很多,Ubuntu 20.04/22.04、Debian 11/12、CentOS 7/8/Rocky Linux都能跑。我推荐用Ubuntu 22.04 LTS,主要原因是内核版本较新,对iptables、overlayfs这些容器相关内核模块的支持更完善,遇到兼容性问题的概率更低。树莓派或国产ARM开发板用官方Raspberry Pi OS或对应的ARM64系统镜像也完全可以。
不管选哪个系统,装k3s之前要做三件事:
关闭swap:kubelet默认不允许swap开启,虽然k3s可以通过参数绕过,但没必要在入门阶段给自己埋坑。加载必要内核模块:overlay和br_netfilter是容器网络转发的基础。确认防火墙规则:如果云厂商安全组或主机防火墙比较严格,至少需要放通TCP 6443端口(API Server)、UDP 8472端口(VXLAN通信),以及TCP 10250端口(kubelet metrics)。
提示:大多数VPS默认防火墙规则宽松,可以跳过后两步。但如果用云厂商安全组,请务必提前确认端口放通情况,否则kubectl get nodes会一直报连接拒绝。
2.4 版本选择与获取方式
k3s官方发布节奏是每月一个稳定版本,版本号沿用k8s的命名规则。直接用官方快速安装脚本默认拉取最新稳定版即可。但有一个细节值得注意:如果你计划后续对接Rancher管理平台或某些特定CSI存储插件,需要先确认它们对k3s版本的最低要求,避免装到过新的版本导致兼容问题。国内服务器从GitHub下载相关资源偶尔会超时,需要配置代理或使用镜像加速,这个后面在踩坑部分详细说。
3. 十分钟搭起第一个单节点k3s集群
3.1 一路默认的安装命令,每个参数都拆开看
单节点安装最简单的方式就是执行官方脚本:
curl -sfL https://get.k3s.io | sh -这条命令做了几件事:下载k3s二进制到/usr/local/bin、生成systemd服务、启动k3s server进程、配置kubectl(生成/etc/rancher/k3s/k3s.yaml)。执行完之后等几十秒,就能看到集群状态。
但实际项目中我更推荐显式指定参数,把控制权握在自己手里:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \ --disable=traefik \ --disable=servicelb \ --data-dir=/data/k3s \ --node-name=k3s-server-01" sh -逐项说明:
--disable=traefik:k3s默认自带Traefik作为Ingress Controller,如果暂时用不到Ingress,关掉能少占约100M内存,并且避免端口80/443被占用导致的本机端口冲突。--disable=servicelb:k3s默认的Service Load Balancer(Klipper LB)会为每个LoadBalancer类型Service分配一个宿主机端口。单节点学习环境用不到,关掉可以减少iptables规则数量,排查网络问题时少一层干扰。--data-dir=/data/k3s:默认数据目录在/var/lib/rancher/k3s。如果系统盘空间不大,提前规划数据目录到数据盘非常重要,etcd和容器镜像都存这里。--node-name=k3s-server-01:给节点起一个可读名字,不设置时默认使用主机hostname。多节点环境强烈建议显式设置,避免后面一片Node名字都叫localhost。
等待脚本执行完毕,用kubectl检查集群状态:
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get nodes状态为Ready说明控制平面和容器运行时都已经正常启动。接着检查核心组件:
kubectl get pods -A正常情况下能看到coredns、local-path-provisioner、metrics-server这几个系统Pod全部处于Running状态。
3.2 部署一个测试应用验证链路是否通畅
集群起来之后,第一时间部署一个无状态应用验证整个链路:
kubectl create deployment whoami --image=nginx:alpine kubectl expose deployment whoami --port=80 --type=NodePort kubectl get svc whoami访问http://<节点IP>:<NodePort>,能看到nginx默认页面就说明容器运行时、kubelet、kube-proxy、Service网络全部正常。很多人在装完集群后忽略了这一步,直接开始学Deployment、Service的YAML写法,结果集群底层有问题而不自知。先跑通一个最小化链路,后面排错时才有对照基线。
3.3 单节点集群的运维日常:配置kubectl和查看日志
k3s的kubectl配置文件默认生成在/etc/rancher/k3s/k3s.yaml,文件里的server地址是https://127.0.0.1:6443。如果需要在本地电脑远程管理集群,把这个文件复制到本地,然后把server地址改成https://<节点IP>:6443,再把certificate-authority-data替换成节点上的/var/lib/rancher/k3s/server/tls/server-ca.crt内容,或者直接用insecure-skip-tls-verify: true绕过证书校验(只建议内网测试环境用)。
查看k3s系统服务日志用journalctl:
journalctl -u k3s -f如果server进程启动失败,日志里基本能看到完整的堆栈和错误原因,大部分安装失败问题都在这个日志里能直接定位。
3.4 关闭和重启集群的正确方式
最后补充一个日常运维常用的操作。k3s作为systemd服务运行,关闭集群:
systemctl stop k3s启动集群:
systemctl start k3s如果只是重启节点机器,k3s服务会随着系统启动自动拉起,不需要额外配置。
4. 把集群升级成高可用:三节点嵌入式etcd方案
4.1 为什么单节点集群不能直接上生产
单节点k3s把所有组件集中在一个进程单机运行,服务器宕机意味着API Server、数据存储、业务Pod全部不可用,关键数据存储在没有主从复制或备份机制的SQLite里。生产环境任何单点故障都不可接受,更麻烦的是,单节点模式下数据全部存在本地SQLite,一旦数据目录损坏,没有副本可以恢复。
k3s官方给出的高可用方案是使用嵌入式etcd。多台server节点组成一个etcd集群,数据自动复制到所有server节点上,部分节点挂掉不影响整体可用性。
4.2 高可用集群的两项硬性条件
- server节点数量必须是奇数:1、3、5……这样保证etcd在任何分区场景下都容易出现多数派,选主逻辑才能跑通。
- server节点数量至少3个:三节点中允许挂掉一个,五节点中允许挂掉两个。两个节点也能组成etcd集群,但任何一个节点故障都会导致失去多数派,集群不可写,所以没有实际意义。
4.3 三节点高可用完整操作流程
假设三台服务器,计划都作为server节点,IP分别为192.168.1.11、192.168.1.12、192.168.1.13。
第一步:在第一台节点上启动server。
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \ --cluster-init \ --disable=traefik \ --disable=servicelb" sh ---cluster-init告诉k3s初始化一个嵌入式etcd集群,而不是默认的单节点SQLite模式。这个参数只加在第一台节点上。
第二步:从第一台节点获取join token。
cat /var/lib/rancher/k3s/server/node-tokennode-token是其他节点加入集群的凭证,内容是一长串包含随机密钥和哈希的字符串。
第三步:在第二、第三台节点上执行join操作。
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \ --server=https://192.168.1.11:6443 \ --token=<node-token值>" sh -这里的关键是把--server指向第一台节点的API Server地址,并且不指定--cluster-init。k3s会检测到这是一个加入既有etcd集群的操作,自动从--server指定的节点同步集群数据并加入etcd成员。
第四步:验证etcd集群状态。
在三台server节点全部启动完毕后,登录第一台节点执行:
kubectl get nodes正常情况下能看到三个server节点状态都是Ready。接着查看etcd健康状态:
k3s etcd-snapshot ls能看到快照列表说明etcd集群工作正常,成员间通信没有问题。
4.4 扩容agent工作节点
高可用环境下,工作负载最好调度到agent节点上,避免和控制平面抢占资源。加入agent节点的命令和加入server节点类似,唯一的区别是把server替换成agent:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="agent \ --server=https://192.168.1.11:6443 \ --token=<node-token值>" sh -agent节点上运行的systemd服务名是k3s-agent,排错时注意日志服务名称和server节点不一样。
4.5 高可用集群的备份与恢复
多一个节点不意味着不需要备份。配置了嵌入式etcd的高可用k3s集群,官方推荐的备份方式是定期执行etcd快照。直接在任意一个server节点执行:
k3s etcd-snapshot save --name=manual-backup恢复时,在故障节点上执行:
k3s server --cluster-reset --cluster-reset-restore-path=/var/lib/rancher/k3s/server/db/snapshots/manual-backup恢复操作需要先把其他节点停掉,避免恢复节点加入一个仍然健康的etcd集群导致数据冲突。这一点务必注意,我在测试环境曾经因为图省事直接对着运行中的集群做cluster-reset,导致etcd成员列表混乱,最后把三台机器全部清空重来。
提示:如果追求更完善的备份策略,可以把快照文件定期同步到对象存储或另一台机器。k3s支持通过配置
etcd-snapshot-dir、etcd-snapshot-schedule-cron和etcd-snapshot-retention参数实现自动快照。
5. 真实环境踩坑复盘:四条最容易卡住的报错和解决路径
5.1 kubelet一直Starting,cgroup驱动不一致
现象:kubectl get nodes能看到节点,但状态永远是NotReady,查看k3s日志反复出现Failed to run kubelet和failed to validate kubelet cgroup相关报错。
排查过程:打开journalctl日志确认具体报错,发现kubelet启动时传入的cgroup驱动是systemd,但containerd默认配置的cgroup驱动是cgroupfs。k3s在检测到系统init进程是systemd时,kubelet使用systemd驱动,但容器运行时如果用的不是同一套cgroup驱动,kubelet无法正确感知容器资源状态,就会一直卡在启动阶段。
解决方案:检查系统是否以systemd作为init进程(多数现代发行版都是),确认后给k3s启动参数加上:
--kubelet-arg=cgroup-driver=systemd同时在agent节点上如果存在同样问题,需要关闭--docker参数(默认使用containerd无需处理)。实际上新版k3s对这个场景做了很多自动化适配,如果你用的是Ubuntu 22.04+Debian 12这些新系统很少遇到,但CentOS 7这一类老系统遇到概率很高。
5.2 安装脚本执行完毕但集群一直Waiting for node
现象:install脚本正常执行完,systemd服务状态是running,但kubectl get nodes卡在Waiting for node或者报connection refused。
排查过程:这类问题八成防火墙或者网络端口问题。先看本地能不能连6443端口:
ss -lntp | grep 6443端口没监听说明k3s server进程没绑定成功,继续看日志。端口有监听但外部连不上,那就检查云厂商安全组和本机防火墙。VXLAN端口8472如果没放通,节点间通信会异常,也会导致节点注册不进来。
另一个隐蔽原因是系统时间偏差过大,apiserver签发证书的通信握手中会校验时间。遇到过一台VPS时间落后8分钟导致证书验证失败的情况,timedatectl set-ntp true同步时间后问题消失。
解决方案:按顺序检查端口监听、防火墙规则、安全组、系统时间,四项逐一排除。平时写排错文档时我习惯从最外层网络逐步往里层排查,直接从应用层入手容易看半天日志发现只是端口没通。
5.3 断电后etcd数据损坏,集群无法启动
现象:测试环境突然断电,重启后systemctl start k3s直接失败,日志提示etcd wal文件损坏。
排查过程:断电后写操作中断,etcd的WAL日志容易损坏。单节点SQLite模式相对皮实,但嵌入式etcd对断电非常敏感。遇到这类情况,第一反应不是删数据,而是先看看快照文件是否还在:
ls /var/lib/rancher/k3s/server/db/snapshots/如果有快照,直接用cluster-reset参数恢复,损失最多是快照之后新增的数据。没有快照的情况下,可以尝试把/var/lib/rancher/k3s/server/db/etcd目录下的member文件夹备份后删除,再用--cluster-reset初始化一个新的etcd节点,但所有历史资源都会丢失。
解决方案:给k3s环境搭配一个定时快照任务,同时确保ups电池正常。生产环境的k3s集群建议开启自动快照并设置保留天数,避免手动备份不及时。
5.4 国内下载k3s安装脚本和二进制超时
现象:curl -sfL https://get.k3s.io | sh -执行到一半卡住,或者报Unable to download错误。
排查过程:install脚本默认从GitHub Releases下载二进制。国内网络访问GitHub稳定性确实不理想,尤其是带宽高峰期。
解决方案:k3s官方提供了镜像加速方案,设置环境变量指向国内镜像站,这是我推荐的第一选择:
export INSTALL_K3S_MIRROR=cn curl -sfL https://get.k3s.io | sh -设置后脚本会从国内镜像下载二进制,速度快非常多。如果安装机器本身无法访问外网,还有完全离线安装的路径:准备一台能访问外网的机器下载好二进制和镜像,通过docker save导出镜像,再传输到目标机器用ctr images import导入。这个过程稍复杂,但确实能解决内网环境问题。
5.5 镜像拉取慢导致Pod一直ImagePullBackOff
现象:测试应用部署后Pod状态一直ImagePullBackOff,kubectl describe pod显示拉取镜像时超时。
排查过程:国内的网络环境拉取Docker Hub镜像确实不稳定,这个问题常见于使用nginx、redis等默认官方镜像时。
解决方案:给containerd配置镜像加速。k3s的containerd配置目录在/var/lib/rancher/k3s/agent/etc/containerd/,修改config.toml,在[plugins."io.containerd.grpc.v1.cri".registry.mirrors]段配置国内镜像源,然后重启k3s服务。配置完成后新拉取的镜像会走加速源,原来失败的应用重新创建Pod即可恢复。
6. 实测性能数据和几个值得关注的参数
6.1 安装耗时与资源占用实测
在一台2核2G的腾讯云轻量服务器(Ubuntu 22.04)和一台1核1G的阿里云抢占式实例(Debian 12)上分别做了单节点安装测试,数据如下:
| 配置 | Ubuntu 22.04 2C2G | Debian 12 1C1G |
|---|---|---|
| 安装耗时 | 约50秒 | 约1分30秒 |
| 安装后空闲内存 | 850M左右 | 420M左右 |
| k3s进程数 | 1个主进程+子协程 | 1个主进程+子协程 |
| 部署nginx应用耗时 | 约8秒 | 约15秒 |
1核1G的机器装完k3s后内存还剩不到一半,客观说跑复杂应用会比较吃力,但启动一个几MB的轻量服务、做配置验证还是完全可能的。这个成绩在标准k8s下不敢想象,同样配置装标准k8s大概率直接在kubeadm init阶段就OOM了。
6.2 稳定运行阶段的关键参数设置
生产环境长时间运行,有几个参数建议根据机器资源情况进行调整。
kubelet资源预留:默认情况下kubelet会贪婪吞掉节点空闲内存,导致节点上可分配的Pod资源无限大,极端情况一个Pod把节点内存打满,控制平面进程被OOM Kill。建议给每个agent节点设置系统预留和kubelet预留:
--kubelet-arg=system-reserved=cpu=200m,memory=512Mi --kubelet-arg=kube-reserved=cpu=200m,memory=512Mi镜像垃圾回收阈值:如果节点的磁盘比较小,可以调整镜像GC策略:
--kubelet-arg=image-gc-high-threshold=70 --kubelet-arg=image-gc-low-threshold=50这个配置含义是磁盘使用率达到70%时启动垃圾回收,回收到使用率降到50%为止。
Pod数量限制:k3s默认允许每节点运行110个Pod,边缘场景下这个数字偏大,且每个Pod会占用一个IP和对应网络资源。通过参数限制每节点的最大Pod数:
--kubelet-arg=max-pods=50根据实际场景设置合理的值,可以减少CNI的IP分配压力。
| 参数 | 建议值 | 说明 |
|---|---|---|
| system-reserved | cpu=200m,memory=512Mi | 系统进程预留 |
| kube-reserved | cpu=200m,memory=512Mi | kubelet预留 |
| image-gc-high-threshold | 70 | 镜像GC触发阈值 |
| image-gc-low-threshold | 50 | 镜像GC停止阈值 |
| max-pods | 50 | 节点Pod上限 |
6.3 容器存储local-path-provisioner的使用问题
k3s默认自带local-path-provisioner,提供基于节点本地磁盘的PV能力。学习环境里用它跑持久化存储很简单:创建PVC时指定storageClassName: local-path,PV会自动创建在/var/lib/rancher/k3s/storage/目录下。
但这个方案有致命限制:数据无法跨节点迁移。Pod调度到另一台节点时,PVC不会跟着走,数据仍然留在原节点的磁盘上。如果你在测试环境里模拟Pod故障转移,会发现应用起在新节点但数据还在老节点,出现数据不一致。高可用环境建议不要使用local-path,改用支持分布式存储的CSI插件(如Longhorn或Rook Ceph)。如果纯粹学习用,这个问题可以忽略。
7. 从k3s通往标准k8s:哪些经验可以直接迁移,哪些地方容易栽跟头
7.1 核心概念和操作完全通用
在k3s上学的kubectl命令、YAML写法、Pod/Deployment/Service/Ingress/ConfigMap/Secret这些核心资源对象,在标准k8s上完全一样。k3s的API和标准k8s保持兼容,意味着你写的所有应用编排文件可以在标准k8s上无缝运行。从k3s迁移到标准k8s最大的成本不在知识层面,而在集群本身的运维层面。
7.2 差异点集中在集群运维层面
标准k8s的etcd要手动配置TLS、手动管理成员、手动做快照备份;k3s把这一切都封装好了。标准k8s的CNI插件(Calico、Cilium)要单独安装和配置,k3s默认集成了一个简化的CNI。标准k8s的Ingress Controller要自己选型部署,k3s默认带Traefik。这些差异在学习阶段基本不感知,但一旦涉及系统调优和排障,就会发现自己欠缺的是k8s底层原理的理解,而不是使用经验。
7.3 建议的学习路线
先用k3s掌握k8s的基本操作和核心概念,部署几个有状态应用和无状态应用,把Deployment滚动更新、Service负载均衡、Ingress域名访问、PV/PVC持久化存储这些日常操作跑熟。然后尝试自己手动搭建一次标准k8s集群,理解每个组件的作用和它们之间的协作关系。最后卸载重来一次,尝试通过k3s etcd-snapshot save做备份,模拟节点故障恢复,把高可用集群的运维能力练出来。
这样一条路线走下来,既有动力(因为环境好搭),又有深度(因为最终要接触底层组件),比直接啃官方文档效率高很多。
最后说点实际的
k3s解决了Kubernetes学习过程中最大的拦路虎——装环境。如果你现在的目标是学会k8s、拿到集群练手、跑几个项目验证想法,不用犹豫,直接上k3s。用最低的成本把环境跑起来,把核心概念玩明白,这笔账怎么算都划算。
根据我个人的使用体验,有几个小建议:第一,节点命名从一开始就规范化,不要默认hostname,后面加多台节点管理起来会非常清晰;第二,数据目录和数据盘分区提前规划,k3s集群运行久了镜像和etcd数据增长速度超出预期,临时迁移数据目录的代价远比提前规划高得多;第三,不要因为k3s安装简单就跳过监控和备份,轻量集群同样是生产环境,该有的底线不能省。
踩过几次坑之后我的体会是,k3s不是玩具,它是帮助你聚焦业务本身、降低基础设施心智负担的好工具。先把它用熟,再往标准k8s深入,这个路径对新手来说踏实得多。