资讯动态

Kubernetes离线部署实战:内网环境从零到可用的完整指南

发布时间:2026/9/16 5:09:05 来源:尧图企业网站定制
最近帮开发团队在一台完全隔离的内网机器上搭了一套Kubernetes开发测试环境整个过程没有外网所有镜像和二进制包都得靠U盘和内部文件服务器搬运。折腾了三天踩了不少坑也把一套可复用的离线部署流程跑通了。这篇东西就是把这套流程整理出来给需要在“开发测试环境”里做“Kubernetes离线部署”的人一个可以直接参考的路线。内容覆盖了环境规划、镜像准备、集群初始化、网络插件、开发测试常用组件以及最让人头疼的离线排错。适合正准备在离线网络里搭建K8s的运维、开发或者平台工程师尤其适合那些被“离线”两个字卡住、不知道从哪下手的同学。1. 先理清离线部署的核心思路1.1 离线部署的第一步不是下载而是规划很多人一上来就到处找离线包这其实是个误区。离线部署Kubernetes本质上不是“把文件拷过去”而是“在一个封闭环境里复现一套可运行的软件系统”。这一步做得好不好直接决定你后面是三天搞定还是三周还在折腾。先说清楚开发测试环境和生产环境的差别。开发测试环境的核心诉求是可以快速重建、方便排查问题、版本迭代灵活至于高可用、性能调优这些不是首要考虑。所以我下面讲的所有方案都是围绕“怎么在离线环境下最快地把集群跑起来同时后续还能维护”来展开的不会上来就上生产级的复杂配置。我建议在开始之前先拿一张纸把下面这些信息列清楚目标机器数量、角色分配master / node、操作系统版本磁盘空间镜像仓库和容器镜像都挺占地方的是否有内部的文件服务器或HTTP服务可以充当“软件分发中心”需要部署哪些附加组件Dashboard、监控、Ingress等Kubernetes版本以及配套的容器运行时版本我这次的环境是3台机器1台master 2台node操作系统是CentOS 7.9Kubernetes选择1.28版本容器运行时用containerd。之所以选这套组合是因为CentOS 7.9在开发测试环境里还是很常见而Kubernetes 1.28属于比较新的稳定版本containerd又是目前默认推荐的运行时踩坑资料也相对好找。1.2 离线部署工具怎么选主流的Kubernetes部署工具有几个kubeadm、kubekeyKubeSphere团队出的、Rancher的RKE还有二进制手动部署。直接说结论如果你是在离线环境部署我强烈建议用kubeadm。原因有三点。第一kubeadm是Kubernetes官方工具版本兼容性有保障出问题容易查资料。第二它的逻辑是“先把所有镜像和二进制准备好再执行init和join”这个流程天然适合离线场景。第三它的配置是明文YAML一切都透明可控不像Rancher那样有一层封装出了问题黑盒感很强。Kubekey我试过一次虽然它确实支持离线部署有个artifact概念但它的离线包制作流程比较重而且升级、维护时灵活性会差一些。开发测试环境要的是“坏了能快速重建”而不是一个很重的大管家。1.3 离线环境需要准备哪些物料离线部署Kubernetes核心物料就两大类一类是“程序文件”一类是“容器镜像”。程序文件包括kubeadm、kubelet、kubectl二进制或者对应的rpm/deb包以及containerd/cri-dockerd的安装包。这些在能上网的机器上下载好再拷到内网机器上安装。容器镜像是大头主要包括Kubernetes核心组件镜像kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、pause、网络插件镜像Calico或Flannel、以及你要部署的附加组件镜像。这里特别提醒一下很多人在准备镜像时会漏掉pause镜像。这个镜像是每个Pod启动都必须的如果离线仓库里没有它集群节点会一直NotReady日志刷屏“failed to pull pause image”。所以准备镜像清单的时候一定要把pause加进去而且版本必须和Kubernetes版本对应。2. 离线物料准备的具体操作2.1 找一个临时“中转机”如果你想在离线环境里部署Kubernetes第一步是找一台能上外网的机器当“中转机”。我一般用一台自己的笔记本或者临时申请的云主机用来下载软件包和镜像然后通过U盘、scp或者内网文件服务器拷到目标机器。中转机的操作系统最好和目标机器保持一致至少大版本要一致否则rpm包可能装不上。比如目标机器是CentOS 7.9中转机也最好是CentOS 7系列。在这个中转机上你需要先把下面这些软件包准备好kubeadm、kubelet、kubectl建议直接用阿里云或者官方源下载指定版本格式为rpm包。containerd可以用Docker官方提供的static二进制包也可以直接安装docker-ce然后启用containerd后者更省事。网络插件相关的YAML文件比如calico.yaml。下载的时候注意固定版本。不要下载“最新版”因为你可能今天下载的最新版配合的Kubernetes版本是另一个兼容区间到时候镜像tag对不上够你折腾的。2.2 镜像清单的生成与导出镜像准备的思路是这样的先把所有需要用的镜像在能上网的机器上pull下来然后通过docker save或ctr image export导出成tar包再拷贝进离线环境。到了离线环境之后如果搭了镜像仓库就导入仓库让节点统一拉取如果没搭仓库但有足够磁盘空间也可以直接在节点上用ctr image import导入本地。我建议搭一个镜像仓库因为多节点集群里每个节点都手动导入镜像太痛苦了而且后续增删组件都要反复操作效率太低。搭仓库最简单的方案是用registry:2镜像起一个容器。你在中转机上先把这个registry镜像拉下来save成tar拷到离线环境里的某台机器上可以放在master节点上然后执行docker load -i registry2.tar docker run -d --name registry \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ --restartalways \ registry:2这样就有了一个内网镜像仓库地址是目标机器IP:5000。后续所有镜像都导入到这台仓库然后节点统一从仓库拉取。镜像导出操作可以写成一个循环脚本在中转机上把镜像一个个打tag再push到离线仓库。具体流程后面详细说。2.3 配置目标机器的容器运行时在目标机器上安装完containerd之后需要修改配置文件。这里有个高频坑containerd默认cgroup驱动是cgroupfs而kubelet默认用systemd驱动两者不一致会导致节点NotReady。解决办法是在/etc/containerd/config.toml里把SystemdCgroup设为true。修改完之后重启containerdsystemctl restart containerd systemctl enable containerd另外既然要使用内网镜像仓库还要在containerd配置里加入insecure registry的配置如果仓库是HTTP访问。在/etc/containerd/config.toml里添加类似下面的内容不同版本字段略有差异[plugins.io.containerd.grpc.v1.cri.registry.configs.192.168.1.10:5000.tls] insecure_skip_verify true或者是旧版本用[plugins.cri.registry.mirrors]配置这个要看具体版本。总之目标是让containerd认为这个仓库是不需要HTTPS的。3. 集群初始化的完整实操过程3.1 基础环境配置目标机器的操作系统层面有几件事必须提前做掉关闭swap、加载内核模块、设置sysctl参数。关闭swapswapoff -a sed -i /swap/s/^/#/ /etc/fstab加载内核模块cat EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfiltersysctl参数配置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 --system这些配置是kubelet运行的基础缺了后面节点大概率NotReady。kubelet、kubeadm、kubectl用rpm包安装完成后先不要启动等后面初始化的时候让kubeadm带着启动。3.2 kubeadm配置与镜像导入在master节点上先确保所有离线镜像都已经导入到内网仓库了。这里给一个镜像导入的参考脚本思路# 中转机上执行 docker pull kube-apiserver:v1.28.2 docker images | grep k8s images.list # 每个镜像打tag推送到内网仓库 docker tag kube-apiserver:v1.28.2 192.168.1.10:5000/kube-apiserver:v1.28.2 docker push 192.168.1.10:5000/kube-apiserver:v1.28.2如果镜像数量多建议写个shell循环从清单文件里逐行读镜像名逐一pull、tag、push。手动敲命令复制粘贴很容易漏而且离线环境下改一次很麻烦。所以强烈建议提前把镜像清单整理成文件带上版本号统一管理。3.3 kubeadm init执行创建kubeadm-config.yaml这是整个流程的核心文件直接决定集群初始化是否成功apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 controlPlaneEndpoint: 192.168.1.10:6443 imageRepository: 192.168.1.10:5000 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.10 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock这里的imageRepository必须改成你的内网仓库地址这样kubeadm就会从内网仓库拉镜像而不会去docker.io。podSubnet要和后面网络插件配置的CIDR保持一致否则Pod网段冲突网络会出问题。执行初始化kubeadm init --config kubeadm-config.yaml --v5加上--v5是为了输出详细日志出问题时能快速定位。初始化完成后按提示配置kubectl的admin.confmkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config3.4 节点加入与网络插件生成join命令kubeadm token create --print-join-command在node节点上执行输出的这条命令即可加入集群。如果master节点有多个高可用场景还需要加入control-plane-endpoint和certificate-key参数开发测试环境一般单master就够了这里不展开。网络插件的选择我推荐Calico。它在离线环境里配置起来不难而且相比Flannel支持的策略更多后续做网络策略实验也方便。Calico离线安装的要点是先把calico.yaml文件下载下来里面包含所需的镜像然后用sed替换镜像地址或者先把镜像导进内网仓库再直接修改yaml里的image地址。修改完后执行kubectl apply -f calico.yaml等待Pod都Runningkubectl get pods -n kube-system如果看到calico-node和calico-kube-controllers都Runningmaster节点状态应该就是Ready了。当时我就卡在这里好一阵后面排查发现是镜像tag问题改完就好。4. 开发测试环境的常用组件部署4.1 Ingress-Nginx控制器开发测试环境经常需要通过域名访问服务Ingress是必备组件。离线部署Ingress-Nginx的方法和Calico类似下载deploy.yaml拉取镜像导入内网仓库修改image地址后apply。有一点需要区分如果你用deploy.yaml默认方式Service类型会是LoadBalancer但在自建集群里一般没有LoadBalancer实现所以需要修改为NodePort方式或者直接用hostNetwork: true让Ingress Controller直接占用宿主机端口。开发测试环境里我个人喜欢用NodePort这样端口管理更加明确。4.2 Kubernetes DashboardDashboard在离线环境部署同样要改镜像地址。启动后通常用token登录创建一个管理员用户apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard然后获取tokenkubectl -n kubernetes-dashboard create token admin-user这个token在开发测试环境可以长期有效比较方便。4.3 自定义镜像仓库与私有镜像拉取开发测试环境里开发人员经常会把镜像推送到一个内部Harbor或者自建RegistryKubernetes要从这个仓库拉取私有镜像。这里有一个隐藏问题Kubernetes默认使用Docker/containerd配置来拉取镜像如果仓库需要认证必须在Pod里配置imagePullSecrets。为了避免每个Deployment都去配置Secrets可以在Namespace的default ServiceAccount上关联imagePullSecrets这样这个Namespace下的Pod默认就有权限拉取私有仓库镜像。这个细节在开发环境里能省不少事因为开发人员不用关心认证问题。操作方法是kubectl create secret docker-registry registry-secret \ --docker-serverregistry.example.com \ --docker-usernameadmin \ --docker-passwordadmin123 \ -n default kubectl patch serviceaccount default -n default \ -p {imagePullSecrets: [{name: registry-secret}]}5. 常见问题与排查技巧实录5.1 镜像拉取失败ImagePullBackOff这个问题出现的频率极高几乎每个人离线部署都会遇到。排查步骤先看Pod事件kubectl describe pod pod-name如果是Failed to pull image xxx: rpc error: code Unknown desc failed to pull and unpack image...说明节点上无法访问该镜像地址。常见原因有两个一是镜像地址写的是外网仓库目标节点没有外网二是内网仓库地址写的是HTTP但节点上的containerd没有配置insecure registry。解决方法确认镜像地址已经改成内网仓库的完整地址并确认containerd已经配置了insecure_skip_verify。5.2 节点NotReady节点NotReady大概率是CNI网络插件没有装好或者pause镜像没拉到。排查顺序如下kubectl get node -o wide查看状态kubectl describe node node-name查看Conditions看是NetworkUnavailable还是Ready为False如果是网络问题检查Calico或Flannel的Pod是否Running如果Calico Pod报错用kubectl logs -n kube-system calico-pod看详细日志常见原因是IP池冲突、跨节点网段不一致、镜像tag不对另外别忘记检查集群节点的kubelet日志journalctl -u kubelet -f一般问题都能在这里找到答案。5.3 证书过期与时间同步开发测试环境虽然不像生产环境那么严格但节点时间不同步也会导致证书报错和Pod调度异常。尤其是离线环境如果机器没有配置NTP时间漂移会非常明显。建议在所有节点上安装并启用chrony或ntpd。配置简单收益大。如果已经出现证书报错比如x509: certificate has expired or is not yet valid可以先执行kubeadm alpha certs renew all老版本命令是kubeadm alpha certs renew all1.28里是kubeadm certs renew all然后重启kubelet和相关容器。如果实在不行就kubeadm reset重新初始化开发测试环境里重来成本很低不用太纠结。5.4 其他易踩的坑kubelet默认cgroup驱动与containerd配置不一致需要统一改成systemd安装containerd后没有重启导致配置文件不生效防火墙或安全组没有放行6443、10250等端口节点之间通信失败在Node节点上没有保存join命令的备份token过期了又要重新生成。建议初始化完成后马上把join命令存成一个文件使用kubeadm reset清理环境时要手动清理CNI残留目录/etc/cni/net.d否则之后重新部署可能出怪问题我把上面这些排查项整理成一个简单速查表现象大概率原因排查命令解决办法ImagePullBackOff镜像地址不可达或tag错误kubectl describe pod修改镜像地址、导入正确tag节点NotReadyCNI未装好或pause镜像缺失kubectl describe node检查Calico Pod、补拉pause镜像cgroup驱动不一致配置未同步journalctl -u kubelet统一改为systemd并重启containerd证书报错时间漂移date -R配置chrony必要时renew certs节点端口不通防火墙未放行telnet 10250开放所需端口或关闭防火墙6. 离线部署后的维护建议集群跑起来只是第一步后续日常使用和维护同样重要。但开发测试环境没有运维团队跟的时候更要提前把流程固定下来省得每次都是现场考古。拿我自己来说有几点建议很实用第一把镜像仓库和集群配置作为“基础设施即代码”来管理。镜像列表、kubeadm-config.yaml、calico.yaml这些文件都存到一个git仓库里下次重建直接照着操作不用再从头回忆。第二定期把镜像仓库打包备份。开发测试环境里的镜像越来越多一旦仓库所在磁盘挂了所有依赖镜像都要重新找非常麻烦。我一般用registry容器的导出工具把整个目录定期备份到另一台机器。第三节点系统层面的配置要固化到安装脚本里。比如sysctl参数、cgroup驱动、内核模块、chrony配置这些全部写成一个init脚本新节点加入时直接跑一遍避免人工执行遗漏。这里顺便放一个我常用的一键配置脚本片段可以按需调整#!/bin/bash set -e # 关闭swap swapoff -a sed -i /swap/s/^/#/ /etc/fstab # 加载内核模块 cat /etc/modules-load.d/k8s.conf EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # sysctl cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --system # containerd配置 containerd config default /etc/containerd/config.toml sed -i s/SystemdCgroup false/SystemdCgroup true/g /etc/containerd/config.toml # 如果使用内网HTTP仓库取消注释并修改地址 # sed -i s#https://registry-1.docker.io#http://192.168.1.10:5000# /etc/containerd/config.toml systemctl restart containerd systemctl enable containerd最后再分享一个小技巧在离线环境里如果你发现某个镜像在某个节点上拉不下来而其他节点正常别着急改配置先用crictl images看看这个节点本地是不是已经有镜像了有的话直接在Pod里把它指定为imagePullPolicy: IfNotPresent可以省去很多网络排查时间。开发测试环境讲究效率能绕开的坑尽量绕开。这套流程我在好几个内网环境里验证过跑通之后集群稳定性和重建速度都不错。如果你也正在做类似的离线部署希望这篇内容能帮你少踩几个坑省下一些时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价