最近有个做运维的朋友问我:kubeadm 都这么成熟了,还有必要折腾二进制部署么?
我当时正在给一套生产环境规划多主多从架构,正好在这块踩了不少坑。kubeadm 确实快,点两下就能拉起集群,但它把很多东西都封装成了黑盒——证书怎么签的、组件参数怎么拼的、apiserver 和 etcd 之间到底怎么认证的,出了问题你很难第一时间定位。换成二进制部署,每个组件都是自己亲手拉起来的,配置文件逐行写,逻辑链路自然就清楚了。这篇文章把我在 Ubuntu 24.04 上基于 containerd 部署 K8S 1.35.0 二进制多主多从集群的完整过程记录下来,包括架构规划、证书体系设计、etcd 集群搭建、各组件配置细节,以及我在实际部署中踩过的坑和排查思路。适合想深入理解 Kubernetes 组件交互、或者需要在离线环境/定制化环境中交付集群的读者。
1. 部署前必须想清楚的事——架构设计远远先于命令执行
1.1 为什么选二进制部署而不是 kubeadm
kubeadm 的定位是"快速拉起一个标准集群",它帮你把 kubelet、kube-proxy、control plane 组件以静态 Pod 或 systemd 服务的形式装好,证书、kubeconfig、RBAC 这些全都自动处理。好处是快,坏处是出问题时你面对的是一个黑盒。比如证书 SAN 里漏了某个 IP、apiserver 连不上 etcd、kubelet 的 bootstrap 证书轮换失败,这类问题用 kubeadm 排查时你还需要反向去猜它到底生成了一份什么配置。
二进制部署则把每一步都摊开在眼前:
- 组件从哪里下载、怎么解压、放到哪个目录,完全由你定义。
- 证书体系从 CA 开始手工签发,每一个证书的用途、SAN、签发者都清清楚楚。
- apiserver、controller-manager、scheduler 各自以独立的 systemd 服务运行,参数逐行可查。
- 更容易做离线交付或者定制化改造,比如替换网络插件、调整审计日志策略、对接企业内部 PKI,二进制方式完全不依赖 kubeadm 的固定逻辑。
当然,二进制方式也有明显的代价:首次部署需要准备的步骤多,容易出错,对操作者的理解深度要求高。但反过来想,你一旦手工把集群拉起来一遍,后面再遇到任何集群故障,脑子里对组件间的调用关系已经有了完整的图景。
1.2 多主多从的组件分布与流量路径
这次部署采用 3 主 3 从的拓扑。3 个 master 节点运行 apiserver、controller manager、scheduler 以及 etcd 集群(这里选择 etcd 与控制面混部,适合中小规模环境,如果数据量很大,建议把 etcd 拆到独立节点)。3 个 worker 节点运行 kubelet、kube-proxy 及业务负载。
在动手之前,先画清楚各组件的数据流:
- 用户或客户端通过负载均衡器(我这里用 HAProxy + Keepalived 提供虚拟 IP)访问任意一个 master 的 6443 端口,请求进入 kube-apiserver。
- apiserver 是所有组件通信的唯一入口。它需要访问 etcd 集群读写集群状态,同时接收 controller-manager 和 scheduler 的连接——这两个组件自身不直接对外提供服务,它们通过 kubeconfig 指向 apiserver 完成认证,然后持续 watch 资源变化。
- kubelet 运行在每个节点上,主动向 apiserver 注册节点、上报状态,并从 apiserver 获取分配给本节点的 Pod 定义。
- kube-proxy 通过 watch apiserver 中的 Service/EndpointSlice 资源,维护节点上的转发规则。
这套架构里有一个很关键的细节:controller-manager 和 scheduler 对 apiserver 的访问走的是内部 kubeconfig,而 kubelet 访问 apiserver 走的是客户端证书认证。所以在证书体系设计阶段,就要把每个角色的身份区分好,避免一个证书到处用。
1.3 版本选型与下载清单
K8S 1.35.0 属于较新的版本,部署时需要注意内核版本和系统版本的兼容性。Ubuntu 24.04 默认内核是 6.8 系列,配合 containerd 2.x 使用比较稳妥。以下是本次使用的版本组合:
| 组件 | 版本 | 说明 |
|---|---|---|
| Kubernetes | v1.35.0 | 官方二进制 tar 包,内含所有组件 |
| etcd | v3.5.18 | 3.5 系列稳定性比较好 |
| containerd | 2.0.x | 使用 CRI 集成方式,无需额外安装 cri-containerd |
| CNI plugins | v1.6.0 | 提供基础网络插件支持 |
| cfssl | 1.6.x | 用于签发集群证书 |
下载地址方面,Kubernetes 二进制包从官方 GitHub Releases 页面获取,etcd 和 containerd 同样从官方仓库下载。下载之后必须做校验,官方包一般会提供 sha512 或 sha256 校验值,这一步能避免很多莫名其妙的问题,尤其是离线中转场景。
2. 环境基线:主机规划、内核参数与 containerd 落地
2.1 主机规划与系统基础配置
节点规划表如下(这里用私有网段示例,实际按企业环境替换):
| 主机名 | IP | 角色 |
|---|---|---|
| k8s-master01 | 192.168.10.11 | control plane / etcd |
| k8s-master02 | 192.168.10.12 | control plane / etcd |
| k8s-master03 | 192.168.10.13 | control plane / etcd |
| k8s-worker01 | 192.168.10.21 | worker |
| k8s-worker02 | 192.168.10.22 | worker |
| k8s-worker03 | 192.168.10.23 | worker |
| k8s-lb | 192.168.10.10 | 虚拟 IP(Keepalived VIP) |
所有节点在部署前先做几件基础工作:关闭 swap,原因是 kubelet 对 swap 的支持虽然从 1.22 开始逐步放开,但生产环境仍然建议关掉,避免内存回收影响 Pod 稳定性;禁用防火墙或者放行必要端口,Ubuntu 自带的 ufw 如果不关,后面组件之间通信会被莫名阻断;设置主机名和 hosts 解析。在 hosts 里建议不仅写节点 IP 对应关系,还要加一条虚拟 IP 对应 apiserver 域名的记录,比如192.168.10.10 apiserver.k8s.local,这样各节点的 kubeconfig 里可以直接用域名访问 apiserver,后续如果换 LB 地址不需要重新分发 kubeconfig。
SSH 免密登录也在这个阶段配置好。二进制部署经常需要在三台 master 之间互传证书和配置,没有免密会很痛苦。用 ssh-keygen 生成密钥后,把公钥分发到所有节点即可。
2.2 内核参数与模块加载
Kubernetes 集群对内核参数有明确要求。主要调整以下几项,把 net.bridge.bridge-nf-call-iptables 设为 1,确保经过 bridge 的流量也能被 iptables 规则处理,这是 Kubernetes Service 和 Pod 网络正常工作的前提。net.ipv4.ip_forward 必须为 1,否则容器网络无法转发数据包。此外还需要加载 overlay 和 br_netfilter 这两个内核模块,containerd 的 overlayfs 存储驱动依赖 overlay 模块,而 bridge 流量过滤依赖 br_netfilter。
写入配置文件时注意持久化。在 /etc/modules-load.d/k8s.conf 里写上 overlay 和 br_netfilter,在 /etc/sysctl.d/k8s.conf 里写上上述参数,然后执行 modprobe 加载模块、sysctl --system 应用参数。这一步做完建议 reboot 一次再继续,避免模块加载问题在集群运行后才暴露。
2.3 containerd 部署与配置细节
containerd 的安装方式有两种,用 apt 直接装或者用官方二进制包解压。推荐后者,因为 apt 源里的版本可能滞后,而且二进制包解压到 /usr/local 目录即可运行,方便统一管理。安装 containerd 之后并没有默认配置文件,需要先执行containerd config default > /etc/containerd/config.toml生成默认配置,然后修改几个关键位置。
第一个是 sandbox_image。默认配置里指向的是 registry.k8s.io/pause:3.x 镜像,国内网络环境拉取可能超时。我一般改成registry.aliyuncs.com/google_containers/pause:3.10或者内网镜像仓库对应的地址。这里要特别注意:K8S 1.35.0 对 pause 镜像版本有要求,太旧版本可能无法正常启动 Pod 沙箱。
第二个是 SystemdCgroup。找到SystemdCgroup = false改成SystemdCgroup = true。这个参数决定了 containerd 采用哪种 cgroup driver 管理容器资源。Ubuntu 24.04 默认使用 systemd 作为 init 系统,如果 containerd 用 cgroupfs 而 kubelet 用 systemd,两个组件对 cgroup 的管理方式不一致,轻则节点状态异常,重则 Pod 无法启动。这个锅我背过不止一次,务必检查。
第三个是镜像仓库配置。在企业内网环境通常需要配置 mirror。containerd 2.x 支持在 config.toml 里通过 [plugins."io.containerd.grpc.v1.cri".registry.mirrors] 段落配置,格式和旧版本略有不同,以官方文档为准。配置完成后重启 containerd 服务,并用ctr ns list或crictl version验证启动是否正常。crictl 需要单独安装,它是排查 CRI 运行时问题的最常用工具。
3. 证书体系:牵一发动全身的 PKI 设计
3.1 证书规划清单与核心原则
Kubernetes 集群内部几乎所有的安全通信都依赖 TLS 证书。二进制部署时,证书体系的合理设计直接决定了集群能不能健康运行。需要签发的证书包括:
- CA 根证书(etcd CA 和 Kubernetes CA 可以合并也可以分开,建议分开,这样风险隔离更好)
- etcd 服务端证书:供 etcd 各节点之间 peer 通信、以及客户端访问 etcd 使用
- kube-apiserver 服务端证书:需要包含所有 master 节点 IP、虚拟 IP、域名、Service 网段中第一个 IP(通常是 kubernetes.default 解析到的地址)
- kube-controller-manager 客户端证书
- kube-scheduler 客户端证书
- kubelet 客户端证书(支持自动轮换)
- kube-proxy 客户端证书
- admin 用户证书:用于 kubectl 访问集群
这里有一个最常见的坑:apiserver 证书的 SAN 列表少填了虚拟 IP 或 Service IP。一旦少填,kubectl 通过虚拟 IP 访问 apiserver 时证书校验失败,报 x509: certificate is valid for xxx, not xxx。而且这类问题只影响通过该 IP 访问的场景,用节点 IP 访问可能完全正常,隐蔽性很强。所以签发 apiserver 证书时,把下面这些全部列进去:三台 master 的 IP、虚拟 IP、hostname(k8s-master01/02/03)、域名 apiserver.k8s.local、kubernetes.default.svc、kubernetes.default.svc.cluster.local、127.0.0.1、以及 Service 网段第一个 IP(例如 10.96.0.1)。
3.2 用 cfssl 还是 openssl
生成证书的工具链有两种选择。cfssl 是 cloudflare 推出的 PKI 工具,配置文件是 JSON 格式,生成逻辑更贴合 Kubernetes 的证书需求。openssl 是传统工具,写配置文件时自由度更高。我在实际项目中两种都用过:cfssl 适合一次性批量签发多张证书,配置模板复用性强;openssl 适合临场单张签发生成,但是多条命令组合容易写错。
这次推荐 cfssl,原因是 Kubernetes 证书的使用场景里,需要重复签发多个具有不同 CN 和 SAN 的证书,cfssl 用配置文件一跑就完事,不容易漏字段。安装 cfssl 时注意,它会拆成 cfssl、cfssljson、cfssl-certinfo 三个工具。生成证书时用到 cfssl 生成 CSR、cfssljson 把 JSON 输出转成文件、cfssl-certinfo 校验证书内容。
3.3 CA 与组件证书的签发过程
先创建 CA 配置文件 ca-config.json,里面定义证书的有效期(企业内部建议 10 年)和使用场景(client/server/peer)。再创建 ca-csr.json 定义 CA 的 CN 和 key 大小,这里 CN 建议写一个不容易和集群内组件混淆的名字,比如 k8s-ca。执行cfssl gencert -initca ca-csr.json | cfssljson -bare ca得到 ca.pem 和 ca-key.pem,然后根据同样的逻辑签发 etcd CA。
组件证书签发时,通过 hostname 字段指定 SAN。以 apiserver 为例:
{ "CN": "kube-apiserver", "hosts": [ "192.168.10.11", "192.168.10.12", "192.168.10.13", "192.168.10.10", "10.96.0.1", "127.0.0.1", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local", "apiserver.k8s.local" ], "key": { "algo": "rsa", "size": 2048 } }签发命令统一为cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server-csr.json | cfssljson -bare kube-apiserver。注意 profile 的选择要与 ca-config.json 里定义的 profiles 对应,server 和 client 不要搞混。etcd 证书要选 profile 为 peer,Kubernetes 内的组件证书用 client 或 server。
3.4 证书分发与文件权限避坑
证书生成之后,需要按组件分发到对应节点的指定目录。我习惯统一放到 /etc/kubernetes/pki 下,目录结构清晰,方便后续查看。分发完成之后必须关注文件权限:kubelet 运行时会读取证书目录,如果权限过宽可能报警,生产环境一般设置成 644 权限、root 属主即可。kubeconfig 文件权限设置为 600,因为这些文件里包含私钥信息。
另外,kubelet 的证书轮换机制在二进制部署中建议直接开启。在 kubelet 配置里设置 rotateCertificates: true,并在 apiserver 中配置对应的 RBAC 权限,允许 kubelet 通过 node authorization 模式申请新证书。这样证书到期后 kubelet 会自动续签,不需要人工介入。初期忘了配置轮换,运营一年后证书突然集体过期,那种"全家桶式故障"的滋味不好受。
4. etcd 集群:先把控制面的存储底座立起来
4.1 etcd 节点间通信与成员发现机制
etcd 集群需要三个节点互相通信,peer 端口默认 2380,client 端口默认 2379。集群成员发现通过 initial-cluster 参数静态指定,每一个节点启动时都会带上自己和其他两个节点的地址。这里有一个常见误区:initial-cluster 里的地址格式必须是hostname=https://192.168.10.11:2380,等号左边的名字要与节点的 name 参数完全一致,否则 etcd 启动会报成员冲突。
我先在三台 master 上分别建好 etcd 数据目录和证书目录。数据目录位置建议单独规划,比如 /var/lib/etcd,确保有足够的磁盘空间。生产环境 etcd 对磁盘 IO 比较敏感,最好在单独的磁盘或 SSD 上。etcd 的 systemd 服务配置里,关键参数有这几个:--name 指定节点名;--data-dir 指定数据目录;--listen-client-urls 监听地址,建议同时监听 127.0.0.1 和本机 IP,方便本机维护和远程访问;--listen-peer-urls 监听 peer 通信,必须监听集群可达的地址;--initial-advertise-peer-urls 用于其他节点发现本节点;--advertise-client-urls 用于 apiserver 访问。
证书相关的参数同样重要。--cert-file 和 --key-file 是 etcd 服务端证书;--peer-cert-file 和 --peer-key-file 是 peer 通信证书;--client-cert-auth=true 表示启用客户端证书校验;--peer-client-cert-auth=true 同理。
4.2 三节点 etcd 的 systemd 配置与服务启动
以 k8s-master01 为例,/etc/systemd/system/etcd.service 主要内容如下:
[Unit] Description=etcd server After=network.target [Service] Type=notify ExecStart=/usr/local/bin/etcd \ --name=k8s-master01 \ --data-dir=/var/lib/etcd \ --listen-client-urls=https://192.168.10.11:2379,https://127.0.0.1:2379 \ --advertise-client-urls=https://192.168.10.11:2379 \ --listen-peer-urls=https://192.168.10.11:2380 \ --initial-advertise-peer-urls=https://192.168.10.11:2380 \ --initial-cluster=k8s-master01=https://192.168.10.11:2380,k8s-master02=https://192.168.10.12:2380,k8s-master03=https://192.168.10.13:2380 \ --initial-cluster-state=new \ --cert-file=/etc/kubernetes/pki/etcd-server.pem \ --key-file=/etc/kubernetes/pki/etcd-server-key.pem \ --client-cert-auth=true \ --trusted-ca-file=/etc/kubernetes/pki/etcd-ca.pem \ --peer-cert-file=/etc/kubernetes/pki/etcd-peer.pem \ --peer-key-file=/etc/kubernetes/pki/etcd-peer-key.pem \ --peer-client-cert-auth=true \ --peer-trusted-ca-file=/etc/kubernetes/pki/etcd-ca.pem Restart=on-failure RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.targetType=notify 这里有一个细节:etcd 通过 systemd notify 机制向 systemd 报告自己启动完成,如果配置错了,服务明明已经运行但 systemctl 显示仍在 activating,排查起来很迷惑。三台节点配好之后,先启动第一台,等它正常进入运行状态再启动第二台、第三台。全部启动后,用etcdctl endpoint health --cluster检查集群健康状态。如果出现超时,多半是证书里的 SAN 没包含对端地址,或者防火墙没放行 2380 端口,用 etcdctl 加 --debug 参数可以看到具体卡在哪一步。
4.3 数据安全与备份策略
etcd 是整个集群唯一的持久化状态存储,备份不是可选而是必须。二进制部署环境下,一般用 etcdctl snapshot save 做定时快照。生产环境建议每小时全量快照一次,保留 24 小时的备份文件,同时每日异地备份。快照文件可以压缩后归档,etcd 的数据通常不会特别大,但恢复流程要提前演练。恢复的时候,先停掉所有 etcd 节点,用 etcdctl snapshot restore 在每台节点上把快照恢复到本地数据目录,再重新启动 etcd 集群。这个流程里的一个关键是:恢复后的 etcd 集群需要用 --initial-cluster=new 重新初始化成员关系,同时各节点的 name 不能重复,否则恢复后的集群会拒绝启动。
5. 控制平面组件逐一落地——apiserver、controller-manager 与 scheduler
5.1 kube-apiserver 的配置文件与启动参数
apiserver 是整个集群的中枢,部署时把它放在所有组件最前面。二进制包里已经包含了 kube-apiserver 可执行文件,拷贝到 /usr/local/bin 后,需要把它运行所需的主要参数一条条列清楚。
首先是服务监听相关:--bind-address=0.0.0.0,--secure-port=6443。注意 apiserver 默认还有一个 8080 端口(--insecure-port),新版本已经默认关掉,不要开启,避免绕过 TLS 直接访问集群。
其次是与 etcd 的对接:--etcd-servers=https://192.168.10.11:2379,https://192.168.10.12:2379,https://192.168.10.13:2379,同时指定 --etcd-cafile、--etcd-certfile、--etcd-keyfile 三个证书参数。这三个参数对应的是 apiserver 作为 etcd 客户端时使用的 CA 和证书,如果证书配置不对,apiserver 会一直报 etcd 连接错误。
然后是集群 CIDR 规划:--service-cluster-ip-range=10.96.0.0/16,--service-node-port-range=30000-32767。这个网段要在部署前就定好,后续不能随意修改,否则 Service 的 ClusterIP 会全部失效。Pod 网段 --cluster-cidr=172.26.0.0/16 在 apiserver 层面配置,这是为网络插件预留的 Pod 地址范围,与 Service 网段要避开。
认证授权相关:--client-ca-file 指定 CA 证书,用于校验客户端证书;--service-account-key-file 指定 Service Account token 签名公钥(对应 controller-manager 那边的私钥);--authorization-mode=Node,RBAC 开启节点授权和 RBAC。
sertificate SAN 相关:--tls-cert-file 和 --tls-private-key-file 就是前面签的 apiserver 证书和私钥。
apiserver 的 systemd 单元里,还要注意几个通用参数:--enable-admission-plugins 保持默认即可,--audit-log-path 建议开启审计日志并配置 log 目录,避免后续排查问题时没有日志可查。配置完后,用curl --cacert /etc/kubernetes/pki/ca.pem https://192.168.10.11:6443/healthz验证 apiserver 是否正常。这里 curl 返回 ok 说明 apiserver 已经能与 etcd 通信并启动成功。
5.2 controller-manager 与 scheduler 的参数配置
kube-controller-manager 和 kube-scheduler 相对简单,它们都通过 kubeconfig 连接 apiserver。需要为它们各自生成专属的 kubeconfig 文件,kubeconfig 里嵌入了客户端证书和 apiserver 地址。这里有一个关键参数:--leader-elect=true。多主架构下这三个控制面组件必须开启选主,否则多个副本同时执行调度或控制器逻辑,会产生重复操作。比如多个 controller-manager 同时处理 Deployment 扩容,可能创建出多余副本。开了 leader-elect 后,同一时刻只有一个实例持有 lease 并工作,其他实例处于 standby 状态。验证选主是否生效,可以在这两台节点上用kubectl get leases -n kube-system,能看到对应的 lease 对象只有一个 holder。
controller-manager 还需要配置 --cluster-cidr 与 apiserver 保持一致;--service-account-private-key-file 指向刚才 apiserver 公钥对应的私钥;--root-ca-file 指向 CA 证书,这是让 controller-manager 在创建 ServiceAccount 时把 CA 注入到 Pod 里的原因。
scheduler 的 kubeconfig 指向 apiserver 后,基本参数不多。需要注意的是 --bind-address 建议绑定到 127.0.0.1,避免调度器端口暴露到外部网络。这是一个很好的安全习惯,controller-manager 同样如此,这两个组件本来就不需要对外提供服务。
5.3 多主高可用接入与本地验证
三台 master 的 apiserver 都起来后,还缺少统一的入口。生产环境一般用 HAProxy 加 Keepalived 的方式,虚拟 IP 192.168.10.10 漂移在三台 master 之间。Keepalived 负责 VIP 漂移,HAProxy 负责把 6443 流量负载均衡到三台 apiserver。haproxy.cfg 里配置 backend 时,注意 balance 算法用 roundrobin,加 option tcp-check 做健康检查——检查的是 apiserver 的 6443 端口是否可连接,而不是简单的 TCP 握手。
配置完成后,先本地验证每个 apiserver 的 /healthz 是否返回 ok,再用虚拟 IP 验证:curl --cacert ca.pem https://192.168.10.10:6443/healthz。这一步通了,说明外部的流量入口已经没有问题。
接下来生成 kubectl 使用的 admin kubeconfig。admin 证书的 CN 必须是 kubernetes-admin,因为集群默认的 RBAC 里有 cluster-admin 绑定到这个用户。配置好 kubectl 后,先执行kubectl cluster-info和kubectl get cs看各组件健康状态。注意新版 Kubernetes 里kubectl get cs经常返回 Unhealthy,其实是因为组件端口的健康检查方式变了,不代表真的有问题,不要被误导。
6. worker 节点接入:kubelet 引导、kube-proxy 与网络打通
6.1 kubelet 目录结构与启动参数
worker 节点相对 master 简单,核心是 kubelet 加 kube-proxy,再加上 CNI 插件。首先要把 kubelet、kube-proxy 二进制文件拷到 /usr/local/bin,并把 CNI 插件解压到 /opt/cni/bin,网络配置目录放在 /etc/cni/net.d。
kubelet 这里有一个二进制部署独有的细节:它需要配置 /var/lib/kubelet、/etc/kubernetes/pki 等目录,在启动参数里明确指定 --root-dir、--kubeconfig、--bootstrap-kubeconfig。有两种接入集群的方式:手动签发 kubelet 证书和生产 kubeconfig,或者用 bootstrap token 机制让 kubelet 自动申请证书。推荐后者,操作更便捷也支持证书轮换。先在 master 上生成一个 bootstrap token,写入 worker 节点的 bootstrap-kubeconfig,指定 apiserver 地址和 token,然后 kubelet 首次启动时会自动用 token 向 apiserver 发起 CSR,管理员审批后,kubelet 会自动下载签发的证书并写入 /var/lib/kubelet 下的 kubeconfig。
kubelet 参数中几个容易踩坑的地方:--cgroup-driver 必须与 containerd 保持一致,即 systemd;--network-plugin=cni 开启 CNI 支持,否则 Pod 创建后没有网络;--cluster-dns 指向 CoreDNS 的 ClusterIP,--cluster-domain 对应集群域名后缀,缺了这两个参数,Pod 内 DNS 解析会异常;--hostname-override 建议配置为节点主机名,和 kubelet 注册到集群的名字保持一致。
开启证书轮换的配置需要放到 kubelet 配置文件中。在 systemd unit 里用 --config 参数指定 YAML 格式的 kubelet 配置文件,内容里设置 rotateCertificates: true。
启动 kubelet 后,用kubectl get csr查看是否有 Pending 状态的请求,逐个 approve。全部 approve 后kubectl get nodes应该能看到 worker 节点进入 Ready 状态。经常出现节点一直 NotReady 的情况,排查优先级是:容器运行时是否正常(crictl ps 能不能返回)、CNI 插件是否装好、kubelet 日志里有没有连接 apiserver 的异常。
6.2 kube-proxy 与网络插件选型
kube-proxy 以 DaemonSet 方式运行在集群中比较常见,但二进制部署场景下一般直接以 systemd 服务方式启动。kube-proxy 需要生成一个独立的 kubeconfig,默认的启动参数是 --proxy-mode=iptables,这是最稳妥的模式,iptables 性能在小规模集群下完全够用。如果集群规模很大、Service 数量很多,可以考虑 ipvs 模式,但需要节点内核加载 ip_vs 相关模块。
网络插件方面,我在生产环境用得比较多的是 Calico 和 Flannel。Calico 功能更丰富,支持 NetworkPolicy,适合对安全隔离有要求的场景;Flannel 简单轻量,配置少,适合中小集群快速起步。这里以 Calico 为例,安装时只需要在 master 节点上应用一个 manifest 文件,但需要注意:Calico 默认读取的 Pod 网段是 192.168.0.0/16,如果 apiserver 的 --cluster-cidr 规划的 Pod 网段不是这个,需要修改 manifest 中的 CALICO_IPV4POOL_CIDR 环境变量,否则 Calico 创建的 Pod 分配出来的 IP 和预期不一致。同类问题在 Flannel 里也存在——--iface 参数如果没有正确指定网卡,跨节点 Pod 通信会断。
网络插件起来的标志是 kube-system 命名空间下所有 pod 变成 Running。CoreDNS 经常因为 pending 无法分配 IP,就是因为网络插件没有就绪。一旦网络插件正常,CoreDNS 会自动调度并获取 IP。
6.3 集群验证清单与常见故障排查
所有节点接入集群后,按以下清单逐项自检:
- kubectl get nodes 所有节点处于 Ready。
- kubectl get pods -n kube-system 所有系统组件处于 Running。
- kubectl run test-pod --image=busybox -- sleep 3600 能成功创建 Pod;进入 Pod 执行 ping 外部 IP,确保网络出网正常。
- 创建两个测试 Pod,一个访问另一个的 ClusterIP,验证 Service 转发正常。
- 对 Service 执行 DNS 解析测试,CoreDNS 正常返回解析结果。
- 将 Keepalived 的主节点网卡 down 掉,用 kubectl 工具通过 VIP 访问集群,验证仍能正常工作。
我在一次部署中遇到过 CoreDNS 反复 CrashLoopBackOff 的情况,排查时发现 kubelet 配置的 --cluster-dns 是 10.96.0.10,而 CoreDNS 实际分配的 ClusterIP 却是 10.96.0.2。原因是修改 Calico 网段配置时,不小心把 CoreDNS manifest 里的 ClusterIP 也改了。这类低级错误在手工部署时很容易出现,排查思路是始终围绕:组件之间约定的 IP、证书、密码是否完全一致。逻辑链断在哪一环,故障就在哪一环。
还有一个高频问题是节点 kubelet 一直报 x509 certificate signed by unknown authority。夸张的是有时候重启服务器后这个问题就出现,原因是 etcd 的 CA 证书和 Kubernetes 的 CA 证书在节点上被某个操作覆盖了。所以证书分发时,同一台节点的 /etc/kubernetes/pki 目录里要同时存放 all CA 和组件证书,文件命名要带前缀区分,避免覆盖。
最后再分享几点实际体会
这套集群从规划到全部跑通,总共用了两天时间。第一次做二进制部署的读者,大概率会卡在证书签发和组件参数这两块。证书的问题往往不是生成出错,而是 SAN 漏了、有效期设短了、或者分发错了节点。组件的问题更多是参数不一致,比如 cgroup driver、CIDR 规划、apiserver 地址,同一份配置在三台 master 之间反复复制时,很容易手误改掉一个端口或 IP。
建议在动手前把表设计做细:每台节点的角色、IP、证书清单、配置文件路径都列清楚。部署过程中每完成一步就做一次验证,不要攒到最后一起排查——组件之间是强依赖关系,apiserver 没起来就往下装 kubelet,日志会把你绕晕。
另外,这套方法最大的价值还是排错和定制能力。之后如果要在集群上接入自建的证书体系、修改审计策略、或者把 etcd 迁到独立节点,你手里有完整的配置文件和启动参数,改起来心里有底。