ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

K8s Master节点配置原理与故障排查实战

K8s Master节点配置原理与故障排查实战

1. 这不是“装系统”,而是给K8s集群安上大脑——为什么master节点配置是入门绕不开的第一道硬坎

很多人点开“K8s入门”教程,前几节还在兴奋地敲kubectl get nodes、看pod飘起来,一到“配置master节点”这节就卡住,甚至直接放弃。不是因为命令多难记,而是根本没搞清:你正在搭建的不是一个服务,而是一套分布式控制系统的神经中枢。它不跑业务代码,却决定所有业务能否运行、何时扩容、故障时往哪迁移——就像交响乐团的指挥,自己不发声,但每个音符都听它的。

我带过十几期K8s实操训练,发现新手最常踩的坑,90%都出在master节点配置阶段:etcd证书对不上、apiserver启动后curl不通、kubelet反复报“NotReady”、dashboard打不开却死活找不到日志在哪查……这些都不是配置写错了,而是对master组件的协作逻辑缺乏具象认知。比如,你以为kubeadm init是一键安装?其实它背后在30秒内完成了27个关键动作:生成CA证书链、初始化etcd集群、启动静态pod管理的kube-apiserver/kube-controller-manager/kube-scheduler、配置kubelet与apiserver的双向TLS认证、生成admin.conf供kubectl使用……任何一个环节的网络策略、时间同步或文件权限偏差,都会让整个集群“失语”。

这个环节的核心关键词就是证书信任链、组件通信拓扑、静态pod生命周期管理。它不涉及业务开发,但决定了你后续所有操作是否可信、可追溯、可审计。所以本篇不讲“怎么复制粘贴命令”,而是带你把master节点拆开来看:每个组件装在哪、和谁说话、用什么凭证说话、说错话会触发什么连锁反应。你会看到,所谓“配置master”,本质是在Linux系统之上,用YAML和证书构建一套自验证、自修复的控制平面信任网络。适合两类人重点细读:一是刚从Docker Compose转过来、习惯“单机思维”的开发者;二是负责生产环境部署但缺乏K8s底层调试经验的运维同学。只要你希望下次遇到kubectl get nodes返回空列表时,能立刻定位是kubelet没连上apiserver,还是etcd根本没起来,而不是重启服务器碰运气——这篇就是为你写的。

2. 配置不是填空题,而是解构控制平面的信任网络

2.1 为什么必须亲手配置master?云厂商托管版不能直接学?

新手常问:“阿里云ACK、腾讯TKE不是点点鼠标就创建好集群了吗?为什么还要学手动配master?”这个问题直击本质。托管K8s服务确实省去了master配置,但它同时隐藏了三个关键事实:

  • 证书体系完全黑盒化:你无法查看CA根证书、无法理解client-certificate-data和client-key-data如何生成,更无法在证书快过期时主动轮换。某次线上事故中,某公司TKE集群因CA证书自动续期失败导致所有kubectl命令超时,排查耗时4小时——只因他们从未接触过/etc/kubernetes/pki/目录下的证书结构。

  • 组件版本与参数不可见:托管服务默认启用--feature-gates=AllAlpha=true等实验性参数,而你的本地测试环境若未同步开启,同一份Helm Chart在两地行为可能完全不同。我们曾遇到一个Ingress Controller在TKE正常,在自建集群503错误,最终发现是kube-proxy的--proxy-mode=ipvs参数未显式声明,导致fallback到iptables模式引发连接复用问题。

  • 故障面被大幅压缩:当kubectl get nodes失败时,托管平台只给你一句“控制平面异常”,而自建环境你能逐层检查:systemctl status kubelet→journalctl -u kubelet -n 100→curl -k https://localhost:6443/healthz→etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt cluster-health。这种分层诊断能力,是所有K8s深度使用者的肌肉记忆。

因此,手动配置master不是为了“复古”,而是为了建立对K8s控制平面的物理感知——知道证书存哪、进程跑在哪、端口开在哪、日志写在哪。就像学开车先了解离合器原理,哪怕你永远只开自动挡。

2.2 master节点的四大核心组件:它们不是并列关系,而是主从嵌套

很多教程把kube-apiserver、kube-controller-manager、kube-scheduler、etcd并列为“master组件”,这容易误导。实际上,它们构成三层嵌套结构:

第一层:etcd —— 所有状态的唯一真相源
它不关心K8s语义,只提供key-value存储。所有资源对象(Pod、Service、ConfigMap)的JSON序列化结果,最终都以/registry/pods/default/nginx-abc123这样的路径存入etcd。注意:etcd本身无K8s概念,它只是数据库。当你执行kubectl delete pod nginx,实际发生的是:apiserver向etcd发送DELETE请求,删除对应key。因此,etcd的健康度直接决定集群生死——它挂了,apiserver立即变成“无脑指挥官”,所有读写操作均失败。

第二层:kube-apiserver —— 唯一入口网关
它是集群的“国境线”,所有内外部请求(kubectl、controller、scheduler、甚至webhook)必须经它鉴权、准入、转换后才能访问etcd。关键特性:

  • 无状态设计:apiserver自身不存任何状态,所有数据来自etcd。这意味着你可以水平扩展多个apiserver实例(需配合负载均衡),只要它们共用同一套etcd集群。
  • 强一致性保证:通过--etcd-quorum-read=true参数确保读操作从多数派etcd节点获取数据,避免读到过期值。
  • Watch机制:controller和scheduler不轮询apiserver,而是建立长连接监听资源变更事件(如/api/v1/pods?watch=true),这是K8s响应式架构的基石。

第三层:kube-controller-manager与kube-scheduler —— 两个“永动机”
它们都依赖apiserver的Watch机制工作,但角色截然不同:

  • controller-manager:像一群“管家”,持续比对etcd中存储的期望状态(desired state)与实际状态(actual state)。例如,ReplicaSet controller发现当前pod数少于replicas字段值,就调用apiserver创建新pod;Node controller检测到节点心跳超时,就标记其为NotReady并驱逐pod。
  • scheduler:像“房产中介”,只做一件事:为Pending状态的pod找到合适的node。它不执行调度,只更新pod的spec.nodeName字段;真正绑定动作由apiserver完成。因此,scheduler崩溃不会影响现有pod运行,只会导致新pod卡在Pending。

提示:初学者常混淆controller与operator。Controller是K8s内置的通用协调器(处理Deployment、Service等原生资源),而Operator是用户编写的特定领域协调器(如MySQL Operator、Prometheus Operator),它通过CRD扩展K8s API,再用自定义controller实现复杂应用生命周期管理。

2.3 静态Pod机制:master组件如何实现“自我托管”?

这是K8s最精妙的设计之一,也是新手最难理解的点。你可能会疑惑:kubelet是运行在master节点上的进程,而kube-apiserver、controller-manager等又是以Pod形式运行——那谁来启动这些Pod?岂不是“鸡生蛋”问题?

答案是:kubelet通过静态Pod(Static Pod)机制启动master组件,且这些Pod不由apiserver管理。具体流程如下:

  1. 在master节点上,kubelet启动时通过--pod-manifest-path=/etc/kubernetes/manifests参数指定一个YAML文件目录;
  2. kubelet周期性扫描该目录,发现kube-apiserver.yaml、kube-controller-manager.yaml等文件后,直接在本机Docker/Podman中启动对应容器;
  3. 这些容器在etcd中没有对应的Pod对象记录,kubectl get pods -A看不到它们,但docker ps能看到;
  4. kubelet会为每个静态Pod在etcd中创建一个Mirror Pod(镜像Pod),仅用于在UI中显示状态,不参与调度。

这种设计带来两大优势:

  • 启动解耦:master组件启动不依赖apiserver,避免循环依赖;
  • 故障隔离:即使apiserver崩溃,kubelet仍能通过本地文件重启组件,实现基础自愈。

注意:静态Pod的YAML文件必须放在kubelet指定的目录下,且文件名必须以.yaml或.yml结尾。我曾见过因文件名写成kube-apiserver.yml.txt导致组件始终无法启动的案例——Linux下ls命令不显示隐藏后缀,肉眼极难发现。

3. 实操全流程:从裸机到可交互控制平面的12个关键步骤

3.1 环境准备:别让基础环境成为第一个拦路虎

在开始配置前,请用以下命令确认环境已满足硬性要求(以Ubuntu 22.04为例,其他发行版类似):

# 检查CPU与内存(master节点最低2核4G,生产环境建议4核8G+) lscpu | grep "CPU\(s\| MHz\)" free -h # 关闭swap(K8s强制要求,否则kubelet启动失败) sudo swapoff -a sudo sed -i '/ swap / s/^/#/' /etc/fstab # 加载br_netfilter模块(必需,用于iptables处理bridge流量) sudo modprobe br_netfilter echo 'br_netfilter' | sudo tee -a /etc/modules # 配置sysctl参数(永久生效) cat <<EOF | sudo 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 EOF sudo sysctl --system # 安装containerd(替代Docker,K8s 1.24+默认运行时) sudo apt-get update && sudo apt-get install -y containerd sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml sudo systemctl restart containerd

这里需要强调三个易忽略点:

  • 时间同步:所有master节点必须NTP校时,误差超过1分钟会导致证书校验失败。执行timedatectl status确认System clock synchronized: yes;
  • 主机名唯一性:hostname -f返回的FQDN必须全局唯一,且不能含下划线(_),否则etcd集群初始化失败;
  • 防火墙放行端口:master节点需开放6443(apiserver)、2379-2380(etcd)、10250(kubelet)、10259(scheduler)、10257(controller-manager)。用sudo ufw allow 6443逐一放开,切勿直接关闭防火墙。

实操心得:我曾在某次部署中因忘记关闭swap,kubeadm init报错[ERROR Swap]: running with swap on is not supported。看似简单,但错误日志里混杂着大量证书生成信息,新手极易忽略这条关键提示。建议把“关闭swap”作为环境检查清单的第一项,用free -h | grep Swap确认输出为Swap: 0B 0B 0B。

3.2 初始化集群:kubeadm init背后的27个隐式动作

执行kubeadm init远不止启动几个进程那么简单。以下是它实际完成的关键动作分解(基于v1.28版本):

  1. 预检(Preflight Checks):验证CPU、内存、swap、内核模块、端口占用、容器运行时等12项条件;
  2. 证书生成:在/etc/kubernetes/pki/下创建CA证书、apiserver证书、front-proxy证书、etcd证书等共11个证书文件;
  3. etcd初始化:启动临时etcd容器,写入初始集群配置,生成/etc/kubernetes/manifests/etcd.yaml;
  4. 生成静态Pod清单:创建/etc/kubernetes/manifests/kube-apiserver.yaml等4个YAML文件;
  5. 配置kubelet:生成/var/lib/kubelet/config.yaml,设置cgroup驱动、证书轮换等参数;
  6. 启动kubelet服务:systemctl enable --now kubelet,此时kubelet开始加载静态Pod;
  7. 等待apiserver就绪:循环执行curl -k https://localhost:6443/healthz,超时300秒;
  8. 生成admin.conf:创建/etc/kubernetes/admin.conf,包含CA证书和管理员token;
  9. 配置kubectl:将admin.conf复制到$HOME/.kube/config,设置当前上下文;
  10. 部署CoreDNS:通过kubeadm内置的addon机制部署DNS服务;
  11. 生成join命令:生成kubeadm join命令供worker节点加入。

执行命令时务必指定关键参数:

sudo kubeadm init \ --kubernetes-version=v1.28.2 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12 \ --cri-socket=unix:///run/containerd/containerd.sock \ --upload-certs

参数详解:

  • --kubernetes-version:明确指定版本,避免kubeadm自动拉取最新版导致不兼容;
  • --pod-network-cidr:必须与后续CNI插件(如Flannel)的网段一致,否则Pod间无法通信;
  • --service-cidr:Service VIP地址池,不能与物理网络或Pod网段重叠;
  • --cri-socket:指向containerd socket路径,若用Docker则为unix:///var/run/docker.sock;
  • --upload-certs:生成并上传证书密钥,为后续添加master节点做准备。

注意:kubeadm init成功后,终端会输出三段关键信息:

  1. Your Kubernetes control-plane has initialized successfully!—— 主体成功;
  2. To start using your cluster...—— 配置kubectl的命令;
  3. Then you can join any number of worker nodes...—— worker节点加入命令。
    务必复制保存第3段,它包含24小时内有效的token和discovery-token-ca-cert-hash,过期需重新生成。

3.3 验证控制平面:从“看到”到“理解”每层状态

初始化完成后,不要急着部署应用,先用分层验证法确认各组件真实就绪:

第一层:操作系统级验证

# 检查kubelet服务状态 sudo systemctl status kubelet # 查看kubelet日志(重点关注最后50行) sudo journalctl -u kubelet -n 50 --no-pager # 检查静态Pod是否运行(注意:此处看到的是容器,非K8s Pod对象) sudo crictl ps | grep -E "(apiserver|controller|scheduler|etcd)"

第二层:API Server级验证

# 使用admin.conf直连apiserver(绕过kubectl配置) curl -k --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt \ --key /etc/kubernetes/pki/apiserver-kubelet-client.key \ https://localhost:6443/healthz # 检查API Server提供的API组 kubectl api-versions | head -10

第三层:K8s资源级验证

# 查看所有命名空间下的Pod(应看到coredns、etcd等) kubectl get pods -A # 检查节点状态(master节点应为Ready) kubectl get nodes -o wide # 查看apiserver的Endpoint(确认Service已正确关联到Pod) kubectl get endpoints kubernetes -n default

常见异常及定位:

  • kubectl get nodes返回空:先执行sudo crictl ps,若看不到apiserver容器,则是静态Pod未启动,检查/etc/kubernetes/manifests/下YAML文件语法;
  • kubectl get pods -A显示coredns为Pending:通常是CNI网络插件未部署,或--pod-network-cidr参数与CNI配置不匹配;
  • kubectl get nodes显示NotReady:执行kubectl describe node <node-name>,查看Events中是否有NetworkPluginNotReady或ContainerRuntimeNotReady。

实操心得:我习惯在每次kubeadm init后立即执行kubectl get componentstatuses(虽已弃用但仍有参考价值),它会列出apiserver、controller-manager、scheduler的HTTP健康检查结果。若其中某项为Unknown,说明该组件未在apiserver注册,需检查对应组件的--bind-address和--secure-port参数是否与apiserver通信端口一致。

3.4 部署CNI网络插件:让Pod真正“活”起来

K8s集群初始化后,Pod只能运行在单节点,跨节点通信必须依赖CNI(Container Network Interface)插件。Flannel是最轻量的选择,部署步骤如下:

# 下载官方Flannel YAML(注意版本匹配) wget https://raw.githubusercontent.com/flannel-io/flannel/v0.24.2/Documentation/kube-flannel.yml # 修改网段以匹配kubeadm init时指定的--pod-network-cidr sed -i 's/10.244.0.0\/16/10.244.0.0\/16/g' kube-flannel.yml # 部署(此操作会创建flannel命名空间、DaemonSet、ConfigMap等) kubectl apply -f kube-flannel.yml # 验证(等待约1分钟,flannel-ds应在所有节点Running) kubectl get pods -n kube-flannel

关键配置解析:

  • net-conf.json中的Network字段必须与kubeadm init --pod-network-cidr值完全一致;
  • Flannel默认使用VXLAN后端,通过host-gw模式性能更高,但要求所有节点二层互通;
  • 若使用--network-plugin=cni参数启动kubelet(kubeadm默认启用),则无需额外配置。

验证网络连通性:

# 创建测试Pod kubectl run nginx-test --image=nginx --restart=Never # 获取Pod IP kubectl get pod nginx-test -o wide # 从master节点ping该IP(需确保ICMP未被防火墙拦截) ping <pod-ip> # 进入Pod执行curl测试(验证DNS和Service网络) kubectl exec -it nginx-test -- curl -I http://kubernetes.default.svc.cluster.local

提示:Flannel部署后,会在每个节点创建cni0网桥和flannel.1VXLAN设备。执行ip addr show cni0可看到分配的网段(如10.244.0.1/24),这正是该节点上所有Pod的网关地址。理解这一点,对后续排障至关重要。

4. 故障排查实战:从日志、证书、网络三维度定位真凶

4.1 日志分析:kubelet日志是故障定位的黄金入口

当集群异常时,90%的问题根源藏在kubelet日志中。不要盲目重启服务,按以下顺序深挖:

# 查看最近100行日志(聚焦ERROR/WARNING) sudo journalctl -u kubelet -n 100 --no-pager | grep -i -E "(error|warn|fail)" # 追踪实时日志(部署时保持此窗口打开) sudo journalctl -u kubelet -f # 查看kubelet启动参数(确认配置是否生效) sudo ps aux | grep kubelet

典型日志模式与对策:

  • failed to load Kubeconfig:/etc/kubernetes/kubelet.conf文件丢失或权限错误,检查ls -l /etc/kubernetes/kubelet.conf;
  • connection refused:apiserver未启动或端口被占用,执行sudo ss -tuln | grep 6443;
  • x509: certificate has expired or is not yet valid:系统时间错误或证书过期,用date和openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout | grep -A2 Validity验证;
  • cni plugin not initialized:CNI配置文件缺失,检查/etc/cni/net.d/目录下是否有10-flannel.conflist等文件。

注意:kubelet日志中大量"PLEG is not healthy"警告通常不是问题,而是Pod生命周期事件处理器(PLEG)的正常心跳。真正的故障信号是连续出现"Failed to create pod sandbox"或"ImagePullBackOff"。

4.2 证书体系排查:一张图看懂K8s证书信任链

K8s证书体系常被诟病复杂,其实只需掌握四类证书及其用途:

证书类型存储路径用途常见问题
ca.crt/etc/kubernetes/pki/ca.crt根证书,所有其他证书的签发者被误删导致所有组件启动失败
apiserver.crt/etc/kubernetes/pki/apiserver.crtapiserver服务端证书,供客户端(kubectl、kubelet)验证与--advertise-address不匹配导致连接拒绝
apiserver-kubelet-client.crt/etc/kubernetes/pki/apiserver-kubelet-client.crtapiserver作为客户端访问kubelet的证书kubelet启动时提示x509: certificate signed by unknown authority
front-proxy-client.crt/etc/kubernetes/pki/front-proxy-client.crt用于聚合API(如metrics-server)metrics-server无法注册到apiserver

验证证书有效性命令:

# 检查证书是否过期 openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout | grep -A1 Validity # 检查证书Subject是否匹配节点IP openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout | grep "IP Address" # 测试apiserver证书是否被信任(模拟kubectl行为) curl -v --cacert /etc/kubernetes/pki/ca.crt https://localhost:6443/version

实操心得:某次客户集群升级后apiserver无法启动,日志显示x509: certificate is valid for 10.96.0.1, not 192.168.1.100。原因是kubeadm init时未指定--apiserver-advertise-address=192.168.1.100,导致生成的证书只包含Service ClusterIP(10.96.0.1),不包含节点物理IP。解决方案:备份原证书,用kubeadm init phase certs apiserver --apiserver-advertise-address=192.168.1.100重新生成。

4.3 网络连通性诊断:从物理层到应用层的五步穿透法

当Pod无法通信时,按OSI模型自底向上排查:

Step 1:物理层连通性

# 检查节点间TCP 6443端口是否可达(master节点间) nc -zv <other-master-ip> 6443 # 检查etcd端口(2379) nc -zv <etcd-node-ip> 2379

Step 2:容器运行时网络

# 查看cni0网桥是否创建 ip link show cni0 # 检查flannel是否分配了子网 cat /run/flannel/subnet.env

Step 3:Pod网络连通性

# 进入Pod内部测试 kubectl exec -it <pod-name> -- sh # 在Pod内ping其他Pod IP(验证CNI) ping <other-pod-ip> # 在Pod内nslookup Service名称(验证CoreDNS) nslookup kubernetes.default.svc.cluster.local

Step 4:Service网络连通性

# 查看Service的ClusterIP和Endpoints kubectl get svc kubernetes -o wide kubectl get endpoints kubernetes # 从Pod内curl ClusterIP(验证kube-proxy) curl -I http://10.96.0.1:443/version

Step 5:DNS解析验证

# 检查CoreDNS Pod状态 kubectl get pods -n kube-system -l k8s-app=kube-dns # 查看CoreDNS日志 kubectl logs -n kube-system -l k8s-app=kube-dns # 手动测试DNS解析(使用busybox) kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default.svc.cluster.local

提示:若nslookup超时,优先检查/etc/resolv.conf中nameserver是否为10.96.0.10(CoreDNS Service IP),以及/etc/kubernetes/manifests/coredns.yaml中- --clusterIP=10.96.0.10是否与kubeadm init --service-cidr参数一致。

5. 进阶实践:从单master到高可用集群的平滑演进

5.1 为什么单master节点永远不该出现在生产环境?

单master节点存在三个致命缺陷:

  • 单点故障:apiserver宕机,整个集群失去控制能力,所有自动化运维(如HPA、ClusterAutoscaler)立即失效;
  • 性能瓶颈:所有API请求、Watch事件、etcd读写集中于一台机器,1000+节点规模时apiserver CPU常达90%+;
  • 维护停机:系统升级、内核补丁等需重启服务器,导致集群不可用。

高可用(HA)的本质不是“多装几个master”,而是解耦控制平面组件,使其可独立扩缩容。现代K8s HA方案采用“外部etcd集群 + 多apiserver负载均衡”架构:

  • etcd集群:3或5节点独立部署,不与master节点混部,通过--initial-cluster参数组成Raft集群;
  • apiserver:无状态,可水平扩展,前端挂载负载均衡器(如HAProxy、Nginx);
  • controller-manager/scheduler:通过leader election机制保证同一时刻仅一个实例活跃,其余为standby。

5.2 基于kubeadm构建三节点HA集群的关键步骤

假设已有单master节点(master1),新增master2、master3:

Step 1:在master1上生成证书分发密钥

# 此命令生成加密密钥,用于安全传输证书到新节点 sudo kubeadm init phase upload-certs --upload-certs # 输出一串24位字符的密钥,务必保存!

Step 2:在master2、master3上执行join命令

# 替换<master1-ip>、<token>、<cert-hash>、<cert-key> sudo kubeadm join <master1-ip>:6443 \ --token <token> \ --discovery-token-ca-cert-hash sha256:<cert-hash> \ --control-plane --certificate-key <cert-key>

Step 3:配置负载均衡器
在独立服务器上部署HAProxy,配置/etc/haproxy/haproxy.cfg:

frontend k8s-api bind *:6443 mode tcp default_backend k8s-masters backend k8s-masters mode tcp balance roundrobin server master1 <master1-ip>:6443 check server master2 <master2-ip>:6443 check server master3 <master3-ip>:6443 check

Step 4:更新kubectl配置

# 将kubectl的server地址改为HAProxy VIP kubectl config set-cluster kubernetes --server=https://<haproxy-vip>:6443

注意:添加新master节点后,kubectl get nodes会显示所有节点为master(<none>角色),需手动打标签区分:kubectl label node <node-name> node-role.kubernetes.io/control-plane=(保留control-plane标签)和kubectl label node <node-name> node-role.kubernetes.io/worker=(标记为worker)。

5.3 证书轮换:让集群“永生”的必要维护动作

K8s默认证书有效期为1年,到期后apiserver将拒绝所有TLS连接。轮换不是“重装”,而是在线更新:

# 查看证书过期时间 kubeadm certs check-expiration # 自动轮换所有证书(需kubeadm v1.15+) sudo kubeadm certs renew all # 重启相关服务使新证书生效 sudo systemctl restart kubelet sudo crictl ps | grep -E "(apiserver|controller|scheduler)" | awk '{print $1}' | xargs -I {} sudo crictl stop {}

关键原则:

  • 轮换不影响业务Pod:证书只用于控制平面组件通信,业务流量走Service ClusterIP,不受影响;
  • 必须逐节点操作:先轮换master1,验证无误后再轮换master2,避免所有master同时重启导致集群不可用;
  • 备份原始证书:轮换前执行sudo cp -r /etc/kubernetes/pki /etc/kubernetes/pki-backup-$(date +%Y%m%d)。

实操心得:某金融客户因未监控证书有效期,生产集群在凌晨3点证书过期,所有CI/CD流水线中断。此后我们强制在所有集群部署Prometheus告警规则:kubeadm_certs_expiration_seconds{job="kubernetes-certs"} < 86400(提前24小时告警)。记住:证书轮换不是“救火”,而是“定期体检”。

6. 最后分享一个血泪教训:那个让我重装三次集群的SELinux陷阱

在CentOS/RHEL系统上部署K8s,最大的隐形杀手不是网络也不是证书,而是SELinux。它默认阻止containerd访问/etc/kubernetes/pki/下的证书文件,导致kubelet启动时疯狂报错:

failed to load client CA file /etc/kubernetes/pki/ca.crt: open /etc/kubernetes/pki/ca.crt: permission denied

执行ls -Z /etc/kubernetes/pki/ca.crt会看到:

unconfined_u:object_r:default_t:s0 /etc/kubernetes/pki/ca.crt

而containerd进程的SELinux上下文是system_u:system_r:container_runtime_t:s0,二者不匹配。

解决方案只有两个:

  • 彻底禁用SELinux(不推荐生产环境):sudo setenforce 0 && sudo sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config;
  • 正确配置SELinux策略(推荐):
    # 安装policycoreutils-python-utils sudo yum install -y policycoreutils-python-utils # 为kubernetes pki目录添加正确上下文 sudo semanage fcontext -a -t container_file_t "/etc/kubernetes/pki(/.*)?" sudo restorecon -Rv /etc/kubernetes/pki # 为kubelet服务添加网络访问权限 sudo setsebool -P container_manage_cgroup 1

这个坑我踩了三次:第一次以为是文件权限问题,chmod 600无效;第二次怀疑证书损坏,重生成证书仍失败;第三次才想起SELinux。现在我的标准操作流程中,“检查SELinux状态”是环境准备的第一步,执行getenforce,输出Enforcing就立即执行策略配置。

K8s的优雅在于其抽象,而它的残酷在于抽象之下的每一层细节都可能成为故障源。配置master节点不是终点,而是你真正开始读懂K8s心跳节奏的起点。当你能看着kubectl get componentstatuses的绿色输出,同时心里清楚每个组件在磁盘上存哪、在内存中跑哪、在网络中和谁对话——那一刻,你才算拿到了K8s世界的入门钥匙。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进