
1. 准备工作一主两从集群的前置环境规划1.1 为什么选择 kubeadm而不是二进制或 minikube搭建 Kubernetes 集群目前主流的方案就那么几种kubeadm、二进制部署、minikube、kind 等。如果你在三台机器上搭正式一点的测试环境或者准备上生产我建议直接选 kubeadm。为什么二进制部署虽然让你对每个组件都了如指掌但手动生成证书、配置 kubelet、编排 etcd 的步骤太多一次配置错一个参数排查几个钟头是常事。minikube 和 kind 更适合单机学习跑在虚拟机或 Docker 里做不了真正意义上的“一主两从”跨节点集群。kubeadm 则把控制平面组件容器化证书、kubeconfig、etcd 这些基础工作全部自动化同时保留了对部署细节的控制权踩坑少、可复现程度高也是社区目前最主流的初始化方式。1.2 节点规划与硬件建议一主两从拓扑上就是一个 master 节点加两个 worker 节点。这篇文章里我用的节点信息方便你直接对照节点角色主机名配置建议操作系统masterk8s-master2C4G 起磁盘 50GCentOS 7.9 或 Ubuntu 22.04worker1k8s-node12C4G 起磁盘 50GCentOS 7.9 或 Ubuntu 22.04worker2k8s-node22C4G 起磁盘 50GCentOS 7.9 或 Ubuntu 22.04这里多说一句如果你条件充裕master 建议 4C8G毕竟 etcd、kube-apiserver、kube-controller-manager 都压在 master 上资源吃紧时第一个崩的往往是 etcd。磁盘方面别用机械盘凑合SSD 和 NVMe 带来的 etcd 写入性能差异非常明显生产环境这块不能省。1.3 系统初始化三台机器统一配置拿到三台全新的服务器之后需要先把系统层面的公共配置拉齐。这一步不做好后面装组件全是坑。首先修改主机名然后配置 hosts 解析保证三台机器能互相通过主机名访问。# 每台节点分别设置自己的主机名 hostnamectl set-hostname k8s-master # 或者 k8s-node1、k8s-node2对应你自己的规划 # 三台机器都编辑 /etc/hosts加入以下内容 cat /etc/hosts EOF 192.168.1.10 k8s-master 192.168.1.11 k8s-node1 192.168.1.12 k8s-node2 EOF然后关闭防火墙和 swap。很多人不理解为什么要关防火墙其实不是安全洁癖问题而是 Kubernetes 的 kube-proxy 使用 iptables 或 IPVS 转发流量firewalld 有自己的一套 iptables 管理逻辑两者同时操作很容易出现规则互相覆盖、节点间通信时通时不通的诡异现象。测试环境直接关掉省心生产环境如果需要防火墙必须放行 6443、2379、2380、10250、30000-32767 等端口段。# 关闭防火墙 systemctl stop firewalld systemctl disable firewalld # 关闭 swap swapoff -a sed -i / swap / s/^\(.*\)$/#\1/g /etc/fstab # 加载内核模块 cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter关键的内核参数也要调整一下让 iptables 能正确查看桥接流量这个不配置的话Kubernetes 的 Service 转发会出问题最典型的症状就是 Pod 内访问 Service ClusterIP 超时。cat EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables 1 net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 vm.swappiness 0 EOF sysctl --system2. 容器运行时安装从 Docker 到 containerd 的选择与配置2.1 为什么不直接用 Docker这里要回答一个很多新手会困惑的点Kubernetes 1.24 之后已经移除了 dockershim也就是说 kubelet 不再直接支持 Docker 作为运行时了。当然你可以装 cri-dockerd 来过渡但这属于额外多维护一层适配器没什么必要。现在的标准做法是直接上 containerd。containerd 本身就是 Docker 底层的那套容器运行时精简、稳定而且 Kubernetes 原生通过 CRI 与其对接少了一层中间代理性能表现也更好。我这里直接选择 containerd 1.7 系列稳定性和兼容性都足够。2.2 containerd 安装与配置全过程如果是 CentOS 7 体系先装 yum-utils 和 yum-config-manager 工具然后从官方仓库拉取。Ubuntu 22.04 则是用 apt 的方式逻辑一样这里以 CentOS 为例yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y containerd.io装完 containerd 之后核心任务是生成默认配置并修改两个关键参数。第一步生成默认配置mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml然后编辑/etc/containerd/config.toml。最关键的是 SystemdCgroup 这个参数默认值为 false必须改成 true否则容器内的进程无法被 kubelet 正确管理Pod 会出现各种资源统计异常甚至部分容器启动后直接被杀掉。另一个是 sandbox_image国内环境默认的registry.k8s.io/pause:3.x拉不下来需要改成阿里云或你本地镜像仓库的地址。[plugins.io.containerd.grpc.v1.cri] sandbox_image registry.aliyuncs.com/google_containers/pause:3.9 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true改完之后重启并设置开机自启验证 containerd 状态正常systemctl restart containerd systemctl enable containerd systemctl status containerd ctr version这里补充一个验证技巧用ctr命令是可以直接和 containerd 交互的但它不识别 CRI 体系下的 Pod 概念。真正模拟 kubelet 拉镜像建议用crictl这个工具后续排查问题会频繁使用。安装方式是把二进制下载到/usr/local/bin然后配置crictl config --set runtime-endpointunix:///run/containerd/containerd.sock --set image-endpointunix:///run/containerd/containerd.sock。2.3 镜像加速与离线场景的预拉取如果你所在网络环境拉取 Docker Hub 镜像很慢或者直接连不上registry.k8s.io我强烈建议你提前把后续 kubeadm 初始化会用到的镜像在本地拉好。kubeadm 本身提供了一个命令来查看和拉取初始化镜像kubeadm config images list kubeadm config images pull不过kubeadm config images pull默认使用的源还是registry.k8s.io如果你已经准备挂代理或者镜像加速可以在初始化时用--image-repository参数指定镜像仓库指向你本地加速地址或者阿里云的镜像源kubeadm init --image-repositoryregistry.aliyuncs.com/google_containers这会在初始化阶段自动从指定仓库拉取所有控制平面组件镜像省去手动一个个 pull 再 retag 的繁琐操作。对于离线环境则建议在有网机器上提前拉好镜像然后docker save/ctr images export打包上传到离线节点再 import 进去。3. kubeadm、kubelet、kubectl 组件安装与版本锁定3.1 安装方式和版本选择逻辑这三件套是 Kubernetes 集群的核心工具kubeadm 负责初始化和管理集群生命周期kubelet 是每个节点上最核心的 Agent 组件负责与容器运行时交互并执行 Pod 的生命周期管理kubectl 是和集群交互的客户端命令行工具。三个组件的版本必须严格一致否则初始化阶段就会报版本不匹配的错误。在版本选择上我用的是 1.28.x 这个版本线。为什么不追最新Kubernetes 社区发布节奏一年三个大版本最新的版本往往引入很多新特性但相应的生态组件比如网络插件、Ingress Controller 的兼容性可能还没完全跟上。1.28 属于当时相对成熟且还在维护期内的版本etcd 版本、CoreDNS 版本、CNI 插件都是经过大量真实验证过的组合。你现在搭集群一定先确认清楚要用哪些上层组件有没有兼容版本要求再反推 Kubernetes 版本而不是先装了再说。3.2 安装操作与版本锁定以 CentOS 7 为例最标准的做法是使用阿里云镜像仓库cat EOF | tee /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck1 repo_gpgcheck1 gpgkeyhttps://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF yum install -y kubeadm-1.28.2-0 kubelet-1.28.2-0 kubectl-1.28.2-0注意我直接指定了完整的版本号这是避免“装了个最新版改天节点扩容又装了个别的版本版本漂移导致集群混乱”的关键操作。然后设置 kubelet 开机自启但先不要启动它因为此时 kubelet 还没有任何配置启动也会不断报错重启。systemctl enable kubelet这里有个细节kubelet 的配置文件路径是/etc/kubernetes/kubelet.conf这个文件是 kubeadm init 之后才生成的。如果你手痒提前启动 kubelet它会找不到配置而反复崩溃属于正常现象不必慌张。3.3 版本校验与命令确认装完后做一个基本校验确认三件套版本一致kubeadm version kubelet --version kubectl version --client三个命令输出的版本号必须一致。如果不一致在 yum 安装时就要指定对齐版本号。我遇到过不少人kubeadm 装的 1.28kubectl 因为之前装过别的版本结果 client 和 server 版本差太多导致kubectl get nodes时 API 兼容层报错白白排查了半天。所以这个习惯要养成所有客户端组件和 server 版本保持同步。现在三台节点的运行时和工具都就绪了可以进行 master 节点的初始化。4. master 节点初始化kubeadm init 最优参数组合4.1 初始化命令的核心参数解读到这一步我们正式推进集群的创建。在 master 节点执行 kubeadm init但这不是一条命令跑完就完事的问题参数必须根据你的环境做调整。一个典型的初始化命令如下kubeadm init \ --image-repositoryregistry.aliyuncs.com/google_containers \ --kubernetes-versionv1.28.2 \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/16 \ --apiserver-advertise-address192.168.1.10 \ --control-plane-endpointk8s-master各个参数的含义你需要完全清楚。--image-repository前面说了是加速源--kubernetes-version要和 kubeadm 版本一致--pod-network-cidr是 Pod 网段10.244.0.0/16 这个具体地址要和后面装的网络插件匹配--service-cidr是 Service 网段一般保留默认的 10.96.0.0/16 即可不用改。--apiserver-advertise-address就是 master 节点的内网 IP其他的节点要通过这个 IP 来访问 API Server。关于 Pod 网段的选择一个经验性的考量是10.244.0.0/16 是 Flannel 默认插件体系里使用最广泛的网段几乎所有的 Flannel 配置示例都默认这个值这意味着很少需要为它做额外适配。如果你用 Calico通常默认是 192.168.0.0/16。所以这一步的选值不是随机的它取决于你后面到底用哪个 CNI 插件先定插件再定网段比较稳妥。4.2 初始化过程的完整输出解读初始化过程会持续 1-3 分钟如果网络状况不理想可能更久。这段时间 kubeadm 会依次完成如下工作先执行 preflight 检查校验系统配置、端口占用、swap 状态等。然后生成 CA 证书、各组件证书、ServiceAccount 密钥。接着把 kube-apiserver、kube-controller-manager、kube-scheduler 以静态 Pod 的方式写入/etc/kubernetes/manifestskubelet 检测到该目录下的 yaml 后会自动启动这些容器。最后为 kubeconfig 生成 admin.conf、controller-manager.conf、scheduler.conf 等文件。初始化成功后末尾会打印三段非常重要的信息一段是让你执行的 kubeconfig 配置命令一段是 join 命令还有一段是让你安装 CNI 插件的提示。这段输出建议直接复制保存到本地文件因为 join 命令里包含了 token 和证书哈希后续找不回来很麻烦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。这并不是异常因为还没有安装 CNI 网络插件节点网络没有就绪。接下来最关键的一步就是把网络打通。4.3 kubectl 自动补全和效率配置磨刀不误砍柴工在继续之前我把 kubectl 的命令补全和别名配置好后续操作体验会提升很多。echo source (kubectl completion bash) ~/.bashrc echo alias kkubectl ~/.bashrc source ~/.bashrcalias kkubectl这个习惯我强烈推荐每次少敲几个字母积少成多效率提升非常明显。5. worker 节点加入集群join 的机制与后续验证5.1 join 命令的使用与原理现在到两个 worker 节点上执行 master 初始化完成后输出的 join 命令。如果你当时没有保存输出可以通过下面的命令重新生成 tokenkubeadm token create --print-join-command在 worker 节点上执行 join 时kubelet 会先通过 token 向 API Server 发起 bootstrap 请求使用 TLS bootstrap 机制自动请求一个客户端证书用于后续通信。这个过程是自动完成的无需手工干预。join 命令长这样kubeadm join 192.168.1.10:6443 --token abc123.abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxx注意 join 命令要在每台 worker 上都执行一遍。执行成功后回到 master 上运行kubectl get nodes就能看到三个节点。刚开始节点状态是 NotReady等 CNI 装好并初始化完成后会转为 Ready。5.2 节点注册与资源信息确认节点加入成功后先用 kubectl 检查节点状态和角色标签。如果一切正常master 上的输出会是这样kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP k8s-master Ready control-plane 6m v1.28.2 192.168.1.10 k8s-node1 Ready none 88s v1.28.2 192.168.1.11 k8s-node2 Ready none 69s v1.28.2 192.168.1.12通过-o wide可以看到节点的内部 IP、内核版本、容器运行时等额外信息。确认三个节点全部 Ready 后一主两从的控制面已经完成了。但这里要提醒你集群就绪不等于可以稳定运行业务CNI 网络插件没装Pod 之间、节点之间是无法通信的所以下一阶段的核心任务是把网络层搞定。6. 网络插件选型与安装Flannel 与 Calico 的实战对比6.1 为什么网络插件是集群必不可少的一环Kubernetes 网络模型要求所有 Pod 可以直接通过 IP 互相访问无论它们在哪个节点上且不需要 NAT。这个能力由 CNI 插件提供。没有 CNI 插件kubelet 无法为 Pod 分配集群网络地址Pod 会一直停留在 ContainerCreating 状态CoreDNS 也起不来集群基本是“半瘫痪”状态。CNI 插件选 Flannel 还是 Calico主要看使用场景。Flannel 胜在轻量、配置简单Underlay 的网络模型在中小规模集群里非常稳定性能也足够适合测试环境和业务简单的场景。Calico 功能更丰富支持 NetworkPolicy、BGP 路由模式性能在大规模集群下表现更好但配置复杂度也上了一个档次。对于一主两从这样一个三节点规模的学习环境选 Flannel 能让你更快把集群跑起来把精力聚焦在 Kubernetes 本身的工作机制上而不是先被网络策略的配置绕晕。这篇文章先以 Flannel 为例把网络打通。6.2 Flannel 的完整安装流程Flannel 的安装方式是一条 kubectl apply 命令它会创建 DaemonSet在每个节点上运行一个 flanneld 进程。先下载官方 manifestwget https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml由于网络原因这个文件在你的环境可能拉取不到。你可以从其他镜像源获取或者直接编辑已有的 kube-flannel.yml 文件把 image 地址替换成docker.io/flannel/flannel相关的可用镜像。核心注意点是kube-flannel.yml中 ConfigMap 里的Network参数必须和 kubeadm init 时指定的--pod-network-cidr保持一致。如果你初始化时用了 10.244.0.0/16这里就不用改。kubectl apply -f kube-flannel.yml安装完成后需要观察 flannel Pod 是否正常运行kubectl get pods -n kube-flannel -o wide kubectl get nodes正常情况下三台节点的 STATUS 会在几十秒内变为 Ready。如果始终 NotReady大概率是 flannel 的镜像拉取问题或网段配置不一致。可以把 flannel Pod 的日志拉出来看kubectl logs -n kube-flannel pod-name确认报错原因是无法连接 API Server、镜像拉取超时还是网段冲突。这里我再强调一次网段必须对齐这是网络插件排障第一位的检查项。6.3 Calico 的安装差异与选型建议如果你决定用在生产环境我建议直接上 Calico。安装 Calico 的核心是操作一套 operator 和 CRDkubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yamlCalico 默认的 Pod 网段是 192.168.0.0/16所以如果你在 kubeadm init 时用的是 10.244.0.0/16就必须编辑 calico.yaml 文件中 CALICO_IPV4POOL_CIDR 这个环境变量的值把它改成你初始化时设置的网段。修改配置后 apply随后观察 calico-system 命名空间下的 Pod 状态。Calico 还支持通过 IPIP 和 VXLAN 两种跨节点封装模式默认使用 IPIP。在小规模集群里两者差异不大但如果你在公有云环境搭集群IaaS 底层的网络限制可能会影响 IPIP 的通信这种情况下切换成 VXLAN 更稳。这个切换操作涉及修改 calico.yaml 中的 IP_AUTODETECTION_METHOD 和 IPIP 相关配置需谨慎处理。7. 集群功能验证从 Pod 到 Service 的完整链路测试7.1 给 Master 节点打上可调度标签默认情况下控制平面节点不参与业务 Pod 调度这是为了保障集群管理组件的稳定性。但在一主两从这种测试环境里master 资源可能比较空闲你可以让 master 也跑一些 Pod发挥资源价值kubectl taint nodes k8s-master node-role.kubernetes.io/control-plane:NoSchedule-这个命令在 master 上执行后master 节点将允许被调度普通业务 Pod。注意生产环境一定不要这样做。去掉污点后整个集群三个节点都是可调度状态。7.2 部署测试应用验证集群功能接下来部署一个最简单的 Nginx 测试应用验证 Pod 能够正常调度、跨节点访问能够互通kubectl create deployment nginx-test --imagenginx:1.25 kubectl expose deployment nginx-test --port80 --target-port80 --typeNodePort等待 Pod 运行起来后检查状态kubectl get pods -o wide你会发现 Pod 分布在某个节点上接下来访问 Nginx 服务。以 NodePort 方式暴露的服务会分配一个 30000-32767 范围内的端口可通过集群内任意节点的 IP 加这个端口访问。用kubectl get svc查看具体端口然后浏览器或 curl 访问http://192.168.1.10:30080如果能看到 Nginx 的欢迎页说明集群的核心链路已经通了。7.3 CoreDNS 与服务发现验证很多初学者忽略了 CoreDNS 的验证。CoreDNS 是集群内部域名解析的核心组件部署在 kube-system 命名空间通常有 2 个副本由 Deployment 管理。检查方式kubectl get pods -n kube-system -o wide kubectl exec -it nginx-test-xxxxx -- sh nslookup kubernetes.default.svc.cluster.local一个完整的服务发现链路是Pod 内通过 DNS 解析 Service 名称获取 ClusterIP然后访问对应服务。在 Nginx Pod 里执行nslookup kubernetes.default能正常返回解析结果说明 CoreDNS 工作正常。如果解析失败重点排查 CoreDNS Pod 是否能正常访问 API Server以及 CoreDNS 是否收到了来自节点的 DNS 请求。常见的坑是 kubelet 的--cluster-dns参数配置如果初始化指定了特殊的 service-cidr这里的 DNS 地址要与 kubeadm 自动生成的 10.96.0.10 保持一致。8. 节点与集群常见问题排查记录8.1 Pod 一直处于 Pending 状态这是新手遇到最多的问题核心原因一般有两个一是节点资源不足调度器无法满足 Pod 的资源请求二是存在污点Pod 没有对应的容忍度。排查方法非常直白kubectl describe pod pod-name在输出的 Events 里调度器会明确告诉你没有可用节点满足调度条件。常见提示包括0/3 nodes are available后面跟的具体原因。有一种情况博主经常遇到某个 Pod 请求了 4 核 CPU但三个节点单核数都不到 4 核自然调度不上去。检查一下资源请求是否合理或者节点上是否还有足够的可分配资源。另外如果你的节点一直处于 NotReady 状态Pod 也会 Pending。这时候先看节点状态再用journalctl -u kubelet -f查看 kubelet 日志通常能定位到是网络插件问题、磁盘空间耗尽问题还是容器运行时通信异常。磁盘空间占满是最容易被忽略的一个/var/lib/containerd 所在分区空间不足时kubelet 无法正常创建容器节点会持续报错。8.2 节点 NotReady 的常规排查流程节点 NotReady 是在一主两从集群搭建过程中最容易出现的故障也是所有 Kubernetes 从业者必须掌握的排查基本功。排查流程我总结为一条固定的操作路径第一步在 master 上用 kubectl 查看节点详情确定问题从哪个阶段开始kubectl describe node k8s-node1重点看 Conditions 区域NotReady 对应的为 False 就表示节点失联本质是 Kubelet 和 API Server 之间的心跳NodeLease续约失败。接下来在问题节点上检查 kubelet 状态systemctl status kubelet journalctl -u kubelet -f --no-pager -n 200kubelet 日志会输出具体原因。最常见的几个原因是容器运行时 socket 连不上、CNI 插件安装失败导致 Pod sandbox 无法创建、节点上的网络层不通导致无法访问 API Server。针对容器运行时连通性可以手动执行crictl info来确认 containerd API 是否正常响应。如果 containerd 都挂了kubelet 自然无法工作。8.3 镜像拉取失败的解决思路在国内环境部署 Kubernetes镜像拉取失败基本是绕不开的问题。三个典型症状kubeadm init 卡在 Pulling 阶段、Pod 一直 ImagePullBackOff、静态 Pod 反复重启。解决方案核心就一条确保 kubelet 和 containerd 拉取镜像时能访问到可用的镜像仓库。kubeadm 初始化时通过--image-repository指定仓库containerd 则通过/etc/containerd/config.toml中的 sandbox_image 配置。对于 Kubernetes 生态组件如 metrics-server、ingress-nginx这些 helm 安装的组件一般都有自己的默认仓库安装前先检查 values 里的 image 配置把仓库地址替换成你本地能访问的镜像源。还有一种情况是 Pod 里配置了私有镜像仓库但没配置 imagePullSecret拉取私有镜像时会报 unauthorized这种需要在 Pod 所在命名空间创建 Secret并引用到 Deployment 的 imagePullSecrets 字段。8.4 token 过期与证书续期问题kubeadm 生成的 bootstrap token 默认有效期为 24 小时如果你隔了一天再想加入新节点就得重新生成kubeadm token create --print-join-command另外注意join 命令里的--discovery-token-ca-cert-hash是对集群 CA 证书的哈希校验值每次初始化时这里是固定不变的用上面的命令重新生成的 join 命令里会自动带上正确的哈希值所以你直接复制命令用即可。集群证书默认有效期是 1 年到期后各组件会陆续报证书过期错误。如果你长期使用集群注意提前规划证书更新。kubeadm 提供了一个非常实用的命令kubeadm certs check-expiration kubeadm certs renew all在维护期需要重启各控制平面组件来加载新证书。另外有一个比较常见的坑是如果你的集群搭建时用了多个 master 节点高可用所有 master 的证书都需要逐一更新漏掉一个会导致该节点无法连上集群所以建好集群后证书过期时间要记在运维日程表上。8.5 问题排查命令速查表将上面提到的排查手段汇总成一个速查表方便你遇到问题时快速定位场景核心命令排查重点节点 NotReadyjournalctl -u kubelet -fkubelet 报错内容、网络连通性Pod Pendingkubectl describe podEvents 中的调度失败原因Pod CrashLoopBackOffkubectl logs业务进程日志、启动参数镜像拉取失败crictl pull / crictl images镜像仓库网络、tag 是否正确Service 访问不通kubectl get endpointsEndpoints 是否有可用后端DNS 解析失败kubectl exec -it pod -- nslookupCoreDNS Pod 状态、kubelet 配置9. 集群账单与资源观察搭建完的第一件事集群搭建完第一件事不是急着部署业务而是确认好集群本身的资源使用情况这能帮你建立对集群运行预期的基础认知。kubectl top nodes kubectl top pods --all-namespaces如果报错说 metrics API 不可用是因为缺少 metrics-server 组件。这个组件是查看集群资源使用情况的必备工具安装方式如下kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml多数情况下组件会有一个 TLS 证书校验的问题kubelet 自签的证书和 metrics-server 默认证书校验不匹配。解决方式是在 Deployment 中给 metrics-server 添加--kubelet-insecure-tls参数或者提供正确的证书配置。修改之后通过kubectl top nodes能正常输出三个节点的 CPU 和内存使用率。这时候你可以观察到一主两从场景下控制平面的 etcd 和 kube-apiserver 会占掉 master 节点不少内存所以我在开头才强调 master 的最低配置要 4G 起步否则打几个测试应用就开始内存吃紧、节点反复 NotReady。掌握上面的排查思路后基本上主从集群的日常运维都能应对了。集群搭建好不是终点它是你后续学习容器编排、故障演练、应用发布的基础设施。我个人这几年的体感是Kubernetes 的报错信息通常都很直白绝大多数问题都能通过kubectl describe、kubectl logs、journalctl -u kubelet这三板斧定位出来。你只要把集群的组件角色和通信链路理清楚一线故障大多能在几分钟内找到方向。另外实操中一定要养成记录命令和输出的习惯尤其是 kubeadm init 和 join 这类一次性命令把输出留存好后面扩容、更新、排查都能省下大把时间。这篇文章里涉及的配置命令和排查路径都是我实际搭集群时一一验证过的照着操作一遍你也能拥有一套干净可用的一主两从环境。