Kubernetes集群从零到可用:kubeadm部署避坑指南
简介《Kubernetes部署指南》是一份面向容器运维、平台工程师及K8s初学者的系统部署手册聚焦集群从零搭建与落地部署中的真实问题。文档覆盖单机部署、kubeadm、kops、Kubespray、Azure、Windows、LinuxKit、kubeasz及Kubernetes-The-Hard-Way等主流路线并详细列出Etcd、Docker、Go、CNI、CSI等组件的版本依赖关系帮助读者规避版本兼容风险同时包含kubectl安装、证书与密钥生成、Etcd集群、控制节点与计算节点部署、Sonobuoy测试以及国内镜像加速等实战步骤。资源包共1个PDF文件大小3.91MB目录结构清晰便于翻阅检索。已有178人学习下载。通过这份指南读者可选择适合自身环境的部署方式按步骤搭建高可用Kubernetes集群并快速定位镜像拉取慢、配置证书等典型难题显著提升容器平台交付效率。1. 这份Kubernetes部署指南到底在解决什么问题如果你刚被分到一套新环境急着把Kubernetes集群跑起来大概率会在“初始化”“节点加入”“网络插件”这几个环节反复折腾。Kubernetes部署这件事难的不是把kubeadm安装上而是从一台干净的Linux机器到能稳定运行工作负载的完整链路内核参数、容器运行时、证书、网络组件、DNS任何一环没对齐集群就会用各种玄学报错回应你。这份部署指南要解决的正是这个完整链路覆盖从选型到踩坑的落地细节。适合三类人第一次搭集群的新手、准备把测试环境升级成生产环境的运维、以及想搞明白部署参数背后逻辑的开发者。读完你至少能照着跑通一个可用集群并在出问题时知道先查哪、去哪查。2. 先选型再动手部署方式对比与版本参数决策2.1 三种主流部署路径kubeadm、二进制、发行版我一般会先问一句话你要的是可控性还是速度Kubernetes部署没有银弹常见做法是在三种路径里选。第一种是kubeadm这是官方推荐的社区标准方案。它把集群搭建拆成kubeadm init和kubeadm join两步证书、kubeconfig、控制平面组件都由工具自动生成你只需要准备容器镜像和网络插件。第二种是纯二进制部署下载kube-apiserver、kube-controller-manager、kube-scheduler等二进制文件用systemd管理。这种方式能让你看清每个组件的启动参数排障时直接面对最底层逻辑但步骤多、易出错。第三种是直接用发行版比如k3s、k0s或RKE2它们把etcd、控制面封装成一个包适合边缘或资源受限场景。这三者并不互斥。生产环境我常看到“kubeadm搭底座 定制参数”的组合kubeadm负责证书和静态Pod编排你通过自定义配置文件控制apiserver的审计日志、etcd的磁盘配额等细节。二进制部署虽然原始但在需要接入公司内部CA、或对组件参数有极端定制时反而是唯一选择。而k3s这类发行版则不适合需要完整Kubernetes API清单的团队因为部分特性被裁剪或延长了启动流程。选择的关键是你的团队修复一个组件故障的容忍时间是多少。如果只想要最小精力维护优先kubeadm如果对每个进程都要去进程树里数一遍再考虑二进制。当你决定走kubeadm路线后第一个需要确认的是版本对齐。这里有一个常被忽略的约束kubeadm、kubelet、kubectl与Kubernetes控制面组件之间的版本偏移不能超过一个次要版本。比如控制面是1.28工作节点上的kubelet可以是1.27、1.28或1.29但不能是1.26。更稳妥的做法是让所有节点版本完全一致。同时kubeadm会安装最新稳定版如果你不显式指定--kubernetes-version很可能拉到一个刚发布的新版本而镜像仓库的tag还没同步全直接导致拉取失败。我在部署前一定会去官方CHANGELOG确认目标版本的补丁号再配合crictl pull先手动验证镜像存在。2.2 版本匹配与镜像源最容易在第一步翻车的地方很多人会在kubeadm init时被一个超时错误卡住failed to pull image registry.k8s.io/...:latest。原因通常是两个一是默认镜像源在你的网络环境不可达或极慢二是没指定kubeadm和kubelet的版本导致它默认拉取latest而你本地只有特定版本。解决方法是提前确定好一个Kubernetes版本并用kubeadm config images list查看所需镜像列表。然后有两种做法在企业内网环境你可以用私有镜像仓库中转先把镜像从可访问的源拉下来重新tag成registry.k8s.io的地址再推送到内网仓库最后在kubeadm init时指定--image-repository指向内网地址。另一种更省事的做法是直接设代理或配置docker daemon的registry-mirrors但这在部分生产环境不现实。有些同学会忽略kubeadm配置文件的作用。命令行虽然能跑通基础集群但多个参数堆在命令行里很难维护。我习惯写一个kubeadm-config.yamlapiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.21 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 controlPlaneEndpoint: 192.168.1.20:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration maxPods: 110 systemReserved: cpu: 500m memory: 1Gi这里controlPlaneEndpoint写的是VIP或负载均衡地址适用于高可用部署如果只是单控制面可以省略kubeadm会自动用初始化节点的IP。podSubnet必须和后面要安装的网络插件对应比如Flannel默认用10.244.0.0/16Calico默认用192.168.0.0/16这写错会导致Pod间无法通信。criSocket指定为containerd的socket因为从Kubernetes 1.24之后dockershim被移除docker不再是默认运行时。systemReserved是给系统进程预留的CPU和内存如果机器配置不高这里可以调低否则调度器会认为节点可分配资源过少。提示kubeadm config images list --kubernetes-version v1.28.2可以查看该版本需要哪些镜像。结合--image-repository和--image-pull-timeout调节拉取策略超时时间默认5分钟网络差时先调大到10分钟。3. 用kubeadm一步步拉起集群控制平面与工作节点的完整操作3.1 初始化前的主机准备内核参数、swap、容器运行时先把操作系统层面的三个坑填平关闭swap、加载内核模块、设置转发参数。swap不关闭的话kubelet会拒绝启动因为kubelet默认要求cgroup的swap功能关闭。可以用swapoff -a临时关闭再编辑/etc/fstab注释掉swap行永久生效。接着需要加载br_netfilter和overlay模块让iptables能处理桥接流量这样Pod之间的流量才能被正确转发和过滤。cat EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter cat EOF | sudo tee /etc/sysctl.d/99-kubernetes.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sudo sysctl --system上面这段把overlay和br_netfilter模块配置开机加载并设置三个内核参数。net.bridge.bridge-nf-call-iptables是让宿主机iptables规则对桥接的容器流量也生效net.ipv4.ip_forward是开启路由转发kube-proxy的iptables模式和Pod间通信都依赖它。执行完sysctl --system后用lsmod | grep br_netfilter验证一下没有输出就是没加载成功继续查模块路径或重启。容器运行时这里选containerd它比docker更轻也是Kubernetes社区默认的选择。安装后需要修改它的sandbox镜像地址因为默认的registry.k8s.io/pause:3.9在部分地区拉取困难。修改/etc/containerd/config.toml容易踩一个隐藏坑containerd默认没有完整的config.toml需要先执行containerd config default /etc/containerd/config.toml生成默认配置再修改其中的sandbox_image为内网镜像地址。改完后systemctl restart containerd。3.2 控制平面初始化kubeadm init的关键参数与网络插件控制平面初始化是整条链路里最需要耐心的步骤。我通常先把镜像拉齐而不是等kubeadm init一边初始化一边拉镜像这样可以更快定位网络问题。使用kubeadm config images pull带上--image-repository参数先拉一遍看到所有镜像都是Pulled状态后再执行真正的初始化。sudo kubeadm init \ --config/etc/kubernetes/kubeadm-config.yaml \ --upload-certs \ --ignore-preflight-errorsSwap mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config--config指定我们刚才写好的配置文件这样命令行里不需要堆一长串参数。--upload-certs用于多控制面场景它会把证书上传到kubeadm-managed的secret里后续其他控制面节点加入时可自动获取。--ignore-preflight-errorsSwap是兜底参数如果你已经关过swap就不需要但我习惯保留它避免云镜像预装的swap分区清理不干净导致卡在检查。初始化成功后终端会输出一个kubeadm join命令务必保存这是工作节点接入集群的凭证。初始化完成不代表集群可用还差最后一层CNI网络插件。这一步晚于控制平面启动因为网络插件需要操作集群内的CRD和ServiceAccount。以Calico为例先确认etcd没有异常然后应用官方manifestkubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26/manifests/tigera-operator.yaml kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26/manifests/custom-resources.yaml如果你的Pod网段不是192.168.0.0/16需要先修改custom-resources.yaml里的cidr字段。很多人省略这步最终节点NotReady或Pod一直ContainerCreating只能回头看日志。安装完Calico等到calico-system命名空间下的Pod都变为Running控制平面才算就绪。3.3 工作节点加入与集群状态验证工作节点只需要装好kubeadm、kubelet、containerd在主节点准备好的join命令后面加上--node-name指定节点名就可以接入。这里有个容易忽略的细节如果主节点初始化时用了--cri-socket工作节点也要保持相同的criSocket路径否则kubelet探不到运行时。join成功后观察kubectl get nodes的状态kubectl get nodes -o wide输出中STATUS列从NotReady变到Ready通常需要几十秒。如果卡在NotReady查看kubelet日志是第一步journalctl -u kubelet -f。常见原因有CNI未生效、Pod网段冲突、或节点时间不一致。这里推荐一个相对冷门但高效的验证手法在所有节点上执行kubectl get pod -n kube-system -o wide | grep核对每个Pod是否被调度到预期节点而不是只看节点状态。节点Ready只是表象coredns调度到某个节点后是否能解析集群内域名才代表网络链路真的OK。4. 部署后的必备检查命名空间、核心组件与DNS验证清单4.1 用kubectl get pods验证核心组件集群跑起来后第一件事不是急着部署业务而是把控制面组件一个个梳理一遍。kubectl get pods -n kube-system能看到apiserver、controller-manager、scheduler、etcd、coredns、kube-proxy这些系统级Pod。注意观察每个Pod的READY状态如果有0/1或CrashLoopBackOff说明启动过程有问题。此时不要盲目重启先看描述和日志kubectl describe pod -n kube-system coredns-xxxx -n kube-system kubectl logs -n kube-system coredns-xxxxdescribe能告诉你是镜像拉取失败、探针失败还是Volume挂载错误。比如之前遇到Readiness probe failed: HTTP probe failed with statuscode: 503这种通常是coredns的Corefile被改坏了或者网络插件还没就绪。日志则更直接看见plugin/errors: etcd dial tcp 127.0.0.1:2379: connect: connection refused说明coredns想连本机etcd但被拒绝大概率是网络插件没起来导致coredns的Pod网络没有打通。控制面组件的静态Pod清单在/etc/kubernetes/manifests/如果你改过apiserver的启动参数这里的yaml文件会被kubelet感知并自动重启。所以检查核心组件时先看这个目录下的文件内容是否和预期一致再去看Pod状态能少走很多弯路。4.2 部署nginx验证DNS与调度系统组件全Running后我会部署一个无状态应用来验证从容器调度到服务发现的全链路。最常见的验证对象是nginx正好对应很多线上实际场景。创建一个最小部署kubectl create deployment nginx-test --imagenginx:1.25 --replicas3 kubectl expose deployment nginx-test --port80 --typeNodePort这里--replicas3同时验证了多副本调度--typeNodePort让外界能访问。接着检查Pod是否分布在多个节点再通过ClusterIP和NodePort两种方式访问。kubectl get svc会看到ClusterIP和NodePort端口。在集群内用curl http://cluster-ip能通说明kube-proxy的iptables规则正常在节点外访问http://node-ip:nodeport能通说明转发链没问题。再进到另一个Pod里测试DNSkubectl run -it --rm debug-pod --imagebusybox:1.36 --restartNever -- nslookup kubernetes.default.svc.cluster.localkubernetes.default这个服务名解析成功说明coredns工作正常。如果nslookup提示server cant find kubernetes.default检查coredns的ConfigMap和网络策略。很多部署翻车不是主链路挂了而是这类细节没有验证。比如DNS Pod运行在节点A但业务Pod在节点BB上的Pod访问DNS时走的是ClusterIP如果当前节点的iptables规则没更新或conntrack表满了就会出现间歇性解析失败。5. Kubernetes部署避坑5条血泪教训5.1 现象kubeadm init卡在拉镜像超时这是我见过最多的问题。现象是执行kubeadm init后长时间停在[wait-for-control-plane]然后报错failed to pull image ... context deadline exceeded。原因是默认的registry.k8s.io镜像源在多数网络环境下连接不稳定。解决方法是先用ctr image pull测试到镜像仓库的连通性再决定是配代理还是切内网仓库。我习惯在初始化前执行kubeadm init --dry-run它能快速校验配置文件和镜像拉取能力不会真的创建集群。如果发现仓库不走代理把containerd的proxy配置加到/etc/systemd/system/containerd.service.d/http-proxy.conf里然后daemon-reload重启containerd。5.2 现象节点NotReady但coredns一直CrashLoopBackOff节点状态一直NotReadykubectl get pods -n kube-system看到coredns反复重启。用journalctl -u kubelet查日志看到Container runtime network not ready: NetworkPlugin cni failed to set up pod ...。这个问题几乎都是CNI网络插件没装好。Calico或Flannel的Pod没有Running或者Pod网段与CNI配置不一致。解决办法是先卸载再重新安装CNI并且严格核对podSubnet。很多人在kubeadm init时顺手写了10.244.0.0/16但Calico的默认IPPool是192.168.0.0/16两者不对齐Calico的Pod能起来但无法给业务Pod分配IP。卸载Calico时还要清理残留的/etc/cni/net.d/里的配置文件不然重装后仍会读到旧的网段配置。5.3 现象svc无法访问但pod正常Pod都Runningkubectl describe svc也看到Endpoints有地址但从集群内部访问Service ClusterIP就是不通。排查方法从kubectl get svc -o yaml看selector匹配是否精确再到节点上检查kube-proxy的iptables规则iptables -t nat -L KUBE-SERVICES | grep cluster-ip。如果规则存在但请求不通可能是开启了nf_conntrack的BE_liberal问题或节点上的rp_filter配置太严格丢弃了返程包。最让人困惑的一次是多个节点上iptables规则完全一样但只有某个节点上应用访问svc失败后来发现是这个节点的conntrack_max太小导致连接被丢弃。这类问题外表像网络插件故障实际是内核参数问题。5.4 现象证书过期后集群拒绝连接部署完三个月后同事跑kubectl get nodes报Unable to connect to the server: x509: certificate has expired or is not yet valid。原因是kubeadm签发的证书默认有效期是一年控制面证书、etcd证书、admin.conf里的客户端证书都会过期。解决方法是先备份/etc/kubernetes目录然后用kubeadm certs renew all更新证书更新完需要重启apiserver、controller-manager、scheduler这些静态Pod容器以及更新用户侧的~/.kube/configsudo kubeadm certs renew all sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo systemctl restart kubelet这里要注意etcd的证书虽然也会renew但如果etcd是外部集群kubeadm无法管理它的证书。生产环境我在部署第一天就会加一个定时任务每个月检查一次kubeadm certs check-expiration避免集群在凌晨悄无声息地断掉。5.5 现象部署nginx时镜像拉取失败工作节点把业务Pod调度上去后容器长时间ImagePullBackOff。kubectl describe pod看到Failed to pull image nginx:1.25: rpc error: code Unknown desc failed to pull and unpack image ... connection refused。这不是Kubernetes的问题而是工作节点上的containerd拉取Docker Hub镜像被限制。解决方式是给容器运行时配置registry mirror如果你的环境允许可以在/etc/containerd/config.toml里配置镜像仓库加速地址。对于生产环境更可靠的做法是部署一个内部镜像仓库业务镜像统一推送到内网节点上的containerd将docker.io前缀替换为内网地址。这里尤其注意不要只改主节点每个工作节点都要同步配置。6. 部署完后的进阶验证用kubectl top与etcd健康检查摸清集群家底集群能跑不算完我还会花一刻钟做一次“体检”重点看资源水位和etcd状态。首先部署metrics-server否则kubectl top nodes会报Metrics API not available。metrics-server从节点上通过kubelet的10250端口采集指标但新集群的聚合层默认是打开的主要障碍是证书。它需要连接kubelet的只读端口并校验server证书反映到现象就是Pod起来了但无法采集数据。常见做法是在metrics-server部署参数里加--kubelet-insecure-tls或--kubelet-preferred-address-typesInternalIP前者跳过证书校验适合内网后者避免它拿着节点hostname去解析。装上后kubectl top nodes kubectl top pods -n kube-systemtop nodes能看到每个节点的CPU和内存请求与实际的对比。如果某个节点CPU使用率已经上了70%后续调度就要小心。top pods则能发现那些占了资源但没有实际工作的Pod。etcd是Kubernetes的黑匣子它不健康往往表现为集群间歇性不可用。我用三种方式验证它。第一种是看etcd Pod日志kubectl logs -n kube-system etcd-node如果有leader changed频繁出现说明etcd在选主可能是磁盘慢或网络抖动。第二种是直接调用etcd的metrics接口curl -s http://127.0.0.1:2379/metrics | grep etcd_server_has_leader返回1说明当前实例认为集群有leader返回0说明它失去了leader视图。第三种是检查etcdctl endpoint health --cluster如果某个节点返回failed基本可以判定它的数据目录或WAL有问题。这些检查做完你对集群的脾气就有数了。最后分享一个我吃了不少亏后养成的习惯每次部署完把kubeadm init的配置、镜像版本、内核参数、CNI版本全部记到一个Markdown文件里命名带日期。三个月后你大概率会忘了当初用的Calico是哪个补丁版本找这份存档比翻历史记录快得多也是换人接锅时唯一能靠住的文档。希望这个落地流程能帮你在Kubernetes部署上少走几趟夜路。本文还有配套的精品资源点击获取