首页 / 资讯中心 / 文章详情

虚拟化环境手动部署Kubernetes v1.23.1集群实战指南

虚拟化环境手动部署Kubernetes v1.23.1集群实战指南 ★ FEATURED ARTICLE
1. 为什么在虚拟化环境里手动搭一套 Kubernetes 集群仍然值得做如果你已经用惯了云厂商托管的容器服务可能会觉得手动部署 Kubernetes 是一件费力不讨好的事。但我在实际工作中发现真正能把集群运维做扎实的人几乎都经历过从裸机或虚拟机上一砖一瓦搭建集群的过程。原因很简单托管服务把控制平面藏起来了你只能看到 API Server 暴露出来的那一层一旦遇到网络插件异常、证书过期、etcd 性能抖动这类问题没有亲手搭过集群的人往往连排查的入口都找不到。这次要聊的就是在本地虚拟化环境里用 kubeadm 部署一套 Kubernetes v1.23.1 集群的完整过程。选这个版本不是随便挑的v1.23 是 Kubernetes 生命周期里一个比较特殊的节点它正式移除了对 Docker 作为容器运行时的直接支持默认走 containerd同时 v1.23.x 系列在 kubeadm 的配置 API 上也做了不少调整。换句话说网上大量基于 v1.20 甚至更早版本的教程直接照抄到 v1.23 上会踩一堆坑这也是我写这篇东西的核心动机。这套方案适合谁如果你是想系统学习 Kubernetes 架构的运维或后端开发或者需要在本地复现一套接近生产拓扑的测试环境再或者你所在团队要自建机房里的容器平台那这套流程基本可以直接参考。整套集群我用的是一台控制平面节点加两台工作节点操作系统选的是 Ubuntu 20.04虚拟化平台用的是常见的桌面级虚拟机软件。资源分配上控制平面给 2 核 4G工作节点各 2 核 2G这个配置跑一个学习或测试集群足够了但如果要压测或者跑重负载内存至少翻倍。需要提前说明的是v1.23.1 这个版本对容器运行时的要求比较明确containerd 是首选而且 kubelet 和 containerd 之间的接口走的是 CRI不再依赖 dockershim。这个变化直接影响了后面安装步骤里的很多细节比如你不能再通过docker ps去看容器状态得用crictl。这些差异我会在对应环节里逐个点出来。2. 部署前的整体设计与关键选型拆解2.1 集群拓扑与资源规划思路在动手之前先把拓扑定下来。我采用的是一主两从的经典结构控制平面节点承担 API Server、Controller Manager、Scheduler 和 etcd 的全部职责两个工作节点只跑业务负载。这种结构在测试环境里最省资源同时又能完整演示调度、服务发现、滚动更新这些核心能力。资源分配上有个经验值可以参考控制平面的内存不能低于 2G否则 etcd 在写入频繁时容易 OOM工作节点的内存取决于你要跑什么如果只是跑几个 Nginx 和测试用 Pod2G 够用但一旦涉及 Java 应用或者带 sidecar 的服务网格4G 起步。CPU 方面每个节点至少 2 核因为 kubelet 和容器运行时本身就要占掉一部分。网络规划是另一个容易被忽视的点。我用的 Pod 网段是10.244.0.0/16Service 网段是10.96.0.0/12这两个网段不能和宿主机所在网段重叠否则会出现路由冲突。虚拟机的网络模式我选的是 NAT三台机器在同一个虚拟网段内互通同时能访问外网拉取镜像。如果你用的是桥接模式记得给每台机器分配独立的 IP并且确保它们之间没有防火墙拦截。提示虚拟机克隆出来的节点网卡 MAC 地址和 machine-id 会重复必须手动改掉否则 kubelet 注册节点时会出问题。这个坑我在第一次搭的时候踩得很深后面会详细说怎么处理。2.2 为什么选 containerd 而不是 Dockerv1.23 这个版本最显著的变化就是弃用了 dockershim。在 v1.23 之前kubelet 内置了一个 dockershim 组件用来把 Docker 的 API 翻译成 CRI 接口。从 v1.23 开始这个组件被正式移除意味着 kubelet 不能再直接跟 Docker 对话了。那为什么是 containerd 而不是 CRI-O从我的实际使用来看containerd 的生态成熟度更高配置相对简单而且它本身就是 Docker 的底层运行时镜像格式完全兼容。CRI-O 更轻量但配置起来对新手不太友好文档也相对少一些。对于学习和测试场景containerd 是更稳妥的选择。这里有个关键点要理解containerd 和 Docker 用的是同一套镜像但命令行工具不一样。以前你用docker ps看容器现在要用crictl ps以前用docker images现在用crictl images。crictl是 CRI 的通用客户端它直接跟容器运行时对话不经过 Docker 那一层。刚开始会不习惯但用久了你会发现它其实更直接。2.3 kubeadm 的定位与它能做什么、不能做什么kubeadm 是官方提供的集群引导工具它的职责很明确帮你把控制平面的核心组件跑起来把节点加入集群把证书生成好。但它不负责网络插件、不负责存储、不负责监控这些都要你自己装。很多人误以为 kubeadm 能一键搞定所有事结果装完发现 Pod 之间不通就是因为没装 CNI 插件。kubeadm 的工作流程大致分两步kubeadm init在控制平面节点上初始化集群生成证书、kubeconfig 文件启动静态 Pod 形式的控制平面组件kubeadm join在工作节点上执行通过 bootstrap token 向控制平面注册自己。整个过程是幂等的如果中途失败可以用kubeadm reset清理后重来。v1.23 的 kubeadm 在配置 API 上用的是kubeadm.k8s.io/v1beta3这个版本和之前的 v1beta2 有一些字段差异比如ClusterConfiguration里的controlPlaneEndpoint和networking字段的写法有微调。如果你从旧教程复制配置文件一定要检查 API 版本号否则 kubeadm 会直接报错拒绝解析。3. 核心细节解析与实操前的系统准备3.1 操作系统层面的基础配置三台虚拟机装好 Ubuntu 20.04 之后第一件事是更新系统并安装基础工具。我习惯先执行apt update apt upgrade -y然后装上curl、apt-transport-https、ca-certificates、gnupg、lsb-release这几个包。这些工具在后面添加软件源、下载二进制文件时都会用到。接下来是关闭交换分区。Kubernetes 的设计假设是节点有足够的内存它不希望 kubelet 把内存页换到磁盘上因为那会严重影响调度和性能。临时关闭用swapoff -a永久关闭要编辑/etc/fstab把 swap 那一行注释掉。改完之后用free -h确认 swap 那一行全是 0。内核参数也需要调整。主要是开启网桥的 iptables 转发以及确保 IPv4 转发是打开的。具体操作是创建/etc/modules-load.d/k8s.conf文件写入br_netfilter和overlay两个模块名然后用modprobe加载。接着在/etc/sysctl.d/k8s.conf里写入net.bridge.bridge-nf-call-iptables1、net.bridge.bridge-nf-call-ip6tables1、net.ipv4.ip_forward1最后执行sysctl --system让配置生效。注意这几步看起来琐碎但少做任何一步都可能导致后面 Pod 网络不通或者节点无法注册。我建议把这几条命令写成一个脚本三台机器都跑一遍省得漏掉。3.2 主机名与 hosts 解析的规范设置主机名要改成有意义的名称我一般用k8s-master、k8s-node1、k8s-node2这种命名。用hostnamectl set-hostname改完之后记得在每台机器的/etc/hosts里加上三台机器的 IP 和主机名映射。这一步很关键因为 kubeadm 在初始化时会用主机名去注册节点如果解析不了join 的时候会卡住。另外虚拟机克隆导致的 machine-id 重复问题必须处理。每台机器的/etc/machine-id和/var/lib/dbus/machine-id要保证唯一。最简单的办法是删掉这两个文件然后执行systemd-machine-id-setup重新生成。网卡 MAC 地址如果也重复了需要在虚拟机设置里重新生成或者在系统里手动改配置文件。3.3 容器运行时 containerd 的安装与配置containerd 的安装我推荐用官方提供的二进制包而不是 apt 源里的版本因为 apt 源里的版本往往偏旧可能不满足 v1.23 的要求。下载地址可以从 containerd 的 GitHub Release 页面找选containerd-1.4.x或1.5.x的 linux-amd64 压缩包解压后把bin目录下的文件复制到/usr/local/bin。安装完之后要生成默认配置。执行containerd config default /etc/containerd/config.toml然后编辑这个文件。有两个地方必须改一是SystemdCgroup要设成true因为 kubelet 默认用 systemd 作为 cgroup 驱动如果 containerd 用 cgroupfs两者不一致会导致 Pod 启动失败二是sandbox_image要改成能访问到的镜像地址默认的k8s.gcr.io/pause在国内网络环境下可能拉不下来。改完配置后用 systemd 管理 containerd 服务。创建/etc/systemd/system/containerd.service文件内容参考官方模板然后systemctl daemon-reload、systemctl enable containerd、systemctl start containerd。启动后用systemctl status containerd确认状态是 active再用ctr version看看客户端和服务端版本是否一致。3.4 kubelet、kubeadm、kubectl 的安装这三个组件的安装方式我习惯用 apt 源。先添加 Kubernetes 的 apt 仓库导入 GPG 密钥然后apt update接着指定版本安装。v1.23.1 对应的包版本号是1.23.1-00安装命令是apt install -y kubelet1.23.1-00 kubeadm1.23.1-00 kubectl1.23.1-00。装完之后要做的第一件事是apt-mark hold把这三个包锁定防止系统自动升级。Kubernetes 的版本升级需要遵循特定的顺序不能随便让 apt 帮你升。锁定命令是apt-mark hold kubelet kubeadm kubectl。kubelet 服务这时候会自动启动但因为它找不到配置文件会不断重启。这是正常现象等 kubeadm init 执行完之后kubelet 会拿到配置并正常运行。你可以先用systemctl status kubelet看一眼确认它确实在反复重启而不是报了什么奇怪的错误。4. 控制平面初始化与工作节点加入的完整实操4.1 kubeadm init 的配置文件与参数计算直接跑kubeadm init加一堆命令行参数也能成功但我更推荐用配置文件因为可读性好也方便版本管理和复用。配置文件我命名为kubeadm-config.yaml内容大致如下apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.23.1 controlPlaneEndpoint: k8s-master:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd这里有几个参数需要解释。controlPlaneEndpoint我写的是主机名加端口这样即使以后控制平面 IP 变了只要 hosts 解析对集群还能用。podSubnet要和后面装的 CNI 插件网段一致我用的是 Flannel 的默认网段10.244.0.0/16。cgroupDriver设成 systemd和 containerd 的配置保持一致。执行初始化之前可以先跑kubeadm config images pull把需要的镜像拉下来。v1.23.1 需要的镜像包括kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、pause、etcd、coredns。如果网络环境拉取官方镜像有困难可以提前用kubeadm config images list列出所有镜像然后逐个替换成可访问的镜像源。初始化命令是kubeadm init --config kubeadm-config.yaml --upload-certs。--upload-certs是为了后面加控制平面节点时能自动分发证书虽然我们这次只用一个控制平面但加上也无妨。执行过程大概需要一两分钟成功后会输出一段 join 命令一定要复制保存下来工作节点加入时要用。4.2 初始化后的收尾配置init 成功之后按照提示配置 kubectl 的 kubeconfig。普通用户执行mkdir -p $HOME/.kube、cp /etc/kubernetes/admin.conf $HOME/.kube/config、chown $(id -u):$(id -g) $HOME/.kube/config。如果是 root 用户直接export KUBECONFIG/etc/kubernetes/admin.conf也行。这时候用kubectl get nodes应该能看到控制平面节点状态是NotReady。这是正常的因为还没装 CNI 插件。用kubectl get pods -n kube-system可以看到控制平面的静态 Pod 都在运行但 CoreDNS 可能处于 Pending 状态也是因为网络没就绪。4.3 安装 Flannel 网络插件CNI 插件我选的是 Flannel原因是配置简单对测试环境足够用。安装方式是直接 apply 官方的 YAML 文件。但要注意Flannel 的默认网段是10.244.0.0/16必须和 kubeadm 配置里的podSubnet一致否则 Pod 之间无法通信。apply 之后用kubectl get pods -n kube-flannel观察 Pod 状态。正常情况下每个节点上会起一个 Flannel Pod状态从Init变成Running。等所有 Flannel Pod 都 Running 之后再kubectl get nodes控制平面节点应该变成Ready了。提示如果 Flannel Pod 一直起不来先检查节点的/etc/hosts解析是否正常再检查内核模块br_netfilter是否加载。我遇到过因为br_netfilter没加载导致 Flannel 无法创建网桥的情况排查了半天才发现是内核参数没生效。4.4 工作节点加入集群工作节点的加入命令就是 init 输出里那段kubeadm join。在 node1 和 node2 上分别执行注意要加--v5方便看日志。执行过程中会先做预检然后从控制平面拉取配置最后启动 kubelet 并注册节点。join 成功之后回到控制平面执行kubectl get nodes应该能看到三个节点都是Ready状态。再用kubectl get pods -n kube-system -o wide确认所有系统 Pod 都分布在三个节点上正常运行。如果 join 失败最常见的两个原因是 token 过期和网络不通。token 默认 24 小时过期过期了就在控制平面执行kubeadm token create --print-join-command重新生成。网络不通的话检查工作节点能不能 ping 通控制平面的 6443 端口以及防火墙有没有放行。5. 常见问题与排查技巧实录5.1 节点 NotReady 的排查路径节点 NotReady 是最常见的问题排查顺序我一般是这样先kubectl describe node 节点名看 Conditions 部分找到具体是哪个条件不满足。如果是NetworkUnavailable为 true说明 CNI 没装好如果是KubeletNotReady就要去节点上看 kubelet 的日志。kubelet 日志用journalctl -u kubelet -f看重点找failed和error关键字。我遇到过因为 containerd 的SystemdCgroup没开导致 kubelet 报failed to create pod sandbox的情况日志里会明确写 cgroup 驱动不匹配。还有一种情况是节点时间不同步证书校验失败这时候要装ntp或chrony做时间同步。5.2 镜像拉取失败的应对方法v1.23.1 的很多镜像在默认源里可能拉不下来尤其是k8s.gcr.io开头的。解决办法有两个一是提前在能访问的机器上拉好镜像然后docker save成 tar 包再ctr images import到目标节点二是修改 kubelet 的配置把镜像地址替换成可访问的镜像源。用ctr导入镜像的时候要注意命名空间。kubelet 用的是k8s.io命名空间所以导入命令要加-n k8s.io参数比如ctr -n k8s.io images import pause.tar。如果不加这个参数镜像导入到了 default 命名空间kubelet 是看不到的。5.3 证书过期与集群恢复kubeadm 生成的证书默认有效期是一年。如果集群放了一年没动再启动时 kubelet 会报证书过期。这时候可以用kubeadm certs check-expiration查看所有证书的到期时间用kubeadm certs renew all续期。续期之后要重启控制平面的静态 Pod方法是把/etc/kubernetes/manifests下的 YAML 文件临时移走再移回来kubelet 会自动重建这些 Pod。注意证书续期后/etc/kubernetes/admin.conf里的客户端证书也需要更新否则 kubectl 会连不上。最省事的办法是续期后重新执行一遍 kubeconfig 的复制步骤。5.4 常见问题速查表问题现象可能原因排查命令解决方法节点 NotReadyCNI 未安装kubectl describe node安装 Flannel 或其他 CNIPod 一直 Pending资源不足或污点kubectl describe pod调整资源或去掉污点镜像拉取失败网络不通或镜像源不可用crictl pull 镜像替换镜像源或手动导入kubelet 反复重启配置错误或证书问题journalctl -u kubelet检查配置和证书有效期join 失败token 过期或端口不通kubeadm token list重新生成 token 或检查防火墙Pod 之间不通网段冲突或内核参数ip route、sysctl -a检查 podSubnet 和转发参数5.5 几个我踩过的坑和对应的经验第一个坑是虚拟机克隆后 machine-id 重复。表现是第二个节点 join 的时候控制平面把它识别成了第一个节点导致节点信息混乱。解决办法就是前面说的删掉 machine-id 文件重新生成。第二个坑是 containerd 的sandbox_image没改。默认配置里写的是k8s.gcr.io/pause:3.6拉不下来导致所有 Pod 都卡在SandboxNotReady。改成本地能访问的 pause 镜像地址就好了。第三个坑是 Flannel 的网段和 kubeadm 配置不一致。我一开始 kubeadm 里写的podSubnet是10.244.0.0/16但 Flannel 的 YAML 里默认也是这个按理说没问题。但后来我改过 kubeadm 的网段忘了改 Flannel结果 Pod 之间完全不通。所以这两个地方的网段必须严格一致。第四个坑是时间不同步。虚拟机挂起再恢复后时间可能和宿主机差很多导致证书校验失败。装个chrony并配置成开机自启基本能避免这个问题。6. 集群验证与日常运维的实用建议6.1 用测试 Pod 验证集群功能集群搭好之后别急着往上跑业务先用一个简单的测试 Pod 验证基本功能。我一般会跑一个 Nginx Deployment副本数设成 3然后用 NodePort 类型的 Service 暴露出来。如果三个副本能均匀分布在三个节点上并且通过任意节点的 IP 加 NodePort 都能访问到 Nginx 页面说明调度、网络、服务发现都是正常的。验证命令大概是kubectl create deployment nginx --imagenginx --replicas3然后kubectl expose deployment nginx --port80 --typeNodePort。用kubectl get pods -o wide看 Pod 分布用kubectl get svc看分配的 NodePort最后用curl测试访问。6.2 日常巡检的几个关键指标集群跑起来之后日常巡检我重点关注几个东西节点状态、系统 Pod 状态、etcd 健康度、证书有效期。节点状态用kubectl get nodes一眼就能看系统 Pod 用kubectl get pods -n kube-system检查有没有频繁重启的etcd 健康度可以用kubectl -n kube-system exec etcd-节点名 -- etcdctl endpoint health来看证书有效期用kubeadm certs check-expiration定期查。资源使用率也要关注尤其是控制平面的内存。如果 etcd 的内存持续增长可能是写入太频繁或者有大量历史版本没压缩。etcd 默认会保留最近 5 分钟的修订版本如果写入量大内存会涨得比较快。可以通过调整--auto-compaction-retention参数来控制压缩频率。6.3 集群的备份与恢复思路etcd 的数据是集群的核心备份 etcd 就等于备份了整个集群的状态。备份命令是etcdctl snapshot save恢复用etcdctl snapshot restore。备份的时候要注意指定正确的证书路径否则连不上 etcd。恢复的时候要先停掉控制平面的静态 Pod恢复完数据再重新启动。除了 etcd/etc/kubernetes目录下的证书和配置文件也要备份。这些文件丢了集群就彻底起不来了。我习惯把这两个东西打包放到一个安全的地方每次集群有重大变更后都更新一次备份。6.4 版本升级的注意事项v1.23.1 升级到更高版本时不能跳版本必须一个小版本一个小版本地升。比如从 1.23 升到 1.24要先升 kubeadm再升控制平面再升 kubelet 和 kubectl最后升工作节点。每一步都要用kubeadm upgrade plan确认可行性不能直接apt upgrade。升级前一定要备份 etcd 和证书升级过程中如果卡住可以用kubeadm upgrade的回滚功能恢复。另外升级前要确认所有节点都是 Ready 状态没有 Pending 的 Pod否则升级过程中可能出问题。6.5 资源清理与集群重置如果集群玩坏了想重来用kubeadm reset可以清理节点上的 Kubernetes 组件。但 reset 不会清理 CNI 的网络配置和 iptables 规则需要手动清理。我一般会执行kubeadm reset -f然后rm -rf /etc/cni/net.d、rm -rf $HOME/.kube再用iptables -F iptables -t nat -F iptables -t mangle -F iptables -X清空 iptables 规则。最后重启一下机器基本就干净了。提示reset 之前确认一下这个节点上没有重要的数据因为 reset 会删掉所有容器和卷。如果是生产环境千万别随便 reset。这套集群我前后搭了不下十次每次都会遇到一些新的小问题但核心流程是稳定的。v1.23.1 虽然已经不是最新版本但它的很多设计思路和后续版本是一致的把这套流程走通再去看更新的版本或者托管服务理解起来会轻松很多。我个人在实际操作中的体会是手动搭集群最大的价值不在于搭本身而在于搭的过程中被迫去理解每一个组件的作用和它们之间的依赖关系这种理解是看多少文档都换不来的。
阅读完成 · 觉得有帮助?
咨询建站