搞K8s集群,第一步就是配置环境,但很多人恰恰是在这一步被劝退的。网上教程一抓一大把,版本新旧混杂,照着敲命令动不动就报错,折腾一天可能连kubelet都起不来。我自己前前后后搭过好几套集群,从单机测试到多节点生产环境都踩过不少坑,这篇内容是把环境配置阶段的完整思路和操作细节梳理出来,按照一套可复现的标准流程走一遍,把为什么要这么配、哪些参数是关键的、哪些坑是必踩的都讲清楚。这个系列会按阶段拆开写,这是第一篇,专注把底层环境全部准备好,适合刚接触K8s、准备自己动手搭集群,或者搭到一半卡在初始化之前的朋友参考。
1. 主机规划与版本选型思路
1.1 节点角色怎么分
K8s集群不是随便拿几台机器跑起来就行,节点角色和资源规划直接决定了后面高可用和调度能力的上限。常见的分法是Master节点和Worker节点,Master负责API Server、调度器、控制器这些大脑组件,Worker负责真正跑业务容器。我按三台Master加若干Worker的规模来规划,这套配置在学习阶段够用,后续要扩Worker也方便。
| 节点角色 | 主机名建议 | 配置建议 | 数量 |
|---|---|---|---|
| Master | k8s-master01/02/03 | 4C8G,系统盘50G以上 | 3 |
| Worker | k8s-worker01/02 | 8C16G,系统盘100G以上 | 2 |
生产环境里Master节点不建议跑业务负载,学习环境压力不大,Master资源压力也不明显。硬盘这块我特别注意过,K8s的镜像、容器日志、etcd数据都很吃空间,系统盘低于50G,跑一段时间就会报警。还有一点,所有节点要求有两块网卡的话配置会灵活一点,主机名规划阶段就把名字定好,后面很多配置直接依赖主机名。
1.2 版本组合别乱配
版本选型是整个环境配置里最需要动脑子的部分。很多人图省事直接装最新版,结果组件之间版本不兼容,报错信息看不懂,排查半天。我用的组合是Kubernetes 1.28.2、containerd 1.7.13、Calico 3.26,操作系统选择Rocky Linux 9.2,这套组合在社区生态和稳定性上很成熟,网上踩坑案例也齐全。
版本选择的逻辑有三点。第一,K8s的版本支持周期是12个月左右,1.28这个版本在功能、稳定性、文档完整度上都处于成熟期。第二,容器运行时用containerd而不是Docker,K8s从1.24版本开始就彻底移除了dockershim,直接使用containerd作为默认运行时,这是主流趋势,现在新装集群再配Docker反而是绕远路。第三,Calico网络插件对版本的要求不苛刻,但保持和K8s主版本接近,减少兼容性问题。
1.3 网段规划提前定
网段规划是环境配置环节最容易被忽视、后期出问题最头疼的点。K8s内部涉及两个虚拟网段,一个是Pod网段,一个是Service网段,另外还要预留出物理网络不冲突的地址段。我习惯用Pod网段10.244.0.0/16,Service网段10.96.0.0/12,这两个网段是社区里的默认值,Calico和Flannel都能直接兼容。
规划前先确认物理网络的CIDR,比如公司内网是192.168.1.0/24,那就避开这个段,用10.x段就不会冲突。后面如果要用MetalLB这类方案暴露外部访问IP,还要规划好可用IP池,别在环境配置阶段就把地址段占死。这个细节我在实际部署Master节点初始化时踩过,Pod网段和物理网段冲突会直接导致容器网络起不来,每个Pod都拿不到IP,排查起来非常痛苦。
2. 操作系统基础配置是一切的根基
2.1 主机名、hosts、防火墙清理
操作系统装好之后,第一件事是设置主机名。K8s节点之间的通信依赖主机名解析,主机名乱写会导致节点的标识混乱,日志里看起来全是莫名其妙的主机名。我习惯在主节点上把所有节点的hosts条目都写进去。
hostnamectl set-hostname k8s-master01然后在每台机器上编辑/etc/hosts,加入所有节点的主机名和IP对照,三台Master的域名解析记录我提前写好,后面做高可用时直接用域名通信,不用记IP:
cat >> /etc/hosts <<EOF 192.168.1.101 k8s-master01 192.168.1.102 k8s-master02 192.168.1.103 k8s-master03 192.168.1.201 k8s-worker01 192.168.1.202 k8s-worker02 EOF防火墙这块,K8s集群内部要开很多端口,学习环境我直接关闭firewalld,减少干扰。别急着杠生产环境不能关防火墙,生产环境应该用云平台的安全组规则或者独立防火墙策略精确放行,而不是靠系统自带的firewalld做粗放管理。学习阶段最重要的是先把集群跑起来,网络问题排查的范围越小越好。
systemctl stop firewalld systemctl disable firewalld2.2 关闭swap和SELinux的原因
swap和SELinux这两个东西,是K8s初始化时最常见的两个拦路虎。先看swap为什么必须关。Kubernetes的kubelet在运行时会根据节点上的内存资源做调度决策,如果开启了swap,kubelet无法准确判断节点可用内存,导致调度器把Pod调度到一个实际上内存压力很大的节点上,系统性能就会很诡异。官方要求的做法是禁用swap,这个没得商量。
swapoff -a sed -i '/ swap /s/^/#/' /etc/fstabsed命令把/etc/fstab里的swap行注释掉,保证重启后swap不会自动挂载回来。不注释存在一个隐患:重启后swap重新启用,kubelet直接报错起不来。这个坑我遇到太多次了,每次复制系统镜像到新机器都会忘记检查fstab。
SELinux的作用是强制访问控制,K8s组件对文件系统的访问模式很特殊,SELinux的默认策略会拦截很多K8s组件的文件操作,导致各种Permission denied。学习阶段直接设置成permissive模式,别改成disabled,因为disabled之后如果要恢复SELinux,需要重启并且重新打标签,permissive模式只是记录违规但不拦截,后面排查问题时还能从SELinux日志里看到线索。
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config改完配置后先setenforce 0让当前会话立即生效,重启后再检查配置确认状态。
2.3 内核模块与关键参数
K8s的容器网络依赖Linux内核的overlay和br_netfilter模块。overlay文件系统是容器镜像分层存储的基础,br_netfilter让Linux网桥上的流量也能经过iptables过滤,K8s的Service转发依赖iptables规则来处理流量。先加载模块,再配置参数让重启后自动加载:
modprobe overlay modprobe br_netfilter cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF接下来是内核参数,这几个参数直接关系到K8s网络转发和iptables规则是否正确生效。我见过不少集群初始化成功但跨节点Pod网络不通,最后查到就是net.ipv4.ip_forward没有开。把配置写入/etc/sysctl.d/k8s.conf并执行sysctl --system:
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 net.ipv4.conf.all.forwarding = 1 EOF sysctl --systemsysctl --system是读取所有配置目录并应用,执行完成后可以用sysctl net.ipv4.ip_forward确认当前值已经是1。注意bridge-nf-call-iptables这个参数依赖br_netfilter模块,如果模块没加载成功,设置参数时系统会提示文件不存在,这就是排查思路的起点。
2.4 时间同步与基础依赖
K8s整套体系非常依赖证书和时间的准确性。etcd、kubelet、apiserver之间的通信都用TLS证书,证书的有效期校验要靠系统时间,如果节点间时间漂移超过几秒,集群直接报x509证书校验失败,这种错误最容易让人误判成证书过期,实际只是时间不对。我建议所有节点统一配置chrony时间同步,指向同一台NTP服务器。
dnf install -y chrony systemctl enable --now chronyd另外把基础工具装齐,包括net-tools、vim、wget、curl、ipvsadm这些。ipvsadm是给kube-proxy的IPVS模式准备的,虽然不装也能跑iptables模式,但在节点数量多了之后IPVS的性能优势会很明显。配置内核参数开启IPVS的负载均衡支持,这个在环境配置阶段准备好,后续kubeadm init时直接加--proxy-mode=ipvs参数:
dnf install -y ipvsadm ipset cat <<EOF | sudo tee /etc/sysctl.d/k8s-ipvs.conf net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.all.arp_announce = 2 EOF sysctl --system每个节点都要做全这些操作,Master和Worker没有区别。我建议写好一个shell脚本,在每台机器上执行,这一步减少大量重复劳动,也避免某台机器漏配参数。
3. 容器运行时换成containerd
3.1 为什么不用docker做运行时
K8s早期版本默认用Docker作为运行时,那时候kubelet通过dockershim与Docker通信,接口层复杂还有额外性能损耗。K8s在1.24版本之后正式移除了dockershim,容器运行时直接通过CRI接口对接,containerd就是目前最主流的CRI运行时实现。它比Docker少一层守护进程,资源占用更小,部署Pod的链路更短,排查问题也少一环。
很多初学者习惯先装Docker再装K8s,这个思路在现在的新版本里其实没必要。K8s集群的运行时只需要containerd,业务容器如果需要Docker构建镜像,可以在开发环境单独装,集群节点上没必要。我甚至见过有人装了Docker又把containerd也装上,两个运行时抢占cgroup资源,kubelet起不来的情况。
3.2 containerd二进制安装流程
安装containerd我习惯直接用官方release包,它自带了runc和CNI插件,省去自己拼装的麻烦。使用cri-containerd-cni包,解压后会自动把containerd、runc、CNI插件放到对应目录。
wget https://github.com/containerd/containerd/releases/download/v1.7.13/cri-containerd-cni-1.7.13-linux-amd64.tar.gz tar Cxzvf / cri-containerd-cni-1.7.13-linux-amd64.tar.gz解压完成后,把containerd的服务管理文件放到/etc/systemd/system/目录。我通常直接复制release包里的containerd.service到系统目录,然后执行systemctl daemon-reload。这里如果跳过这一步,containerd服务根本找不到启动脚本,systemctl start会提示unit not found。
systemctl enable --now containerd启动之后用ctr version验证版本,用systemctl status containerd看运行状态。接下来是生成配置文件,containerd首次启动不会自己生成配置,直接启动会使用内置默认值,但内部CRI配置、sandbox镜像地址都不能改,所以必须手动生成:
mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml3.3 SystemdCgroup与sandbox_image两个关键配置
生成的config.toml里有几个关键点必须改。第一是把SystemdCgroup改成true。为什么必须改?因为K8s推荐使用systemd作为cgroup驱动,而containerd的默认配置是cgroupfs。如果运行时用cgroupfs而kubelet用systemd,两边对cgroup的接管方式不统一,Pod启动后资源限制失效,甚至kubelet直接报错说cgroup驱动不匹配。
第二个关键点是sandbox_image,这个是Pod的pause容器镜像地址。默认配置写的是registry.k8s.io/pause:3.8,国内网络环境拉不下来,初始化时会在拉镜像阶段卡住。改成国内镜像源:
sed -i 's#registry.k8s.io/pause:3.8#registry.aliyuncs.com/google_containers/pause:3.9#' /etc/containerd/config.toml网上的仓库地址各有不同,我用的是阿里云镜像仓库的pause镜像。改完配置之后重启containerd,再验证一下配置是否生效:
systemctl restart containerd ctr images list还要检查一下CRI插件的日志,用crictl ps看能不能和containerd通信。crictl命令如果提示找不到,需要在/etc/crictl.yaml配置里指定runtime-endpoint,containerd的CRI默认监听unix:///run/containerd/containerd.sock。
cat <<EOF > /etc/crictl.yaml runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 3 EOF到这一步,每台机器的容器运行时都准备好了。注意,Worker节点不需要额外安装其他运行时,containerd一套搞定所有节点的Pod生命周期管理。
4. kubeadm、kubelet、kubectl三件套
4.1 yum源的选择与版本锁定
三个核心组件中,kubelet是节点上的代理服务,kubeadm是集群初始化的工具,kubectl是命令行管理工具。这三个必须装在同一组版本上,版本不一致会导致API接口不兼容,最典型的错误是kubelet和apiserver的版本差距过大,节点加入集群时报错。我建议直接用官方Kubernetes rpm源,这个源支持Rocky Linux 9系统,配置起来也很直接。
创建/etc/yum.repos.d/kubernetes.repo:
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key exclude=kubelet kubeadm kubectl EOF这个源里的repodata key会随着版本变动,直接用官网文档里的当前地址就行。安装命令加上版本号,不允许装这个源里最新版,防止多个节点装到不同小版本:
dnf install -y kubelet-1.28.2-0 kubeadm-1.28.2-0 kubectl-1.28.2-0装完后立刻锁定版本。我用dnf versionlock来锁:
dnf install -y python3-dnf-plugin-versionlock dnf versionlock add kubelet kubeadm kubectl锁定版本这个操作太重要了。你想象一下,某天执行dnf update系统更新,kubelet跟着升级到1.30版本,而集群apiserver还是1.28,节点加入时直接报版本不匹配,整个集群的加入逻辑全部卡住。我在一个测试环境里吃过这个亏,kubelet版本比apiserver新,节点调度过来后容器反复重启。
安装完成后让kubelet开机自启,但不要现在启动它,因为还没有集群配置,现在启动只会报错。这个细节后面再说:
systemctl enable kubelet4.2 初始化参数逐项说明
所有前置步骤都完成后,在第一台Master节点上执行kubeadm init。这个命令的参数基本上决定了集群的网络模型、证书体系和控制面地址。把参数拆开看:
kubeadm init \ --kubernetes-version=v1.28.2 \ --image-repository=registry.aliyuncs.com/google_containers \ --control-plane-endpoint=k8s-master01 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12每项参数的含义:
- --kubernetes-version必须和前面装的kubeadm版本一致,不一致时kubeadm会尝试从yum仓库里拉对应版本,但可能因为版本锁定导致失败。
- --image-repository指定镜像仓库地址,默认从registry.k8s.io拉取控制面镜像,国内网络根本拉不动,换成阿里云镜像仓库保平安。
- --control-plane-endpoint这里我直接写了master01的主机名和IP,它表示控制面的统一入口。学习环境先用单机入口,后续做高可用时再换成负载均衡器的虚拟IP加域名。
- --pod-network-cidr是Pod网段,要和后面装的Calico配置保持一致,用10.244.0.0/16就没有冲突。
- --service-cidr是Service网段,10.96.0.0/12也是社区默认值。
你也可以加上--apiserver-advertise-address参数指定apiserver对外通告的IP,默认会自动选择节点上的默认路由IP。多网卡的环境必须显式指定这个参数,否则apiserver暴露的IP是错的,其他节点根本连不上6443端口。
初始化过程会自动拉取镜像并启动控制面组件,整个过程大约三到五分钟。执行完成后,输出结尾会有两段指令,一段是kubeconfig配置命令,一段是Worker节点加入集群的命令,这两段都先保存下来。
4.3 初始化后验证与kubectl配置
初始化输出成功后,先把kubectl的配置文件拷贝到当前用户目录,不配置的话kubectl连接集群时找不到证书:
mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config检查集群状态:
kubectl get nodes kubectl get pods -n kube-system这个时候有个很常见的情况:Master节点状态是NotReady,coredns和etcd相关Pod显示Pending。千万别慌,这不是集群坏了,是还没安装CNI网络插件,节点没有网络插件驱动,Pod调度不到可运行的网络环境里。在装好Calico之前,NotReady是正常状态,coredns起不来也是正常的。
验证控制面组件是否正常,看kube-system命名空间里这几个核心Pod有没有Running,etcd、apiserver、controller-manager、scheduler这四个起来就说明控制面没有大问题。网络问题就等CNI装完再看。
我在环境配置阶段通常会再多做一个验证:把第二个Master节点和Worker节点通过kubeadm join加入集群。这样能验证节点之间的网络通道、证书分发是否正常。执行加入命令时要带上--v=5参数查看详细日志,出错时能看到具体卡在哪个阶段。加入命令按初始化输出末尾的模板来,不要自己拼命令,证书token和CA证书hash值都在输出里。
5. 环境配置阶段常见问题速查
5.1 卡在拉镜像与网络无关的排查
初始化卡住是出现频率最高的问题。执行kubeadm init后一直停在拉镜像的阶段,进度条不动,这时候很多人去检查网络是否通。实际上镜像仓库的网络连通性没问题,先确认当前kubeadm用的是哪个仓库,执行kubeadm config images list看输出地址,如果地址是registry.k8s.io开头,说明你忘记加--image-repository参数了,或者参数没有正确传入。
另一个坑是版本参数和镜像不匹配。--kubernetes-version=v1.28.2和yum源里装的kubeadm版本必须严格一致,kubeadm会根据这个版本去拼镜像tag。如果装的是1.28.2-0,但init写的是v1.28.3,kubeadm就会去拉1.28.3的镜像,仓库里可能没有这个tag,卡住就很正常。
建议先把镜像预先拉下来。这样init执行时如果镜像都拉好了,集群初始化时间会缩短很多,排查拉镜像问题也更简单:
kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers5.2 kubelet日志与CNI状态判断
kubelet服务没有启动、不断重启,这是初始化不成功的直接表现。排查顺序是从kubelet日志开始,logs命令拿到实时日志,kubelet的错误信息是很直白的:
journalctl -u kubelet -f日志里最常见的几类错误。第一类是not found或者permission denied,优先检查SELinux状态,确认是permissive,然后看containerd的cgroup驱动配置。第二类是container runtime is down,说明kubelet连不上containerd,检查containerd服务状态以及crictl是否能正常通信。第三类是节点上存在旧的集群信息,如果你之前重置过一次,kubelet可能还在用旧的证书尝试连接apiserver,这时候需要完整执行kubeadm reset清理环境再重新初始化。
CNI状态的判断逻辑要清楚。kubectl get nodes看到NotReady,先看节点上的Pod状态,执行kubectl describe node观察Conditions部分输出。CNI没装好时节点状态显示NetworkUnavailable,这是正常现象。装完Calico之后如果还是NotReady,就要检查Calico的Pod是否Running,以及Pod网段和节点所在物理网段是否冲突,冲突会导致Calico的BGP邻居起不来。
5.3 重置环境的标准操作
环境配置阶段的错误往往需要反复重来,掌握标准的重置流程能省掉大量时间。我见过有人直接删除/etc/kubernetes目录、清空containerd里的镜像、手动杀掉所有残留进程,这些都是野路子,容易留下隐性问题,后续每次初始化都出怪毛病。
标准做法是:
kubeadm reset -f这条命令会清理集群配置、清空CNI相关配置、停止kubelet服务。执行完成后再手动清理网络配置残留:
rm -rf /etc/cni/net.d最后清掉containerd里残留的pause容器和系统镜像组,这个看实际情况,如果kubeadm reset执行后仍有残留容器影响初始化,就重新排查。确认干净之后重新执行kubeadm init。
还有一个细节:重置时如果kubelet一直在重启,系统日志会刷屏,可以先停掉kubelet再执行reset。reset完的节点再加入新集群时,token和CA信息都是全新的,不会互相污染。
搞完这篇之后,下一阶段就是安装Calico网络插件、验证Pod网络的跨节点通信,然后做高可用控制面的负载均衡配置。环境准备阶段做扎实了,后面每一步都会顺畅很多。至少我从这套流程里得到的体会是,环境配置阶段最值得花时间的地方是理解每个参数的行为,而不是照抄命令,参数理解透了,后面集群出问题时排查方向就不会乱。