ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

麒麟V11离线部署K8s 1.32.11与KubeSphere完整指南

麒麟V11离线部署K8s 1.32.11与KubeSphere完整指南 在没外网、没有可用Yum源、只有刚拆箱的麒麟V11服务器、还要求把K8s和KubeSphere全离线装起来的机房场景里焦虑感是实打实的。标题里这个“信创-k8s”项目说白了就是国产化服务器开源容器平台内网隔离环境的一次硬核落地。这篇文章把整条链路拆开讲清楚从麒麟V11系统初始化、containerd 2.1.5离线部署、k8s 1.32.11集群初始化到KubeSphere可视化平台接入每一步都给出可直接抄作业的命令、参数和坑位。无论你是刚接触信创项目还是被临时拉去搞离线交付的运维照着这篇走能少熬好几个通宵。1. 项目背景与场景还原1.1 这到底是个什么场景所谓“全离线安装”和普通内网环境还不一样。普通内网至少有些基础软件源而项目现场通常是一个完全隔离的专用网络连系统补丁都带不进去。我这次面对的是三台银河麒麟服务器版V11内存64G、磁盘1.2T SSD系统刚装好没有配置任何外部软件源。要求是在这个环境里交付Kubernetes 1.32.11集群并挂上KubeSphere作为可视化集群管理工具。麒麟V11不是简单换个Ubuntu内核的发行版它的系统目录结构、服务管理方式、默认防火墙策略都有自己的脾气。最直观的感受是基于glibc 2.38以上的用户态环境对containerd 2.x这类新版本容器运行时兼容性很好但一些老经验里的RHEL系操作路径并不完全通用。比如firewalld和nftables并存的问题、SELinux的默认状态、systemd服务单元的配置方式都会在部署过程中变成不同形态的坑。另一个关键点是“信创”这个背景。它意味着软硬件选型必须考虑供应链安全与国产化适配但落到技术层面本质上还是Linux生态一脉相承的部署逻辑。k8s和containerd都是CNCF开源项目在国产系统上跑没有license问题只是需要确认版本兼容矩阵和二进制运行库依赖。1.2 这个方案解决的问题一套完整的离线交付方案要回答三个问题软件包里怎么进来、镜像怎么分发、节点间怎么通信。后面所有操作都是围绕这三个问题展开的。软件包怎么进来我提前在有网络的机器上把k8s三个核心二进制、containerd、runc、CNI插件全部下载好带进内网。这一步没有捷径只能依赖官方release页和镜像仓库的导出功能。镜像怎么分发两种路线可选。节点少时可以直接在每个节点上解压镜像tar包节点多时在物理服务器上起一个私有registry用内网HTTP分发。我这次用的registry方案因为后面还要灌KubeSphere几十个镜像统一走私有仓库最省事。节点间怎么通信k8s集群需要Pod网络、Service网络、节点网络三层打通。物理层没问题Pod网络我选了Flannel的VXLAN模式镜像小、配置简单、与containerd调度兼容性好适合离线快速验证。排除掉Docker这个中间层直接使用containerd 2.1.5作为容器运行时是从k8s 1.24之后社区的主流选择。麒麟系统不需要装Docker也就少了一大堆兼容性问题。2. 方案选型与离线物料准备2.1 版本适配思路与兼容性判断版本选择是离线部署里最容易踩雷的环节。我最终敲定的版本组合是麒麟V11 containerd 2.1.5 k8s 1.32.11 KubeSphere以官方兼容矩阵为准的近期release。这里先说适配逻辑。containerd 2.x在v2配置格式下默认的cri插件路径、runc运行时参数和1.x有较大差异。k8s 1.32.x的kubeadm对CRI支持很成熟只要containerd的SystemdCgroup打开socket路径正确init基本不卡壳。选择1.32.11这个具体patch版本是因为它是1.32系列的稳定维护版本修掉了一批已知的kubelet和etcd兼容问题。KubeSphere这边要单独说。新版KubeSphere对上游Kubernetes版本的兼容矩阵通常滞后一到两个小版本而k8s 1.32.11属于比较新的大版本主线。我在现场的做法是先在k8s集群里装好ks-installer然后立刻查看kubectl describe clusterconfiguration的内部状态确认KubeSphere各核心组件能正常创建Pod。如果KubeSphere版本过旧导致控制器无法识别1.32的API资源我建议要么升级KubeSphere版本要么把集群降到它官方支持的相邻版本。但既然项目标题锁定了1.32.11我就按“用新版KubeSphere”的逻辑推进。2.2 离线物料清单离线安装最怕临场发现少了包所以在有网环境准备物料时要按清单核对三遍。以下是我这次实际准备的内容类型物料版本 / 路径容器运行时containerd-2.1.5-linux-amd64.tar.gz官方release下载容器运行时辅助runc.amd642.1.x配套版本CNI插件cni-plugins-linux-amd64-v1.6.2.tgz官方release下载k8s二进制kubeadm、kubelet、kubectlv1.32.11 对应rpm或二进制集群镜像kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、pause、etcd、coredns通过kubeadm config images list获取精确版本Pod网络flannel v0.25.x镜像 manifestGitHub release或镜像导出KubeSphereks-installer、ks-chart、全部组件镜像KubeSphere release包或镜像导出私有仓库registry:2镜像docker.io导出或离线包这里面必须多带两样东西nerdctl和crictl。nerdctl是containerd的命令行工具离线导入镜像时语法和docker CLI类似crictl是k8s标准的CRI调试工具后面所有“容器起没起来”的问题都得靠它查。2.3 网络拓扑与镜像分发路线设计三台机器的规划是这样的角色IP职责master1192.168.10.11控制平面、etcd、KubeSphere核心组件worker1192.168.10.12应用负载worker2192.168.10.13应用负载同时我把master1当作内网软件分发节点私有registry容器就跑在这台机器上监听5000端口。所有镜像提前打好tag推入registry其余节点通过containerd的registry hosts配置连接。这个设计直接把“镜像同步到每台机器”的问题消灭了节点join时只需拉取不需要拷贝十几GB的tar包。3. 麒麟V11系统侧初始化3.1 安装分区与基础配置麒麟V11的安装程序比较友好但默认分区不会给容器大量留空间我装系统时手动把/var/lib/containerd和/var/lib/kubelet所在分区开大至少预留300G以上。磁盘满了是k8s集群最常见的隐性故障容器镜像长期堆积能把SSD写爆所以“磁盘空间就是生命线”这句话在容器环境里不是夸张。装完系统第一件事是配置静态IP和主机名。三台分别改成master1、worker1、worker2并把主机名解析写到/etc/hosts。有些部署教程默认让kubeadm自己解析但在离线环境里DNS往往不完整手动写hosts是最稳的。3.2 关闭不需要的服务与安全策略麒麟V11默认开启了firewalld和SELinux视发行版定制而定。我实践中的做法是systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i s/^SELINUX.*/SELINUXdisabled/ /etc/selinux/configSELinux和containerd的cgroup挂载、CNI插件端口绑定存在摩擦日常排障成本远大于安全收益。信创安全合规通常要求最低权限但这个要求交给后续的安全基线扫描工具去做部署阶段先保证系统能跑起来。然后确认swap关闭kubelet在1.32版本默认要求swap为0swapoff -a sed -i s/.*swap.*/#/ /etc/fstab3.3 内核参数与模块加载容器网络依赖的关键内核模块必须提前加载。我敲了下面这组配置主要是开启overlay、br_netfilter以及四层转发所需的nf_conntrackcat /etc/modules-load.d/containerd.conf EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfilter cat /etc/sysctl.d/99-k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 net.ipv6.conf.all.forwarding 1 vm.swappiness 0 vm.overcommit_memory 1 EOF sysctl --system这里解释一个原理k8s的Service是通过iptables/ipvs规则实现的Pod网桥上的数据包必须经过宿主机的netfilter框架才能做DNAT和SNAT所以不开启bridge-nf-call-iptables集群内部Service访问就会出现间歇性不通。这类问题最坑因为网络基础看起来正常curl到Service IP却卡死。4. containerd 2.1.5全离线部署与配置4.1 为什么绕开Docker直接上containerdk8s从1.24版本开始彻底移除dockershim意味着即使装了Dockerkubelet也无法直接操作这些容器。要在生产环境用Docker作为运行时就得额外装cri-dockerd这个垫片多一层转换就多一个不稳定点。而containerd本身实现了CRI插件kubelet可以直接通过gRPC调用链路最短。从信创适配角度看containerd是CNCF毕业项目没有历史包袱二进制依赖面小非常契合国产化系统的离线交付。麒麟V11的用户态库对containerd的支持很好我在部署中几乎没有遇到动态链接库缺失的情况。4.2 二进制安装与目录布局离线装containerd其实就是解压和配置tar Cxzvf /opt/containerd-2.1.5-linux-amd64.tar.gz -C /opt cp /opt/containerd/bin/* /usr/local/bin/ install -m 755 runc.amd64 /usr/local/bin/runc mkdir -p /opt/cni/bin tar Cxzvf cni-plugins-linux-amd64-v1.6.2.tgz -C /opt/cni/bin生成并修改配置mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml这个默认配置是v2格式的和我们以前见到的v1格式完全不同关键要改三处第一处SystemdCgroup必须设成true。如果不设置容器内的cgroup路径由runc自己管理和kubelet的systemd cgroup驱动冲突Pod会反复CrashLoopBackOff并且报错里会出现“failed to run Kubelet: cgroup-driversystemd”之类的话。[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] runtime_type io.containerd.runc.v2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true第二处pause镜像地址改成私有仓库。k8s每个Pod都会先启动一个pause容器来持有网络命名空间这个镜像默认指向registry.k8s.io/pause:3.10离线环境拉不到必须替换[plugins.io.containerd.grpc.v1.cri] sandbox_image 192.168.10.11:5000/k8s.io/pause:3.10第三处配置registry hosts让containerd能从内网私有仓库解析镜像。在/etc/containerd/certs.d/192.168.10.11:5000/hosts.toml写入server http://192.168.10.11:5000 [host.http://192.168.10.11:5000] capabilities [pull, resolve] skip_verify trueskip_verify true这段很重要。私有仓库如果用的是自签HTTPS证书containerd默认会拒绝连接内网环境直接走HTTP省去证书管理但会失去传输加密只适合隔离网络。4.3 systemd管理与故障自检用下面这个unit文件让containerd开机自启[Unit] Descriptioncontainerd container runtime Afternetwork-online.target local-fs.target Wantsnetwork-online.target [Service] ExecStart/usr/local/bin/containerd Restartalways RestartSec5 Delegateyes KillModeprocess LimitNOFILE1048576 [Install] WantedBymulti-user.target启动后验证systemctl daemon-reload systemctl enable --now containerd crictl info如果crictl info提示连接失败先检查socket路径。containerd默认监听unix:///run/containerd/containerd.sockcrictl工具也默认走这个socket一般不冲突。真正的常见问题是系统里残留了一个老版本的containerd在占用进程或目录排查时用ps -ef | grep containerd看一下进程特征。5. k8s 1.32.11集群初始化与网络插件5.1 kubeadm、kubelet、kubectl的离线安装麒麟V11如果用rpm方式安装kubelet依赖关系里可能会默认拉出docker相关的包所以我这次直接采用官方二进制方式。从k8s release页下载kubernetes-server-linux-amd64.tar.gz解压后把kubeadm、kubelet、kubectl放到/usr/local/bin再写kubelet的systemd配置。kubelet的启动参数不多核心是指向containerd[Unit] Descriptionkubelet: The Kubernetes Node Agent Wantsnetwork-online.target [Service] ExecStart/usr/local/bin/kubelet \ --container-runtime-endpointunix:///run/containerd/containerd.sock \ --kubeconfig/etc/kubernetes/kubelet.conf \ --config/etc/kubernetes/kubelet-config.yaml Restartalways RestartSec5 KillModeprocess [Install] WantedBymulti-user.target启动前把kubelet登记为开机启动但先不要启动它等kubeadm init后有了配置文件再统一拉起。5.2 集群镜像离线导入与私有仓库准备这一步是整个离线流程的核心。先在master1上启动registryctr -n k8s.io images import registry-2.tar ctr -n k8s.io run --rm registry:2 registry更稳妥的方式是写一个registry容器配置挂载持久化目录并监听5000端口这样才叫真正的内网镜像源。镜像导入到私有仓库有两条路。如果只是少量镜像直接用ctr或nerdctlnerdctl load -i k8s-images-1.32.11.tar nerdctl tag k8s.gcr.io/kube-apiserver:v1.32.11 192.168.10.11:5000/k8s.io/kube-apiserver:v1.32.11 nerdctl push 192.168.10.11:5000/k8s.io/kube-apiserver:v1.32.11但几十个镜像一个个tag太慢我推荐用循环脚本。核心思路是先把镜像load进containerd然后用ctr images list拿到完整列表再用shell批量打tag并push。等这批操作完成用curl http://192.168.10.11:5000/v2/_catalog能看到仓库里全部镜像说明内网源已经具备服务能力。5.3 kubeadm init参数解析准备工作做完后在master1上执行kubeadm init \ --kubernetes-version1.32.11 \ --apiserver-advertise-address192.168.10.11 \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/12 \ --cri-socketunix:///run/containerd/containerd.sock \ --image-repository192.168.10.11:5000/k8s.io \ --upload-certs几个参数的取舍逻辑值得说清楚。pod-network-cidr用10.244.0.0/16是为了和Flannel默认清单保持一致避免篡改网络插件配置。service-cidr保持k8s默认的10.96.0.0/12这样集群内DNS地址固定为10.96.0.10后续KubeSphere和业务应用都依赖这个地址。--upload-certs是给高可用场景用的即使你暂时只规划单master加上它也能保留控制平面证书的上传能力未来扩容控制平面节点会方便很多。执行前先在master1上跑一遍kubeadm config images list --kubernetes-version1.32.11核对映像列表是否和私有仓库里的tag完全一致。这一步能提前发现大小写、版本号差异问题我遇到过最离谱的情况是kubeadm内置的pause版本比containerd的默认sandbox版本落后一个Patch导致初始化时反复尝试拉取旧版本镜像。5.4 工作节点join与Flannel离线安装master1初始化成功后工作节点join是重复劳动。将kubeadm init输出的join命令复制到worker1、worker2上执行注意join命令里包含token和CA hash如果执行超时或报错重新用kubeadm token create --print-join-command生成新的。join完成后给所有节点打上标签kubectl get nodes # 确认所有节点处于 NotReady 状态这是正常的因为还没有安装网络插件Flannel离线安装需要两个东西镜像和manifest。先通过ctr images import flannel.tar把flannel镜像导入master1的containerd然后修改flannel的yaml文件里的image前缀为私有仓库地址再kubectl apply -f kube-flannel.yml。Flannel Pod启动后查看节点状态kubectl get nodes -o wide如果节点有IP但Ready状态为NotReady最快诊断方式是查看kubelet日志journalctl -u kubelet -f大多数情况下都是镜像拉取失败或CNI配置文件未生成。6. KubeSphere离线接入6.1 版本兼容与安装方式选择KubeSphere接入k8s集群有两条路线一个是KubeKey一键创建集群另一个是在已有集群上通过ks-installer安装。因为集群已经手工搭好了这里只能走第二条路线把KubeSphere的核心组件作为工作负载部署到k8s集群中。离线环境下KubeSphere的部署包数量非常多核心组件包括ks-apiserver、ks-console、ks-controller-manager、ks-installer同时会拉起redis、mysql、minio等支撑中间件。我在有网环境提前把这些镜像全部导出打成一个导入tar包进内网后逐个load进containerd再重新打tag推到私有仓库。6.2 镜像改造与私有仓库地址替换KubeSphere默认镜像地址前缀是docker.io/kubesphere或registry.cn-beijing.aliyuncs.com/kubesphere离线环境必须全部替换。这个替换工作量大我用sed批量处理yaml文件。关键修改点有两个第一所有ks-installer相关的YAML文件里的镜像地址换成私有仓库前缀。比如sed -i s|docker.io/kubesphere|192.168.10.11:5000/kubesphere|g *.yaml第二在ClusterConfiguration中配置私有仓库参数。KubeSphere的安装包文档里一般会提到spec.local_registry这个字段它专门用于离线部署写入私有仓库地址后所有组件镜像都会从这个地址拉取spec: local_registry: 192.168.10.11:5000这步配错会导致ks-installer组件全部停留在ImagePullBackOff。排查时可以直接看Pod的events例如kubectl describe pod ks-installer -n kubesphere-system从Events里的Failed to pull image报错能一眼看到镜像前缀对不对。6.3 部署ks-installer与验证组件状态部署命令严格按官方文档执行kubectl apply -f kubesphere-installer.yaml kubectl apply -f cluster-configuration.yaml然后观察namespace内的Pod情况kubectl get pods -n kubesphere-system kubectl get pods -n kubesphere-monitoring-system等待时间会比较长因为KubeSphere的监控组件要部署Prometheus、Grafana、Alertmanager这些组件在离线环境下启动时还会做一长串的配置注入。用一个循环等待就够kubectl -n kubesphere-system get pod -l appks-installer --watch最后通过kubectl exec -n kubesphere-system $(kubectl get pod -n kubesphere-system -l appks-installer -o jsonpath{.items[0].metadata.name}) -- ls -l确认组件全部Ready。控制台访问地址是任一节点的NodePort端口通常在30880初始用户名和密码可以通过ks-installer日志找到。6.4 存储与中间件的离线配套KubeSphere默认需要StorageClass纯离线环境没有云盘我这次直接用local-path-provisioner把所有中间件数据落到本机磁盘。虽然这个是单点存储但对于验证场景已经足够后续上生产再换信创存储阵列。local-path-provisioner的镜像也是提前导入的。安装后把它设置为默认StorageClasskubectl patch sc local-path -p {metadata: {annotations:{storageclass.kubernetes.io/is-default-class:true}}}这样KubeSphere里创建的PVC会自动通过local-path分配目录不会卡在Pending状态。7. 故障排查与信创落地心得7.1 常见故障速查表这里把我在现场反复遇到的几类问题整理成表信息密度比长篇日志高得多症状根因解决办法kubeadm init时报container runtime is not runningcontainerd未启动或socket路径错误systemctl status containerd确认/run/containerd/containerd.sock存在Pod一直Creatingcrictl ps无输出kubelet无法通过CRI连接containerd检查kubelet的--container-runtime-endpoint参数kubelet日志报Failed to create pod sandbox: open /run/netns: permission deniedSELinux拦截CNI创建网络命名空间关闭SELinux后重启containerd和kubeletPod拉取镜像超时registry地址不通或skip_verify未配置用crictl pull 192.168.10.11:5000/k8s.io/pause:3.10单独测试Flannel一直CrashLoopBackOffCNI插件目录路径不匹配确认/opt/cni/bin下cni-plugins解压完整KubeSphere ks-installer不响应ClusterConfiguration未配置local_registry查看ks-installer日志确认image地址前缀节点失联报PLEG is not healthy因swap或Cgroup配置变化导致kubelet假死彻底重启机器保持系统参数一致7.2 离线版本锁定与升级的额外建议在信创项目里“锁定版本”是个非常重要的运维习惯。因为没有外网任何一次升级都意味着重新准备物料、重做测试。我建议所有镜像在导入私有仓库后立即通过ctr images list导出一次仓库内的全量清单这份清单要留档后续审计和版本追溯都靠它。还有一个容易被忽略的点内网节点的时间同步。麒麟V11默认会启用chronyd但如果内网没有NTP服务器所有节点时间偏差会越来越大而KubeSphere的监控组件对数据时间戳非常敏感。我建议至少在一台机器上部署一个基础NTP服务或者在离线包里带好chrony配置并手动纠正一次时间否则监控曲线全是锯齿状排障时完全没法看。7.3 个人实操体会整套流程踩下来的最大感受是离线部署的难点不在于技术本身而在于耐心。containerd 2.x和k8s 1.32的版本组合比老一代dockerdockershim方案清爽太多但前提是把私有仓库、镜像tag、CNI配置等基础设计想清楚。只要提前把物料清单准备好、每个节点的系统配置保持一致整个集群从空系统到KubeSphere控制台可视化在三台机器上完全可以在一天内完成。最后再分享一个小技巧所有离线安装的压缩包、镜像列表、YAML清单不要只放在一个U盘里最好在项目现场用一台笔记本再架一个临时HTTP服务配合一台内部NTP服务器这样后面加节点、扩环境时会发现所有操作都轻松很多。
返回列表