在HoRain云的ECS实例上用CentOS7从零搭建一套可用的K8S集群这件事我前后折腾了整整三天。从环境初始化到集群初始化再到网络插件、节点加入、故障排查每一步都藏着坑。这篇指南我按实际操作的顺序来写把每个命令、每个参数为什么这么设都讲清楚争取让你照着做能一次跑通。先说背景我这边用的是三台HoRain云服务器一主两从系统镜像CentOS 7.6内核3.10。这套配置跑一个学习型K8S集群完全够用生产负载的话建议节点规格往上提。下面所有命令都在干净系统上实测过节点规格和系统版本一致的前提下直接复制基本能跑通。1. 搭建前的核心准备与架构选型思路1.1 版本选型为什么是CentOS7和1.23集群搭建最怕的不是命令敲错而是版本组合在你不知不觉时已经过时或冲突。这次选CentOS7主要看中它的存量资料多遇到问题基本都能搜到成熟解法。CentOS7默认内核是3.10跑K8S没有硬性问题但需要把内核模块和系统参数提前配好后面小节会专门讲。K8S版本这里我推荐1.23.x。原因很实在1.24版本开始容器运行时不再默认使用Docker如果你习惯docker ps这套操作又不打算额外装cri-dockerd选1.23.x能省掉一堆配置纠结。想体验新特性的话1.24到1.28也能装但得提前搞明白containerd和cri-dockerd这套体系不然初始化到一半容易卡住。组件版本严格一致是铁律。kubeadm、kubelet、kubectl三个二进制版本必须完全一样混装会在集群初始化时直接报版本不匹配。我这边统一用的是1.23.17三个组件全部锁到这个版本。1.2 节点规划与资源评估我这次用了三台云服务器一主两从。直接给出一份可参考的规格表节点角色主机名建议配置系统盘内网IP示例Masterk8s-master2核4G50G SSD10.0.0.10Worker1k8s-node12核4G50G SSD10.0.0.11Worker2k8s-node22核4G50G SSD10.0.0.12内存是硬门槛。K8S系统组件加Calico这些基础Pod1G内存根本跑不动2G只是勉强学习用想跑业务应用至少4G起步。磁盘方面系统盘建议给到50G以上镜像和日志增长非常快我实际遇到过磁盘写满导致节点异常的情况。公网IP不是必须的但如果节点都在内网后续想从本地电脑访问Dashboard或应用就得规划端口转发或跳板机。另外安全组至少要放行6443kube-apiserver、10250kubelet和NodePort端口段。我在云平台控制台忘记放行10250导致Worker节点死活注册不上这是后话。1.3 环境初始化每个节点都必须完成的五件事三台节点打好镜像后环境初始化要在每台机器上重复执行。第一步是设置主机名和hosts解析。主机名不能随便起节点注册时用的就是这个名字我统一用k8s-master、k8s-node1、k8s-node2。hosts文件里加上三台机器的内网IP和主机名对应关系避免组件之间通过主机名解析失败。第二步是关闭防火墙和SELinux。很多教程说生产环境不建议关防火墙但对快速搭建来说这一步能省掉大量排查白名单的时间。CentOS7上执行systemctl stop firewalld systemctl disable firewalldSELinux在 /etc/selinux/config 里改成disabled然后重启。内网测试环境直接关掉没问题生产环境建议后续用安全组做精细控制。第三步是关闭swap。K8S默认不支持swap开着swap节点初始化会直接报错。swapoff -a只是临时生效要永久关闭得注释掉 /etc/fstab 里的swap行不然重启后swap又回来了这个坑我踩过。第四步是加载内核模块和调整系统参数。K8S依赖ip_vs、overlay等内核模块还要把桥接的iptables转发打开。标准配置是这样cat EOF /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 vm.swappiness 0 EOF sysctl --system modprobe br_netfilternet.bridge.bridge-nf-call-iptables这个参数如果不开Pod跨节点通信会出现诡异问题例如有的服务通有的不通。ip_forward是转发用的不开的话Pod流量出不去。K8S的Service和Pod网络依赖iptables做DNAT/SNAT而iptables对网桥流量默认不生效所以必须主动开启桥接转发开关。第五件事是配置软件源和时钟同步。云服务器自带源有时候速度波动大我习惯先备份原来的CentOS-Base.repo再替换成国内镜像源。时钟同步依赖chronyK8S组件对时间偏差很敏感时间差超过几百毫秒集群健康检查就会出现异常。环境初始化完成后建议reboot重启一次确保内核参数和SELinux配置都生效。2. Docker运行时安装与配置2.1 Docker的安装和版本锁定K8S本身不直接运行容器它需要一套容器运行时。CentOS7时代最常用的就是Docker。安装前先装yum-utils然后添加Docker官方yum源yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo国内环境访问这个官方源速度波动大但一般还是能装上不行就找发行版自带的Docker源。重点是版本我推荐锁定在20.10.x这个版本和K8S 1.23兼容性最好也是社区验证最多的组合。安装命令yum install -y docker-ce-20.10.21 docker-ce-cli-20.10.21 containerd.iocontainerd.io要一起装Docker的运行依赖它。安装完成后先别急着启动下面还有关键配置要改。2.2 daemon.json里最重要的两个配置Docker装好后必须修改/etc/docker/daemon.json。最关键的是cgroup驱动。K8S官方要求容器运行时的cgroup驱动必须和kubelet保持一致否则kubelet会报failed to run Kubelet这类错误。CentOS7上典型的daemon.json是这样{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m }, storage-driver: overlay2 }exec-opts里的native.cgroupdriversystemd就是关键。Docker默认的cgroup driver是cgroupfs而kubelet初始化时默认也是cgroupfs但实际部署中推荐都用systemd这样在systemd管理的进程树中能保持一致避免资源统计混乱。另外把日志文件限制到100M防止容器日志把磁盘吃满这一步强烈建议加上。很多人忘记重启Docker或没检查后面初始化集群时就莫名报错。改完配置执行systemctl daemon-reload systemctl restart docker然后通过docker info确认输出里的Cgroup Driver: systemd。确认没问题后把Docker设为开机自启systemctl enable --now docker。到这一步运行时基础就打好了。2.3 通过简单容器验证环境配置无误后用最简单的容器验证Docker能否跑起来docker run --rm hello-world如果拉取不到这个测试镜像可以换成已存在的镜像或者用docker pull nginx试试。只要能拉取、能启动、能看到容器状态说明Docker就绪。这一步也是在间接验证daemon.json配置有没有低级错误JSON语法不对的话Docker服务根本起不来。补充一点Docker 20.10版本默认使用的containerd组件后续排查镜像问题时可能会用到 crictl 命令。crictl需要单独安装是K8S社区推荐的容器运行时命令行工具提前装好后面排查会方便很多。3. K8S核心组件安装与集群初始化3.1 添加K8S软件源并锁定版本接下来进入正题。三台机器上都要安装kubeadm、kubelet和kubectl。这三个组件通过YUM安装时需要先配置K8S软件源cat EOF /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck0 EOF yum clean all yum makecache用国内镜像源的原因很简单默认的软件源地址在本地环境基本不可达镜像源能让你少等很长时间。gpgcheck设为0省去导入GPG密钥的步骤测试环境完全够用。安装时直接指定版本号而不是装latestyum install -y kubelet-1.23.17 kubeadm-1.23.17 kubectl-1.23.17为了防手滑升级可以在/etc/yum.conf里加上excludekubelet kubeadm kubectl或者每次yum update时加 --exclude 参数。不加的话某次yum update就可能把集群组件版本搞乱这是非常现实的教训。装完后用kubeadm version确认输出里的版本信息应该显示v1.23.17。3.2 初始化前的镜像准备kubeadm init时会拉取一系列镜像包括apiserver、controller-manager、scheduler、etcd、coredns等。默认情况下这些镜像来自k8s.gcr.io或registry.k8s.io国内环境拉取很容易超时。解决办法是更换镜像仓库。先查看当前版本需要哪些镜像kubeadm config images list --kubernetes-version v1.23.17然后在init命令里用--image-repository参数指定阿里云容器镜像服务地址具体是registry.aliyuncs.com/google_containers。这个镜像仓库覆盖了K8S核心组件的镜像实测速度和可靠性都不错。也可以提前把镜像拉取到本地kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers这一步能全部成功的话后续init基本不会卡在镜像阶段。我当时就是在init过程中卡了十几分钟手动拉取才发现是网络问题。3.3 用kubeadm init初始化Master节点准备工作做完开始真正初始化Master。这里给出实测可用的完整命令kubeadm init \ --apiserver-advertise-address10.0.0.10 \ --image-repository registry.aliyuncs.com/google_containers \ --kubernetes-version v1.23.17 \ --pod-network-cidr10.244.0.0/16参数拆开讲一下。--apiserver-advertise-address是Master的内网IPapiserver会监听这个地址对外提供服务。如果你写的是公网IP或写错网卡IP后续节点加入会把地址搞乱。--pod-network-cidr是Pod网段10.244.0.0/16是Flannel插件的默认网段选Calico的话也可以用这个段但必须与后面安装的CNI插件配置保持一致这是无数人踩过的坑。初始化成功后会输出一大段信息包括控制面初始化完成的提示、配置kubectl的命令、以及一条kubeadm join命令。这段输出很重要丢了就麻烦了建议直接复制保存到本地文件。然后配置kubectl的管理员权限mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config执行kubectl get nodes能看到Master节点状态是NotReady。这是正常的网络插件还没装。3.4 Worker节点加入集群Worker节点上装好kubeadm等组件后执行初始化输出里的kubeadm join命令kubeadm join 10.0.0.10:6443 --token xxxxx.xxxxxxxxxxxx \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxx如果token过期了在Master上重新生成一条join命令kubeadm token create --print-join-commandtoken有效期默认24小时隔天再添加节点大概率过期重新生成即可。Worker节点加入后kubectl get nodes能看到三台节点但状态都是NotReady直到CNI网络插件运行起来。3.5 安装Calico网络插件网络插件决定Pod之间如何通信。我选的是Calico相比FlannelCalico支持NetworkPolicy网络策略性能也更好生产环境用得更多。安装过程kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.25/manifests/calico.yaml要注意Calico的yaml中默认的IP池网段是192.168.0.0/16如果前面init时用的Pod网段不是这个需要修改calico.yaml里的CALICO_IPV4POOL_CIDR环境变量。最省事的方案是把init时的--pod-network-cidr也设成192.168.0.0/16这样Calico默认配置就能直接用。为了和Flannel兼容而用10.244.0.0/16的话那就得改yaml。无论哪种方式保证两边CIDR一致是铁律。apply完成后watch kubectl get pods -n kube-system等所有Pod变成Running。calico相关镜像比较大首次拉取加上创建时间可能需要几分钟。全部Running后kubectl get nodes三台节点应该都变成Ready。到这里一个最基础的1主2从K8S集群就已经可用了。4. 集群高可用与故障转移实践4.1 单Master集群的边界与生产环境要求上面搭起来的单Master集群用来学习和验证完全没问题但如果是生产场景单Master存在单点故障风险。Master挂了意味着apiserver、controller-manager、scheduler全部不可用整个集群控制面瘫痪。Worker节点上已有容器还能继续跑但没人调度、没人管理Deployment异常也就没人修复。所以生产环境至少三台Master同时配合负载均衡器把请求分散到多个apiserver上。这是K8S高可用架构里最常见的一类方案也是面试里经常被问到的三台Master怎么保证高可用。4.2 三Master高可用的标准架构生产级高可用方案通常分两层。第一层是控制面组件多副本每台Master上都有独立的kube-apiserver、kube-controller-manager和kube-scheduler进程controller-manager和scheduler通过选主机制保证同一时刻只有一个leader在干活。第二层是接入层的负载均衡在Master前面架一台负载均衡器常见的软件方案是HAProxy配合keepalived做VIP漂移也可以用云平台提供的负载均衡服务对外暴露一个虚拟IP或域名所有kubeadm join和kubectl请求都指向这个虚拟地址。etcd这里也要规划。堆叠模式下etcd跟着Master走三台Master上各跑一个etcd实例数据自动同步条件允许的话也可以把etcd单独部署在三台独立机器上控制面和存储互不影响。堆叠模式部署简单单机房场景够用独立部署更适合跨机房容灾。搭建多Master集群时kubeadm init后还需要用kubeadm join --control-plane把其他Master加入控制面这一步比Worker加入多一个etcd和证书处理参数上要指定 --control-plane。整个流程对操作顺序要求很高建议先在一台Master上init通过再把另外两台Master以控制面方式加入最后再添加Worker。4.3 证书续签一个容易被忽视的故障点K8S控制面证书默认有效期是一年。很多集群跑了大半年后突然apiserver报证书过期、kubectl连不上根源就是这个。kubeadm管理的集群用下面命令在Master节点上手动续签所有证书kubeadm certs renew all续签完成后需要重启kube-apiserver、kube-controller-manager、kube-scheduler这些静态Pod组件。最简单的办法是重启节点或者用systemctl restart kubelet让kubelet重新拉起静态Pod。实际操作中我更建议把这个做成自动任务。每台Master上写一个定时任务30 3 * * * /usr/bin/kubeadm certs renew all /var/log/kube-cert-renew.log 21 systemctl restart kubelet每天早上3点半自动续签一次。但要注意这个任务只处理Master上的现有证书如果Master节点数量有变化或者续签后组件没有正常重启集群可能出现健康检查异常。所以高可用环境里证书续签一定要和监控报警联动而不是只靠定时任务盲跑。理论上证书续签对集群影响很小但实际执行时务必观察节点状态。5. 常见问题排查与避坑实录5.1 镜像拉取失败的几种处理方式初始化或运行中拉取镜像失败是最常见的问题。现象一般就是Pod一直ContainerCreating事件提示Failed to pull image。如果是因为默认镜像仓库网络不通解决办法已经讲过用--image-repository换国内镜像仓库。如果是运行中的某个应用镜像拉不到先确认镜像tag是否存在、是否私有仓库需要认证。还有一种情况是磁盘空间不足导致镜像层写入失败用docker system df或df -h一看便知。K8S 1.23环境里还可以用crictl images直接查看节点上的容器镜像比docker images的维度更贴近K8S视角。遇到部署缓慢先用crictl pull测试能不能拉下来能拉下来再排查yaml问题。5.2 kubelet起不来先看journalctl很多节点加入失败表面看是join命令报错实际上问题出在kubelet。K8S的报错信息经常是笼统的error execution phase preflight真正的细节藏在kubelet日志里。排查命令journalctl -xeu kubelet -f常见原因有swap没关彻底、cgroup驱动不一致、kubelet没配systemd驱动。swap的问题前面已经说了这里重点说cgroup驱动不一致。如果daemon.json里写的是systemd但kubelet启动时用的还是cgroupfs两者就会打架。解决办法是在/etc/sysconfig/kubelet里加上KUBELET_EXTRA_ARGS--cgroup-driversystemd然后重启kubelet。还有一类问题kubelet无法访问apiserver。报错通常是connection refused或timeout。检查节点的hosts解析是否正确以及安全组是否放行了6443端口。我在实际环境里遇到过节点注册时用主机名解析到了错误IP导致join阶段校验失败改完hosts马上就好。5.3 CoreDNS一直CrashLoopBackOff集群初始化完成后kube-system里的CoreDNS状态是CrashLoopBackOff。多数情况下这是在网络插件装好之前CoreDNS无法运行导致的等Calico或Flannel Pod跑起来后会自动恢复。如果网络插件已经就绪还是Crash看日志kubectl logs -n kube-system -l k8s-appkube-dns --tail50日志里如果出现no servers found这类DNS解析失败的错误通常是Pod网段的DNS配置没正确写入 /etc/resolv.conf。还有一种少见但存在的场景宿主机本身的resolv.conf有问题或者CoreDNS Pod被调度到了某个网络不通的节点。排查时可以kubectl describe pod -n kube-system -l k8s-appkube-dns看事件。5.4 节点一直NotReady怎么查节点NotReady我按这个顺序排查先kubectl describe node xxxx看Conditions里有没有提示再kubectl get pods -n kube-system看网络插件Pod是否Running然后上到节点机器看kubelet日志最后检查网络模块和路由表。Calico Pod反复重启是典型问题原因经常是Pod网段和Calico配置不一致或者kubelet的cgroup驱动配置有问题。还有一个容易被忽略的坑节点的内网IP地址变了。云主机如果被迁移或网卡重建内网IP变化会导致kubelet注册信息过期节点状态变成NotReady或直接消失。这种情况要把旧的kubelet证书和kubeconfig删掉重新join或重启kubelet。5.5 常见问题速查表整理一张速查表遇到问题先对照一遍现象常见原因处理思路kubeadm init卡在Pulling镜像仓库访问慢使用国内镜像仓库拉取kubelet报cgroup driverDocker与kubelet驱动不一致统一为systemd并重启节点NotReadyCNI未起或Pod网段不一致检查calico/flannel状态CoreDNS CrashLoop网络插件未就绪或DNS配置错等待CNI或修复resolv.confjoin时token过期token 24小时有效kubeadm token create重新生成6443连接超时安全组未放行或hosts错在云控制台放行6443/10250Pod长时间Pending资源不足或污点未容忍describe pod查看调度事件证书过期一年有效期kubeadm certs renew all6. 集群验证与后续扩展思路6.1 用Deployment快速验证集群集群Ready只是开始建议立刻部署一个简单的应用验证整个链路的可用性。我用Nginx做冒烟测试kubectl create deployment nginx --imagenginx kubectl expose deployment nginx --port80 --typeNodePort kubectl get svckubectl get svc会看到Service的NodePort端口比如80:31000/TCP。这时候在任意Worker节点上执行curl localhost:31000能返回Nginx欢迎页说明集群的网络链路、调度、kube-proxy转发都正常。再验证一下副本调度和自愈能力kubectl scale deployment nginx --replicas3 kubectl get pods -o wide把其中一个Pod删掉kubectl delete pod nginx-xxxxx几秒钟后控制器会重新拉起一个这就是K8S的自愈能力。这一步验证通过集群基本就达到可用标准了。6.2 生产化方向监控、日志、存储与Ingress能用的K8S集群和能上生产的K8S平台差距主要在配套组件上。监控优先做用Prometheus Operator加Grafana是最主流的方案能覆盖Node、Pod、容器的指标采集。第一次做建议先装kube-prometheus整体方案自带告警规则比从零拼装省太多时间。日志方面轻量选择是Loki重量级选择是EFK。Loki资源占用小很多更适合小集群EFK在日志检索和大规模场景下更成熟。存储这块如果要在集群里跑数据库或有状态应用建议上Rook-Ceph。Rook可以在K8S集群内部直接编排Ceph存储集群部署流程稍长但跑起来后StatefulSet的持久化存储问题就彻底解决了。最后是Ingress Controller。默认的Service类型是ClusterIP和NodePortNodePort端口一般很大不适合直接对外暴露。装一个nginx-ingress-controller后可以用域名和路径做路由再配合cert-manager自动管理证书对外发布服务方便得多。6.3 维护集群的常用命令清单日常维护高频命令建议收藏kubectl get nodes # 查看节点状态 kubectl get pods -A # 查看所有namespace的Pod kubectl logs -f pod_name -n namespace # 跟踪日志 kubectl describe pod pod_name # 定位问题核心 kubectl get svc -A # 查看Service kubectl top nodes # 查看节点资源使用率这一套组合下来日常巡检和问题定位基本够用。metrics-server没有预装可以应用官方yaml安装装完才能看到kubectl top输出。最后说一下这轮搭建下来最深的几个体会。第一版本组合一旦定下来就不要轻易改kubeadm、kubelet、kubectl、Docker、Calico这些版本是相互约束的升级任何一个都要全盘评估。第二网络插件一定要在init之前想好是Flannel还是Calico再去定Pod网段省得后面改来改去。第三所有初始化输出都要存好尤其是join命令和token丢失的代价比重新安装还大。我就是因为没保存好token第二天重新生成了一次虽然流程很快但生产环境里这就是一次事故隐患。提前把这些点处理好整个流程会顺畅很多。