ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

kubeadm init到nginx暴露:Kubernetes v1.26部署避坑实录

kubeadm init到nginx暴露:Kubernetes v1.26部署避坑实录 最近重新搭一套Kubernetes测试环境终端敲下kubeadm init第一行蹦出[init] using kubernetes version: v1.26.0紧跟着是[preflight] running pre-flight checks。这一串输出我见过很多次但每次看到都还是会有点警觉——说明后面每一步都可能冒出新的坑。群里不少朋友也在问kubeadm部署v1.26到底要注意什么为什么节点总是NotReadynginx怎么才能暴露成可访问的服务。干脆把这次从头到尾的实操过程写成一篇散记既是给自己存档也给准备从零搭集群的人一个参考。这篇不会去讲高深原理更偏重于实际动手时看到的输出、日志和排查路径尤其是preflight检查、init 后节点状态以及最后把 nginx 跑起来并暴露访问这几件事。如果你手里正好有一台干净的主机想照着走一遍那这篇就很对路了。1. 看到v1.26.0这行日志版本背后藏着一套新习惯1.1 被大量教程默认使用的版本Kubernetes 的发布节奏是一年三个大版本v1.26.0 在 2023 年初被广泛使用。虽然现在的新版本已经往前走了很久但大量企业内部环境、培训机构、以及网上流传的部署脚本依然停留在 v1.26 附近。原因很简单这个版本在稳定性、功能完整度和周边工具的兼容性之间基本处于一个舒服的位置。更关键的是v1.26 所处的时代已经是容器运行时必选 containerd的阶段。从 v1.24 开始项目正式把 dockershim 从源码里移掉v1.26 对这个事情已经沉淀了两个版本各种踩坑案例也足够多。也就是说你在这个版本上用 containerd不会再遇到太多找不到参考资料的尴尬。我在初始化之前先确认了一遍三样东西kubeadm、kubelet、kubectl三个二进制版本对齐都是 v1.26.0containerd已安装并且systemctl status containerd正常主机系统开启了 IP 转发swap 处于关闭状态很多人容易在这一步忽略版本对齐只装了 kubeadm 就执行 init。kubeadm 本身对版本有个容忍范围但 kubelet 和 kubeadm 相差过大时preflight 阶段就会直接报错提示版本不匹配。与其等到报错再回头不如开始前一次到位。1.2 新环境里先确认的系统参数在正式执行 init 前我用两分钟把系统参数过了一遍cat /proc/sys/net/ipv4/ip_forward sysctl net.bridge.bridge-nf-call-iptables swapoff -a sed -i s/^\(.*swap.*\)$/# \1/ /etc/fstab第一个参数的值必须为 1否则 kubelet 启动后节点流量转发会不正常第二个参数依赖br_netfilter内核模块如果显示 0需要先加载模块再设置。这两个小地方是我之前踩过最多的坑而且它们不会让你的 init 失败只会让后面 Pod 之间通信出现非常诡异的不通现象。swap 这件事也一样kubeadm init在默认情况下会禁止在有 swap 的主机上初始化虽然可以通过--ignore-preflight-errorsSwap硬闯但硬闯之后 kubelet 的资源分配逻辑会受到很大干扰。与其和它对抗不如直接关掉。2. preflight那行输出部署前它替我挡下了四个实际坑2.1 preflight到底检查了哪些东西[preflight] running pre-flight checks这句话看起来轻描淡写实际上 kubeadm 在背后做了一大串检查。它不是走过场而是把“你大概率会忽略的问题”提前摆到桌面上。我自己整理了一个最需要关注的检查清单检查项具体检查内容失败时的典型提示端口占用6443、10250、10257、10259 是否已被监听Port 6443 is already in use内核模块是否加载 br_netfilter、ip_vs 相关模块报缺少 /proc/sys/net/bridge 配置swap 状态swap 是否关闭running with swap on is not supportedCRI socket能否找到 containerd 的 socketFileContent--proc-sys-net-bridge-bridge-nf-call-iptables或 CRI 版本错误已有集群残留/etc/kubernetes 是否已存在证书或配置/etc/kubernetes/manifests/kube-apiserver.yaml already exists硬件资源CPU 核数、内存大小是否满足最低限制insufficient memory很多初次部署的人只关注第一项其实后面几项才是真正的拦路虎。CRI socket 找不到是我遇到过最多的问题装了 containerd但是版本太老或者 socket 路径不是 kubeadm 默认的/run/containerd/containerd.sock检查就会直接卡死。2.2 我实际踩过的preflight失败现场这次部署我特意留了一台“不干净”的机器想看看 preflight 到底能拦住多少雷。第一次执行 init直接报了两个错误第一个是端口 10259 被占用。10259 是 kube-scheduler 的默认端口之前测试时有一个旧的 scheduler 进程没有清掉。排查命令很简单ss -lntp | grep 10259看到 PID 后确认不是重要服务就直接 kill再用kubeadm reset把环境清干净。第二个错误更有意思preflight 提示/etc/kubernetes/manifests目录已经存在。这种情况常见于你曾经在这台机器上执行过kubeadm init即使后来按教程删了 Pod残留文件还在。处理方式不是直接rm -rf而是先备份mv /etc/kubernetes /etc/kubernetes.bak.$(date %Y%m%d)不直接删是因为万一后面排查需要旧配置还能找回来。备份之后重新执行 init这次就顺畅了。这里提一个比较容易被忽视的细节preflight 检查不是所有失败都会让你停下。部分检查项只是 warning比如某个系统参数不推荐但还没到致命程度。如果你看到[preflight] The following warnings were printed这行不要慌先看清楚 warning 内容再决定是忽略还是修复。绝大多数情况建议修复尤其是 cgroup driver 相关和网络桥接相关的 warning后面会演变成大问题。3. init成功后的输出每一行都在提醒你下一步动作3.1 从init到kubeconfig集群骨架是怎么搭起来的执行成功之后kubeadm 会打出一长串输出。新手往往被最后“You can now join any number of control plane nodes”吸引忽略了中间的信息。实际这一系列输出在告诉你证书已经生成存放在/etc/kubernetes/pki各组件静态 Pod 清单已经写入/etc/kubernetes/manifestsadmin.kubeconfig、kubelet.kubeconfig 等文件已生成CoreDNS、kube-proxy 作为插件被标记安装注意此时集群还不能用。你需要在当前节点上配置 kubectl 才能访问mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config如果不做这一步后面所有kubectl get nodes都会报连接 refused 或 no configuration provided。还有一个常见情况是明明配置了但还是报context was not found多半是KUBECONFIG环境变量指向了别的文件执行unset KUBECONFIG或者彻底改成新路径即可。第一次看到kubectl get nodes返回 NotReady 是正常现象不用急着怀疑自己哪里做错了。绝大多数单节点或者多节点 kubeadm 集群在刚 init 完的几分钟内节点都是 NotReady 状态原因很简单CNI 网络插件还没装。没有 CNI节点上的 Pod 无法获得网络地址kubelet 的健康检查也就过不了。3.2 网络插件选择我为什么翻车后又选了Calico我的经验是kubeadm init阶段就要想好 pod 网段并且和 CNI 插件的默认网段保持一致。这次我用了--pod-network-cidr10.244.0.0/16这个经典网段几乎兼容所有常用插件。安装 Calico 时最省事的方式是拿官方 manifest 直接应用kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml如果你 init 时自定义过网段而不是用10.244.0.0/16记得在 calico.yaml 里找到CALICO_IPV4POOL_CIDR这处配置改成对应网段否则集群虽然能就绪但 Pod 之间的路由会乱成一锅粥。我也试过先在没装 CNI 的情况下就开始部署 nginx结果非常典型nginx Pod 一直 Pendingkubectl describe pod 显示 0/1 nodes available原因是 node 上有 taint 或者网络未就绪。解决顺序应该是kubectl get nodes kubectl wait --forconditionReady node --all等节点全部 Ready 再往后走。Calico 装完不需要手动做什么它会自动处理节点路由和 Pod 网段分配。看到节点 Ready意味着控制面这半边彻底通了。4. 把nginx跑起来从Deployment到真正能访问4.1 不要直接裸奔Pod用Deployment管理很多人喜欢搜“kubernetes 部署 nginx”搜到的第一个命令是kubectl run nginx --imagenginx然后发现 Pod 被删了或者连不上。kubectl run适合临时调试真正做事应该用 Deployment它能帮你保证副本数、滚动更新、故障自愈。我的做法是kubectl create deployment nginx --imagenginx:1.25 --replicas2 kubectl get deployment nginx这里有几个小经验镜像最好写具体版本标签不要用latest。在 K8s 里latest会被每个节点按自己的imagePullPolicy处理很容易出现同一套 yaml 在不同时间部署出不同版本的阴影。如果镜像拉取失败大概率是镜像仓库访问问题或者 tag 写得不对kubectl describe pod的 Events 里会直接显示Failed to pull image。生产环境建议把镜像提前推到私有仓库Deployment 里配 imagePullSecrets避免每个节点都去公网拉。kubectl create deployment创建的是最简 Deployment不包含探针和资源限制。如果要模拟真实生产我会再kubectl edit deployment nginx补上 readinessProbe 和 limitsresources: requests: memory: 128Mi cpu: 100m limits: memory: 256Mi cpu: 250m readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 10没有资源限制的 Pod 在节点压力大的时候不会被优先驱逐但可能把节点打到内存耗尽这是很经典的运维事故值得一开始就避免。4.2 Service类型怎么选流量到底怎么走Pod 创建出来后 IP 是动态的不能直接用。需要 Service 做一层稳定入口。Kubernetes 的 Service 类型绝大多数情况下只需要在下面几个里选Service 类型访问方式适用场景ClusterIP集群内部通过虚拟 IP 访问微服务间调用NodePort通过任意节点 IP 30000-32767 端口访问测试、小规模暴露LoadBalancer云厂商 LB 分配到节点 NodePort生产入口ExternalNameDNS CNAME 映射外部服务集成外部系统个人部署经验NodePort 是最快能看到效果的暴露方式。kubectl expose deployment nginx --port80 --target-port80 --typeNodePort kubectl get service nginx执行完能看到类似下面这样的输出注意PORT(S)一列NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx NodePort 10.108.xx.xx none 80:31887/TCP 5s这时候从集群任意节点访问节点IP:31887就能拿到 nginx 页面。31887 是 K8s 自动分配的高位端口也可以手动固定只要在 30000-32767 范围内即可。请求的实际路径是客户端 → 节点 IP:31887 → kube-proxy 在节点上做的 iptables/ipvs 规则 → 后端某个 nginx Pod。也就是说NodePort 的入口是所有节点共享的你访问 master 或 worker 的 IP 都能通前提是节点的安全组或防火墙放行了对应端口。如果你把这个测试环境放到云上记得去安全组里放行 NodePort 端口否则本地浏览器访问会超时。这个坑我在线下环境没遇到过一上云就暴露了排查了半个小时最后发现是安全组没加规则。4.3 验证访问链路时顺手做的事Pod 数量一多流量怎么分就需要看证据了。默认 kube-proxy 用 iptables 模式可以在节点上执行iptables -t nat -L KUBE-SERVICES -n | grep nginx iptables -t nat -L KUBE-SVC-XXXXXX -n能看到 DNAT 目标是一组随机概率相等的 Pod IP说明 kube-proxy 规则在正常生效。如果节点比较多想用更高效的负载均衡方式可以在 kube-proxy 配置里把 mode 改成 ipvs然后在所有节点加载 ip_vs、ip_vs_rr 等内核模块。两者没有绝对好坏iptables 兼容性最好ipvs 在大规模服务场景下规则数量和转发性能更好。对我这个测试环境来说iptables 完全够用。5. Pod出故障时别再频繁删Pod了5.1 我的排查顺序是一刀切还是分步走部署 nginx 的过程中几乎没有人能一次成功我自己就经常碰到 Pod 一直处于 ContainerCreating。很多新手的第一反应是把 Pod 删了重建但这样会把错误事件也一起删掉最后无限循环。我的固定排查顺序kubectl get pod kubectl describe pod pod-name kubectl logs pod-name如果 Pod 还没 Readykubectl logs可能拿不到日志因为容器还没真正启动。这时候可以看 describe 输出里的 Events里面会写清楚卡在哪一步。5.2 ImagePullBackOff背后的常见真相Events 里出现Failed to pull image或ImagePullBackOff一般逃不出这几个原因镜像 tag 写错仓库里根本没有这个 tag节点机器无法访问镜像仓库或者拉取超时私有仓库需要认证但没配置 imagePullSecret磁盘空间不足镜像层无法写入典型排查动作是手动在节点上ctr -n k8s.io images pull nginx:1.25或crictl pull nginx:1.25看能不能复现。能复现的话错误信息比 K8s 的 Events 更直接。还有一次比较隐蔽的问题是 containerd 没配置好私有仓库的 hostsPod 拉取镜像时只会去默认仓库改用crictl pull立刻能看到 404。这种问题跟 K8s 本身没关系纯粹是容器运行时配置不到位。5.3 ContainerCreating卡住时cgroup driver是最大嫌疑Pod 一直 ContainerCreating 且 describe 里没有任何 ImagePull 错误优先级最高的怀疑对象是kubelet 和容器运行时的 cgroup driver 不一致。简单说systemd 环境下推荐 kubelet 和 containerd 都用systemd作为 cgroup driver如果 kubelet 用的 cgroupfs、containerd 用的 systemd节点会反复报容器启动失败。排查路径cat /var/lib/kubelet/config.yaml | grep cgroupDriver cat /etc/containerd/config.toml journalctl -u kubelet -f如果 containerd 的配置里没有显式开启 SystemdCgroup需要改成[plugins.io.containerd.grpc.v1.cri] systemd_cgroup true然后重启 containerdsystemctl restart containerd注意重启 containerd 会把当前节点上运行中的容器全部重启节点会短暂 NotReady所以这个操作尽量在业务低峰做。cgroup driver 问题改完之后之前卡住的 Pod 需要删除重建一次让 kubelet 按新参数重新创建容器。6. 散记里的零碎经验给刚搭完集群的人提个醒6.1 节点join时最容易忽略的token过期多节点集群往往在 init 之后先部署 nginx回头想加 worker 节点时发现 token 找不到了。kubeadm init 的输出里有 join 命令但 token 默认 24 小时后过期。重新生成的方式很顺手kubeadm token create --print-join-command --ttl0--ttl0表示不过期仅限测试环境。worker 节点加入后同样要装 CNI 之前先确保网络通。如果 join 命令里的--discovery-token-ca-cert-hash忘记了可以重新获取一下 CA 证书哈希不要凭记忆乱填否则 worker 会一直报认证失败。6.2 证书和控制面文件的备份比你想的重要Kubernetes 集群的应急恢复能力往往取决于/etc/kubernetes/pki下的证书有没有备份。证书一旦丢失光靠kubeadm reset重建是很痛苦的所有已接入的 worker 节点都要重新 join。我的习惯是每次部署稳定后立刻把/etc/kubernetes打成 tar 包再加上 etcd 的快照备份。etcd 是集群状态的唯一来源Pod、Service、ConfigMap 全在那里定期备份 etcd 才是真正意义上的备份。很多刚入门的人只备份了 yaml 文件以为集群能原地重建实际上没有 etcd 快照集群的运行时状态都会丢。6.3 不要追新版本看到 v1.26 这个版本号时有些人第一反应是“太旧了吧”。但 K8s 版本升级不是普通软件升级跨大版本跳跃往往伴随着 API 变更、默认行为变化和插件兼容问题。集群不是越新越好而是要找到团队里大家都熟悉的稳定版本。尤其要注意像 calico、ingress-nginx、metrics-server 这类周边组件都有各自的 Kubernetes 版本兼容矩阵不是随便 download 最新版就能直接用。我习惯在装任何 addon 之前先看一眼官方兼容表避免把时间花在和组件版本的斗争上。6.4 最后补一个偷懒但好用的技巧如果你只是想在单机测试环境里跑 nginx又不想被 NodePort 的高位端口折腾可以直接用kubectl port-forwardkubectl port-forward service/nginx 18080:80随后浏览器访问localhost:18080就能看到 nginx 页面。这个方式只适合低流量调试性能瓶颈明显但它能让你快速验证 Deployment、Service、Pod 链路是否正常。我经常先用它确认应用没问题再切到 NodePort 做后续的流量验证。这篇散记得主要内容差不多就是这些。从 v1.26.0 的 init 日志开始到 preflight 检查再到 nginx 成功暴露访问整个链路其实并不复杂真正容易让人卡住的往往是版本没对齐、CNI 没装、cgroup driver 不一致这几个老问题。如果非要从这次经历里提炼一句话我会说先把 preflight 输出的每一行都读懂后面的坑至少能少一半。下一篇散记我打算聊聊 Pod 的安全上下文和资源限制在实际生产里的组合用法到时候再继续记。
返回列表