ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

kubeadm部署生产高可用K8s集群:架构设计与避坑实录

kubeadm部署生产高可用K8s集群:架构设计与避坑实录 K8s部署这事儿网上教程一抓一大把但绝大多数都是拿单master集群糊弄事真正到了生产环境高可用、网络、存储、监控这些坑一个都躲不掉。我这两年经手过的集群没有十个也有八个从裸机到云主机都踩过这篇就把从零搭建一套生产可用K8s集群的完整过程捋一遍重点是三master高可用架构怎么设计、kubeadm怎么用才不出幺蛾子以及部署完Prometheus、GPU调度这些扩展能力时那些文档里不写的细节。适合刚考完CKA想落地实操的人也适合已经在跑集群但总被各种诡异问题折腾的运维同学。1. 部署K8s前的四个关键决策1.1 为什么选kubeadm而不是二进制或发行版自带工具K8s集群的搭建方式大致有三条路纯二进制手动部署、kubeadm工具部署、Rancher/Kubekey这类发行版自带部署工具。我见过不少人一上来就挑战二进制部署觉得这样才能“掌握原理”实际上二进制方式光是把etcd、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy这些组件的证书、参数、systemd unit文件理顺就够折腾一整天的而且很容易出一些低级的配置错误排查起来特别费劲。我个人的建议是无脑选kubeadm。原因很实在kubeadm是官方维护的部署工具大部分版本升级、证书续期这些操作官方都替你考虑到了还能通过配置文件kubeadm-config.yaml把集群参数固化下来方便后续审计和复现。相比二进制部署kubeadm把最复杂的TLS引导、RBAC授权、控制面组件静态Pod生成这些环节自动化了但它又不是黑盒你完全可以通过kubeadm config print init-defaults查看默认配置通过kubectl -n kube-system get pod -o yaml查看组件的真实启动参数这对于理解K8s内部机制完全够用。至于Rancher、Kubekey这类图形化部署工具适合对K8s本身不熟、主要是想快速跑业务的人。但用这类工具的问题在于一旦集群出问题排查问题时你对底层组件是无感的反而更难定位。而且Kubekey这种工具虽然能帮你快速搭出高可用集群但版本定制、网络插件、运行时方案这些选项它都替你做了默认选择你如果不清楚这些默认值的含义后面调整起来会非常被动。1.2 高可用架构怎么设计3个master的来龙去脉生产环境至少要3个master节点这个不是玄学而是etcd的选举机制决定的。etcd作为K8s的唯一数据源必须通过Raft协议保证数据一致性而Raft协议要求集群中超过半数的节点存活才能选出leader、才能对外提供服务。2个master节点挂掉1个就只剩50%不满足“超过半数”的要求集群直接不可用。3个master允许挂掉1个5个master允许挂掉2个以此类推所以奇数个是性价比最高的选择。很多人会忽略一个问题三台master的高可用不仅仅指etcd的高可用还包括kube-apiserver的高可用。etcd是集群存储层kube-apiserver是K8s的入口worker节点的kubelet、kube-proxy以及所有kubectl命令都要访问apiserver。如果只做了etcd高可用apiserver挂了整个集群依然处于“能存数据但无法读写”的状态等于白搭。所以三台master架构中每台master上都会运行一套完整的控制面组件apiserver、controller-manager、scheduler其中controller-manager和scheduler通过--leader-elect参数实现选主同一时刻只有leader真正工作这个机制K8s已经内置无需额外处理。唯独apiserver是无状态的它不选主需要前端有一个负载均衡器来分发流量。这就是为什么在搭建高可用集群时总要在master前面加一层HaproxyKeepalived或者云厂商的SLB。负载均衡器把来自kubelet、kubectl、kube-proxy的请求分发给三台master的apiserver某一台master挂了负载均衡自动摘除它集群控制面就依然是可用的。1.3 容器运行时docker被弃用后containerd成为主流K8s从1.24版本开始彻底移除了对Docker作为运行时dockershim的支持这个消息当时劝退了不少人但实际用起来没那么可怕。Docker本身是一套完整的容器工具链包含build、ship、run等多个模块而K8s真正需要的只是其中“运行容器”的那一部分——也就是containerd。Docker底层本来就是调用containerd来跑容器的所以K8s直接对接containerd等于把中间商去掉了性能还更好。部署containerd时有个容易踩坑的地方默认配置文件里SystemdCgroup是false而K8s要求容器运行时的cgroup驱动必须和kubelet保持一致都使用systemd。如果这里不改成true集群初始化后节点会一直报kubelet cgroup driver相关的错误pod创建后状态异常。另外国内网络环境下containerd拉取镜像需要配置registry mirror这个后面实操部分会细说。选择containerd还有一个隐藏的好处排查问题的时候更干净。以前用docker作为运行时docker ps、docker logs、docker exec这些命令都能查到容器状态现在统一用crictl这一套命令行工具crictl ps、crictl logs、crictl exec它本身就是K8s为容器运行时设计的标准CLI和kubelet、kube-proxy等组件协作更直接不需要在docker和crictl之间来回切换。1.4 版本与组件选型稳定优先K8s的版本迭代非常快每年会有3-4个小版本发布但生产环境千万别追新。我见过有人用最新的K8s版本搭配旧版Calico结果网络插件死活起不来折腾了半天发现是版本兼容性问题。选版本的核心原则是在官方支持周期内选最新稳定版同时确认所有配套组件containerd、Calico、CoreDNS等的版本兼容矩阵。这里有一个官方工具可以帮你确认版本兼容性kubectl version --short查看当前版本K8s官方文档页面有详细的“已发布版本”和“版本偏差支持策略”。例如kubeadm支持安装的K8s版本比kubelet最多提前一个次要版本。实操中我建议选一个已经发布了3-6个月的小版本比如说K8s 1.28.x或1.29.x这个阶段的bug修得差不多了各种配套组件的新版本也都适配了网上的踩坑经验也比较丰富。配套组件方面容器运行时containerd选1.7.x因为1.7是稳定维护分支网络插件Calico选v3.27这个版本对K8s 1.28支持良好Ingress Controller选ingress-nginx版本4.8监控套件用kube-prometheus-stack的25.x版本。把这些组件的版本提前锁定部署的时候才不会手忙脚乱。2. 环境准备与基础配置2.1 服务器规划与网络规划假设你手里有三台master和三台worker小规模生产环境这个配置够用规划时要考虑操作系统、硬件配置、网络三段式设计。操作系统方面我优先推荐Ubuntu 22.04 LTS或Rocky Linux 9这两个系统对容器生态的支持都很好内核版本也够新不会出现因为内核太老而无法运行overlayfs或iptables nftables的问题。Ubuntu的好处是文档多、踩坑经验多Rocky的好处是更贴近RHEL系运维习惯。两者选哪个不重要重要的是所有节点的操作系统版本必须一致内核参数配置必须一致否则后面排查问题的时候环境差异会严重干扰判断。内存和CPU规划有一个底线master节点至少4核8Gworker节点至少8核16G如果机器太差apiserver、etcd调度大量pod时CPU和内存都会吃紧。磁盘方面所有节点建议SSDetcd所在的master节点尤其不能省因为etcd的fsync性能直接决定集群的响应速度机械盘跑etcd简直是灾难。网络规划是很容易被忽略的点。K8s集群内部需要三个网段节点网络物理机IP、Pod网段Pod IP分配范围、Service网段Service VIP分配范围。这三个网段必须提前规划好并且不能互相重叠。比如节点网络是192.168.10.0/24Pod网段可以定10.244.0.0/16Service网段可以定10.96.0.0/12。这个规划逻辑是Pod网段和Service网段是内部虚拟网络不影响外部网络但如果不提前规划好两个网段重叠后K8s内部路由会乱套排查起来极其痛苦。2.2 基础环境配置主机名、hosts、内核参数、防火墙这部分内容虽然简单但每一步都很关键而且顺序不能乱。先设置主机名的规范。我的建议是k8s-master01、k8s-master02、k8s-master03、k8s-node01、k8s-node02、k8s-node03这种命名格式。主机名不要用默认的localhost或者带特殊字符的名字因为K8s的节点名称就是主机名kubectl查看节点时显示的就是这个名字主机名混乱会严重影响多节点管理。然后修改所有节点的/etc/hosts文件把6台机器的IP和主机名对应关系写进去。这一步是为了让节点之间可以互相通过主机名解析通信避免后续kubeadm init时因为节点名无法解析而报错。内核参数是K8s部署的重中之重特别是以下两个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 vm.swappiness 0 EOF sudo sysctl --systemnet.bridge.bridge-nf-call-iptables 1确保iptables规则能作用于通过Linux网桥的流量这个参数不设的话K8s Service的ClusterIP模式会失效因为kube-proxy的流量转发规则只在主机网络层生效对网桥转发流量不生效。net.ipv4.ip_forward 1开启IP转发这是CNI插件和Service流量转发的前提。vm.swappiness 0尽量关闭swap的使用因为K8s对swap的支持比较特殊1.22之前直接要求关掉1.22之后虽然支持swap但默认行为还是不建议。防火墙和swap的处理文档上说的是“关闭”但实际执行时我建议分情况处理。如果是测试环境、内网环境直接swapoff -a并且注释掉fstab中的swap条目防火墙可以关闭。如果是生产环境防火墙关不关取决于你公司的安全策略但至少要把6443apiserver、2379-2380etcd、10250kubelet等端口放通。swap则必须关闭因为K8s默认不允许节点使用swap不关的话kubelet会直接报错拒绝启动。2.3 安装containerd并完成关键配置安装containerd有两种方式apt/dnf源直接安装或者从GitHub Release下载二进制包。用apt/dnf安装最方便但要注意源里containerd版本可能比较老建议先确认版本再装。Ubuntu下安装sudo apt-get update sudo apt-get install -y containerd装完以后生成默认配置并修改关键参数sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml然后编辑/etc/containerd/config.toml重点改两处第一处是SystemdCgroup。在[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options]段落中把SystemdCgroup false改成SystemdCgroup true。这个参数让containerd使用systemd cgroup驱动与kubelet保持一致否则kubelet启动后节点会一直处于NotReady状态。第二处是镜像加速。在[plugins.io.containerd.grpc.v1.cri.registry.mirrors]段落中加入[plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://docker.m.daocloud.io]如果你所在的网络环境访问docker.io本身没问题这步可以跳过。但如果是在国内环境或者公司内网环境不配置镜像加速的话后面kubeadm init拉取kube-apiserver、etcd这些镜像时会直接超时失败。改完配置后重启containerdsudo systemctl restart containerd sudo systemctl enable containerd用systemctl status containerd确认服务状态为running。这里有个小技巧crictl info命令可以快速查看containerd的配置是否生效如果能看到systemdCgroup: true说明配置改对了。2.4 安装kubeadm、kubelet、kubectl三个组件的版本必须一致。安装前先确认你的K8s目标版本比如1.29.1然后用以下命令安装指定版本Ubuntu下sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo deb [signed-by/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet1.29.1-1.1 kubeadm1.29.1-1.1 kubectl1.29.1-1.1注意这里必须加版本号直接装最新版容易和你想用的版本不一致。还要执行sudo apt-mark hold kubelet kubeadm kubectl把版本锁住防止意外升级导致kubelet和apiserver版本不匹配。安装完成后暂时不要启动kubelet。因为kubelet在加入集群之前启动会一直报错找不到apiserver这是正常的。所有配置工作就绪后由kubeadm在init或join时自动启动它。3. 高可用集群搭建实操3.1 部署HaproxyKeepalived负载均衡层在三台master节点上部署Haproxy和Keepalived实现apiserver的负载均衡和VIP漂移。VIP虚拟IP是部署高可用集群的关键比如规划VIP为192.168.10.100这个IP会由Keepalived自动绑定到当前的健康master节点上。先在所有master上安装sudo apt-get install -y haproxy keepalivedHaproxy的配置/etc/haproxy/haproxy.cfg如下global log /dev/log local0 maxconn 4096 defaults log global mode tcp option tcplog option dontlognull retries 3 timeout connect 5s timeout client 50s timeout server 50s frontend k8s-apiserver bind *:16443 mode tcp default_backend k8s-masters backend k8s-masters mode tcp balance roundrobin server k8s-master01 192.168.10.11:6443 check server k8s-master02 192.168.10.12:6443 check server k8s-master03 192.168.10.13:6443 check这里有个细节Haproxy监听在16443端口而不是6443是因为apiserver本身占用了6443Haproxy用16443接收外部流量再转发给各master的6443。负载均衡层与上层流量之间是解耦的。Keepalived的配置/etc/keepalived/keepalived.confglobal_defs { router_id LVS_K8S } vrrp_instance VI_1 { state MASTER interface ens33 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 192.168.10.100 } }注意三台master的state分别设置为MASTER、BACKUP、BACKUPpriority分别设置为100、90、80。同一时间只有最高priority的节点持有VIPMaster挂了之后BACKUP自动接管。重启服务后用ip addr show确认VIP是否绑定在当前节点上。3.2 初始化第一个master节点初始化前需要准备一个kubeadm配置文件因为默认配置下Pod网段是10.96.0.0/12Service网段而我们需要自定义Pod网段为10.244.0.0/16。这里我强烈推荐用配置文件而不是纯命令行参数因为配置文件可以复查、可以版本管理。创建kubeadm-config.yamlapiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.29.1 controlPlaneEndpoint: 192.168.10.100:16443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.10.11 bindPort: 6443这里controlPlaneEndpoint必须设置为VIP加Haproxy端口192.168.10.100:16443这是三master高可用集群最核心的配置。所有节点的kubelet、kube-proxy都会通过这个地址访问apiserver即使当前master节点宕机VIP漂移到其他master后流量依然能正常转发。执行初始化sudo kubeadm init --configkubeadm-config.yaml --upload-certs--upload-certs参数会把控制面证书上传到etcd中这样后面的master节点join时才能自动获取证书。初始化过程中如果报错先不要慌kubeadm reset之后检查前面的配置项特别是SystemdCgroup和镜像加速90%的问题都出在这两个上面。初始化成功后会输出一段join命令包括master节点join和worker节点join的命令一定要保存下来。然后配置kubectlmkdir -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网络插件这是正常的。3.3 加入其余master节点与worker节点第二个和第三个master节点加入的方式与第一个不同不能直接执行kubeadm init要用join命令。master02节点执行sudo kubeadm join 192.168.10.100:16443 \ --token token \ --discovery-token-ca-cert-hash sha256:hash \ --control-plane --certificate-key certificate-key这里的关键点是--control-plane参数表示以控制面节点身份加入。加入成功后master02会自动生成apiserver、etcd等静态Pod。--certificate-key就是kubeadm init --upload-certs时输出的那个key用来解密上传到etcd的证书。worker节点加入时不需要--control-plane和--certificate-keysudo kubeadm join 192.168.10.100:16443 \ --token token \ --discovery-token-ca-cert-hash sha256:hash如果token过期了可以在master节点上重新生成sudo kubeadm token create --print-join-command所有节点加入后在master01上执行kubectl get nodes应该能看到3个master和3个worker节点状态都是NotReady还没变成Ready是因为网络插件还没安装。3.4 验证集群状态与高可用故障演练集群搭建好后做一次完整的高可用验证非常有必要不要带着隐患上线。先验证基础状态kubectl get nodes -o wide kubectl get pods -n kube-system -o wide kubectl get cskubectl get cs查看控制面组件状态如果输出的是unhealthy不要慌这是1.24版本后kube-controller-manager和kube-scheduler的健康检查端口没有暴露导致的误报不影响实际功能。然后是故障演练。模拟master01宕机在master01上执行sudo shutdown -h now然后观察VIP是否漂移到master02在master02上执行ip addr show检查kubectl get nodes是否还能正常返回现有pod是否还在正常运行正常情况下master01宕机后VIP会漂移apiserver请求自动分发到master02和master03集群对外服务不受影响。同时etcd集群中master02、master03依然满足多数派条件读写正常集群状态为healthy。这就是三master高可用的价值所在。故障恢复后master01重新开机节点会自动重新加入集群无需手动干预。但这个过程中要注意时间同步所有节点都要配置NTP服务如果节点间时间偏差超过阈值etcd会报etcdserver: request timed out之类的错误到时候排查起来又得浪费半天。4. 网络插件与基础运维命令4.1 CNI网络插件选定与安装CalicoCNI插件是K8s集群的“神经系统”负责给每个pod分配IP并打通跨节点通信。市面上主流的CNI插件有Calico、Flannel、Cilium等我选型时的标准很简单性能优先选Cilium兼容性优先选Calico。Calico是基于BGP协议的三层网络方案性能好、支持NetworkPolicy而且部署简单、排错直观是生产环境最常见的CNI。安装方式kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml安装前要确认calico.yaml中CALICO_IPV4POOL_CIDR变量值是否和你规划的Pod网段一致默认是192.168.0.0/16需要改成10.244.0.0/16。如果不改Pod IP的分配网段和你规划的网段对不上网络很容易乱。等Calico的pod全部Running后集群节点状态会从NotReady变成Ready。验证网络是否正常kubectl run test-pod --imagebusybox -- sleep 3600 kubectl exec test-pod -- ping 另一个节点的pod IP能ping通则说明跨节点网络正常。不能ping通的话检查各节点的/proc/sys/net/ipv4/ip_forward是否为1以及防火墙是否放行了BGP端口默认179端口。4.2 K8s常用命令速查部署完集群日常运维命令大概这几类整理成速查表场景命令查看节点及资源kubectl get nodes -o wide查看pod及所在节点kubectl get pods -A -o wide查看pod详情kubectl describe pod pod-name -n namespace查看pod日志kubectl logs -f pod-name -n namespace进入pod执行命令kubectl exec -it pod-name -- /bin/sh查看service/endpointskubectl get svc,ep -A查看deployment伸缩kubectl scale deployment name --replicas5 -n ns节点维护排空kubectl drain node --ignore-daemonsets --delete-emptydir-data节点恢复调度kubectl uncordon node查看事件排错核心kubectl get events --sort-by.metadata.creationTimestamp这里单独强调两个高频命令。kubectl get events是排错时的第一选择deployment创建后pod状态一直Pending或CrashLoopBackOff多半能在这里看到拉镜像失败、磁盘压力、配额不足等明确信息。kubectl drain是做节点维护时的标准操作它会先驱逐节点上的pod到其他节点然后标记节点不可调度这样重启服务器不影响业务。但要注意--ignore-daemonsets参数必须加否则DaemonSet的pod被驱逐后会立刻重建导致drain卡住。4.3 Service对外暴露NodePort、LoadBalancer、ExternalIPs的实战取舍K8s中pod本身是不能被外部直接访问的需要Service来做一层抽象。Service的对外暴露方式有三种很多人都搞不清怎么选我这里直接给结论。NodePort是最简单的暴露方式Service会分配一个30000-32767之间的端口每个节点都会监听这个端口并转发到对应的pod。适合临时调试、内网访问不适合生产环境对外提供服务因为会占用节点端口而且客户端访问时直接打到了物理节点上缺少负载均衡和高可用。LoadBalancer一般配合云厂商SLB或裸金属上的MetalLB使用。Service定义type: LoadBalancer后云平台会自动创建一个负载均衡器外部流量通过LB进入集群。这是云上K8s服务的标准方案但在裸金属集群中需要额外部署MetalLB来提供类似的能力。ExternalIPs是一个容易被忽略的选项。给Service指定externalIPs字段后外部流量可以通过这个IP直接访问Service而无需经过NodePort或LoadBalancer。这个方案适合集群中已经预留了公网IP的情况配置直观但缺少健康检查和负载均衡只适合IP固定且数量少的场景。我生产环境常用的组合是前端外部流量走LoadBalancer或者Ingress LoadBalancer集群内部服务之间直接使用Service的ClusterIP访问调试时临时用kubectl port-forward来验证单个pod。这三个方式配合起来基本覆盖各种访问需求。4.4 命名空间与资源限额管理多业务共用一个集群时命名空间Namespace和资源配额ResourceQuota一定要提前规划否则后面管理会失控。命名空间是逻辑隔离单位每个团队或业务一个命名空间之间通过RBAC控制访问权限。创建命名空间kubectl create namespace dev kubectl create namespace prod资源配额的目的是防止某个团队把集群资源占满。给命名空间配置一个默认资源额度apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: 10 requests.memory: 20Gi limits.cpu: 20 limits.memory: 40Gi pods: 50配置完成后dev命名空间下所有pod的资源请求总和不能超过上述额度。如果新建的pod没有声明resources字段会被LimitRange拦截。这个配置能有效避免测试环境把生产环境资源挤爆的情况。5. 扩展能力GPU调度与监控体系5.1 让K8s调用GPUdevice plugin工作原理如果你的工作负载涉及AI训练或推理需要在K8s上使用GPU资源。K8s本身不认识GPU需要NVIDIA device plugin让kubelet感知到节点上的GPU资源。核心流程是在GPU节点上部署一个DaemonSetnvidia-device-plugin这个插件通过nvidia-container-toolkit申请GPU设备并将节点上的GPU数量上报给kubelet。之后kubelet会将这些GPU以nvidia.com/gpu资源的形式暴露给调度器。调度器在调度pod时如果pod声明了resources.limits[nvidia.com/gpu]: 1就会把pod调度到有GPU的节点上并保证独占一个GPU。实测部署步骤如下在GPU节点上安装NVIDIA驱动和nvidia-container-toolkitsudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtimecontainerd sudo systemctl restart containerd部署device pluginkubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/main/nvidia-device-plugin.yml验证GPU资源可见kubectl describe node gpu-node | grep nvidia.com/gpu输出能看到nvidia.com/gpu: 4之类的信息说明这个节点有4个GPU可分配。需要提醒的是GPU节点打上污点taint是常见实践防止普通pod占用GPU节点。但如果所有GPU节点都打了NoSchedule污点而GPU工作负载没有设置对应的容忍tolerationpod会一直处于Pending状态。所以要么GPU工作负载声明容忍要么单独用节点亲和性控制调度。5.2 部署Prometheus监控K8s集群K8s集群跑起来后监控就是刚需。官方推荐的最省事方式是直接部署kube-prometheus-stack它是一整套预配置好的监控方案内置了Prometheus、Alertmanager、Grafana以及一堆现成的告警规则和Dashboard。用Helm部署helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm upgrade --install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ --namespace monitoring --create-namespace \ --version 55.0.0部署完成后Grafana默认账号密码是admin/prom-operatorDashboard模板已经内置了K8s集群资源、节点指标、Pod指标、Etcd等常用视图。验证监控目标是否正常kubectl get svc -n monitoring kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090:9090打开http://localhost:9090/targets检查各targets的up状态。常见的坑是某些Exporter因为RBAC权限不足无法拉取指标或者Node Exporter与集群节点网络不通targets显示down。这些问题的排查路径基本都是先用kubectl get pods -n monitoring确认Exporter Pod是Running再查看它的日志找到权限或网络相关的具体报错。Prometheus监控数据落盘有两点建议一是单独挂一块PV给Prometheus避免数据落在系统盘被写满二是根据集群规模调整storage.tsdb.retention.time默认保留15天就够了不要贪多磁盘吃紧会导致Prometheus写入失败数据反而不完整。5.3 Operator模式K8s自动化的精髓K8s项目看多了你会发现K8s Operator几乎成了复杂应用部署的标配。Operator不是什么神秘技术简单说就是把运维某个应用的经验和自动化逻辑写进代码用K8s控制器模式来管理这个应用的生命周期。以最经典的Prometheus Operator为例。我们上面用Helm部署的kube-prometheus-stack底层就是Prometheus Operator在起作用。你不再需要手动管理Prometheus配置而是通过定义ServiceMonitor资源来描述要监控的目标Operator会监听这些资源的变化自动生成Prometheus的抓取配置。新增一个服务的监控只需要创建一个ServiceMonitor对象抓取路径、间隔、端口全都在里面声明Operator替你落盘配置并触发Prometheus热加载。Ez在MySQL、Redis这些中间件上也有对应Operator如Percona XtraDB Cluster Operator它们能让数据库在主从切换、备份恢复这些操作上实现高度自动化。虽然学习Operator本身有成本但一旦掌握了声明期望状态 控制器调谐这个模型你会发现K8s的大部分运维工作都可以抽象成这套逻辑这也是为什么面试题里经常出现Operator相关的案例——因为它真正反映了K8s的设计哲学。6. 常见问题与避坑实录6.1 初始化失败的排查思路kubeadm init报错的场景我见过太多次了新手最容易在初始化失败后反复reset再试浪费大量时间。其实把排查思路理顺大多数问题能在几分钟内定位。第一类问题是镜像拉取失败。确认方法是执行kubeadm init --configkubeadm-config.yaml -v 5增加日志级别或者在报错信息中看到failed to pull image或http: server gave HTTP response to HTTPS client。解决办法优先配置containerd镜像加速然后再试。不要用docker拉镜像再通过ctr导入这个办法虽然有些人推荐但导入的镜像tag容易对不上kubeadm的期望tag麻烦得很。第二类问题是kubelet启动失败。确认方法是看kubelet服务状态和日志journalctl -u kubelet -fkubelet启动失败的常见原因是SystemdCgroup配置不一致报错信息里会出现failed to run Kubelet: failed to validate kubelet flags或cgroup driver相关的提示。这个问题的根因前面说过containerd和kubelet必须使用同一套cgroup驱动。解决方法是修改containerd配置后重启containerd如果kubelet尝试启动过且产生了旧配置执行kubeadm reset后重新init。第三类问题是apiserver证书配置错误。报错信息会出现certificate signed by unknown authority或failed to parse...apiserver.crt。遇到这类问题检查kubeadm-config.yaml中的controlPlaneEndpoint和advertiseAddress是否填对特别是IP地址和hosts文件中的映射是否一致。6.2 镜像拉不下来的处理办法K8s集群跑起来后业务pod镜像拉不下来是高频问题。这个问题的难点在于不同的镜像源、不同的网络环境表现不一样处理方式也不一样。对于docker.io官方镜像前面配置镜像加速就能解决。对于quay.io这类国外源镜像加速不覆盖可以在配置中继续添加多个mirror或者通过本地私有镜像仓库中转。对于私有镜像仓库K8s需要提前在节点上配置好仓库访问凭证kubectl create secret docker-registry registry-key \ --docker-serverregistry-url \ --docker-usernameusername \ --docker-passwordpassword然后在Deployment中引用imagePullSecretsspec: imagePullSecrets: - name: registry-key还有一个隐藏坑有时候镜像明明存在但拉取还是失败报manifest unknown。这种情况往往是因为镜像tag不规范比如tag带了大写字母或者特殊字符。K8s对镜像tag的命名规范限制比docker更严格统一小写加数字的tag能避免这个问题。6.3 节点NotReady问题节点状态为NotReady是非常常见的集群异常引起的原因也五花八门我按出现频率整理成表现象原因排查方法kubelet不停重启cgroup driver不一致检查containerd的SystemdCgroup节点显示NotReady但pod正常网络插件Calico故障检查calico-node pod状态和日志系统资源不足磁盘满、内存耗尽df -h、free -h检查清理日志证书过期节点加入时间过长kubeadm certs check-expiration查看证书状态磁盘满这个坑尤其隐蔽。K8s节点长时间运行后/var/log下的容器日志、/var/lib/docker或/var/lib/containerd的镜像层文件都可能占满磁盘。特别是默认的logrotate配置在没有配置的情况下是不生效的会导致日志无限增长。解决方法是在集群中部署一个日志清理的DaemonSet比如logrotate的容器化版本或者给每个节点配置journald的日志限额设置SystemMaxUse参数防止日志无限积累。6.4 证书过期与集群升级证书管理是K8s集群运维的一个隐藏大坑。K8s组件间通信的大量证书默认有效期是1年kubeadm生成的到期后集群会突然不可用而且报错信息往往很隐晦。检查证书有效期kubeadm certs check-expiration输出中会列出每个证书的到期时间。证书到期前可以手动续期kubeadm certs renew all执行后需要重启kubelet和kube-apiserver等组件让新证书生效一般是重启所有控制面组件的podkubectl -n kube-system delete pod -l componentkube-apiserver这种方式。集群升级的路径是逐版本升级不能跨大版本跳。比如1.28升1.29要先升级kubeadmkubeadm upgrade apply v1.29.1再升级kubelet逐节点kubeadm upgrade node最后更新kubectl。这个过程看起来繁琐但它保证apiserver和kubelet的版本差不至于失控。这里最大的经验教训是证书续期和版本升级这种操作一定要先在测试集群上演练一遍然后再在生产环境执行。我见过一个生产集群因为证书过期导致全部节点的kubelet无法连接apiserver只好半夜起来批量操作那个教训至今印象深刻。6.5 几个容易忽略的集群全局配置除了上面这些具体问题还有几个全局配置项配置时容易忽略但影响面极大。第一个是时间同步。K8s集群所有节点必须保证时间一致偏差过大会导致etcd集群节点间心跳超时频繁选主证书校验失败证书的validity period判断依赖系统时间日志、事件时间戳混乱排查问题时无法对齐所有节点必须配置systemd-timesyncd或chrony进行NTP同步这个成本极低但收效极高。第二个是DNS配置。K8s集群内的CoreDNS是服务发现的基础但很多集群的CoreDNS偶尔会出现解析失败的问题。不只是kubectl exec进入pod中的DNS解析更关键的是业务pod之间通过Service名称互相访问时如果CoreDNS故障整个微服务架构直接瘫痪。建议把CoreDNS设置为多副本默认是2副本并且给CoreDNS的pod设置合适的反亲和性避免所有副本调度到同一台节点上。第三个是etcd的定期备份。etcd存储了整个集群的所有状态一旦数据损坏或误删恢复成本极高。建议配置一个cron任务定期执行etcdctl snapshot save备份数据保存到独立位置比如对象存储或另一台机器。不要等到集群出问题了才想起来备份——没有备份的恢复操作通常会发展成“重建集群加手动恢复业务”这个痛苦我希望你永远不用经历。写到这里K8s部署从架构设计、环境准备、高可用集群搭建、网络配置、监控扩展到常见故障排查一条线走完了。我个人的体会是K8s部署本身不复杂复杂的是搞清楚每一步背后的原理以及提前想到那些早晚会遇到的问题。证书、时间同步、磁盘清理、etcd备份这四件事在集群刚建好时就做好后续能省掉大量半夜救火的麻烦。框架搭好之后再往里面加监控、加GPU调度、加Operator都会顺畅很多。每个坑都亲自踩过一遍之后下次再搭集群你也会有自己的那份checklist。
返回列表