Kubernetes集群二进制安装实战:从证书签发到组件启动全解析
如果有人问我Kubernetes集群用二进制安装部署到底值不值得折腾我的答案通常是值得但要看情况。现在网上大量教程默认kubeadm默认一条init命令搞定一切可等你真把集群搭起来碰到apiserver起不来、kubelet反复CrashLoopBackOff的时候你会发现kubeadm藏起来的那一大堆配置细节才是问题根源。这篇博文就基于我做的一次单Master节点的Kubernetes集群二进制安装部署完整记录从环境准备、证书签发到组件启动、网络插件接入的整套过程给那些不想把集群当黑盒、想彻底搞懂K8s内部协作关系的读者一个可以直接复现的参考。单Master拓扑适合的场景其实比很多人想象中广学习环境、内部演示系统、轻量级测试平台都够用。当然生产环境还是建议多Master高可用但先把单Master吃透后面加节点、升级组件都是水到渠成的事。本文不会只给命令我会尽量把每个步骤背后的原因也讲清楚。1. 为什么放着kubeadm不用非选二进制安装1.1 kubeadm把什么藏起来了kubeadm确实很好用它把Kubernetes部署变成了“生成证书 生成static Pod清单 拉取镜像 启动容器”这条流水线。但对排障来说这个流水线是个麻烦事。我之前用kubeadm装过一个集群某天apiserver突然起不来日志一翻全是容器重启记录真正的错误信息却埋在kubelet日志和容器日志之间排查效率很低。kubeadm隐藏掉的东西至少有这几样kube-apiserver、kube-controller-manager、kube-scheduler的启动参数都不直接暴露它们以static Pod方式跑在kubelet下面配置文件由kubeadm自动生成证书体系完整但分散想把某个证书单独拎出来看SAN、看有效期还得先找到它在/etc/kubernetes/pki下的位置kubelet的启动参数也被收敛到kubelet config文件里默认行为和二进制方式下看到的直接参数有些差异。如果只是想快速得到一个可用集群kubeadm非常好。但如果你想搞清楚“kubernetes.default.svc这个域名为什么能解析”“apiserver的--service-account-key-file和controller-manager的--service-account-private-key-file为什么要配对”“etcd的peer证书和client证书到底分别给谁用”这类问题二进制安装是最直接的答案。1.2 二进制部署的真实适用场景先说结论别为了“显得专业”去折腾二进制。我见过有人为了一个演示环境硬生生花了两天排二进制证书的坑其实kubeadm十分钟就能搞定。真正适合二进制安装的情况我总结下来就几类想学透K8s组件原理的人每个组件用什么参数、怎么互相发现、证书怎么流转全部暴露在你面前。离线或内网环境手里只有一堆二进制包没法从镜像仓库拉kubeadm需要的镜像这时候手工分发二进制反而是最务实的选择。对启动参数有特殊定制需求比如要改apiserver的审计策略、要嵌入式etcd变外部etcd、要指定非默认的ServiceAccount密钥文件等。为后续做高可用打基础二进制方式对多Master架构的操作路径更清晰Master节点之间只要能共享证书和配置横向扩展很直观。所以如果看完这篇你还是觉得“太麻烦了”那就用kubeadm这没什么不好意思的。工具选型从来不是越高阶越好而是越合适越好。2. 节点规划、版本选型与二进制文件准备2.1 单Master拓扑的资源划分这次部署我用了两台虚拟机一台当Master一台当Node。Master上跑etcd、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxyNode上只跑kubelet和kube-proxy再加上容器运行时。如果想省机器也可以在Master上打掉taint让它也调度业务Pod但测试环境这么玩可以别把生产环境搞成这样。内存和CPU的底线我给个参考Master节点建议至少2核4Getcd和apiserver都挺吃内存的尤其是etcd内存不足会频繁触发压缩和fsync延迟Node节点2核2G起步具体看业务需求。磁盘方面etcd的数据目录最好用单独的盘IO性能会直接影响整个集群的API响应速度。IP和网段规划是安装前就必须定下来的后面证书里的SAN、apiserver的--service-cluster-ip-range、CNI插件的Pod网段都要围绕这个规划展开。我这次用的规划是项目取值Master节点IP192.168.100.10Node节点IP192.168.100.11Pod网段10.244.0.0/16Service网段10.96.0.0/12Cluster Domaincluster.localDNS Service IP10.96.0.10这里面的DNS Service IP是Service网段里的第10个地址Canonical规定是service网段的10.2但实际上CoreDNS的Service IP我习惯取10.96.0.10后面配置CoreDNS时会用到。Pod网段和Service网段不重叠是个基本原则同时也别跟宿主机网段冲突。2.2 操作系统初始化系统层面有几件事必须在装集群之前做好否则后面全是莫名其妙的坑。第一关闭swap。kubelet默认情况下是不允许swap存在的虽然新版K8s对swap的支持策略有调整但二进制部署时还是老老实实关掉省得排查时多一个变量。swapoff -a后再注释掉/etc/fstab里的swap行重启后依然有效。第二关闭防火墙和SELinux。单节点测试环境直接关掉最省事生产环境应该用安全组规则精确放行端口。需要放行的端口主要是组件端口kube-apiserver6443etcd2379、2380kubelet10250、10255kube-proxy30000-32767NodePortflannel/CNI8472VXLANSELinux不关的话kubelet和容器运行时经常遇到权限问题特别是挂载卷的时候setenforce 0先顶着。第三加载内核模块并设置sysctl参数。K8s的网络转发依赖net.ipv4.ip_forward开启iptables对网桥流量的处理需要br_netfilter模块。直接写到sysctl配置里modprobe br_netfilter cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 vm.swappiness 0 EOF sysctl --system这里的vm.swappiness设置成0是让系统尽量别用swap配合关swap一起用。2.3 版本选型与安装包准备版本我建议选当前稳定的中段版本别用太新的“尝鲜版”也别用已经EOL的远古版本。我这次以Kubernetes 1.26.8为例后面的命令里版本号都按这个来。选择标准就是你用的CNI插件、容器运行时、Ingress控制器是否都适配这个版本。需要准备的二进制文件有这些kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy、kubectl从Kubernetes官方Release页面下载kubernetes-server-linux-amd64.tar.gz解压出来。etcd从etcd官方Release页面下载etcd-v3.5.x-linux-amd64.tar.gz。注意etcd版本别太老跟Kubernetes 1.26搭配建议用3.5系列。cni-plugins从containernetworking官方Release页面下载cni-plugins-linux-amd64-v1.x.x.tgz。容器运行时我用containerd需要下载containerd的tar包和runc。下载方式如果碰到网络问题用国内镜像站加速即可。二进制版本之间要保证Kubernetes主版本一致etcd和CNI插件的版本兼容性以各自官方文档为准。我把所有二进制统一放到/opt/kubernetes/bin目录下然后软链到/usr/local/bin方便后续管理。整个部署过程中所有组件都保持一套版本别混搭。3. 全链路证书签发单Master部署里最绕也最关键的一步3.1 K8s证书体系到底有几层二进制安装K8s最劝退的地方就是证书。证书体系不梳理清楚后面每个组件起来都会报x509错误。简单来说K8s集群里有两类证书在同时工作一类是etcd集群内部通信用的另一类是Kubernetes控制面各个组件互相访问用的。从方向上来分大致是这样通信方向使用的证书kubectl - apiserveradmin证书apiserver - etcdetcd client证书etcd节点之间etcd peer证书apiserver对外服务apiserver server证书controller-manager - apiservercontroller-manager client证书scheduler - apiserverscheduler client证书kubelet - apiserverkubelet client证书apiserver - kubeletapiserver kubelet client证书这里有个容易懵的点client证书和server证书不是互斥的同一个组件可能既当客户端又当服务端。比如kubelet它主动连apiserver时是客户端要提供10250端口的调试接口时又是服务端。二进制部署时最省事的做法是统一签发证书让每个组件拿着一个包含正确SAN和CN的证书文件同时满足两种角色。证书生命周期管理也很重要所有证书的有效期最好统一规划到期前通过刷新kubeconfig重新签发来续期。3.2 cfssl生成CA与工作目录证书签发工具我用的是cfssl也可以整openssl但cfssl用JSON描述签名请求在批量生成证书时比openssl命令链更清晰。先把工具下载下来创建证书目录mkdir -p /etc/kubernetes/pki cd /etc/kubernetes/pki wget https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssl_1.6.4_linux_amd64 wget https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssljson_1.6.4_linux_amd64 chmod x cfssl_1.6.4_linux_amd64 cfssljson_1.6.4_linux_amd64 mv cfssl_1.6.4_linux_amd64 /usr/local/bin/cfssl mv cfssljson_1.6.4_linux_amd64 /usr/local/bin/cfssljson先创建CA配置文件和CA签名请求。CA是整个信任链的根所有其他证书都由它来签发。ca-config.json里定义了证书用途分组比如server auth和client auth后面生成证书时通过-profile参数引用{ signing: { default: { expiry: 87600h }, profiles: { kubernetes: { expiry: 87600h, usages: [signing, key encipherment, server auth, client auth] } } } }ca-csr.json则是CA自己的身份信息关键字段是CN和OO表示组织I在证书校验里一般不看但规范化起见还是区分好{ CN: kubernetes-ca, key: { algo: rsa, size: 2048 }, names: [ { C: CN, L: Beijing, ST: Beijing, O: k8s, OU: k8s } ] }然后生成CA根证书cfssl gencert -initca ca-csr.json | cfssljson -bare ca这一步会得到ca.pem和ca-key.pem后面的所有证书都靠这对密钥来签发。3.3 签发核心证书的关键参数核心证书里最容易出问题的是apiserver的server证书。它的SAN列表必须包含所有访问入口Master节点IP、Master节点主机名、Service网段的第一个IP、kubernetes.default、kubernetes.default.svc、kubernetes.default.svc.cluster.local、127.0.0.1。Service IP里我通常把10.96.0.1放进去这是kube-apiserver在Service网络里自动创建的虚拟IPPod里的组件访问集群内APIServer时走的就是这个地址。apiserver-csr.json{ CN: kube-apiserver, hosts: [ 192.168.100.10, 192.168.100.11, 10.96.0.1, 127.0.0.1, kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local ], key: { algo: rsa, size: 2048 }, names: [ { C: CN, L: Beijing, O: k8s, OU: k8s } ] }这里有个很关键的细节将来如果要加新的Master节点或通过新的IP访问apiserver必须同步修改SAN并重新签发证书。我第一次部署时漏了Node节点的IP结果Node上的kubelet访问apiserver时一直报证书校验失败排查了半天。签发完成后统一处理的命令cfssl gencert -caca.pem -ca-keyca-key.pem -configca-config.json \ -profilekubernetes apiserver-csr.json | cfssljson -bare apiserveretcd的证书要分三套peer证书给节点间通信用server证书给客户端访问etcd服务用client证书给apiserver访问etcd用。单Master场景下peer和server基本都指向同一个节点IP。hosts里必须包含etcd监听IP、127.0.0.1以及未来可能成为etcd节点的IP一步到位把扩展性留好。kubelet的证书这里多说一句。在二进制部署中我习惯把kubelet的client证书单独签一份而不是让kubelet启动后通过CSR自动签发。原因很简单自动签发流程需要apiserver配置了bootstrap token或RBAC授权这又增加了排障变量。手动签发时CN填kubelet所在节点的主机名O填system:nodes这是kubelet访问apiserver时的身份验证凭据。admin证书给kubectl用CN填adminO填system:masters这样kubectl的请求会被RBAC识别为管理员。同样的逻辑controller-manager、scheduler、kube-proxy各自生成自己的证书CN就按kube-controller-manager、kube-scheduler、system:kube-proxy来填。3.4 用kubeconfig把证书串起来证书是身份凭据但要真正使用还需要配合kubeconfig。kubeconfig的本质就是把“证书文件路径、CA路径、服务端地址、上下文”打包在一起。二进制部署时每个需要访问apiserver的组件都要有自己的kubeconfig。kubectl的kubeconfig我通常这样生成kubectl config set-cluster kubernetes \ --certificate-authority/etc/kubernetes/pki/ca.pem \ --embed-certstrue \ --serverhttps://192.168.100.10:6443 kubectl config set-credentials admin \ --client-certificate/etc/kubernetes/pki/admin.pem \ --client-key/etc/kubernetes/pki/admin-key.pem \ --embed-certstrue kubectl config set-context kubernetes \ --clusterkubernetes --useradmin kubectl config use-context kubernetescontroller-manager、scheduler、kubelet、kube-proxy的kubeconfig生成逻辑完全一样只是换证书文件和用户名。这里建议每个组件的kubeconfig放到独立目录比如/etc/kubernetes/manifests或者组件自己的配置目录下不要全部堆在/root/.kube里。4. 按顺序启动ETCD、Master组件、Worker组件4.1 ETCD先让“存储层”跑起来安装顺序上etcd必须第一个启动。它是整个集群的状态存储apiserver启动时会先连etcd连不上就直接崩溃循环。把etcd解压之后二进制放到/usr/local/bin配置文件放在/etc/etcd/。etcd的systemd unit文件如下[Unit] DescriptionEtcd Server Afternetwork.target [Service] Typenotify Useretcd Groupetcd ExecStart/usr/local/bin/etcd \ --nameetcd-master \ --data-dir/var/lib/etcd \ --listen-client-urlshttps://192.168.100.10:2379,https://127.0.0.1:2379 \ --advertise-client-urlshttps://192.168.100.10:2379 \ --listen-peer-urlshttps://192.168.100.10:2380 \ --initial-advertise-peer-urlshttps://192.168.100.10:2380 \ --initial-clusteretcd-masterhttps://192.168.100.10:2380 \ --client-cert-authtrue \ --cert-file/etc/kubernetes/pki/etcd-server.pem \ --key-file/etc/kubernetes/pki/etcd-server-key.pem \ --trusted-ca-file/etc/kubernetes/pki/ca.pem \ --peer-client-cert-authtrue \ --peer-cert-file/etc/kubernetes/pki/etcd-peer.pem \ --peer-key-file/etc/kubernetes/pki/etcd-peer-key.pem \ --peer-trusted-ca-file/etc/kubernetes/pki/ca.pem \ --initial-cluster-statenew \ --initial-cluster-tokenetcd-cluster-0 [Install] WantedBymulti-user.target这里面几个参数要重点理解Typenotifyetcd会用systemd通知机制上报启动完成如果配成simple系统会以为它一直在启动中依赖它的服务会提前启动。client-cert-auth和--trusted-ca-file开启客户端证书校验apiserver连etcd时必须出示受信任的client证书。peer-cert-auth和peer相关证书etcd节点之间互相通信时校验对方身份。etcd用户和目录要先准备好useradd -r -s /sbin/nologin etcd mkdir -p /var/lib/etcd chown etcd:etcd -R /var/lib/etcd /etc/kubernetes/pki注意不要把证书目录权限整个交给etcd用户我这里是为了演示方便更安全的做法是只给etcd用户需要的证书文件单独的读权限。启动后验证systemctl daemon-reload systemctl enable --now etcd ETCDCTL_API3 etcdctl \ --cacert/etc/kubernetes/pki/ca.pem \ --cert/etc/kubernetes/pki/etcd-client.pem \ --key/etc/kubernetes/pki/etcd-client-key.pem \ --endpointshttps://192.168.100.10:2379 endpoint health看到“healthy”就说明etcd通了。4.2 kube-apiserverK8s的总入口etcd就绪后下一个启动的是kube-apiserver。它是整个K8s唯一跟etcd直接交互的组件所有通过kubectl发起的请求都要经过它。apiserver的参数很多我挑核心的列一下[Service] ExecStart/usr/local/bin/kube-apiserver \ --etcd-cafile/etc/kubernetes/pki/ca.pem \ --etcd-certfile/etc/kubernetes/pki/apiserver-etcd-client.pem \ --etcd-keyfile/etc/kubernetes/pki/apiserver-etcd-client-key.pem \ --etcd-servershttps://192.168.100.10:2379 \ --secure-port6443 \ --bind-address0.0.0.0 \ --advertise-address192.168.100.10 \ --service-cluster-ip-range10.96.0.0/12 \ --service-node-port-range30000-32767 \ --client-ca-file/etc/kubernetes/pki/ca.pem \ --tls-cert-file/etc/kubernetes/pki/apiserver.pem \ --tls-private-key-file/etc/kubernetes/pki/apiserver-key.pem \ --service-account-key-file/etc/kubernetes/pki/ca.pem \ --service-account-issuerhttps://kubernetes.default.svc.cluster.local \ --service-account-signing-key-file/etc/kubernetes/pki/ca-key.pem \ --enable-admission-pluginsNodeRestriction \ --authorization-modeNode,RBAC \ --allow-privilegedtrue这里解释几个容易困惑的--service-account-key-file和--service-account-signing-key-fileapiserver需要用这个公钥来验证ServiceAccount token签名而controller-manager需要使用对应的私钥来签名token。它们必须配对否则ServiceAccount无法正常工作。这里我直接用CA公钥和CA私钥简单但足够。--etcd-certfile和--etcd-keyfile这是apiserver作为etcd客户端出示的证书不是apiserver自己的服务端证书。--bind-address0.0.0.0让apiserver监听所有网卡这样Node节点和后续新增的Master节点都能访问。如果只想让内网访问也可以绑到一个具体IP。apiserver的systemd unit文件里同样要指定User我用的是root但生产环境建议创建kubernetes用户并最小化权限分配。启动之后用curl验证curl --cacert /etc/kubernetes/pki/ca.pem https://127.0.0.1:6443/healthz返回ok就说明apiserver和etcd的链路通了。此时还没有controller-manager和schedulerkubectl get cs会看到它们Unhealthy这是正常的别急着报错。4.3 controller-manager与scheduler控制面的两个大脑controller-manager负责编排各种控制器比如节点控制器、端点控制器、命名空间控制器。scheduler负责把Pod调度到合适的Node上。它俩的启动参数相对简单都需要指向apiserver的kubeconfig并且controller-manager要多配两个跟ServiceAccount密钥相关的参数[Service] ExecStart/usr/local/bin/kube-controller-manager \ --kubeconfig/etc/kubernetes/kube-controller-manager.kubeconfig \ --service-account-private-key-file/etc/kubernetes/pki/ca-key.pem \ --root-ca-file/etc/kubernetes/pki/ca.pem \ --leader-electtrue \ --allocate-node-cidrstrue \ --cluster-cidr10.244.0.0/16 \ --node-cidr-mask-size24--allocate-node-cidrs和--cluster-cidr这两个参数很重要它们告诉controller-manager为每个Node自动划分一个Pod子网CNI插件比如Flannel会依据这个划分结果来分配Pod IP。如果这个参数不配后面Node加入集群后Flannel的网段分配会乱掉。scheduler的配置就简单了一个kubeconfig一条命令[Service] ExecStart/usr/local/bin/kube-scheduler \ --kubeconfig/etc/kubernetes/kube-scheduler.kubeconfig \ --leader-electtrue--leader-electtrue在单Master下没什么实际意义但保持这个参数开启将来扩展成多Master时就不用再改配置。这两个组件启动后用kubectl get cs可以在组件状态里看到它们变为Healthy。这里我遇到过一个问题controller-manager一直报leader选举失败原因是我给它的kubeconfig里的证书身份不对apiserver返回403导致它始终无法续约lease。看到这类问题先检查kubeconfig对应的证书CN是否符合RBAC绑定关系。4.4 kubelet与kube-proxy把Node节点拉进集群Master侧的三个管理组件跑起来后控制面基本能用了。接下来就是让Node节点真正加入集群。kubelet是Node上最核心的agent它负责Pod的生命周期管理。kubelet的启动方式跟前面几个组件不太一样除了命令行参数还需要一个yaml配置文件apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration address: 0.0.0.0 port: 10250 authentication: x509: clientCAFile: /etc/kubernetes/pki/ca.pem anonymous: enabled: false authorization: mode: Webhook cgroupDriver: systemd clusterDNS: - 10.96.0.10 clusterDomain: cluster.local failSwapOn: true maxPods: 110这里有两个容易踩的坑一是cgroupDriver必须和容器运行时保持一致。我用containerd时如果kubelet是systemd而containerd是cgroupfsPod启动会报failed to create pod sandbox。二是clusterDNS一定要配成CoreDNS的Service IP不然Pod里解析服务名时找不到DNS。kubelet的systemd unit[Service] ExecStart/usr/local/bin/kubelet \ --kubeconfig/etc/kubernetes/kubelet.kubeconfig \ --config/etc/kubernetes/kubelet.yaml \ --container-runtime-endpointunix:///run/containerd/containerd.sock \ --node-labelsnode-role.kubernetes.io/node --pod-infra-container-imagexxx/pause:3.9--pod-infra-container-image这个参数指定pause镜像地址。pause容器是每个Pod里最先启动的基础容器它在集群里的作用是“占坑”和“共享网络命名空间”。默认地址有时在本地环境拉不下来需要改成国内镜像源。这个值也可以不加改成在containerd配置里设置sandbox_image效果一样。Node节点的kubelet启动后回到Master执行kubectl get nodes如果看到了这个Node但状态是NotReady不要慌这通常是因为CNI网络插件还没装。等装完Flannel或Calico后状态会变成Ready。kube-proxy的配置也有讲究。现在大多数集群用IPVS模式替代老的iptables模式性能更好但需要先确保系统有ipset和ipvsadmyum install -y ipset ipvsadmkube-proxy的systemd unit[Service] ExecStart/usr/local/bin/kube-proxy \ --kubeconfig/etc/kubernetes/kube-proxy.kubeconfig \ --proxy-modeipvs \ --cluster-cidr10.244.0.0/16 \ --masquerade-alltrue--masquerade-all为true意味着所有发往其他网段的流量都做SNAT这在内网环境里可以避免跨节点Pod通信时出现回包路由问题。如果只是想最小化配置这个参数也可以不开但要确保Node之间的路由是通的。5. CNI网络插件让Pod真正互通5.1 单Master环境的CNI选型控制面和节点都Ready之后如果不装CNI集群里每个Pod只有一个孤立的网络命名空间无法和其他节点上的Pod通信。CNI插件负责把Pod的网络命名空间接进宿主机网络。单Master环境里选型建议很简单Flannel最简单VXLAN或host-gw模式配置量小适合学习和轻量环境。我这次就用它。Calico支持网络策略性能也不错但组件多配置复杂生产环境如果要用网络策略优先考虑。CiliumeBPF路线功能最丰富但版本要求和内核版本耦合度高单Master测试环境没必要给自己加难度。5.2 安装Flannel与参数对齐装Flannel最经典的方式就是apply一个kube-flannel.yml。但直接用默认配置很容易出问题默认的Pod网段是10.244.0.0/16如果你的--cluster-cidr不是这个网段Flannel的daemonset会因为网段不一致而一直CrashLoopBackOff。我这次用踩过坑换来的经验是先把yaml下载到本地修改两个地方再apply。一是net-conf.json里的Network段改成10.244.0.0/16这里的取值必须和之前controller-manager的--cluster-cidr一致。二是如果Flannel镜像拉不下来把image的地址替换成能访问的镜像源。wget https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml kubectl apply -f kube-flannel.yml装完验证kubectl get pods -n kube-flannel kubectl get nodes看到Flannel pod运行中节点Ready就可以在Master上用kubectl run起一个测试Pod来验证Pod之间能不能ping通。如果集群里有多个网卡Flannel有时候会选错网卡导致跨主机通信失败可以在Flannel的daemonset环境变量里加- name: FLANNEL_OPTIONS value: --ifaceeth0指定对外通信的网卡名称这个参数在有双网卡的测试机上非常实用。6. 集群验证与我在部署过程中踩过的坑6.1 从零到Ready的检查清单集群搭完不是终点我习惯按这个顺序把关键链路都验一遍检查项命令预期结果etcd健康etcdctl endpoint healthhealthyapiserver健康curl /healthzok控制面组件kubectl get csHealthy节点状态kubectl get nodes -o wideReadyCoreDNSkubectl get pods -n kube-systemRunningFlannelkubectl get pods -n kube-flannelRunningService域名kubectl run busybox --imagebusybox -- sleep 300再exec进去nslookup kubernetes.default能解析到10.96.0.1Pod网络两个节点分别起Pod互相ping通这里还有一个很多人忽略的验证点kubelet的日志里有没有反复刷新警告。journalctl -u kubelet -f如果看到频繁出现的PLEG不健康或者failed to ensure that a pod exists说明kubelet和容器运行时之间的协作有问题要尽早处理。6.2 我踩过的坑这次部署过程中有几个坑花的时间特别长值得单独列一下。第一个是证书SAN漏配。我第一次生成apiserver证书时hosts里忘了填Node节点的IP。结果kubectl从Node上执行get nodes时apiserver校验客户端证书发现节点的IP不在证书SAN里直接拒了。这个坑最隐蔽因为从Master上用kubectl访问一切正常只有节点侧报错。解决办法就是重新生成证书时把规划中所有节点的IP都填进hosts然后重启apiserver。第二个是cgroupDriver不一致。我起先用的是containerd默认配置kubelet这边配的是systemdPod一直创建沙箱失败。查日志看到failed to parse kubelet cgroup driver才意识到两边要统一。containerd的配置文件在/etc/containerd/config.toml里把SystemdCgroup改成true重启containerd后问题解决。第三个是CoreDNS的sandbox_image拉不下来。Pod虽然调度成功但sandbox容器创建失败表现为CoreDNS一直ContainerCreating。这个问题的根因是pause镜像地址没法访问。解决方法是把containerd配置里的sandbox_image改成国内镜像源或者给kubelet额外指定--pod-infra-container-image参数。第四个是Flannel网段冲突。我第一次没改默认的Pod网段和controller-manager里规划的--cluster-cidr不一样Flannel pod起来后网络一直异常。实际上这里只要保证Flannel net-conf.json的Network、controller-manager的--cluster-cidr、kube-apiserver的--service-cluster-ip-range这三个网段互不重叠且各自身一致就不会出乱子。6.3 排查工具箱经历过这几个坑后我养成了一个习惯排查K8s问题时按“下层往上盘”的顺序来。先看etcd是否正常再看apiserver日志里有没有大面积错误然后看controller-manager和scheduler有没有leader选举异常最后才看Node侧。Node侧优先查kubelet日志再查容器运行时日志。执行这些查看时几个常用命令journalctl -u etcd -f journalctl -u kube-apiserver -f journalctl -u kubelet -f journalctl -u containerd -f kubectl describe node 192.168.100.11 kubectl describe pod -n kube-system pod名 kubectl get pod -n kube-system -o widekubectl describe这个命令的信息量非常大事件、镜像、挂载、端口映射、状态条件都在里面。遇到Pod调度不了、连不上镜像、探针失败之类的第一条命令应该是describe而不是翻日志。还有一个小技巧修改了apiserver或etcd的systemd unit和证书后很多问题其实不是配置写错了而是服务没有真正重启成功。我习惯改完任何配置文件后都执行systemctl daemon-reload systemctl restart 服务名 systemctl status 服务名 | head -20确认状态不是failed才继续避免把“服务没起来”误判成“配置有问题”。这次把单Master的二进制部署走完之后最大的体会是K8s的组件其实不复杂复杂的是它们之间的信任关系和数据流转。证书、kubeconfig、网段规划、cgroupDriver这些点一旦串起来你对后面所有排障都会更有底气。这个部署结构后面如果要扩成高可用证书和配置文件只需要增加新IP进SAN、把单实例etcd改成集群、把--leader-elect继续保持开启就行。装过一遍二进制你会发现自己看那些kubeadm生成的静态Pod清单时终于不再是一头雾水了。