
手动搭一套 Kubernetes 集群很多人第一反应就是难。尤其当你还要在它上面叠一个 KubeSphere这个集可视化、多集群、监控、日志、DevOps 于一体的全家桶平台光是版本匹配就够折腾一阵子。这篇文章写给想自己动手从裸机开始搭一套完整 k8s 环境的朋友也写给公司内部要交付一个小团队可用平台、但又不想从零造轮子的人。整套流程在 CentOS 7.9 最小化安装环境上跑通最终产出 k8s 1.23.17 KubeSphere 3.4.1 的可用集群中间涉及节点规划、运行时选型、网络方案、存储准备、平台安装以及我认为比官方文档更有价值的踩坑记录。1. 动手前的整体设计与版本权衡1.1 为什么选 k8s 1.23.17 与 KubeSphere 3.4.1版本选型这件事我吃过不少亏。最早图省事直接装最新版 k8s结果碰上容器运行时、网络插件、监控组件一堆兼容性问题排查成本反而更高。k8s 1.23.17 是个比较微妙的版本它是最后一个还在支持 dockershim 的版本但 1.24 之后 kubelet 已经默认移除对 Docker 的直接支持很多还在用 Docker 的朋友在升级时被打了个措手不及。选 1.23.17 的好处在于累了可以退一步用 Docker往前又能顺利走上 containerd 这条路兼容性上卡在了一个比较舒服的位置。KubeSphere 3.4.1 对 Kubernetes 版本的要求是支持 1.19 到 1.261.23.17 正好落在官方支持区间内稳定性经过大量社区验证。如果选太新或者太旧的组合安装 KubeSphere 时可能会遇到 API 版本不匹配、CRD 冲突之类的问题。我自己的建议是除非你有更明确的需求否则新手或者交付场景直接抄这组版本省心程度比想象中高很多。1.2 节点规划与硬件配置参考我这次用的是三台机器一台 master两台 worker。IP 规划如下角色主机名IP配置masterk8s-master192.168.10.314C8G硬盘 50Gworkerk8s-worker1192.168.10.324C8G硬盘 50Gworkerk8s-worker2192.168.10.334C8G硬盘 50G如果只是做演示单节点也可以master 同时作为 worker 跑业务负载。但 KubeSphere 装完以后会有监控、日志、事件等一堆常驻组件单节点 8G 内存会非常紧张我实测过卡到 SSH 都延迟所以至少准备 8G 内存推荐 16G。另外磁盘分区建议保留至少 30G 给/var/lib/containerd和/var/lib/kubelet这两个目录是镜像层和容器数据的主战场。之前有个环境磁盘只有 20G镜像拉多了直接No space left on device排查了半天才发现是分区问题很憋屈。1.3 网络与镜像仓库的提前规划Kubernetes 集群内部有三层网络要分清楚节点网络、Pod 网络、Service 网络。节点网络就是你机器之间的互通网络Pod 网络我按 Calico 的默认习惯留了10.244.0.0/16Service 网络用10.96.0.0/12这两个网段一定要跟现有物理网络错开否则路由冲突会搞得人很崩溃。镜像仓库也需要提前想清楚。kubeadm 初始化时默认会从registry.k8s.io拉镜像在国内网络环境下很容易卡住。我习惯把初始化配置里的imageRepository改成国内可用的镜像仓库地址具体地址根据你自己环境来选只要能稳定拉取就可以。这里强调一个原则不在初始化过程中折腾网络提前在配置阶段把镜像源搞定后面会顺畅很多。2. 从零开始的基础环境准备2.1 操作系统初始化三板斧主机名、防火墙、SELinux三台机器拿到手之后第一步不是装 k8s而是先把操作系统收拾干净。最小化安装的 CentOS 7.9 往往带着一堆默认配置其中最容易出问题的就是 SELinux 和防火墙。先设置主机名比如hostnamectl set-hostname k8s-master hostnamectl set-hostname k8s-worker1 hostnamectl set-hostname k8s-worker2然后关闭 SELinux 和防火墙。这两个操作虽然在安全团队看来有点粗暴但在内网交付场景里为了减少 k8s 组件的流量被拦截、权限被限制的概率我通常会先关掉systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i s/^SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config接着关闭 swap。这一步很多人容易漏掉。kubelet 默认要求关闭 swap否则启动时会报错。如果机器已经在跑其他服务不方便关 swap也有折中方案但最省事的就是直接关swapoff -a sed -i / swap / s/^/#/ /etc/fstab注意swapoff -a只是临时关闭必须把/etc/fstab里的 swap 行注释掉否则重启以后又自动挂载上了kubelet 照样给你报错。2.2 时间同步与必要内核模块时间不同步会引发非常诡异的证书报错。k8s 组件之间大量使用 TLS 证书节点时间偏差超过 5 分钟证书校验失败节点直接 NotReady日志满天飞。CentOS 7 里我习惯用 chronydyum install -y chrony systemctl enable --now chronyd chronyc sources -v看到^*开头的行代表同步成功。这步别省我都记不清踩过多少次时间坑了。然后加载两个内核模块并写入开机加载配置cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter同时调整内核参数让 iptables 能正确处理网桥流量cat EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --systemip_forward 1是 Kubernetes 网络转发的基础没有它 Pod 之间的通信会断。这个参数在 Docker 环境下会自动开启但用 containerd 的纯 k8s 环境经常会漏务必检查。2.3 安装基础依赖包最后装一批通用依赖避免后续装网络插件、存储插件时缺东少西yum install -y yum-utils device-mapper-persistent-data lvm2 wget vim net-tools bind-utils其中yum-utils是后面配置 yum 源必需的。bind-utils里包含dig、nslookup等工具排查 DNS 问题时特别有用建议一起装掉。3. 容器运行时选型与 containerd 部署3.1 为什么我用 containerd 而不是 Dockerk8s 1.24 之后官方就把 dockershim 干掉了到现在再来新装集群继续用 Docker 作为运行时不仅要打补丁还要面对一堆社区讨论得热火朝天的问题。containerd 本身就是容器标准实现的一部分kubelet 直接通过 CRI 跟它通信少了中间层日志排查路径也短。KubeSphere 对 containerd 的适配非常成熟官方安装文档里也是优先推荐这种方式。对于已经用惯 Docker 命令的人过渡其实不难你只需要记住crictl这个命令行工具。真正操作容器时用crictl ps、crictl logs、crictl exec体验跟docker ps区别不大。3.2 containerd 的安装与配置使用 yum 安装 containerdyum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y containerd.io安装完成后先生成默认配置mkdir -p /etc/containerd containerd config default /etc/containerd/config.toml这里有两个地方必须改。第一开启 SystemdCgroup[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true为什么要开这个kubelet 和运行时之间关于 cgroup 驱动必须保持一致kubelet 默认使用systemdcgroup 驱动containerd 你也得把SystemdCgroup设为 true。不一致的时候容器启动会报资源管理相关的错误而且你查日志的时候大概率看不出来是这个原因。第二修改 sandbox_image。这个镜像是每个 Pod 都会有基础 pause 容器kubeadm 默认认为它在官方镜像仓库国内拉不下来。我把sandbox_image改成可用的镜像仓库地址比如[plugins.io.containerd.grpc.v1.cri] sandbox_image registry.aliyuncs.com/google_containers/pause:3.6改完以后重启 containerdsystemctl daemon-reload systemctl enable --now containerd systemctl restart containerd3.3 验证运行时是否真的可用配置完不能直接往后走用crictl做个连通性测试cat EOF | tee /etc/crictl.yaml runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 EOF crictl images如果crictl images能正常返回空列表说明 CRI 通信没问题。我一开始忽略了配置crictl.yaml命令行一直报找不到 containerd socket其实是默认客户端连的是 Docker 的 socket白白浪费了几分钟。实操心得每次改完/etc/containerd/config.toml之后一定执行containerd config --validate检查配置语法。有一次我只是少写了一个引号整个服务直接起不来排查了好久才发现是配置文件格式问题。4. 使用 kubeadm 初始化 Kubernetes 集群4.1 安装 kubelet、kubeadm、kubectl 并锁版本三台机器都需要装这三个组件。首先配置 yum 源cat EOF | tee /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck0 EOF然后安装指定版本yum install -y kubelet-1.23.17-0 kubeadm-1.23.17-0 kubectl-1.23.17-0安装完成后执行systemctl enable --now kubelet。此时 kubelet 大概率会启动失败因为集群还没初始化这是正常现象不用慌。关键是不要让它被 yum 自动升级更新否则版本漂移后谁也救不了你yum -y install yum-plugin-versionlock yum versionlock kubelet kubeadm kubectl4.2 编写 kubeadm init 配置文件在 master 节点上写一个初始化配置apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.10.31 bindPort: 6443 nodeRegistration: criSocket: unix:///run/containerd/containerd.sock --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.23.17 imageRepository: registry.aliyuncs.com/google_containers controlPlaneEndpoint: 192.168.10.31:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs关于imageRepository这里我直接写了国内可用的镜像仓库地址初始化全程基本不需要额外等待太久。controlPlaneEndpoint我填了 IP生产环境有条件的话可以填负载均衡地址或者域名。mode: ipvs是代理模式的选择ipvs 性能比 iptables 好支持更复杂的负载均衡策略kube-proxy 在初始化的时候会用 IPVS 规则承载 Service 流量。4.3 初始化集群与节点加入执行初始化kubeadm init --config kubeadm-config.yaml --upload-certs | tee kubeadm-init.log初始化结束后末尾会输出两段重要信息一段是配置 kubectl 的命令一段是 worker 节点加入集群的命令。先配置本机访问mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config然后查看集群状态kubectl get nodes此时 master 应该是NotReady状态原因很简单Pod 网络还没有就绪这是正常的下一步装完 CNI 插件就会变 Ready。在 worker 节点上直接执行从日志里复制出来的kubeadm join命令。如果 token 过期或丢失可以在 master 上重新生成kubeadm token create --print-join-commandworker 加入成功后在 master 上执行kubectl get nodes -o wide应该能看到三台节点都出现在列表里不过它们目前都会是 NotReady。4.4 关键参数的计算与调整思路podSubnet的选择要跟将来部署的 CNI 插件匹配。Calico 默认分配192.168.0.0/16如果你初始化时写了别的网段就必须修改 Calico 的配置让它跟你的指定网段一致。这里我统一用10.244.0.0/16这个网段在社区里用得最多跟各种网络插件都比较好配合。serviceSubnet是集群内部 Service 的虚拟 IP 网段后续通过 kube-proxy 实现转发。它不会和 Pod 网段冲突两者相互独立。如果这两个网段规划错了后面部署任何应用都会出现路由不通、Service 无法访问之类的问题而且排查起来非常麻烦所以初始化配置时一定要一次写对。注意初始化完成后如果想改这两个网段基本只能重新初始化集群所以动手前仔细确认别偷懒。5. 部署 Calico 网络插件5.1 为什么选 CalicoCNI 插件可选的很多Flannel 简单Cilium 功能强但我的推荐是 Calico。它对跨节点通信、NetworkPolicy 的支持都很成熟性能也比 Flannel 好一些是目前生产环境里覆盖率最高的方案之一。对于 k8s 1.23.17 这个版本Calico v3.25 系列是验证过的组合。不用盲目追求最新版稳定优先。5.2 安装与配置要点下载 Calico 清单curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.25.1/manifests/calico.yaml打开calico.yaml找到CALICO_IPV4POOL_CIDR这个环境变量确认它跟我们初始化配置时的 Pod 网段一致- name: CALICO_IPV4POOL_CIDR value: 10.244.0.0/16如果不一致Pod 会拿不到 IP 或者网络异常表现为 Pod 一直ContainerCreating。改好之后直接应用kubectl apply -f calico.yaml部署过程需要拉取镜像尤其是 master 节点上calico-controllers和每台节点上的calico-node。用kubectl get pods -n kube-system -w观察等所有calico-*Pod 变成 Running再执行kubectl get nodes这时三台节点应该都变成 Ready 了。5.3 验证网络是否真正打通节点 Ready 只是第一步网络是否真的通还得做验证。建一个临时 Pod跨节点访问一下kubectl run test-pod --imagebusybox -it --rm -- sh进入 Pod 后ping 192.168.10.32 # 另一个节点的 Pod IP 或 Service IP不通的话先查 Calico Pod 日志。我遇到过的一种情况是 calico-node 启动时报错说无法检测到正确的网卡原因是机器有多个网卡Calico 自动选择了错误的那个做隧道。解决办法是给calico-node的容器添加环境变量指定正确的网卡名比如env: - name: IP_AUTODETECTION_METHOD value: interfaceeth1这个细节在官方文档里写得不明显但真实环境中非常常见尤其是有内网和公网双网卡的机器。6. 给 KubeSphere 准备存储底座6.1 KubeSphere 对存储的强依赖很多人觉得 KubeSphere 就是个面板装上就完事了。实际上 KubeSphere 的监控、日志、事件、告警这些组件全部需要持久化存储。如果你没有提前准备好默认 StorageClassks-installer 装到一半会卡在某个组件一直 Waiting等你发现再去补存储又得折腾半天。验证集群当前有没有默认 StorageClasskubectl get storageclass kubectl get sc --all-namespaces如果输出为空就得先建一个。6.2 快速部署 local-path-provisioner生产环境通常上 Ceph 这种分布式存储但演示环境不折腾这些用 local-path-provisioner 就够了。它在每台节点上使用宿主机本地目录来提供 PV部署快清理也方便。下载并应用部署清单kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.26/deploy.yaml这个清单会自动帮你创建local-pathStorageClass。然后把它设为默认kubectl annotate storageclass local-path storageclass.kubernetes.io/is-default-classtrue再查看一下kubectl get storageclass输出里应该会有local-path并且(default)字样。注意local-path 存储有一个特点就是每个 Pod 用到的 PVC 会绑定到 Pod 所在节点的本地目录。这意味着数据不会自动跨节点迁移。如果 Pod 被重新调度到别的节点PVC 就会一直处于 Pending。演示环境无所谓但生产环境一定要换集中式存储或分布式存储方案。7. 在已有 k8s 集群上部署 KubeSphere7.1 下载并应用 ks-installerKubeSphere 在已有集群上的安装方式很成熟官方推荐的是 ks-installer。下载安装清单kubectl apply -f https://github.com/kubesphere/ks-installer/releases/download/v3.4.1/kubesphere-installer.yaml然后下载集群配置清单kubectl apply -f https://github.com/kubesphere/ks-installer/releases/download/v3.4.1/cluster-configuration.yamlcluster-configuration.yaml里主要定义的是 KubeSphere 核心组件的开关。默认情况下很多组件是关闭的比如日志、监控、事件、告警这些都不一定是 enabled。你需要根据自己的需求去调整kubectl edit clusterconfiguration ks-installer -n kubesphere-system比如把monitoring、logging、openpitrix应用商店改成enabled: true。不过我要提醒一句功能开得越多对资源的要求就越高。8G 内存的节点同时开监控和日志我实测会有明显卡顿所以按需开启、别一股脑全开。7.2 监控安装进度应用完配置后ks-installer 会以 Deployment 形式跑起来然后依次创建 KubeSphere 的各种组件和 CRD。看安装进度的方法kubectl get pods -n kubesphere-system -w更直观的方式是看 ks-installer Pod 的日志kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l appks-install -o jsonpath{.items[0].metadata.name}) -f日志里会滚动输出当前正在初始化的组件名。安装时长取决于镜像拉取速度和组件数量网络正常的情况下 20 到 40 分钟比较常见。耐心点如果中途卡住别急着重新安装先看看具体卡在哪个组件上。7.3 安装完成标志与登录信息当安装完成时日志末尾会打印一段信息##################################################### Console: http://192.168.10.31:30880 Account: admin Password: P88w0rd #####################################################KubeSphere 控制台默认通过NodePort: 30880对外提供服务。用浏览器访问这个地址输入初始账号密码就能登录。登录后第一件事建议是右上角进入「平台管理」-「平台设置」修改默认密码毕竟默认密码是公开的。如果发现浏览器访问不了 30880 端口优先检查防火墙。前面步骤我们已经关了 firewalld但如果服务器上还有云安全组之类的策略记得放行。集群内部的话可以用curl -I http://192.168.10.31:30880先自测。7.4 组件健康状态检查清单登录进控制台后我习惯按下面列表过一遍「集群管理」-「节点」三台节点都要是健康状态。「集群管理」-「服务」看 kubesphere-system 和 kubesphere-monitoring-system 命名空间里的服务有没有异常。「平台管理」-「基础组件」检查 Istio、监控、日志这些是不是 ready。「工作台」-「企业空间」创建一个企业空间再创建项目确保后续应用可以正常部署。如果某些组件显示不健康回到命令行用kubectl get pods -n 对应命名空间把问题 Pod 找到大概率事件是镜像拉取失败、资源不足或 PVC 没绑定逐一排除即可。8. 全链路验证让空集群真正跑起来8.1 部署一个典型应用验证网络与调度光有控制台界面还不够我每次搭建完都要部署一个实际应用来验证链路是否完整。拿一个 Nginx 做测试kubectl create deployment nginx-demo --imagenginx:1.23 kubectl expose deployment nginx-demo --port80 --target-port80 --typeNodePort查看服务暴露的端口kubectl get svc nginx-demo输出里的30888:80类似格式表示宿主机上的 30888 端口被映射到 Service 的 80 端口。在任意节点上访问curl http://192.168.10.31:30888能返回 Nginx 欢迎页就说明 Pod 创建、Service 转发、NodePort 访问这几条链路全通了。8.2 回到 KubeSphere 观察监控数据在 KubeSphere 控制台进入刚创建的项目点击「工作负载」应该能看到nginx-demo这个 Deployment 的实时状态。点击「监控」页面如果之前开启了监控组件可以查看 CPU、内存、网络等图表。这里如果查询不到数据先确认监控组件有没有正常采集指标可以看 kube-state-metrics 和 node-exporter 相关 Pod 是否在运行。抛一个常见性能问题KubeSphere 默认的监控数据保留周期是 7 天左右数据量大的话对存储空间有要求。local-path 这种本地存储一般没啥问题但要注意磁盘水位满了以后监控组件会报错而很多人只会看应用日志忽略底层存储空间。8.3 验证日志查询链路进入「项目」下的「日志查询」页面搜索一下刚才 Nginx Pod 的日志。如果日志功能是开启的你不需要 SSH 到节点上敲命令直接在控制台就能看到容器标准输出。这个功能在多节点环境里特别值钱省掉了挨个节点翻日志的时间。如果日志查不到大概率是 fluent-bit 或相关日志采集器没起来先看对应命名空间的 Pod 状态。日志功能开起来之后每台节点上的容器日志都会被采集到集群内部存储注意做好索引和容量规划别让日志无限堆积把磁盘塞满。9. 问题排查速查与避坑经验9.1 常见现象、根因与解决思路现象大概率原因排查路径kubelet 启动失败cgroup 驱动不一致 / swap 未关看journalctl -xeu kubelet日志检查 fstabcontainerd 拉镜像超时sandbox_image 配置不对 / 网络受限修改 config.toml 里 sandbox_image 并重启节点一直 NotReadyCNI 插件没装好 / 网卡选择错误查 kube-system 里 calico Pod 日志Pod 一直 Pending没有可用 PVC / 资源不足看 Pod 事件kubectl describe pod xxxKubeSphere 安装卡 Waiting内存不足 / 没有默认 SC调整组件开关确认 StorageClass 存在控制台访问不了 30880防火墙 / 安全组没放行本机 curl检查 firewalld 状态容器跨节点不通Pod 网段冲突 / ip_forward 未开逐层 ping查内核参数和 Calico 配置以上表格是我按问题频率大致排列的前三条在整套流程中最容易出现。遇到问题不要急着重装集群先把日志翻出来对照现象定位修复成本往往比重装低很多。9.2 初始化失败后的重开姿势有时候改来改去把集群搞乱了最干净的办法是重置。kubeadm 提供了残留清理命令kubeadm reset -f rm -rf /var/lib/calico /etc/cni/net.d /var/lib/cni /etc/kubernetes /var/lib/kubelet /var/lib/etcd在 reset 之后再用初始化流程重新走一遍。如果是多节点集群master 和 worker 都要做 reset。做完之后检查iptables -L -n和ipvsadm -Ln有没有残留规则有的话清一遍。这个操作我在内部环境演示时经常用也是面试里比较出色的亮点省事且不容易留垃圾数据。9.3 证书与版本漂移的长期维护k8s 1.23.17 的证书默认有效期是 1 年KubeSphere 本身也会生成自己的证书。半年之后你会突然发现某个组件开始报证书错误这时直接用 kubeadm 自动续期kubeadm certs renew all然后重启相关组件别等证书真的过期再处理。另外之前用yum versionlock把版本锁住了后续如果要升级 k8s 小版本先解锁再操作yum versionlock delete kubelet kubeadm kubectl升级是对所有节点都要操作的不能只动 master顺序也要按照官方文档的指导来先升级 kubeadm 和 kubelet再升级 kubectl避免版本不一致导致的 API 兼容问题。9.4 我反复实操后总结的几条心得第一KubeSphere 组件开启顺序和资源规划直接挂钩。想开全功能至少给 master 留 6G 空闲内存worker 留 4G 空闲内存不然安装过程会卡到想砸键盘。第二local-path 这类本地存储确实方便但换节点数据不跟随所以演示可以交付还是得按照业务需要设计持久化方案。第三集群装完后立即把所有账号密码和证书备份信息记到本地别等半年后再东翻西找。第四所有 yaml 文件尽量走版本管理git 里留一份出了问题可以快速 diff 出谁动了配置。装完这套环境你会明显感觉到一件事Kubernetes 本身只是一个计算调度平台真正让它变成一套“能交付、能运维、能监控”的系统需要你把网络、存储、证书、资源规划统统串起来。KubeSphere 负责了视觉和交互上的最后一公里但底下的每层配置还是得自己心里有数。希望这篇文章能帮你少踩几个我踩过的坑。