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-proxy,Node上只跑kubelet和kube-proxy,再加上容器运行时。如果想省机器,也可以在Master上打掉taint让它也调度业务Pod,但测试环境这么玩可以,别把生产环境搞成这样。

内存和CPU的底线我给个参考:Master节点建议至少2核4G,etcd和apiserver都挺吃内存的,尤其是etcd,内存不足会频繁触发压缩和fsync延迟;Node节点2核2G起步,具体看业务需求。磁盘方面,etcd的数据目录最好用单独的盘,IO性能会直接影响整个集群的API响应速度。

IP和网段规划是安装前就必须定下来的,后面证书里的SAN、apiserver的--service-cluster-ip-range、CNI插件的Pod网段都要围绕这个规划展开。我这次用的规划是:

项目取值
Master节点IP192.168.100.10
Node节点IP192.168.100.11
Pod网段10.244.0.0/16
Service网段10.96.0.0/12
Cluster Domaincluster.local
DNS 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-apiserver6443
etcd2379、2380
kubelet10250、10255
kube-proxy30000-32767(NodePort)
flannel/CNI8472(VXLAN)

SELinux不关的话,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和O,O表示组织,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网络里自动创建的虚拟IP,Pod里的组件访问集群内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 -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json \ -profile=kubernetes apiserver-csr.json | cfssljson -bare apiserver

etcd的证书要分三套: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填admin,O填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-certs=true \ --server=https://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-certs=true kubectl config set-context kubernetes \ --cluster=kubernetes --user=admin kubectl config use-context kubernetes

controller-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] Description=Etcd Server After=network.target [Service] Type=notify User=etcd Group=etcd ExecStart=/usr/local/bin/etcd \ --name=etcd-master \ --data-dir=/var/lib/etcd \ --listen-client-urls=https://192.168.100.10:2379,https://127.0.0.1:2379 \ --advertise-client-urls=https://192.168.100.10:2379 \ --listen-peer-urls=https://192.168.100.10:2380 \ --initial-advertise-peer-urls=https://192.168.100.10:2380 \ --initial-cluster=etcd-master=https://192.168.100.10:2380 \ --client-cert-auth=true \ --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-auth=true \ --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-state=new \ --initial-cluster-token=etcd-cluster-0 [Install] WantedBy=multi-user.target

这里面几个参数要重点理解:

  • Type=notify:etcd会用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_API=3 etcdctl \ --cacert=/etc/kubernetes/pki/ca.pem \ --cert=/etc/kubernetes/pki/etcd-client.pem \ --key=/etc/kubernetes/pki/etcd-client-key.pem \ --endpoints=https://192.168.100.10:2379 endpoint health

看到“healthy”就说明etcd通了。

4.2 kube-apiserver:K8s的总入口

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-servers=https://192.168.100.10:2379 \ --secure-port=6443 \ --bind-address=0.0.0.0 \ --advertise-address=192.168.100.10 \ --service-cluster-ip-range=10.96.0.0/12 \ --service-node-port-range=30000-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-issuer=https://kubernetes.default.svc.cluster.local \ --service-account-signing-key-file=/etc/kubernetes/pki/ca-key.pem \ --enable-admission-plugins=NodeRestriction \ --authorization-mode=Node,RBAC \ --allow-privileged=true

这里解释几个容易困惑的:

  • --service-account-key-file和--service-account-signing-key-file:apiserver需要用这个公钥来验证ServiceAccount token签名,而controller-manager需要使用对应的私钥来签名token。它们必须配对,否则ServiceAccount无法正常工作。这里我直接用CA公钥和CA私钥,简单但足够。
  • --etcd-certfile和--etcd-keyfile:这是apiserver作为etcd客户端出示的证书,不是apiserver自己的服务端证书。
  • --bind-address=0.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和scheduler,kubectl 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-elect=true \ --allocate-node-cidrs=true \ --cluster-cidr=10.244.0.0/16 \ --node-cidr-mask-size=24

--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-elect=true

--leader-elect=true在单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是cgroupfs,Pod启动会报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-endpoint=unix:///run/containerd/containerd.sock \ --node-labels=node-role.kubernetes.io/node= --pod-infra-container-image=xxx/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和ipvsadm:

yum install -y ipset ipvsadm

kube-proxy的systemd unit:

[Service] ExecStart=/usr/local/bin/kube-proxy \ --kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig \ --proxy-mode=ipvs \ --cluster-cidr=10.244.0.0/16 \ --masquerade-all=true

--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:支持网络策略,性能也不错,但组件多,配置复杂,生产环境如果要用网络策略优先考虑。
  • Cilium:eBPF路线,功能最丰富,但版本要求和内核版本耦合度高,单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: "--iface=eth0"

指定对外通信的网卡名称,这个参数在有双网卡的测试机上非常实用。

6. 集群验证与我在部署过程中踩过的坑

6.1 从零到Ready的检查清单

集群搭完不是终点,我习惯按这个顺序把关键链路都验一遍:

检查项命令预期结果
etcd健康etcdctl endpoint healthhealthy
apiserver健康curl /healthzok
控制面组件kubectl get csHealthy
节点状态kubectl get nodes -o wideReady
CoreDNSkubectl get pods -n kube-systemRunning
Flannelkubectl get pods -n kube-flannelRunning
Service域名kubectl run busybox --image=busybox -- sleep 300再exec进去nslookup kubernetes.default能解析到10.96.0.1
Pod网络两个节点分别起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这边配的是systemd,Pod一直创建沙箱失败。查日志看到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 wide

kubectl 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清单时,终于不再是一头雾水了。

相关新闻

k-medoids聚类算法MATLAB实现与代码详解:从原理到可视化

k-medoids聚类算法MATLAB实现与代码详解:从原理到可视化

看到这份标题&#xff0c;第一反应是很典型的“课程作业/入门项目”配置——k-medoids 聚类算法的 MATLAB 源代码&#xff0c;数据导入和画图部分带中文注释。别看它结构简单&#xff0c;这套东西背后把聚类算法的几个关键环节全串起来了&#xff1a;数据读入、距离计算、中心点…

2026/10/21 0:44:31 阅读更多 →
C++20 ranges库实战:视图、管道与惰性求值替代STL迭代器

C++20 ranges库实战:视图、管道与惰性求值替代STL迭代器

1. 为什么是 ranges&#xff1a;先把三个老痛点摆出来1.1 传统 STL 算法的三个痛点你大概率写过类似的代码&#xff1a;一个容器&#xff0c;先过滤出符合条件的元素&#xff0c;再对每个元素做变换&#xff0c;最后把结果收集到另一个容器里。用传统 STL 算法写&#xff0c;就…

2026/10/21 0:44:31 阅读更多 →
XXL-JOB 企业微信机器人报警实战:自定义 JobAlarm 实现与避坑指南

XXL-JOB 企业微信机器人报警实战:自定义 JobAlarm 实现与避坑指南

1. 任务调度报警的痛点与破局思路做过分布式任务调度的同行大概都有过这种体验&#xff1a;凌晨两点&#xff0c;某个核心对账任务挂了&#xff0c;XXL-JOB 的控制台安安静静&#xff0c;直到第二天业务方打电话来问“昨天的数据怎么没跑出来”&#xff0c;你才发现调度失败了一…

2026/10/21 0:44:31 阅读更多 →

最新新闻

Quartz动态设置并发数量

Quartz动态设置并发数量

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 2:48:00 阅读更多 →
AI Agent ROI评估指南:从成本结构到收益测算全解析

AI Agent ROI评估指南:从成本结构到收益测算全解析

1. 先别急着算账&#xff0c;想清楚AI Agent的ROI和传统IT项目有什么不同这两年不少公司都开始折腾AI Agent&#xff0c;有的把它接到客服系统&#xff0c;有的让它帮着写代码&#xff0c;还有的用它处理合同和报表。但当你真坐下来算这笔账时&#xff0c;会发现一个尴尬的问题…

2026/10/21 2:48:00 阅读更多 →
基于卷积神经网络的食物图像识别实战:从ResNet迁移学习到部署

基于卷积神经网络的食物图像识别实战:从ResNet迁移学习到部署

简介&#xff1a;基于卷积神经网络的食物图像识别项目完整代码与数据包&#xff0c;面向具备Python基础并希望实践CNN图像分类的开发者与学习者。资源围绕TensorFlow与tf.keras构建食物识别模型&#xff0c;涵盖数据预处理、网络定义、训练评估与预测全流程&#xff0c;并引入迁…

2026/10/21 2:48:00 阅读更多 →
SECS-II/HSMS调试工具实战:从协议原理到模拟器应用

SECS-II/HSMS调试工具实战:从协议原理到模拟器应用

简介&#xff1a;面向半导体制造与MES系统集成场景&#xff0c;这份SECS-II HSMS调试工具模拟器适合设备工程师、上位机开发人员使用。它可模拟客户端与服务端&#xff0c;用于验证自定义程序是否符合SECS-II通信标准&#xff0c;减少现场联调时的错误与返工。压缩包为rar格式&…

2026/10/21 2:48:00 阅读更多 →
eNSP校园网拓扑设计实战:VLAN划分、DHCP配置与静态路由全解析

eNSP校园网拓扑设计实战:VLAN划分、DHCP配置与静态路由全解析

简介&#xff1a;面向计算机网络期末设计、课程设计及eNSP仿真练习的完整项目资料包&#xff0c;围绕某中学校园网络拓扑设计展开。方案包含网络拓扑选型、综合布线系统规划、教学楼/行政楼/宿舍接入设计&#xff0c;以及VLAN划分、IP地址分配、路由协议配置等模拟配置要点&…

2026/10/21 2:48:00 阅读更多 →
思科模拟器6.2:从安装到避坑的完整实战指南

思科模拟器6.2:从安装到避坑的完整实战指南

简介&#xff1a;这是一份思科模拟器6.2安装包&#xff0c;官方名称为Packet Tracer&#xff0c;面向网络技术学习者、思科认证考生以及开展网络实验教学的教师。软件以图形化方式搭建虚拟网络环境&#xff0c;可模拟路由器、交换机、无线接入点、防火墙等设备&#xff0c;支持…

2026/10/21 2:47:00 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →