RHEL 8上部署K3s边缘集群的实践指南 1. 为什么是K3s边缘场景下的选型考量先把话说在前面很多人一提到Kubernetes脑子里就是那几个大厂的超大规模集群几百个节点、上万Pod、各种高可用机制。但真到了边缘计算场景情况完全是另一回事。我这两年接手过几个边缘项目有的是厂区的数据采集节点有的是连锁门店的本地服务还有的是某个偏远站点的离线计算单元共同点就是机器配置不高、网络环境不稳定、现场没人专职运维但业务又确实需要一套标准的容器编排能力。这种场景下去部署一个完整的Kubernetes集群你会发现光初始化控制平面的痛苦就足够劝退一半人。而上K3s几乎是最顺理成章的选择。1.1 边缘计算对Kubernetes提出了哪些特殊要求边缘节点和云端节点最大的区别在于三个维度第一是资源。边缘节点常见的配置是4核8G甚至2核4G有些工控机更夸张内存可能只有2G。完整版Kubernetes的控制平面组件etcd、kube-apiserver、kube-controller-manager、kube-scheduler加起来光是静态占用就吃掉好几个G内存更别提etcd对磁盘IO的要求。在边缘节点上跑这么一套东西业务还没启动机器已经喘不过气了。第二是网络。边缘节点经常处在一个不稳定的网络环境里可能带宽只有几M可能经常断线重连可能和后端机房之间隔着一层并不那么可靠的专线。完整版Kubernetes默认假设的是一个相对可靠的网络节点频繁断连会导致Pod频繁重新调度反而把业务搞得不稳定。第三是运维。边缘节点散布在各个物理位置不可能每个节点都配一个资深运维。如果某个节点的Kubernetes组件出了毛病现场的人大概率只能重启机器。这就要求整个运行时必须是“能一键重启、能自动恢复、状态尽可能简单”的形态。而K3s恰好在这三个维度上做了非常激进也足够聪明的简化它把kube-apiserver、kube-scheduler、kube-controller-manager、kubelet、kube-proxy、containerd、flannel全部打包进一个二进制文件里默认使用SQLite而不是etcd作为控制平面存储内存占用可以控制在512M以内。这意味着一个2G内存的边缘设备跑着K3s的同时还能正常运行业务容器。1.2 完整版Kubernetes与K3s的关键差异很多初次接触K3s的人会问一个问题它到底是不是一个“阉割版”的Kubernetes从功能覆盖范围来说它确实去掉了一些云厂商相关的组件比如云控制器管理器、存储卷插件等但它保留了Kubernetes最核心的调度、编排、服务发现、网络策略、存储抽象能力。从实际使用体验来看几个关键差异比较明显对比维度完整版KubernetesK3s控制平面组件etcd 四个独立组件进程多内存占用大单二进制集成默认SQLite存储容器运行时通常配containerd或Docker内置containerd开箱即用网络方案需要额外安装CNI插件内置flannel也可以通过配置切换Cilium安装复杂度需要证书、kubeadm配置、负载均衡前置一条命令起server一条命令加agent适合场景大规模、高可用、复杂多租户环境边缘计算、开发测试环境、中小规模生产你在边缘场景里绝大多数情况下并不需要完整版那么多的高级特性。Kubernetes的调度能力、服务自愈能力、声明式配置管理能力K3s一个都没丢。丢掉的主要是与云平台深度集成的部分而这些在边缘机房或工控机上反而根本用不到。1.3 为什么RHEL 8是合适的承载系统可能有人会问边缘节点不是一般都用Debian或Ubuntu吗确实我在很多项目里也见过树莓派跑Ubuntu Server、工控机跑Debian的场景。但RHEL 8在边缘场景里有一批忠实的用户主要来自制造业、能源、政府类项目原因是这些行业对系统认证、稳定性、生命周期有硬性要求。RHEL 8提供10年支持周期这对那些一次性部署在偏远站点、五年内不想动它的项目来说非常关键。RHEL 8自带firewalld和SELinux这既是优势也是坑。优势在于安全基线高默认情况下很多攻击面被堵住了坑在于如果你不熟悉这两样东西K3s装好之后大概率会遇到网络不通、Pod起不来的问题。我后面会专门讲怎么处理SELinux和防火墙的配置这里先记住一个结论在RHEL 8上跑K3s完全可行但前期的系统准备比安装K3s本身更值得花时间。2. RHEL 8环境准备先把地基打牢我见过太多人栽在环境准备这个环节。K3s安装本身十分钟就能搞定但如果你在RHEL 8上一上来就跑官方脚本却不处理防火墙、SELinux和容器运行时兼容性问题那后面排查问题的时间足够你把这个流程重写三遍。2.1 基础系统配置建议先说说硬件的最低要求。如果你只是跑一个单节点的演示环境1核1G就能转起来但不太建议在生产边缘场景里这么干。我自己的经验是管理节点至少2核4G业务节点至少2核2G磁盘建议直接上SSD因为K3s默认的SQLite存储虽然比etcd轻量但对磁盘IO仍然有要求。系统层面的准备工作我按顺序列出关键项配置好主机名和/etc/hosts确保各个节点之间能通过主机名互通关闭或者正确配置swap。Kubernetes从1.8开始就默认不支持swapK3s继承了这一点建议直接关闭swap配合RHEL 8的订阅管理确保系统可以正常获取更新和安装依赖包同步时间。边缘设备如果不做时间同步证书校验和日志排查都会非常痛苦建议配置chrony指向一个可靠的NTP服务器2.2 防火墙与SELinux处理这是RHEL 8上跑K3s最容易踩坑的地方。RHEL 8默认启用firewalld如果你不主动放行端口节点之间的通信会被直接拦掉。常见的端口需求如下端口协议用途6443TCPKubernetes API Server8472UDPflannel VXLAN网络10250TCPkubelet指标采集与健康检查2379-2380TCP仅在使用etcd高可用模式时需要我实操中最常用的做法是直接把端口写进firewalld的规则里不推荐直接防火墙全关。全关确实省事但如果你面对的是一个需要过等保或者有安全审计的客户全关防火墙这一条就足够让项目卡住很长时间。SELinux这块稍微有点复杂。RHEL 8默认是Enforcing模式K3s在安装时会尝试创建/var/lib/rancher等目录并在这个目录下运行containerd、kubelet等组件。我在多个版本上都遇到过SELinux策略阻止K3s进程访问某些文件的问题。如果你的节点是纯边缘业务节点没有其他特殊的安全要求我一般建议把SELinux调整成Permissive模式而不是彻底禁用。Permissive模式只记录不阻止既保留审计能力又不影响K3s运行。注意调整SELinux模式需要修改/etc/selinux/config文件中的SELINUX字段重启后生效。如果想要临时生效可以用setenforce 0命令但重启后恢复。我建议生产环境直接把配置文件改掉避免因为重启机器导致SELinux重新启用而引发服务故障。2.3 容器运行时的选择K3s安装脚本默认会内置containerd不需要你额外安装Docker或Podman。这一点在设计上很聪明因为K3s希望做到“开箱即用”不依赖节点上预先存在的容器运行时。我把实际项目的经验放在这如果你没有特殊的镜像构建需求千万不要在节点上额外安装Docker。原因很简单两个容器运行时并存会让镜像管理变得混乱而且K3s内置的containerd足够应付绝大多数边缘场景。如果你确实需要在节点上构建镜像建议在另一台机器上装Docker构建完成后把镜像推到私有仓库边缘节点只负责拉取和运行。还有一个我在实践中遇到的注意点RHEL 8自带Podman它和K3s内置的containerd之间可能因为端口绑定或者网络栈差异产生冲突。我遇到过Podman占用了某些网络端口导致K3s初始化失败的情况。在准备阶段的建议是如果不需要在边缘节点上直接操作容器把Podman相关的服务停用掉。2.4 内核参数与系统限制调整排查过一些装不上K3s的问题最后发现是系统参数被卡住了。特别要关注这几个方向文件描述符限制kubelet和containerd运行时会打开大量文件描述符默认的1024限制不够用。建议在/etc/security/limits.d/目录下加一个配置文件设置65535或更高内核模块overlay模块是K3s内置containerd默认存储驱动依赖的必须先加载网络转发参数IP转发必须开启否则Pod与外部网络的通信会异常。对应的内核参数是net.ipv4.ip_forward以上这些配置我习惯把所有调优项写在一个sysctl配置文件中然后一次加载避免分散在各处导致后续排查困难。3. 安装部署K3s集群从单节点到多节点环境准备做完之后真正的安装反而是最简单的一步。不过有一点必须先说清楚K3s安装脚本默认从GitHub拉取二进制国内网络环境下拉取速度非常不稳定。我在实际项目中默认都会带上镜像加速参数这能省掉大量等待时间。3.1 安装Server节点的完整流程先看命令curl -sfL https://get.k3s.io | INSTALL_K3S_MIRRORcn sh -这个命令背后的逻辑是下载安装脚本设置使用中国镜像源然后执行安装。安装完成后K3s会自动启动并通过systemd注册为服务。这样一个单节点集群就已经可用了。如果你想更进一步控制安装参数比如禁用内置的Traefik或者调整数据存储路径可以这样curl -sfL https://get.k3s.io | INSTALL_K3S_MIRRORcn INSTALL_K3S_EXEC--disabletraefik --data-dir/data/k3s sh -在我的边缘项目中通常建议把数据目录放到单独的分区或者独立的SSD上避免系统盘写满后整个节点不可用。3.2 添加Agent节点多节点模式下你需要在Server节点上获取NodeTokencat /var/lib/rancher/k3s/server/node-token然后在Agent节点上执行curl -sfL https://get.k3s.io | INSTALL_K3S_MIRRORcn K3S_URLhttps://server节点IP:6443 K3S_TOKEN上面获取的token sh -添加Agent节点的流程很顺但有几个细节值得注意。如果在公司内网环境部署务必通过内网地址通信避免节点之间流量绕到公网回环。另外Server节点默认也作为Agent节点参与工作负载调度如果希望Server不跑业务Pod加上--disable-agent参数。3.3 验证集群状态安装完成后通过kubectl检查状态kubectl get nodes kubectl get pods -A通常在不到一分钟内所有系统级Pod都会进入Running状态。有一个小的检查技巧是直接去看K3s服务的状态systemctl status k3s同时可以查看日志确认没有反复报错。如果一切正常服务状态显示activerunning说明集群已经可以交付了。3.4 数据存储选型SQLite还是etcd默认情况下K3s使用SQLite存储控制平面数据。SQLite轻量、零维护非常适合单Server节点的场景。但如果你需要搭建多个Server节点实现高可用比如三个控制平面节点就必须切换到etcd模式。etcd模式的安装方式和默认安装基本一致只是增加--cluster-init参数curl -sfL https://get.k3s.io | INSTALL_K3S_MIRRORcn INSTALL_K3S_EXEC--cluster-init sh -后续Server节点再通过server参数加入curl -sfL https://get.k3s.io | INSTALL_K3S_MIRRORcn INSTALL_K3S_EXEC--server https://第一个节点IP:6443 sh -我的经验是边缘项目通常不需要上etcd高可用。理由很现实边缘站点的Server节点本来就少维护高可用集群的人力成本常常比业务风险损失成本还高。单Server节点配合Agent节点的模式已经能覆盖绝大多数边缘业务。如果你只有三五台边缘设备老老实实用SQLite就行。4. 边缘场景下的优化配置让集群真正能扛事很多教程讲到安装完成就结束了但安装只是开始。边缘场景里真正体现工程水平的是你做了多少面向不稳定环境的前置优化。这一章我挑几个在真实项目中反复用到、而且被证明有效的配置。4.1 节点能力差异化与调度优化边缘设备最大的特点就是异构。同一个集群里可能有一台性能不错的服务器也有几个性能很弱的工控机。如果你不给节点打标签、不设置污点K3s默认的调度器会把Pod均匀地洒在所有节点上结果往往是性能差的节点负载过载性能好的节点闲着。我的做法是给节点打标签然后配合固定调度策略。比如kubectl label node edge-node-01 node-role.kubernetes.io/gputrue kubectl label node edge-node-02 node-role.kubernetes.io/storagetrue然后在需要GPU的Deployment中加上nodeSelector。对于不希望某些节点运行特定Pod的情况则使用污点kubectl taint nodes edge-node-02 dedicatedstorage:NoSchedule这样Pod就不会被随机调度到该节点只有显式容忍该污点的Pod才会运行在其上。这在边缘场景里特别有用因为不同节点承担的业务职能往往差异很大无差别的自动调度反而会引入不稳定性。4.2 镜像管理离线环境下的核心策略边缘环境最容易被低估的变数就是镜像拉取。网络抖动、带宽不足、镜像仓库在远端这些都会导致Pod长时间处于ImagePullBackOff状态。我在边缘项目里统一采用的方案是两步走第一步在可上网的构建机上把镜像拉取下来然后通过docker save打包成tar文件。第二步把tar文件拷贝到边缘节点再通过ctr导入到K3s内置的containerd中。具体导入命令如下ctr -n k8s.io images import app-image.tar导入完成后镜像会被K3s识别。为了确保节点重启后镜像仍在我在导入前会确认数据目录单独存放且磁盘有足够剩余空间。还有一种更可靠的方案在K3s Server节点上搭建一个私有镜像仓库服务所有Agent节点在containerd配置中添加registry镜像加速地址。这样即使Agent节点与外部网络隔离只要它们和Server节点在同一内网内镜像拉取仍然高效。这个方案的细节我放到后面讲配置代码时说。为离线环境创建的镜像仓库有一个需要注意的问题containerd默认只允许HTTPS方式访问非本地仓库地址如果你用HTTP私有仓库需要在/etc/rancher/k3s/registries.yaml中显式配置。否则Pod拉镜像时会报证书错误这是离线场景里最常见的问题之一。4.3 网络方案与边缘断连容忍K3s默认使用flannel作为CNI插件VXLAN模式。这套方案的好处是部署简单、无需额外依赖适合大部分边缘网络。但如果你对网络性能有更高要求比如大数据量传输、低延迟场景可以切换为Cilium启用eBPF模式。在边缘场景下还有一个容易被忽视的配置kubelet的节点状态更新频率。默认情况下kubelet每10秒上报一次节点状态到Server如果网络短暂抖动节点可能被标记为NotReady进而触发Pod重新调度。这在边缘场景里是不可接受的。应对方法是修改kubelet参数放宽节点状态判定阈值INSTALL_K3S_EXEC--kubelet-argnode-status-update-frequency30s --kubelet-argnode-status-report-interval60s这样即使网络短暂中断节点也不会轻易被判定为故障。配合合适的Pod驱逐容忍时间整个集群在弱网环境下的稳定性会明显提升。4.4 存储方案本地存储优先边缘节点一般不会接SAN存储阵列最常见的存储介质是本地SSD或机械盘。K3s内置了local-path-provisioner默认情况下只需要设置一个StorageClass即可实现本地持久化存储。kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml这样在应用中使用PVC声明就能动态创建本地存储卷。在边缘场景下这个方案比配置远端NFS或Ceph要靠谱得多原因很简单本地存储不依赖网络即使断了网数据也不会丢。不过本地存储有一个明显的局限性数据绑定在单一节点上。如果该节点宕机Pod迁到别的节点后数据无法自动跟随迁移。我在设计边缘应用时通常要求业务自身做数据复制的兜底或者接受一定程度的服务降级。这个问题在边缘场景下很难根治只能在业务层面去规避。4.5 系统服务与开机自启配置边缘节点经常断电重启如果K3s服务没有正确配置为开机自启那现场人员每次都要手动启动服务体验会非常差。还好K3s安装时默认已经通过systemd注册了服务需要确认的是服务启动策略。systemctl enable k3s systemctl enable k3s-agent同时建议把K3s服务的重启策略配置好修改service文件设置Restartalways。这样即便K3s进程异常退出也会被systemd自动拉起。K3s的另一个恢复机制是“状态角标”Server和Agent在重启后会自动尝试重新连接。如果你的Server节点IP变了需要在初始化配置里更新server地址。为了避免现场改配置的麻烦我习惯在安装时为节点分配固定的内网IP并且把server的IP固化在初始化参数里而不是依赖DNS解析。5. 常见问题与排查技巧实录这章是我认为全文最有价值的部分因为很多问题在官方文档里根本查不到只能靠实打实的排障积累。5.1 端口与防火墙导致的注册失败症状Agent节点执行注册命令后日志一直报无法连接到Server的6443端口但SSH连接Server是正常的。我第一反应是检查Server节点的防火墙。RHEL 8的firewalld默认配置下外部IP访问6443端口会被拦截。即使你之前放行了端口如果firewalld服务重启或者配置被覆盖规则可能丢失。排查命令firewall-cmd --list-all如果发现端口没有正确放行用下面的命令修复firewall-cmd --permanent --add-port6443/tcp firewall-cmd --permanent --add-port8472/udp firewall-cmd --permanent --add-port10250/tcp firewall-cmd --reload还有一种隐蔽的情况Agent节点和Server节点之间有额外的防火墙设备有些企业内网默认禁止UDP 8472端口而flannel的VXLAN走的就是这个端口。排查时记得在两个方向上做连通性测试。5.2 cgroup驱动不一致导致kubelet启动失败症状K3s服务启动后立即退出日志中出现Failed to start ContainerManager同时会看到systemd和cgroupfs驱动相关的报错。K3s使用内置的containerd默认cgroup驱动通常是systemd。如果你在安装时手动调整过与cgroup相关的内核参数可能造成驱动不一致。解决方法是显式指定cgroup驱动INSTALL_K3S_EXEC--kubelet-argcgroup-driversystemd这个问题的根源在于Kubernetes组件和容器运行时必须使用相同的cgroup驱动否则进程隔离和资源统计会出现不可预知的错误。5.3 镜像拉取失败与私有仓库证书问题症状Pod状态显示ImagePullBackOff通过describe查看事件时发现“x509: certificate signed by unknown authority”字样。这就是我前面提到的HTTP私有仓库证书信任问题。解决方式是编辑/etc/rancher/k3s/registries.yaml内容如下mirrors: docker.io: endpoint: - http://registry.example.local:5000然后重启K3s服务让配置生效。如果你用的是自签名的HTTPS证书还需要在RHEL 8系统层把证书加入信任列表。这里有个容易忽略的细节修改registries.yaml后需要重启K3s服务才能生效。只重启containerd是不够的因为K3s会自动覆写containerd的配置。5.4 节点IP更换后的故障处理边缘节点如果换了网络环境导致IP变化Server节点可能仍然保留旧的节点信息新IP的节点无法正常注册。这种情况在移动性较强的边缘设备上偶有发生。处理方式分两步先在Server节点上删除旧节点记录kubectl delete node old-node-name然后在Agent节点上清除旧的注册信息重新执行安装脚本。本质上K3s的注册状态存储在/var/lib/rancher/k3s/agent目录里需要清掉重来。这个操作在维护边缘节点时几乎每遇到一次IP变更都要做。5.5 日志清理与磁盘占用控制边缘设备的磁盘通常比云服务器更紧张K3s运行一段时间后系统日志和容器日志会积累大量数据。我习惯在每个边缘节点上配置logrotate限制journald日志的大小并定期清理已退出的容器和不再使用的镜像。journalctl --vacuum-size500M docker rmi $(docker images -f danglingtrue -q)容器日志则建议通过containerd的配置或者应用自身日志策略进行限制。这类措施虽然看起来跟K3s安装无关但在边缘场景的长期运行中它可能比任何性能调优都更关键。6. 实操心得与后续扩展说一个我在项目里反复验证过的做法所有生产级的边缘K3s集群我都不直接使用安装脚本的默认参数而是把所有配置写成一份环境变量文件安装时统一加载。这样既方便复现也方便排查不会出现装完之后自己都忘了当时用了什么参数的尴尬情况。具体形式上我会维护一个配置文件里面记录每台节点的角色、IP、安装参数、镜像清单、定时维护任务。新增节点时照着一套流程走基本上半小时就能完成一个节点的交付。关于后续扩展一个很实用的方向是给K3s集群接上统一的监控告警系统。边缘场景虽然节点分散但Prometheus加上简单的告警规则就能覆盖资源耗尽、节点失联、Pod反复重启这几类核心风险。另一个方向是根据业务需要引入GitOps流程通过自动发布工具统一管理多套边缘集群的配置和版本避免“一台一台上去敲命令”的原始运维方式。最后再分享一个小技巧如果你有一批配置高度一致的边缘节点先把一台节点的配置全部调好并验证然后把K3s的数据目录和服务配置一并打包做成镜像。后续节点直接刷镜像起来之后只需要重新生成节点身份并加入集群即可。这个方案在实际批量部署中能节省大量时间也降低了人为配置失误的概率。我的体会是边缘计算的K3s集群装上去只是起点真正拉开差距的是如何在资源受限、网络不稳、人手不足的环境下持续交付稳定的服务能力。