如果有人问我,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节点IP | 192.168.100.10 |
| Node节点IP | 192.168.100.11 |
| Pod网段 | 10.244.0.0/16 |
| Service网段 | 10.96.0.0/12 |
| Cluster Domain | cluster.local |
| DNS Service IP | 10.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-apiserver | 6443 |
| etcd | 2379、2380 |
| kubelet | 10250、10255 |
| kube-proxy | 30000-32767(NodePort) |
| flannel/CNI | 8472(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 -> apiserver | admin证书 |
| apiserver -> etcd | etcd client证书 |
| etcd节点之间 | etcd peer证书 |
| apiserver对外服务 | apiserver server证书 |
| controller-manager -> apiserver | controller-manager client证书 |
| scheduler -> apiserver | scheduler client证书 |
| kubelet -> apiserver | kubelet client证书 |
| apiserver -> kubelet | apiserver 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 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填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 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] 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 ipvsadmkube-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 health | healthy |
| apiserver健康 | curl /healthz | ok |
| 控制面组件 | kubectl get cs | Healthy |
| 节点状态 | kubectl get nodes -o wide | Ready |
| CoreDNS | kubectl get pods -n kube-system | Running |
| Flannel | kubectl get pods -n kube-flannel | Running |
| 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 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清单时,终于不再是一头雾水了。