这段时间我正好在Rocky Linux上把一套Kubernetes V1.33集群用纯二进制的方式从零搭了起来。整个过程差不多折腾了一周,中间踩了不少坑,但也正因为是二进制部署,Kubernetes 每一个组件、每一层通信、每一张证书都被迫看得清清楚楚。这篇文章就把这次Kubernetes V1.33二进制部署的完整流程记录下来,环境基于Rocky Linux 9.x,从证书签发、etcd集群、控制平面组件到工作节点接入,一步一步拆开讲。
为什么放着好好的kubeadm不用,非要用二进制?一句话:生产环境里需要深度定制的时候,kubeadm的魔法会变成黑盒,出了问题你只能对着日志猜。而二进制部署,每个组件的启动参数、证书位置、依赖关系都由自己控制,排障的时候逻辑是线性的、确定的。这篇文章适合两类人看:一类是想彻底弄懂Kubernetes各组件协作原理的,另一类是需要在离线环境、定制化环境里手工交付集群的。
1. 为什么我坚持用二进制方式部署Kubernetes V1.33
1.1 二进制部署和kubeadm的核心差异
kubeadm本质上是一个“封装好的部署工具”,它会帮你生成证书、生成kubeconfig、编排静态Pod,甚至帮你把etcd装好。听起来省事,但它有几个问题在特定场景下非常难受:
- 组件版本升级不灵活,kubeadm生成的静态Pod参数不是你想要的,想改就得改yaml再重新生成。
- 离线环境或者内网环境,kubeadm拉镜像这一步就可能卡死你。
- 证书的存放位置、CA策略、SAN范围全是由kubeadm默认逻辑决定的,很多时候不符合企业的合规要求。
- 出了问题之后排查链路很长,因为你不知道它到底替你做了哪些决策。
二进制部署则是把Kubernetes的每个组件当成普通的进程来管理,证书自己签、systemd unit自己写、启动参数自己定。好处非常直接:每个参数是什么含义,每个端口监听在哪里,每一张证书被哪个组件使用,都是清清楚楚的。坏处也明确:工作量明显变大,且任何一个环节配置错了,都要自己扛。
1.2 本次集群的架构与版本约定
我这次搭建的集群规划是三节点:一个控制平面节点(也跑etcd),两个工作节点。这在中小规模生产环境里是很常见的最小高可用形态,控制平面节点也可以扩展到三个,把etcd做成三节点集群。
| 节点角色 | 主机名 | 私有IP地址 | 说明 |
|---|---|---|---|
| 控制平面 | master-01 | 192.168.100.101 | 运行etcd、kube-apiserver、kube-controller-manager、kube-scheduler |
| 工作节点 | node-01 | 192.168.100.102 | 运行kubelet、kube-proxy、containerd |
| 工作节点 | node-02 | 192.168.100.103 | 运行kubelet、kube-proxy、containerd |
Kubernetes本次用的具体版本是V1.33,组件版本以官方Release页提供的二进制包为准。etcd使用v3.5.x,containerd使用v2.x,CNI插件使用v1.x。操作系统方面用的是Rocky Linux 9.x,内核版本在6.1以上,这个内核版本跑Kubernetes的IPVS转发和网络插件都没有问题。
1.3 二进制部署的整体流程拆解
很多人在手工部署时容易一头扎进命令里,结果装了一半不知道下一步该干嘛。我先把链路梳理给你,心里有全局图,后面每一步都不会慌。
整体流程可以拆成六段:
- 主机初始化:关闭swap、修改SELinux、加载内核模块、配置防火墙策略。
- 证书体系:用cfssl签发CA证书,然后为每个组件签发独立的证书。
- etcd集群:因为Kubernetes所有数据都存在etcd里,必须先把它跑起来。
- 控制平面组件:依次部署kube-apiserver、kube-controller-manager、kube-scheduler。
- 工作节点组件:部署containerd、kubelet、kube-proxy,让节点加入集群。
- 网络插件与验证:安装CNI插件,打通Pod网络,最后做端到端验证。
这个流程的顺序是固定的。证书没做好,后面apiserver起不来;etcd没起来,apiserver连不上存储;apiserver没起来,controller-manager和scheduler就是白搭。所以你在动手之前,最好先把每张证书对应哪个组件、哪个端口要开哪个方向都列清楚再开始。
2. 主机初始化与证书体系:最不能偷懒的两步
2.1 Rocky Linux系统环境整理
我在动手前先在每台机器上都做了一遍同样的初始化。这里直接给出我用的配置脚本,你可以照着改:
# 设置主机名,按节点角色修改 hostnamectl set-hostname master-01 # 关闭swap swapoff -a sed -i '/swap/s/^/#/' /etc/fstab # 关闭SELinux,生产环境按需调整策略 setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config # 如果使用firewalld管理防火墙,需要放行关键端口 # 或者直接停用firewalld,本次部署直接关闭以便聚焦排查 systemctl stop firewalld && systemctl disable firewalld # 加载内核模块 modprobe br_netfilter modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh modprobe nf_conntrack cat > /etc/modules-load.d/k8s.conf <<EOF br_netfilter ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack EOF # 内核参数 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这一步里最容易忽略的是net.ipv4.ip_forward = 1。如果这个参数不是1,Pod之间的流量转发就是有问题的,服务就算起来了也访问不通。另一个坑是ip_vs相关模块,kube-proxy如果使用IPVS模式,模块没加载会导致它不停报错甚至直接不能用。
2.2 用cfssl规划证书清单
Kubernetes组件之间的通信基本都是TLS,证书就是整个集群的信任基石。我这次用的工具是cfssl,它最大的好处是签发证书时可以在一份JSON配置里塞进多个SAN,这对apiserver这种需要同时支持多个访问地址的证书特别友好。
先规划证书清单,我列一张表方便你对照:
| 证书文件名 | 使用方 | 证书用途 |
|---|---|---|
| ca.pem / ca-key.pem | 集群根CA | 签发其他证书 |
| etcd-server.pem | etcd | 对外提供服务的TLS |
| etcd-peer.pem | etcd | 节点间通信的TLS |
| apiserver.pem | kube-apiserver | HTTPS服务端证书 |
| apiserver-etcd-client.pem | kube-apiserver | 访问etcd时作为客户端证书 |
| admin.pem / admin.kubeconfig | kubectl / 管理员 | 管理员客户端认证 |
| controller-manager.pem | kube-controller-manager | 访问apiserver的客户端证书 |
| scheduler.pem | kube-scheduler | 访问apiserver的客户端证书 |
| kube-proxy.pem | kube-proxy | 访问apiserver的客户端证书 |
| kubelet-client.pem | kubelet | 访问apiserver的客户端证书(也可通过bootstrap自动签发) |
这里要特别说明一下:我习惯用bootstrap token的方式让kubelet自动申请证书,这样新节点加进来时不用手动签发kubelet证书,所以在清单里没有把每台节点的kubelet证书列死。但如果你想完全静态管理,也可以在证书阶段把每个节点的kubelet证书都签出来。
2.3 cfssl签发CA与组件证书
先创建CA配置文件。我用的是默认的签名策略,profile分成client、server、peer三种,这样签发的时候直接指定profile就行,不需要为每个证书写一堆参数。
{ "signing": { "default": { "expiry": "87600h" }, "profiles": { "server": { "expiry": "87600h", "usages": [ "signing", "key encipherment", "server auth" ] }, "client": { "expiry": "87600h", "usages": [ "signing", "key encipherment", "client auth" ] }, "peer": { "expiry": "87600h", "usages": [ "signing", "key encipherment", "server auth", "client auth" ] } } } }生成CA根证书:
cfssl gencert -initca ca-csr.json | cfssljson -bare ca然后在每台节点上创建/etc/kubernetes/ssl和/etc/etcd/ssl目录,把对应的pem和key放进去。证书分发的时候注意权限,key文件我统一设置为600,pem设置为644。
签发apiserver证书时有一个关键点容易被忽略:SAN里必须包含Service的ClusterIP、本机IP、预留的集群VIP,以及域名。Kubernetes内部的Service名字是kubernetes.default.svc.cluster.local,apiserver的证书如果没有这个域名,后面kubectl exec和集群内Pod访问apiserver时TLS校验会失败。
我签发apiserver证书时用的JSON大致长这样:
{ "CN": "kube-apiserver", "hosts": [ "192.168.100.101", "192.168.100.102", "192.168.100.103", "10.96.0.1", "127.0.0.1", "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local" ], "key": { "algo": "rsa", "size": 2048 } }看到那个10.96.0.1了吗?这是Service网段里的第一个地址,也是apiserver在集群内部暴露的ClusterIP,证书里漏了它,后面所有Pod内访问apiserver都会因为证书不匹配而失败。这个坑我踩过不止一次,建议你在写配置时先想清楚集群用哪个Service网段,再回头填SAN。
3. 控制平面组件部署:从etcd到apiserver逐个拉起来
3.1 etcd集群启动与验证
etcd是整个集群的大脑,Kubernetes所有状态都存它里面。二进制部署时,etcd和解压出来的Kubernetes组件一样,直接放到/usr/local/bin,然后通过systemd管理。
我的etcd数据目录放在/var/lib/etcd,证书放在/etc/etcd/ssl。systemd unit文件里几个参数要特别注意:
[Unit] Description=etcd After=network.target [Service] Type=notify ExecStart=/usr/local/bin/etcd \ --name=etcd-1 \ --data-dir=/var/lib/etcd \ --listen-client-urls=https://192.168.100.101:2379,https://127.0.0.1:2379 \ --advertise-client-urls=https://192.168.100.101:2379 \ --listen-peer-urls=https://192.168.100.101:2380 \ --initial-advertise-peer-urls=https://192.168.100.101:2380 \ --initial-cluster=etcd-1=https://192.168.100.101:2380,etcd-2=https://192.168.100.102:2380,etcd-3=https://192.168.100.103:2380 \ --initial-cluster-state=new \ --client-cert-auth=true \ --cert-file=/etc/etcd/ssl/etcd-server.pem \ --key-file=/etc/etcd/ssl/etcd-server-key.pem \ --trusted-ca-file=/etc/etcd/ssl/ca.pem \ --peer-cert-file=/etc/etcd/ssl/etcd-peer.pem \ --peer-key-file=/etc/etcd/ssl/etcd-peer-key.pem \ --peer-trusted-ca-file=/etc/etcd/ssl/ca.pem [Install] WantedBy=multi-user.target如果你只用单节点etcd,initial-cluster里写自己就行。三节点etcd做高可用时,--initial-cluster-state一定要是new,如果之前已经跑起来过再改成existing,否则会报节点已存在之类的问题。三个节点的etcd配置除了--name、--listen-client-urls、--advertise-client-urls、--listen-peer-urls、--initial-advertise-peer-urls不一样,其他基本相同。
启动后我习惯先看一遍状态:
etcdctl --endpoints=https://192.168.100.101:2379 \ --cacert=/etc/etcd/ssl/ca.pem \ --cert=/etc/etcd/ssl/etcd-server.pem \ --key=/etc/etcd/ssl/etcd-server-key.pem \ endpoint health --cluster这里有个细节:访问etcd时用的客户端证书必须是被etcd的--trusted-ca-file信任的。如果你发现endpoint health一直报connection refused或者certificate signed by unknown authority,先检查证书配置和监听地址,不要急着看业务日志。
3.2 kube-apiserver部署与启动参数解析
apiserver是整个集群的前端入口,所有组件的请求都要经过它。我在master-01上建了/etc/kubernetes目录,把kubeconfig和证书都放进去。
apiserver的启动参数很多,但核心参数就几个,我把关键行抽出来:
[Unit] Description=Kubernetes API Server After=network.target [Service] ExecStart=/usr/local/bin/kube-apiserver \ --apiserver-count=1 \ --secure-port=6443 \ --bind-address=0.0.0.0 \ --etcd-servers=https://192.168.100.101:2379,https://192.168.100.102:2379,https://192.168.100.103:2379 \ --etcd-cafile=/etc/etcd/ssl/ca.pem \ --etcd-certfile=/etc/kubernetes/ssl/apiserver-etcd-client.pem \ --etcd-keyfile=/etc/kubernetes/ssl/apiserver-etcd-client-key.pem \ --client-ca-file=/etc/kubernetes/ssl/ca.pem \ --tls-cert-file=/etc/kubernetes/ssl/apiserver.pem \ --tls-private-key-file=/etc/kubernetes/ssl/apiserver-key.pem \ --service-account-issuer=https://kubernetes.default.svc.cluster.local \ --service-account-key-file=/etc/kubernetes/ssl/sa.pub \ --service-account-signing-key-file=/etc/kubernetes/ssl/sa.key \ --service-cluster-ip-range=10.96.0.0/12 \ --service-node-port-range=30000-32767 \ --authorization-mode=Node,RBAC \ --enable-bootstrap-token-auth=true \ --enable-admission-plugins=NodeRestriction \ --kubelet-preferred-address-types=InternalIP \ --runtime-config=api/all=true [Install] WantedBy=multi-user.target--service-account-key-file和--service-account-signing-key-file要匹配,它们是用来签发和验证ServiceAccount Token的。我生成了一对单独的RSA密钥专门给ServiceAccount用,不要直接用CA私钥,这是安全习惯问题。
--enable-bootstrap-token-auth=true这个参数一定不能漏,因为后面kubelet通过bootstrap token接入集群时,apiserver需要校验这个token。如果你漏了,kubelet那边会一直报Unauthorized,看起来像是证书问题,实际上根因在这里。
启动apiserver后,我做的第一件事不是直接部署其他组件,而是先验证端口和健康状态:
curl --cacert /etc/kubernetes/ssl/ca.pem https://127.0.0.1:6443/healthz curl --cacert /etc/kubernetes/ssl/ca.pem https://127.0.0.1:6443/livez我见过很多人在这一步被健康检查卡住,返回的却是{“status”:”ok”}后面还有一堆警告。其实只要返回OK就行,不要被日志里的Warning吓到。
3.3 controller-manager与scheduler部署
controller-manager和scheduler都是控制平面的“后台大脑”,controller-manager负责维护集群期望状态,scheduler负责给Pod选节点。它们的部署方式比apiserver简单很多,只需要一个能访问apiserver的kubeconfig。
controller-manager的systemd unit关键参数:
ExecStart=/usr/local/bin/kube-controller-manager \ --bind-address=127.0.0.1 \ --use-service-account-credentials=true \ --cluster-cidr=10.244.0.0/16 \ --allocate-node-cidrs=true \ --service-cluster-ip-range=10.96.0.0/12 \ --root-ca-file=/etc/kubernetes/ssl/ca.pem \ --service-account-private-key-file=/etc/kubernetes/ssl/sa.key \ --kubeconfig=/etc/kubernetes/controller-manager.kubeconfig \ --leader-elect=true--allocate-node-cidrs=true和--cluster-cidr是配合使用的,controller-manager会根据这两个参数给每个节点分配一个Pod子网段。10.244.0.0/16是Pod网段,这个网段要和你后面装的网络插件保持一致,如果你选了Calico且默认网段是它自己的,那就要把Calico配置里的IP池也改成10.244.0.0/16,否则节点和Pod会一直处于不健康状态。
scheduler的unit文件更简单:
ExecStart=/usr/local/bin/kube-scheduler \ --kubeconfig=/etc/kubernetes/scheduler.kubeconfig \ --leader-elect=true这两个组件部署完成后,在master上配置好admin kubeconfig,第一次执行kubectl get componentstatuses会看到controller-manager和scheduler都变成Healthy,那一刻整个控制平面的骨架就算是立起来了。
3.4 控制平面组件的自检要点
控制平面全部启动后,我强烈建议你做一次系统性的自检,不要急着去搭工作节点。我自己常用的检查命令有这么几条:
kubectl get componentstatuses kubectl get --raw='/healthz' | cat journalctl -u kube-apiserver -f --no-pager | tail -50 ss -lntp | grep -E '6443|2379|2380'如果kubectl get componentstatuses里显示Unhealthy,那我基本会按这个顺序排查:先看etcd是不是还在跑,然后用curl直接打apiserver的healthz确认apiserver本身是不是好的,再看controller-manager和scheduler的kubeconfig有没有配错。我遇到过几次都是kubeconfig里的server地址写成了http://127.0.0.1:8080,而现在新版apiserver默认根本不再监听8080非安全端口,这样自然连不上。
4. 工作节点接入:containerd、kubelet、kube-proxy
4.1 containerd与CNI插件安装
工作节点上要先有容器运行时,Pod才能跑起来。我用的是containerd,v2.x版本,从官方二进制包直接解压安装,不需要走包管理器的仓库依赖。
mkdir -p /opt/cni/bin tar -C /opt/cni/bin -zxvf cni-plugins-linux-amd64-v1.x.tgz tar -C /usr/local -zxvf containerd-2.x-linux-amd64.tar.gz mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml生成默认配置后,有两个地方必须改。第一是sandbox_image,默认指向的pause镜像地址在很多环境里拉不下来,必须换成一个可达的镜像仓库地址。第二是SystemdCgroup,要改成true。如果这个参数不改成true,你会发现容器经常有状态异常,或者kubelet报cgroup相关错误。
改完之后启动containerd:
systemctl enable --now containerd此时可以先手动拉一遍pause镜像,确保仓库地址通畅,不要等kubelet启动后再被这个卡住。
4.2 kubelet配置与bootstrap机制
kubelet是工作节点上最核心的组件,它负责管理Pod生命周期、上报节点状态、执行探针。手工部署时有两种接入方式:一种是给你手动签发每个节点的kubelet证书,另一种是bootstrap机制让节点自己申请证书。我用的是bootstrap机制,新节点加入时省事很多。
先在master上生成一个bootstrap token,这个token是一串十六进制字符串:
KUBE_TOKEN=$(openssl rand -hex 16) kubectl create token bootstrap-token-${KUBE_TOKEN:0:6} \ --namespace kube-system \ --duration=8760h严格来说,bootstrap token本身需要提前创建,传统的做法是先在apiserver里通过一个secret定义token。我更推荐用一条yaml写清楚,token密码部分自动生成:
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Secret metadata: name: bootstrap-token-abc123 namespace: kube-system type: bootstrap.kubernetes.io/token stringData: token-id: "abc123" token-secret: "$(openssl rand -hex 8)" usage-bootstrap-authentication: "true" usage-bootstrap-signing: "true" EOF生成bootstrap kubeconfig:
kubectl config set-cluster kubernetes \ --server=https://192.168.100.101:6443 \ --certificate-authority=/etc/kubernetes/ssl/ca.pem \ --kubeconfig=/etc/kubernetes/bootstrap.kubeconfig kubectl config set-credentials system:bootstrap:abc123 \ --token=abc123.$(openssl rand -hex 8) \ --kubeconfig=/etc/kubernetes/bootstrap.kubeconfig kubectl config set-context bootstrap \ --user=system:bootstrap:abc123 \ --cluster=kubernetes \ --kubeconfig=/etc/kubernetes/bootstrap.kubeconfig kubectl config use-context bootstrap --kubeconfig=/etc/kubernetes/bootstrap.kubeconfig接下来要创建RBAC规则,允许bootstrap组的用户创建CSR,并允许系统自动批准节点客户端的CSR。这一步漏了的话,kubelet会一直停留在Pending状态,节点始终是NotReady。
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kubelet-bootstrap roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:node-bootstrapper subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:bootstrappers --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: node-client-auto-approve roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:certificates.k8s.io:certificatesigningrequests:nodeclient subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:bootstrapperskubelet通过--bootstrap-kubeconfig启动后,会自动使用token向apiserver发起CSR请求,申请自己的客户端证书。apiserver根据RBAC规则自动批准这个请求,kubelet再把签好的证书写进--kubeconfig指定的文件里。
kubelet的systemd unit长这样:
[Unit] Description=Kubernetes Kubelet After=containerd.service [Service] ExecStart=/usr/local/bin/kubelet \ --bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \ --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \ --config=/etc/kubernetes/kubelet-config.yaml \ --container-runtime-endpoint=unix:///run/containerd/containerd.sock \ --node-ip=192.168.100.102 \ --kube-reserved=cpu=200m,memory=256Mi \ --image-gc-high-threshold=80 \ --image-gc-low-threshold=70 Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target--node-ip参数一定要显式指定,尤其在一台机器有多个网卡的场景下。你不指定的话,kubelet可能会选一个错误的IP上报给apiserver,导致节点注册的地址根本访问不通。
kubelet-config.yaml里至少要把集群DNS和域名配置送进去:
apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration clusterDomain: cluster.local clusterDNS: - 10.96.0.10 maxPods: 110 readOnlyPort: 0启动kubelet后,到master上执行kubectl get csr,正常情况下你会看到一堆Pending的CSR。如果刚才的RBAC配好了,它们会自动变成Approved并签发成功。如果一直是Pending,最直接的办法是手动批准:
kubectl certificate approve <csr-name>不过这不是长久之计,建议还是把RBAC配好,让系统自动批准。
4.3 kube-proxy部署与IPVS模式
kube-proxy负责实现Service的转发规则。手工部署时,它需要有一个能访问apiserver的kubeconfig,证书就是我们之前在证书清单里规划的kube-proxy证书。
kube-proxy的systemd unit:
[Unit] Description=Kubernetes Proxy After=network.target [Service] ExecStart=/usr/local/bin/kube-proxy \ --config=/etc/kubernetes/kube-proxy-config.yaml \ --v=2 Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.targetkube-proxy-config.yaml:
apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration clusterCIDR: 10.244.0.0/16 mode: "ipvs" clientConnection: kubeconfig: /etc/kubernetes/kube-proxy.kubeconfigclusterCIDR在这里的作用是用来判断流量是不是发往集群内部Pod的,如果你的Pod网段和主机网段重叠了,kube-proxy转发就会出各种说不清楚的问题。mode: "ipvs"配合我们之前加载的内核模块,性能和转发能力都比老的iptables模式好。
启动后验证一下ipvs规则是否生成:
ipvsadm -L -n正常能看到10.96.0.0/12网段对应的转发规则。如果这里什么都没有,说明kube-proxy的配置有问题,或者节点上根本没有加载ip_vs相关内核模块。
5. 网络插件与集群验证:把最后的通路打通
5.1 为什么必须装CNI插件
Kubernetes集群网络不是内核自动帮你搞定的,CNI插件负责为每个Pod分配IP地址、创建veth网卡对、配置路由规则。不装网络插件的话,节点虽然能注册成功,Pod也会一直处于ContainerCreating状态,因为容器网络根本没建好。
关于网络插件选型,简单场景用Flannel就够了,配置简单、资源占用小,但NetworkPolicy的完整支持比较弱。稍微复杂一点或者对NetworkPolicy有强需求的场景,我会选Calico,它用BGP把Pod网段路由到整个三层网络里,性能好很多。这次部署我选择了Calico,它对二进制环境的适配也相对成熟。
安装Calico时,记得把IP池参数改成和controller-manager的--cluster-cidr一致,也就是10.244.0.0/16。如果Calico默认池是192.168.0.0/16而你的cluster-cidr是别的,那最终结果一定是节点Ready但Pod网络完全不通。
5.2 端到端验证:从节点状态到业务Pod
网络插件部署成功后,在master上重新看节点状态:
kubectl get nodes -o wide两个工作节点应该都是Ready状态。如果显示NotReady,按我经验最可能就是kubelet证书没批准,或者CNI网络不通。这种情况下先看节点上的kubelet日志:
journalctl -u kubelet -f --no-pager | tail -100日志里如果有container runtime is not running,那就去查containerd状态;如果有failed to list pods,基本就是apiserver地址或者认证问题。
节点Ready后,部署一套测试业务验证完整链路:
kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 kubectl run test --image=busybox --rm -it -- wget -O- http://nginx如果wget能顺利拿到nginx的欢迎页,说明Pod调度、CNI网络、Service转发、DNS解析都是通的。我这里还要提一个小细节:kubectl run test创建的那个临时Pod,它能不能解析nginx这个Service名,取决于kubelet配置里的clusterDNS对不对。如果你发现直接用ClusterIP能访问但Service名解析不了,那问题基本都在DNS指向配置上。
6. 常见问题排查与踩坑实录
6.1 证书类和认证类问题
证书问题在二进制部署里出现的频率最高。我遇到过的典型像是apiserver启动后健康检查失败,日志里报TLS握手错误,排查下来发现证书SAN少了节点的IP。这类问题基本没法通过重启解决,只能重新签发证书再替换。
还有一类是controller-manager或scheduler报Unauthorized,大多数时候是它们的kubeconfig里没有配好client cert,或者配的证书CN跟RBAC对不上。这里分享一个我自己的排查习惯:出现认证问题时,先在master上用kubectl auth whoami看当前用户身份,再用openssl x509 -in <cert> -text -noout看证书的CN和SAN,基本就能定位是身份不对还是权限不对。
6.2 网络与运行时问题
网络问题同样让人头疼,但大多数根因其实很集中:
ip_forward=0导致Pod出网不通。- CNI插件没装好或者IP池和cluster-cidr不一致,导致Pod一直ContainerCreating。
- kube-proxy的IPVS模块没加载,导致Service只有一个虚拟IP转发不了。
- 节点IP没指定,多网卡环境下选错了IP,apiserver到kubelet的通信直接失败。
- containerd里
sandbox_image拉取失败,Pod一直Pending或CrashLoopBackOff。
有一个我自己很深刻的经历:kube-proxy启动正常、ipvs规则也列出来了,但Service就是不通。后来查了一圈才发现是Node上的rp_filter内核参数在作怪,它可能把回程包给丢弃了。如果你是类似拓扑的网络,建议一并检查net.ipv4.conf.*.rp_filter相关配置。
6.3 排查命令速查表
| 现象 | 先看这条命令 | 常见根因 |
|---|---|---|
| apiserver起不来/健康检查失败 | journalctl -u kube-apiserver -f --no-pager | etcd没起来、证书SAN缺失、etcd客户端证书错误 |
| 节点NotReady | journalctl -u kubelet -f --no-pager | kubelet证书Pending、CNI不通、containerd未启动 |
| Pod一直ContainerCreating | kubectl describe pod <pod> | CNI插件未配置、sandbox镜像拉取失败 |
| Service无法访问 | ipvsadm -L -n/iptables -L | IPVS模块未加载、clusterCIDR配置错误 |
| kubelet报Unauthorized | kubectl get csr | RBAC自动批准缺失、token错误 |
| 节点注册IP不对 | kubectl get node -o wide | kubelet未指定--node-ip |
| Pod出网不通 | sysctl net.ipv4.ip_forward | 内核转发未开启 |
这些排查路径基本覆盖了二进制部署90%的报错方向。遇到问题时不要先怀疑组件有bug,按“证书 -> etcd -> apiserver -> kubelet -> 网络插件”的链路一层层查,绝大多数问题都能在十分钟内找到根源。
这次二进制部署走完之后,我最大的感受是:虽然比kubeadm多花了好几天时间,但Kubernetes在我脑子里从“一个容器编排平台”变成了“一组互相协作的进程”,它们的端口、证书、启动依赖关系都变得非常具体。如果你也是第一次尝试手工部署,建议先在虚拟机里完整走一遍,把证书签发和RBAC规则提前准备好再接生产。最后再分享一个小技巧:所有systemd unit文件里我都加了Restart=on-failure和RestartSec=10,配合TimeoutStartSec=0,可以让组件在异常退出后自动恢复,这段时间实测下来系统稳定性提升非常明显。