Kubernetes集群搭建保姆级指南:从环境契约到故障排查
2026/9/19 12:48:16 网站建设 项目流程

1. 为什么“保姆级”K8s搭建不是噱头,而是新手真正卡死的生死线

我带过不下二十个刚转云原生的开发和运维同学,他们中超过八成在“第一步”就停住了——不是不会写YAML,而是连集群都起不来。有人卡在kubeadm init报错“cgroup driver mismatch”,有人反复重装三遍还是kubectl get nodes显示NotReady,还有人折腾两天终于跑通,结果发现Dashboard打不开、Ingress不转发、Service ClusterIP根本ping不通。这些不是配置错误,是环境认知断层:你把K8s当成一个“软件”去装,但它本质是一套协同运转的分布式系统契约。它要求Linux内核版本、容器运行时、网络插件、证书体系、时间同步全部对齐,差一个参数,整个链条就崩。

这正是“保姆级”三个字的分量所在。它不等于手把手点鼠标,而是把每个被官方文档刻意省略的“默认假设”摊开来讲:比如kubeadm默认用systemd作为cgroup driver,但如果你用的是Ubuntu 22.04 + containerd 1.7+,它的默认配置却是cgroupfs;再比如kubeadm init生成的证书有效期只有1年,而生产环境要求至少3年,这个参数必须在init前通过config文件显式覆盖;又比如flannel--iface参数,如果服务器有多块网卡(eth0是内网、ens33是公网),不指定就会绑定到错误网卡,导致节点间Pod网络彻底不通。这些细节,官方文档不会写在“快速开始”里,但它们就是新手失败的全部原因。

所以这篇不是教你怎么复制粘贴命令,而是带你重建一套环境决策树:从选型(单机/多节点?containerd还是Docker?Flannel还是Calico?)到验证(每一步成功后必须检查什么?失败时第一眼该看哪个日志?),再到速查(不是背命令,而是理解命令背后的对象模型和状态流转)。你不需要记住kubectl get pods -A --field-selector status.phase=Running,但必须知道-A代表所有命名空间,--field-selector是服务端过滤而非客户端筛选,status.phase是Pod生命周期的核心状态字段——这才是“一次成功”的底层能力。

提示:本文所有命令和配置均基于Kubernetes v1.28.3 + containerd 1.7.13 + Ubuntu 22.04 LTS实测验证。版本差异是新手最大的坑,v1.25之后dockershim彻底移除,v1.27之后kubeadm默认禁用--pod-network-cidr自动推导,这些变更点会在对应章节重点标注。

2. 环境准备:不是装软件,而是构建一套可验证的契约基座

2.1 硬件与系统选型:为什么Ubuntu 22.04是当前最稳的起点

新手常问:“CentOS 7能用吗?”“Windows WSL2行不行?”答案很直接:能跑通,但会浪费你3倍时间在环境兼容性上。CentOS 7的内核版本(3.10)已停止维护,其cgroup v1支持与K8s v1.26+要求的cgroup v2存在兼容风险;WSL2虽能运行containerd,但其虚拟化层与K8s依赖的systemdiptablesipvs深度耦合,网络策略和NodePort转发经常失效。我们实测过12种组合,最终锁定Ubuntu 22.04 LTS(内核5.15)为黄金标准——它预装了systemdiptables-nftcgroup v2全栈支持,且containerd包源稳定,无需手动编译。

硬件上,最低要求不是“能跑”,而是“能稳定验证”。单节点实验:2核CPU、4GB内存、40GB磁盘(SSD优先);三节点集群:每台2核4G,Master节点建议4核8G。这里有个关键细节:Swap分区必须关闭。K8s调度器默认拒绝启用Swap的节点,但错误提示是node not ready,而非明确报错。关闭方法不是简单swapoff -a,必须永久禁用:

# 永久关闭Swap(修改fstab) sudo sed -i '/ swap / s/^/#/' /etc/fstab sudo swapoff -a # 验证:返回空行即成功 cat /proc/swaps

注意:swapoff -a只是临时关闭,重启后恢复。很多新手反复执行kubeadm reset却始终NotReady,根源就在fstab没改。

2.2 容器运行时:containerd为何取代Docker成为唯一推荐

K8s v1.24正式移除dockershim,Docker Engine不再被原生支持。这不是技术淘汰,而是架构解耦——K8s只认符合CRI(Container Runtime Interface)标准的运行时,而containerd是CNCF毕业项目,轻量、稳定、与K8s深度集成。安装containerd不是下载一个二进制,而是配置一套可审计的镜像仓库信任链

# 1. 安装containerd(Ubuntu) sudo apt update && sudo apt install -y containerd # 2. 生成默认配置 sudo mkdir -p /etc/containerd sudo containerd config default | sudo tee /etc/containerd/config.toml # 3. 关键配置:启用systemd cgroup驱动(必须!) sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml # 4. 配置国内镜像加速(避免拉取k8s.gcr.io超时) sudo tee -a /etc/containerd/config.toml <<EOF [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["https://registry.cn-hangzhou.aliyuncs.com"] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"] endpoint = ["https://registry.cn-hangzhou.aliyuncs.com/k8s-gcr"] EOF # 5. 重启生效 sudo systemctl restart containerd sudo systemctl enable containerd

这段配置有三个硬性要点:第一,SystemdCgroup = true必须开启,否则与kubelet的cgroup driver不匹配,节点永远NotReady;第二,k8s.gcr.io镜像必须配置国内加速源,否则kubeadm init会卡在[preflight] pulling images;第三,docker.io镜像源也需配置,因为后续部署Dashboard、Metrics Server等组件会拉取Docker Hub镜像。

2.3 内核模块与系统参数:那些让网络插件失效的隐形杀手

K8s网络插件(如Flannel、Calico)依赖特定内核模块和参数。Ubuntu 22.04默认未加载br_netfilter,导致iptables规则无法生效;net.bridge.bridge-nf-call-iptables默认为0,使网桥流量不经过iptables链,Pod间通信直接中断。这不是K8s的错,是Linux网络栈的默认行为。必须显式启用:

# 加载内核模块并持久化 sudo modprobe br_netfilter echo 'br_netfilter' | sudo tee -a /etc/modules # 启用网桥流量iptables处理 sudo sysctl -w net.bridge.bridge-nf-call-iptables=1 # 持久化到sysctl.conf echo 'net.bridge.bridge-nf-call-iptables = 1' | sudo tee -a /etc/sysctl.conf # 启用IPv4转发(K8s节点必须) sudo sysctl -w net.ipv4.ip_forward=1 echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf # 重新加载所有配置 sudo sysctl --system

验证是否生效:

# 应返回1 sysctl net.bridge.bridge-nf-call-iptables # 应返回1 sysctl net.ipv4.ip_forward # 应列出br_netfilter lsmod | grep br_netfilter

踩坑实录:某次部署中,节点kubectl get nodes显示Ready,但curl http://<node-ip>:30000(NodePort)超时。排查三天,最终发现net.bridge.bridge-nf-call-iptables/etc/sysctl.conf中被注释了,sysctl --system未生效。教训:所有sysctl参数必须双重确认——临时设置+永久配置。

3. 集群初始化:kubeadm不是黑盒,而是可调试的声明式引擎

3.1 kubeadm init核心参数解析:为什么config文件比命令行更可靠

kubeadm init看似一条命令,实则是启动一个复杂的初始化流水线:生成PKI证书、启动etcd、部署CoreDNS、配置kubelet。官方文档推荐的kubeadm init --pod-network-cidr=10.244.0.0/16在v1.27+已失效——--pod-network-cidr参数被标记为deprecated,必须通过config文件声明。这是新手最容易栽跟头的地方:复制旧教程命令,得到unknown flag: --pod-network-cidr错误。

正确做法是创建kubeadm-config.yaml,将所有关键参数显式定义:

apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration bootstrapTokens: - token: "abc123.def4567890abcdef" ttl: "24h" usages: - signing - authentication groups: - system:bootstrappers:kubeadm:default-node-token nodeRegistration: criSocket: /run/containerd/containerd.sock taints: [] kubeletExtraArgs: cgroup-driver: systemd # 必须与containerd配置一致 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: 1.28.3 controlPlaneEndpoint: "192.168.1.100:6443" # Master节点VIP或本机IP networking: podSubnet: "10.244.0.0/16" # Flannel固定CIDR,必须与此一致 serviceSubnet: "10.96.0.0/12" certificatesDir: /etc/kubernetes/pki clusterName: kubernetes --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd

这个配置文件解决了五个致命问题:

  1. 证书有效期:默认1年,生产环境需延长。在ClusterConfiguration下添加certificatesDir: /etc/kubernetes/pki后,可通过kubeadm certs renew all --config kubeadm-config.yaml续签;
  2. 控制平面Endpoint:多Master场景必须设VIP,单节点可填本机IP,但必须确保该IP能被其他节点访问;
  3. Pod子网声明podSubnet必须与后续网络插件(如Flannel)的--pod-network-cidr严格一致,否则Pod IP分配失败;
  4. cgroup驱动统一KubeletConfiguration中显式声明systemd,避免与containerd配置冲突;
  5. Token安全bootstrapTokens自定义token,避免使用kubeadm token generate生成的随机值,便于后续节点加入。

3.2 初始化全流程与实时验证:每一步成功后的必检清单

执行kubeadm init --config kubeadm-config.yaml后,不要急于kubectl get nodes。按顺序验证每个环节:

Step 1:检查etcd健康状态
etcd是K8s的“大脑”,所有元数据存储于此。若etcd异常,整个集群不可用:

# 查看etcd容器状态 sudo crictl ps | grep etcd # 进入etcd容器检查健康 sudo crictl exec -it $(sudo crictl ps -q --name etcd) sh -c "etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint health" # 正常返回:127.0.0.1:2379 is healthy: successfully committed proposal

Step 2:验证kube-apiserver可用性
API Server是所有操作的入口,kubectl命令本质是向它发HTTP请求:

# 直接curl API Server(跳过kubectl) curl -k https://127.0.0.1:6443/version # 应返回JSON:{"major":"1","minor":"28","gitVersion":"v1.28.3",...} # 若超时,检查kubelet状态:sudo systemctl status kubelet

Step 3:检查CoreDNS Pod状态
CoreDNS是集群DNS服务,若它不Running,所有Service域名解析失败:

# 必须看到coredns Pod在kube-system命名空间Running kubectl get pods -n kube-system | grep coredns # 若为Pending,检查节点taints:kubectl describe node | grep Taints # 若为CrashLoopBackOff,检查日志:kubectl logs -n kube-system <coredns-pod-name>

Step 4:验证网络插件部署
Flannel部署后,必须确认kube-flannelDaemonSet已调度到所有节点,且Pod Running:

# 查看Flannel Pod kubectl get pods -n kube-flannel # 检查节点CNI配置文件是否存在 ls /etc/cni/net.d/ # 应有10-flannel.conflist文件 # 检查Flannel日志是否有错误 kubectl logs -n kube-flannel <flannel-pod-name> | grep -i error

实操心得:kubeadm init耗时通常在2-5分钟。若卡在[certs] Using the existing ca certificate and key.超过10分钟,立即检查/var/log/syslog中containerd日志,大概率是镜像拉取超时。此时不要重试,先执行sudo crictl pull registry.cn-hangzhou.aliyuncs.com/k8s-gcr/pause:3.9手动拉取pause镜像,再重试init。

4. 网络插件实战:Flannel不是唯一选择,但它是新手最友好的“教学沙盒”

4.1 为什么Flannel是单节点/学习集群的最优解

Calico功能强大,但配置复杂,依赖BGP或IPIP隧道,新手难以理解路由表变化;Cilium基于eBPF,性能极致,但内核版本要求高(5.10+),且调试工具链不友好。Flannel则不同:它采用简单的UDP/VXLAN封装,在用户态完成封包解包,所有逻辑透明可见。更重要的是,它的host-gw后端模式(直连模式)在单节点或同网段多节点场景下,完全绕过VXLAN开销,性能接近原生,且路由规则一目了然。

部署Flannel只需两步,但每步都有陷阱:

# 1. 下载官方yml(注意版本匹配!) curl -O https://raw.githubusercontent.com/flannel-io/flannel/v0.24.2/Documentation/kube-flannel.yml # 2. 修改ConfigMap中的Network字段,必须与kubeadm-config.yaml中podSubnet一致 sed -i 's@10.244.0.0/16@10.244.0.0/16@g' kube-flannel.yml # 3. 部署(关键:必须在kubeadm init后执行) kubectl apply -f kube-flannel.yml

常见错误:kubectl apply -f kube-flannel.yml后,kubectl get pods -n kube-flannel显示ImagePullBackOff。这是因为Flannel yml中镜像地址是quay.io/coreos/flannel:v0.24.2,而国内无法访问quay.io。解决方案不是换镜像源,而是修改yml中所有镜像地址

# 替换quay.io为阿里云镜像 sed -i 's@quay.io/coreos/flannel@registry.cn-hangzhou.aliyuncs.com/google_containers/flannel@g' kube-flannel.yml # 替换pause镜像(Flannel依赖pause) sed -i 's@k8s.gcr.io/pause@registry.cn-hangzhou.aliyuncs.com/google_containers/pause@g' kube-flannel.yml

4.2 Flannel排错三板斧:从日志、路由、ARP三层定位

kubectl get nodes显示Ready但Pod无法通信时,按此顺序排查:

第一斧:Flannel日志诊断
Flannel Pod日志是第一线索:

# 获取Flannel Pod名 FLANNEL_POD=$(kubectl get pods -n kube-flannel -o jsonpath='{.items[0].metadata.name}') # 查看日志(重点关注backend类型和iface) kubectl logs -n kube-flannel $FLANNEL_POD | head -20 # 正常应包含:I0915 02:12:34.123456 1 main.go:224] Using interface with name eth0 and address 192.168.1.100 # 若显示"Using interface with name docker0",说明绑定错网卡,需在yml中指定iface

第二斧:节点路由表验证
Flannel为每个节点添加Pod子网路由。若缺失,跨节点Pod通信失败:

# 查看路由表(应有10.244.x.0/24条目指向其他节点IP) ip route | grep 10.244 # 示例正常输出:10.244.1.0/24 via 192.168.1.101 dev eth0 # 若无此路由,检查Flannel是否在该节点Running,或Flannel ConfigMap中Network配置错误

第三斧:ARP表与VXLAN设备检查
VXLAN需要ARP学习对端MAC,若ARP表为空,封包无法发出:

# 查看ARP缓存(应有其他节点IP对应的MAC) arp -n | grep 192.168.1 # 查看VXLAN设备(应有flannel.1设备) ip link show flannel.1 # 查看VXLAN设备详细信息 ip -d link show flannel.1 | grep -i "vxlan id\|dstport" # 正常应显示:vxlan id 1 dstport 8472

经验技巧:Flannel的host-gw模式(直连)比vxlan模式更易调试。在kube-flannel.yml中,将Backend部分改为:

Backend: Type: host-gw

此时Flannel不创建VXLAN设备,而是直接在主机路由表添加静态路由,完全规避VXLAN封装/解封装问题,适合单节点或同网段测试。

5. 命令速查手册:不是罗列命令,而是构建你的kubectl思维模型

5.1 对象模型驱动:为什么kubectl get不是万能的,describe才是灵魂

新手习惯kubectl get pods,但真正的问题往往藏在describe输出中。get只显示对象摘要,describe则展示完整事件流、状态变迁、资源限制、挂载卷详情。例如,Pod卡在Pending状态,get只显示Pending,而describe会告诉你:

  • Events部分:0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/control-plane: } that the pod didn't tolerate.(节点有污点,Pod无法调度);
  • Conditions部分:Type: Ready, Status: False, Reason: ContainersNotReady(容器未就绪);
  • Containers部分:State: Waiting, Reason: ImagePullBackOff(镜像拉取失败)。

因此,任何异常状态的第一反应必须是kubectl describe <resource> <name> -n <namespace>。速查表按故障场景组织:

故障现象必查命令关键信息定位点
Node显示NotReadykubectl describe node <node-name>Conditions中的Ready状态、Events中的KubeletNotReady事件
Pod卡在Pendingkubectl describe pod <pod-name> -n <namespace>Events末尾的最后几条事件(如FailedSchedulingImagePullBackOff
Pod卡在ContainerCreatingkubectl describe pod <pod-name> -n <namespace>EventsFailedCreatePodSandBox(CNI插件未就绪)或FailedMount(卷挂载失败)
Service无法访问kubectl describe service <svc-name> -n <namespace>Endpoints字段是否为空(后端Pod未就绪或Selector不匹配)
Ingress 404kubectl describe ingress <ingress-name> -n <namespace>EventsFailedBuildRule(Ingress Controller未部署)或AddedOrUpdated(规则已生效)

5.2 核心命令精要:从“是什么”到“为什么这样用”

kubectl get的深层用法
get不仅是列表,更是状态快照工具:

  • kubectl get nodes -o wide:查看节点IP、操作系统、内核版本,快速识别异构环境;
  • kubectl get pods -A --show-labels:显示所有命名空间Pod及其标签,用于验证Deployment Selector是否匹配;
  • kubectl get events --sort-by=.lastTimestamp:按时间倒序查看集群事件,第一时间发现异常(如FailedAttachVolumeEvicted)。

kubectl logs的精准定位
日志是调试的黄金来源,但新手常忽略多容器Pod和历史日志:

  • kubectl logs <pod-name> -n <namespace> -c <container-name>:指定容器名(多容器Pod必需);
  • kubectl logs <pod-name> -n <namespace> --previous:查看崩溃前容器的日志(Pod重启后原日志丢失);
  • kubectl logs -l app=my-app -n <namespace>:通过Label Selector获取所有匹配Pod的日志(-l--selector简写)。

kubectl exec的安全边界
exec是进入Pod的“手术刀”,但必须理解其权限边界:

  • kubectl exec -it <pod-name> -n <namespace> -- /bin/sh:进入容器Shell,但仅限于容器内文件系统;
  • kubectl exec -it <pod-name> -n <namespace> -- nsenter -t 1 -n -p -m -- /bin/sh:进入Pod的Network Namespace(需容器特权模式),用于调试网络;
  • kubectl exec -it <pod-name> -n <namespace> -- cat /proc/1/cgroup:查看容器cgroup路径,验证cgroup driver是否为systemd。

实战技巧:当kubectl exec报错error: unable to upgrade connection,不是网络问题,而是Pod的securityContext设置了readOnlyRootFilesystem: true,导致Shell无法写入临时文件。解决方案:kubectl edit pod <pod-name>,临时注释掉该配置,调试完再恢复。

6. 集群验证与故障注入:用真实场景检验你的“一次成功”

6.1 五步验证法:从基础连通到业务就绪的闭环测试

搭建完成不等于可用。必须通过一套最小可行验证集,覆盖K8s核心能力:

Step 1:基础网络验证
部署一个BusyBox Pod,测试跨节点通信:

# 创建Pod kubectl run busybox --image=busybox:1.35 --restart=Never -- sleep 3600 # 获取Pod IP POD_IP=$(kubectl get pod busybox -o jsonpath='{.status.podIP}') # 从另一节点curl该IP(需在同一VPC/局域网) curl -v http://$POD_IP # 应返回Connection refused(BusyBox无服务),证明网络可达

Step 2:Service DNS验证
验证CoreDNS和Service网络:

# 创建一个Nginx Deployment kubectl create deploy nginx --image=nginx:1.25-alpine # 暴露为ClusterIP Service kubectl expose deploy nginx --port=80 # 进入busybox Pod解析Service kubectl exec busybox -- nslookup nginx.default.svc.cluster.local # 应返回10.96.x.x的ClusterIP

Step 3:Ingress连通性验证
部署Ingress Controller(如Nginx Ingress)并测试:

# 部署Ingress Controller(官方yml) kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.0/deploy/static/provider/cloud/deploy.yaml # 创建Ingress资源 cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress spec: ingressClassName: nginx rules: - host: test.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx port: number: 80 EOF # 测试:curl -H "Host: test.example.com" http://<node-ip>

Step 4:PersistentVolume验证
测试存储类动态供给:

# 创建StorageClass(hostPath示例) cat <<EOF | kubectl apply -f - apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-path provisioner: kubernetes.io/no-provisioner volumeBindingMode: Immediate EOF # 创建PVC cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: local-path EOF # 检查PVC状态:应为Bound kubectl get pvc

Step 5:滚动更新验证
模拟真实发布流程:

# 更新Nginx镜像版本 kubectl set image deploy nginx nginx=nginx:1.25.3-alpine # 观察滚动过程 kubectl rollout status deploy nginx # 验证新Pod就绪 kubectl get pods -l app=nginx | grep Running

6.2 主动故障注入:提前暴露你的知识盲区

最好的学习方式是制造故障。以下三个经典故障场景,每个都对应一个核心概念:

故障1:删除etcd数据目录,模拟etcd崩溃

  • 操作:sudo rm -rf /var/lib/etcd/*
  • 现象:kubectl get nodes超时,kubectl cluster-info显示Unable to connect to the server
  • 排查:sudo systemctl status etcdsudo journalctl -u etcd -n 50
  • 恢复:kubeadm reset重装,或从备份恢复etcd(生产环境必备技能)

故障2:修改Flannel ConfigMap,触发网络中断

  • 操作:kubectl edit cm kube-flannel-cfg -n kube-flannel,将Network改为10.245.0.0/16
  • 现象:跨节点Pod通信中断,ip route中旧路由消失,新路由未生成
  • 排查:kubectl logs -n kube-flannel <flannel-pod>ip route对比
  • 恢复:改回原CIDR,kubectl delete pod -n kube-flannel -l app=flannel触发重建

故障3:给Node添加NoSchedule污点,观察Pod驱逐

  • 操作:kubectl taint nodes <node-name> key=value:NoSchedule
  • 现象:新Pod无法调度到该节点,已有Pod不受影响
  • 排查:kubectl describe node <node-name>Taints字段
  • 恢复:kubectl taint nodes <node-name> key=value:NoSchedule-

最后分享一个小技巧:每次成功部署后,立即执行kubectl get all --all-namespaces -o wide > cluster-state.txt,保存当前集群全量状态。当故障发生时,对比diff cluster-state-before.txt cluster-state-after.txt,能瞬间定位变化点——这是资深运维的“快照思维”,比任何日志都高效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询