这两年凡是遇到资源紧张的部署场景,我几乎第一反应就是K3s。K3s集群搭建及配置这件事,说简单真的可以五分钟出一条命令跑起来,说复杂也能让你折腾一整天还不明白节点为什么一直NotReady。我在不同机器上反复搭过很多次,从单机测试到多节点生产,过程中踩过的坑、优化过的参数、实际用下来的感受,整理成这篇分享。内容主要覆盖:为什么选K3s而不是标准Kubernetes、节点规划和环境准备、单Server安装、Agent节点接入、高可用配置、部署应用时的组合拳,以及运行三个月后总结的排障经验。不管你是刚接触容器编排的新手,还是想在边缘设备、小内存服务器上把集群跑起来的老手,这篇都能帮你少走弯路。
1. 为什么轻量环境反而更需要K3s:搭过一次就回不去的理由
1.1 K3s到底解决了什么问题
标准Kubernetes集群部署起来有多痛苦,搭过的人都懂。kubeadm拉一堆镜像、初始化控制面、配置网络插件、处理证书,流程长、内存占用高,在一台2G内存或4G内存的VPS上跑起来,光是系统组件就能把资源吃去一大半。而K3s把几乎所有控制面组件打包成单个二进制文件,内置了容器运行时、kubectl、网络插件、负载均衡器,安装包体积只有几十MB,装完以后单Server节点空闲内存占用通常在几百MB以内。它并不是Kubernetes的简化缩水版,而是针对资源受限场景做了工程化裁剪,API兼容性基本保持完整,日常用到的Deployment、Service、Ingress、ConfigMap这些标准对象都能正常操作。
我最初接触K3s是因为一台用来跑定时任务的4G内存小主机,原来的方案是直接跑几个Docker容器,后来发现定时任务变多、配置管理越来越乱,想着要不要上Kubernetes,可是标准Kubernetes光控制面就要占2G左右内存,小主机根本扛不住。换成K3s之后,同样的机器还能再跑两三个生产级应用,这就是它的核心价值。
1.2 它和标准Kubernetes的差别在哪里
很多人第一次听到K3s的时候会问:它是不是把Kubernetes的功能砍掉了很多?实际上主要差别集中在几个工程层面。存储层默认使用本地SQLite替代etcd,单Server节点下不需要额外维护数据库;调度器、控制器、API Server这些组件被集成进同一个进程,而不是一堆独立容器;运行时默认使用containerd,配置好之后就能无缝操作。网络方面默认自带Flannel,服务暴露方面自带ServiceLB,Ingress方面默认集成Traefik,等于开箱即用。
对比一下常见轻量Kubernetes方案会更直观:
| 方案 | 安装复杂度 | 单节点内存占用 | 控制面重量 | 典型场景 |
|---|---|---|---|---|
| 标准Kubernetes | 高,需自行处理网络与证书 | 2G以上 | 重,组件多为独立容器 | 大规模生产集群 |
| K3s | 低,一条命令完成 | 300M-800M | 轻,单二进制进程 | 边缘计算、开发测试、小主机 |
| 其他轻量发行版 | 中,部分需要额外配置网络 | 视方案而定 | 中 | 开发环境、单机演示 |
如果你的核心诉求是把一个可用的Kubernetes环境快速跑起来,并且不在乎一点点组件整合带来的架构差异,那K3s是非常合适的选项。
1.3 什么时候不建议用K3s
工具选型最重要的是知道边界。K3s默认使用SQLite作为存储后端,这对单Server节点的场景完全够用,但如果你要搭一个真正的高可用控制面,就需要切换外部数据库或嵌入式etcd模式,配置复杂度会上升。另一个容易踩的坑是,很多第三方Kubernetes工具会直接访问/etc/kubernetes目录或者特定的证书路径,K3s把相关路径都放在了/etc/rancher/k3s下面,这类工具需要额外适配。如果你需要深度定制容器运行时、特殊网络插件,或者对控制面组件版本有极其严格的要求,标准Kubernetes或者按需自行组装会更合适。
我一般判断标准很简单:要不要在高可用控制面上花大力气,有没有多团队多租户的隔离要求。都没有,优先K3s;都有,再考虑标准Kubernetes。
2. 开工前的节点规划与系统准备:哪些检查能帮你少踩一半坑
2.1 节点角色怎么划分
K3s节点主要分两种:Server节点和Agent节点。Server节点运行完整的Kubernetes控制面组件,同时默认也可以调度普通工作负载;Agent节点更像传统Kubernetes里的Worker节点,只运行kubelet和容器运行时。这样的设计比标准Kubernetes更灵活,因为单台Server节点也能既管又干,对于资源不多的小集群很友好。
我在规划节点时一般遵循一个原则:控制面尽量和业务负载弱耦合。如果手里只有一台机器,不用纠结,它就同时承担Server和Worker职责;如果有三台机器,我会让两台Server负责控制面和高可用,剩下的机器作为Agent跑业务负载。生产环境千万别把业务容器直接放在重要的控制面节点上,除非资源实在紧张,至少也要给Server节点打上污点,防止业务Pod被调度上去。
2.2 系统层面的检查清单
这部分看起来基础,但很多搭建失败的问题都出在系统准备不完整。以下是我每次装机前固定检查的几个项目:
- 关闭或合理配置Swap。Kubernetes对Swap的处理逻辑比较保守,虽然新版默认允许一定程度的Swap使用,但为了避免内存驱逐和性能抖动,我在Server节点上直接把Swap关闭,Agent节点也尽量关掉。
- 确保主机名唯一。三个节点如果都叫localhost,加入集群后节点名称会冲突,尤其是Agent节点加入时经常出现一个节点踢掉另一个节点的情况。
- 同步时间。证书验证严重依赖系统时间,时间偏移超过五分钟,kubectl访问可能直接报证书错误。
- 网络连通性。多数情况集群内网是私网地址,节点之间要能互相访问,尤其是TCP 6443端口和UDP 8472端口。
下面这个表是我经常参考的端口清单:
| 端口 | 协议 | 用途 |
|---|---|---|
| 6443 | TCP | Kubernetes API Server |
| 8472 | UDP | Flannel VXLAN网络 |
| 10250 | TCP | kubelet指标采集 |
| 2379 | TCP | 外部etcd/数据库场景 |
| 30000-32767 | TCP | NodePort服务端口 |
如果你的服务器开了防火墙,建议先把这些端口放行,而不是等到加入节点失败才开始排查。我在第一次搭建时就因为防火墙没放行UDP 8472,导致Agent节点虽然成功加入,但Pod之间网络始终不通,排查了很久才发现是VXLAN流量被丢弃。
2.3 版本选择和下载注意事项
安装K3s有一个很容易犯的错误:直接装latest,然后在几个月后升级时出现兼容性问题。我现在习惯装固定版本,比如安装时指定INSTALL_K3S_VERSION参数,系统管理和升级节奏都可控。还有一个经常被忽略的点:安装脚本是否需要指定镜像加速参数,取决于你的网络环境。K3s安装脚本内置了一个常用的镜像加速选项,设好之后镜像拉取速度和安装脚本下载都稳定很多。
下载安装包时如果反复超时,不要一味重试,先看DNS解析是否正常,再看有没有必要走代理或镜像通道。把安装脚本下载到本地再执行,比每次现场从远程拉更可控。
3. 单Server集群安装:从下载到kubectl可用的完整过程
3.1 一条命令安装到底做了什么
K3s的安装非常简单,一条命令就能完成Server节点的安装:
curl -sfL https://get.k3s.io | sh -跑完以后系统会自动下载二进制、生成配置目录、创建systemd服务,并且自动启动集群。刚执行完命令头一分钟别急着查节点状态,K3s会先拉镜像、初始化网络插件,通常需要几十秒到几分钟不等。我看到很多新手在这一步就开始怀疑集群挂了,其实多等一会儿就行。
如果需要在国内网络环境下安装,官方安装脚本支持指定镜像加速参数:
curl -sfL https://get.k3s.io | INSTALL_K3S_MIRROR=cn sh -这个参数会把镜像拉取通道切到国内节点,很多场景下安装速度和后续拉镜像速度都会明显变快。
安装完成后第一件确认的事情是服务状态:
systemctl status k3s然后查看节点和Pod状态:
k3s kubectl get nodes k3s kubectl get pods -A这里有个细节:默认安装完,kubectl的配置文件在/etc/rancher/k3s/k3s.yaml,用k3s kubectl命令时不需要额外配置。如果想使用独立的kubectl客户端,可以把这份配置拷贝到本机的kube配置目录:
mkdir -p ~/.kube sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config sudo chown $(id -u):$(id -g) ~/.kube/config拷贝后注意文件中server地址指向的是127.0.0.1还是实际IP。默认指向127.0.0.1,在服务器本机操作没问题,如果要从其他机器管理集群,需要把server字段改成Server节点的实际IP或域名,并且在安装时配置tls-san参数,否则证书校验会失败。
3.2 我最常用的几个安装参数
一条命令直接装虽然快,但生产环境还是建议写配置。K3s支持/etc/rancher/k3s/config.yaml配置文件,下面是我一套比较精简的Server节点配置:
write-kubeconfig-mode: "0644" tls-san: - "k3s.example.internal" disable: - traefik node-name: "server-01" node-label: - "role=control"其中disable掉traefik是我个人的习惯,不是因为Traefik不好,而是有小内存主机时,默认的Traefik加上ServiceLB会额外占用资源和端口。如果你本来就打算用Ingress暴露服务,那可以保留。其他常用参数还有:
- --write-kubeconfig-mode:设置kubeconfig文件权限,方便多用户读取。
- --node-name:指定节点名称,避免主机名产生歧义。
- --node-label:给节点打标签,之后可以按标签调度。
- --private-registry:如果使用内部镜像仓库,需要配置私库认证信息,否则拉取私有镜像时会失败。
3.3 单节点跑通后的性能观察
装完单节点之后,可以先观察一下资源占用,确认机器的剩余资源是否满足预期。我实测在空闲状态下,2G内存的机器跑一个K3s Server节点,整个K3s相关进程加起来大概占用500M左右内存,CPU几乎可以忽略。这跟标准Kubernetes控制面全套动辄超过2G内存的情况相比,确实轻量很多。看到这个数据,你就可以放手规划剩余资源的业务负载了。
4. 把Agent节点接进来:从单点变成真正集群的关键动作
4.1 拿到Token再谈集群
单Server节点严格来说还不算“集群”,只是有一个控制面加一个可调度节点。真正的多节点集群需要把Agent节点接入进来。接入的前提是拿到Server节点的Token,这个Token保存在:
cat /var/lib/rancher/k3s/server/node-tokenToken内容是一长串随机字符串,本质上是Agent加入集群时的身份凭证。拿到Token之后,在Agent节点执行:
curl -sfL https://get.k3s.io | K3S_URL=https://server-node-ip:6443 K3S_TOKEN=my-node-token sh -安装完成后查看节点状态:
k3s kubectl get nodes正常情况下新Agent节点几十秒内会进入Ready状态。如果一直NotReady,优先排查Agent节点的网络到Server节点的6443/TCP是否通,以及主机名是否和已有节点冲突。
4.2 Agent节点的配置管理
和Server节点一样,Agent节点也支持配置文件。一个典型的Agent配置:
server: "https://server-node-ip:6443" token: "my-node-token" node-name: "agent-01" node-label: - "role=worker"配置文件路径同样是/etc/rancher/k3s/config.yaml,安装脚本执行时会自动读取。用配置文件的好处是升级重装时不会丢参数,尤其是需要固定节点名、标签的场合。如果没有配置node-name,Agent节点会默认使用系统主机名,一旦主机名在不同节点间重复,集群里就会出现同名节点互相挤兑的问题。
4.3 节点角色与调度策略的补充
在标准Kubernetes里,Worker节点天然不具备控制面组件,但在K3s里,Server节点默认也是可以调度业务Pod的。如果不想让业务负载跑上控制面节点,可以给Server节点加污点。比如安装Server节点时加上:
--node-taint "node-role.kubernetes.io/control-plane=true:NoSchedule"这样普通Pod就不会调度到Server节点,只有能够容忍该污点的Pod才能上去。对于Agent节点集群,我还习惯给不同Agent打上不同标签,比如GPU节点、大存储节点,然后在Deployment的nodeSelector里指定目标标签,实现精细调度。这是K3s多节点集群从“能跑”走向“好用”非常关键的一步。
5. 高可用配置:外部数据库、固定Token与多Server架构
5.1 单Server节点的数据库瓶颈在哪里
默认安装的K3s使用SQLite做数据存储,数据文件放在/var/lib/rancher/k3s/server/db目录。SQLite对单Server节点是完全够用的,但一旦Server节点宕机,整个控制面就不可用了,因为SQLite不支持多个Server节点并发访问。很多人想当然地启动第二个Server节点指望它自动接替控制面,结果发现新节点一直处于启动失败状态,原因就是SQLite文件被占用或数据版本不匹配。
如果集群对控制面高可用有要求,第一步就是把数据后端从SQLite换掉。K3s支持的外部数据库包括MySQL、PostgreSQL、MariaDB,也可以切换到嵌入式etcd。我自己最常用的切换方式是外部PostgreSQL,逻辑清晰、备份简单。
5.2 外部数据库模式的配置过程
假设已经有一个PostgreSQL实例,先创建数据库和账号:
CREATE DATABASE k3sdb; CREATE USER 'k3suser' WITH PASSWORD 'strongpassword'; GRANT ALL PRIVILEGES ON DATABASE k3sdb TO 'k3suser';然后在Server节点配置文件中加入:
datastore-endpoint: "postgres://k3suser:strongpassword@postgres-host:5432/k3sdb" token: "my-fixed-token" tls-san: - "k3s.example.internal"关键一点:从SQLite切换到外部数据库不是改一下配置文件那么简单。对于已存在的集群,直接用新的datastore-endpoint启动,K3s并不会自动导入旧数据。我一般建议三个Server节点从一开始就统一用外部数据库搭建,避免后期迁移数据带来的麻烦。如果必须迁移,可以提前备份现有资源清单,重建集群后再用kubectl apply重新创建。
这里还有一个容易踩的坑:非常不建议直接把原来用SQLite的server目录完整拷贝到新服务器去恢复,因为证书和节点身份信息是绑定的,拷贝不当会导致旧节点证书过期问题。
5.3 固定Token为什么重要
多Server架构下有一个细节经常被忽略:token不是一成不变的。如果服务器重启后某Agent拿着旧Token来连接,发现控制面换了一批Token,就可能会出现一系列异常。解决方法是安装配置时通过token参数指定一个固定字符串:
curl -sfL https://get.k3s.io | sh -s - --token my-fixed-token所有Server和Agent节点都用同一个固定Token,之后无论控制面怎么重启、Server增加或减少,Agent都能正常找到并连接控制面。这个习惯我从一开始就坚持,之后再也没遇到过节点批量掉线的问题。
5.4 多个Server节点之间的负载均衡怎么接
控制面有多个Server节点之后,Agent不能直接写死单台Server的IP,否则这台挂了Agent也没法注册。常见做法是在前面放一个四层负载均衡,把6443端口的流量分发到所有Server节点。Agent的K3S_URL就指向这个负载均衡地址。负载均衡器可以是云厂商的LB,也可以是自建的四层转发方案,重点是保证6443端口可达且会话保持时间合理。
我在三台Server节点的场景里,会把其中一台节点同时作为负载均衡的入口,通过系统服务和iptables规则转发到另外两台Server,这个方案对节点数少的集群够用,省去额外维护一台专用LB的成本。配置完成之后,停掉其中一台Server实测,kubectl命令依然正常响应,这才是高可用控制面该有的样子。
6. 部署第一个真实应用:Ingress、存储与服务暴露的组合拳
6.1 最小部署先验证调度链路
集群搭好以后,光看节点Ready还不够,要真实部署一个应用验证整个调度链路。下面这个例子我几乎每次搭建完都会跑一遍:
apiVersion: apps/v1 kind: Deployment metadata: name: echo-server spec: replicas: 2 selector: matchLabels: app: echo-server template: metadata: labels: app: echo-server spec: containers: - name: echo-server image: registry.cn-hangzhou.aliyuncs.com/google_containers/echoserver:1.10 ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: echo-server spec: selector: app: echo-server ports: - port: 80 targetPort: 8080保存为echo-server.yaml后执行:
kubectl apply -f echo-server.yaml kubectl get pods -o wide kubectl get svc通过kubectl get pods -o wide能看到两个副本是否被调度到不同的节点,这一步能验证节点间网络和调度是否正常。如果两个副本都堆在同一个节点,先别急着调整调度策略,看看是不是另一个节点有污点或者资源不足。
6.2 服务暴露方式选哪种
K3s默认的服务暴露方式有三种可选:NodePort、ServiceLB和Ingress。NodePort最直接,但生产环境不建议对外暴露高位端口;ServiceLB是K3s内置的负载均衡器,能在没有云LB的情况下给Service分配外部IP;Ingress则通过域名和路径路由,适合HTTP服务对外发布。
刚装好的K3s默认带有Traefik Ingress Controller,如果你安装时没有disable它,就可以直接创建Ingress资源,通过域名访问服务。比如下面这个:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: echo-server spec: rules: - host: echo.example.internal http: paths: - path: / pathType: Prefix backend: service: name: echo-server port: number: 80记得把域名解析到集群任意节点的80端口,Traefik默认监听宿主的80和443端口。如果你安装时禁用了Traefik,又想用Ingress,就需要自己装一个小型Ingress控制器。我个人在轻量集群上的选择是保留Traefik,它的配置灵活度和资源占用都在可接受范围内,禁掉它反而少了很多便利。
6.3 存储怎么解决:本地存储还是外置存储
有状态应用绕不开存储。K3s默认自带了local-path Provisioner,能自动在节点本地目录创建持久卷。默认存储类名称是local-path,使用方法很简单,PVC里直接指定存储类即可。本地存储适合日志、临时数据、测试环境,但需要注意的是数据绑定了具体节点,节点挂了数据可能就丢了。
对于需要跨节点共享数据的场景,建议接外置存储。最朴素的做法是节点上挂载NFS,然后通过Kubernetes的NFS驱动创建PV;也可以先装一个块存储方案,再部署CSI插件。轻量集群上我一般优先NFS,因为它够通用、维护简单,唯一的缺点是性能和单点问题,但绝大多数内部业务对这个要求并不高。
6.4 资源限制在轻量集群上的重要性
在标准Kubernetes集群上,不写资源requests可能是懒惰,但在K3s这种资源有限的环境里,不写资源requests会直接导致节点过载。我见过最典型的情况是:一台4G机器上的多个Pod都不写内存限制,结果一个容器内存泄漏把整台机器打爆,节点进入MemoryPressure,所有Pod都被驱逐,集群进入雪崩状态。
所以我的习惯是所有Deployment都预算好requests和limits,即使不确定具体数值,也会给一个保守估计。比如下面这样:
resources: requests: memory: "64Mi" cpu: "50m" limits: memory: "128Mi" cpu: "100m"不要觉得给低一点显得小气,在轻量集群上,保护整机稳定比让单个应用跑满更重要。
7. 运行三个月后总结的配置心得与排障笔记
7.1 证书类问题怎么处理
K3s证书机制是自签名的Root CA自动签发生成,日常运行中碰到的绝大多数证书问题有两个来源:一是系统时间漂移,二是Server节点重启后证书陈旧。前者通过配置NTP同步即可解决,后者典型现象是kubectl突然报出x509证书过期之类的错误。遇到这类问题,先别急着重建集群,尝试重启一下K3s服务:
systemctl restart k3sK3s服务启动时会自动重新生成或轮转部分过期证书配置,很多证书问题在重启之后会自行恢复。这里要提醒一点:不要去手动改证书有效期参数,也不要随便换CA,改不好会让Agent节点全部失联,排错成本极高。
7.2 节点NotReady的第一排查思路
节点NotReady是最常见的异常。先看kubelet日志:
journalctl -u k3s -f再看系统资源是否耗尽。很多时候节点NotReady不是因为网络不通,而是节点磁盘满了。K3s默认镜像数据和容器数据都存放在/var/lib/rancher/k3s/agent/containerd目录下,长时间不清理镜像,这个目录会越滚越大。我通常定期做一次镜像清理,把不再使用的镜像删掉,给节点留出充足空间。
如果是多节点场景下某一个Agent节点一直NotReady,重点检查该节点和Server节点之间的UDP 8472端口是否可以互通。这个端口是Flannel的VXLAN通道,不通的话节点虽然加入集群,但Pod之间无法通信,节点状态也会异常。可以临时关闭防火墙或用策略放行,然后重启Agent节点的K3s服务,一般能恢复。
7.3 升级和备份怎么做
K3s升级最简单的方式是重新执行安装脚本,脚本会检测到已安装版本并升级到目标版本;也可以直接下载新版二进制替换旧的k3s文件,然后重启systemd服务。无论哪种方式,升级前先备份数据。
备份的重点是/var/lib/rancher/k3s/server/db目录和/etc/rancher/k3s目录。如果使用SQLite,直接打包db目录就能达到备份目的;如果使用外部数据库,则直接用数据库自己的备份机制。另一种方式是用kubectl导出资源清单,把Deployment、Service、ConfigMap、Secret等都导出保存,这种做法在灾难恢复时最直观。
我在三个月运维中的经验是:定期备份数据库目录,一个月至少一次;配置文件变更前先复制一份原文件;升级前先看Release变更记录再决定是否执行。这套习惯让几次看似危急的问题都变成了小插曲。
7.4 一个小技巧:小机器上尽量做减法
如果你也是把小主机当作K3s主力机器,一定要学会做减法。默认安装的K3s会启用Traefik和ServiceLB,如果暂时用不上,安装时直接设参数禁掉,能明显降低内存占用。K3s还默认安装了一些系统组件,对于内部实验环境,把用不到的Ingress控制器停掉,资源占用又能再降一档。
另一个减法是控制镜像数量。不要看到镜像就想拉,K3s节点本身维护的数据少一分,故障概率就少一分。运行一段时间后,用crictl工具查看当前节点上到底有哪些容器和镜像,心里有个底,比等到磁盘满了再处理要舒服得多。
我最后再分享一个体会:K3s集群搭建及配置这件事,最大的收益不是省了多少内存,而是让你在资源有限的条件下依然能用完整Kubernetes的思维方式管理应用。这套集群我已经连续跑了大半年,中间经历过节点重启、磁盘告警、证书异常,每一次排障都让后面的运维更顺手。如果你也打算在轻量环境里上Kubernetes,K3s值得成为第一个尝试方案。