Kubernetes离线部署这件事,放在开发测试环境里,基本是每个团队迟早都会撞上的一道坎。很多项目的研发网段完全隔离,或者企业内部对系统外联有严格约束,apt、yum、docker pull这些日常操作全被卡死,可是K8s从系统依赖到容器镜像,每一步都在向网络要资源。这篇指南就是冲着这个场景来的,把在断网机器上从零搭一套K8s开发测试集群所需的完整物料、操作步骤、踩坑经验都列清楚,照着做基本能把集群跑起来,中间不会再被“拉不到镜像”这种问题逼疯。
这次分享的目标读者很明确:被内网环境折磨的DevOps、需要自己在测试环境搭建平台的开发同学,还有刚接触K8s想搞一套完整离线实践的新手。内容覆盖制品准备、基础环境配置、kubeadm初始化、常用插件离线安装、以及高频问题排查,整体按“先在线打包、后离线安装”的思路展开,用到什么解释什么。
1. 项目概述与整体设计思路
在动手之前,我先把离线部署的底层逻辑讲透。开发测试环境的离线部署,本质上不是“在没有网络的地方变魔法”,而是把在线环境下原本自动完成的依赖获取动作,提前手动做完,再搬到目标环境里执行。
1.1 为什么开发测试环境需要离线方案
这个问题我在不同团队被问过很多次。真实场景大概有三类:一是涉密研发网或企业内网,物理隔离、外网禁访,这是最严格的;二是托管机房或云上VPC,网络策略只开放白名单端口,公共镜像源根本不通;三是临时搭建的演示、培训或比赛环境,场地网络不稳定,与其现场赌运气不如全部离线。
开发测试环境还有一个容易被忽略的特点:它需要频繁重建。版本升级、环境损坏、人员交接,都可能要求你快速再拉一套集群。如果这套集群的搭建过程高度依赖外网,那么每次重建都是一次完整的外网依赖窗口,这在隔离环境下是不可接受的。离线部署方案把依赖固化成制品包,重建集群只是重复执行,时间成本和失败风险都会大幅下降。
1.2 方案选型:kubeadm还是二进制手动部署
搭建K8s集群的常见路线有三种:kubeadm引导安装、二进制手动部署、以及基于Playbook的自动化工具(比如kubeasz、sealos)。
开发测试环境我建议优先选kubeadm。原因很直接:kubeadm是官方维护的集群引导工具,生产环境大量使用,它的配置文件可审查、可版本化、可重复执行;同时它把etcd、apiserver、controller-manager、scheduler这些组件的静态Pod定义直接生成到集群里,架构清晰,排查问题更容易。二进制手动部署在离线场景下不是不行,但需要自己处理大量systemd单元文件和证书签发,开发测试环境没必要背这个复杂度。
另外要提醒一下,不要一上来就想着用sealos这类封装工具。sealos确实能把离线部署压缩到一条命令,但它的镜像方案相对黑盒,出了问题不好定位。等到你用kubeadm把流程跑通、理解底层逻辑之后,再谈工具化封装也不迟。本指南核心就是kubeadm加containerd的组合。
1.3 离线部署的思路闭环:制品准备、搬运落地、离线安装
离线部署如果拆成三个阶段来看,思路会非常清晰。
第一个阶段是制品准备,在能联网的机器上完成所有依赖的下载和归档。需要准备的制品包含三类:操作系统层依赖(rpm包或deb包)、K8s核心二进制与工具(kubeadm、kubelet、kubectl),还有集群运行所需的所有容器镜像。这一阶段的核心目标是把“网络依赖”彻底固化成“文件依赖”。
第二个阶段是搬运落地,通过U盘、内网传输通道等介质,把制品包拷到目标机器上,再完成解压归档、本地仓库初始化等操作。这个阶段要特别注意目录规划、校验值和版本锁定。
第三个阶段是离线安装,目标机器完全断网状态下,向本地仓库和本地镜像库请求资源,完成K8s集群初始化、节点加入、网络插件安装和应用组件部署。整个过程不需要任何外网请求,这就是离线部署的意义所在。
2. 离线资源准备与制品清单
整个离线部署工程里,资源准备是最枯燥但最关键的环节。前面版本选错一个,后面可能全部推倒重来。
2.1 需要准备的完整制品清单
我整理了一份开发测试环境最常用的清单,以CentOS 7.9(或兼容的Rocky Linux 8)加Kubernetes 1.28.x为例,直接按这个清单准备就不会缺东西。
| 制品类别 | 具体内容 | 版本建议 | 用途说明 |
|---|---|---|---|
| 操作系统 | CentOS 7.9/Ubuntu 22.04 | 稳定版 | 统一基座,避免系统差异 |
| 容器运行时 | containerd | 1.7.x | K8s默认CRI运行时 |
| 辅助工具 | crictl | 与K8s版本匹配 | 命令行查看容器和镜像 |
| K8s组件 | kubeadm、kubelet、kubectl | 1.28.x | 集群引导与日常管理 |
| 系统依赖rpm | libseccomp、conntrack等 | 以安装提示为准 | containerd和kubelet的系统依赖 |
| 核心镜像 | 见2.3小节 | 与K8s版本严格对应 | apiserver、etcd、pause等 |
| 网络插件镜像 | Calico或Flannel | 与K8s兼容 | Pod网络方案 |
| 辅助组件镜像 | Dashboard、metrics-server | 选择稳定版 | Web管理、资源监控 |
提示:版本锁定是离线部署的第一原则。K8s镜像与kubeadm版本必须一一对应,etcd和pause版本也不要手动修改,否则初始化时镜像拉取环节校验会直接报错。建议以kubeadm config images list命令在联网机器上输出的结果为准。
2.2 制作离线yum仓库
操作系统和K8s相关的rpm包,在离线机器上必须有一个本地仓库来源。最常用的做法是在联网机器上准备一个目录,把需要的rpm包放进去,然后用createrepo生成仓库元数据,最后通过HTTP或直接拷贝分发到目标机器。
先在一台联网机器上创建目录结构:
mkdir -p /opt/k8s-offline/rpms cd /opt/k8s-offline/rpms # 下载K8s核心组件rpm包(以1.28.2为例) yumdownloader --resolve kubeadm-1.28.2 kubelet-1.28.2 kubectl-1.28.2 # 下载containerd及其依赖 yumdownloader --resolve containerd.io # 加入常见系统依赖 yumdownloader --resolve libseccomp-devel下载完成后,生成仓库元数据:
yum install -y createrepo createrepo /opt/k8s-offline/rpms把整个rpms目录拷到离线机器后,在离线机器上创建本地repo文件:
cat > /etc/yum.repos.d/k8s-local.repo <<'EOF' [k8s-local] name=Kubernetes Local Repository baseurl=file:///opt/k8s-offline/rpms enabled=1 gpgcheck=0 EOF yum clean all && yum makecache注意:gpgcheck设为0在这里是合理的,因为包来源已经通过内网传输链路做了可信保证,继续开启校验反而可能因为缺少GPG公钥导致安装失败。
2.3 获取K8s核心镜像与组件镜像
K8s集群运行所需的镜像,必须在联网机器上提前拉取并导出为tar包。用kubeadm命令可以直接拿到需要的镜像清单:
kubeadm config images list --kubernetes-version v1.28.2输出大概是这样:
registry.k8s.io/kube-apiserver:v1.28.2 registry.k8s.io/kube-controller-manager:v1.28.2 registry.k8s.io/kube-scheduler:v1.28.2 registry.k8s.io/kube-proxy:v1.28.2 registry.k8s.io/pause:3.9 registry.k8s.io/etcd:3.5.9 registry.k8s.io/coredns/coredns:v1.10.1逐一拉取并打tar包:
# 安装docker(仅用于镜像搬运)或使用skopeo、ctr等方式 docker pull registry.k8s.io/kube-apiserver:v1.28.2 # 以此类推,将全部镜像拉取到本地 mkdir -p /opt/k8s-offline/images docker save registry.k8s.io/kube-apiserver:v1.28.2 -o /opt/k8s-offline/images/kube-apiserver.tar开发测试环境如果还需要Dashboard、metrics-server、Calico等组件,也一并拉取导出。推荐的辅助镜像清单:
| 组件 | 镜像 | 版本 |
|---|---|---|
| Dashboard | kubernetesui/dashboard | v2.7.0 |
| Dashboard metrics | kubernetesui/metrics-scraper | v1.0.8 |
| metrics-server | registry.k8s.io/metrics-server/metrics-server | v0.6.4 |
| Calico | calico/cni、calico/node、calico/kube-controllers | v3.26.1 |
实操心得:所有tar包导出后顺手生成一个sha256校验文件,传到离线机器后先校验再导入。镜像动辄几百MB甚至上GB,拷贝过程中偶尔会发生文件损坏,没有校验机制你会花大量时间排一个根本不存在的问题。
2.4 目录规划与传递介质安排
我习惯把离线制品包按统一目录结构组织,目标机器直接照抄这个结构,减少出错。
/opt/k8s-offline/ ├── rpms/ # yum仓库目录,含createrepo元数据 ├── images/ # 所有镜像tar包 ├── packages/ # kubeadm、kubelet、kubectl二进制(如果不用rpm) ├── configs/ # kubeadm配置文件、calico yaml等 └── checksum.sha256 # 所有文件的校验文件传递介质建议使用内网共享存储或高速U盘,大镜像文件通过U盘拷贝时注意文件系统格式,建议用exFAT或ext4,避免单文件4GB限制。
3. 基础环境初始化与离线安装准备
制品搬运完成后,离线机器上要开始真正的安装动作。这一阶段以“可重复执行”为原则,所有修改都留有日志和记录。
3.1 主机规划与节点划分建议
开发测试环境我建议至少准备一台控制节点加一台工作节点。如果资源有限,单机All-in-One也可以,但最好在虚拟机里做快照,方便出问题时回滚。推荐的资源基线:
| 节点角色 | 配置建议 | 用途 |
|---|---|---|
| 控制平面节点 | 4C8G 100GB磁盘 | 运行etcd、apiserver、controller-manager、scheduler |
| 工作节点 | 4C8G 100GB磁盘起 | 运行业务Pod,如果跑大模型推理类应用建议加GPU |
| 单机All-in-One | 8C16G 200GB磁盘 | 演示、开发联调,注意etcd和业务资源抢占问题 |
主机名和hosts解析提前配置好,所有节点的主机名都不能重复,并且集群内所有节点都能通过主机名互相访问。
hostnamectl set-hostname k8s-master01 cat >> /etc/hosts <<EOF 192.168.10.11 k8s-master01 192.168.10.12 k8s-node01 EOF3.2 系统基础配置:内核模块、系统参数、关闭swap
K8s对系统层有一个硬性要求:swap必须关闭,否则kubelet启动会直接报错退出。这是因为K8s的Pod内存管理机制依赖cgroup来控制和统计内存使用,swap的存在会让这种统计失真。执行以下操作:
# 关闭swap并注释/etc/fstab相关行 swapoff -a sed -i '/ swap / s/^/#/' /etc/fstab # 加载K8s所需内核模块 cat > /etc/modules-load.d/k8s.conf <<EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # 设置内核参数 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这几个内核参数的作用,我用个直观的方式解释:net.ipv4.ip_forward控制着K8s集群内部Pod和Service的流量转发,如果不开启,ClusterIP模式的Service流量无法正确路由到Pod,集群内Pod之间也无法跨节点通信。br_netfilter相关参数是确保经过Linux网桥的流量也能被iptables规则处理,这是K8s网络策略和Service转发的基础。
3.3 安装containerd并处理关键配置
离线机器上安装containerd,首选从离线yum仓库直接安装:
yum install -y containerd.io安装完成后,初始化配置并调整关键项。containerd的默认配置文件是/etc/containerd/config.toml,先生成默认配置再修改:
mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml然后修改两个地方。第一是sandbox(pause)镜像地址,确保和K8s版本一致:
[plugins."io.containerd.grpc.v1.cri"] sandbox_image = "registry.k8s.io/pause:3.9"第二是cgroup驱动,必须改为systemd:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true关于cgroup驱动,这里多写几句。Linux系统有两套cgroup管理方式:cgroupfs和systemd。K8s官方强烈建议kubelet使用systemd驱动,因为systemd本身在初始化时就会接管cgroup,如果kubelet用cgroupfs而systemd也在管理cgroup,两者同时控制系统资源分配会出现“双头管理”,轻则资源统计异常,重则节点崩溃。这就是为什么kubelet和containerd的cgroup驱动必须要一致且都为systemd。
修改完成后启动containerd并验证:
systemctl enable containerd && systemctl start containerd ctr version3.4 安装kubeadm、kubelet、kubectl并锁定版本
K8s三件套的安装同样走本地yum仓库:
yum install -y kubeadm-1.28.2 kubelet-1.28.2 kubectl-1.28.2安装完成后需要把版本锁定,防止意外升级导致版本漂移:
yum install -y yum-plugin-versionlock yum versionlock kubeadm kubelet kubectl然后设置kubelet开机启动(先不启动,等初始化完成后才会正常运行):
systemctl enable kubelet4. 使用kubeadm离线初始化集群
制品和基础环境都就绪后,进入核心的集群初始化环节。这一阶段的操作要注意每一步的校验输出,很多坑在输出日志里其实有明确提示。
4.1 将镜像tar包导入到containerd
离线环境下Crictl的镜像导入方式与docker有很大不同。对containerd来说,需要用到ctr命令,并且必须指定K8s的命名空间k8s.io,否则kubelet无法识别:
# 在images目录下执行 for img in *.tar; do ctr -n=k8s.io images import "$img" done验证一下镜像是否导入成功:
crictl images注意:crictl默认连接containerd的socket,配置一下/etc/crictl.yaml会更顺手:
runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false4.2 编写离线kubeadm配置文件
kubeadm init支持通过配置文件传参,离线场景强烈建议用配置文件而不是纯命令行参数,因为配置可以保存、review、复用。以下是一个基础的控制平面节点配置:
apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.10.11 bindPort: 6443 nodeRegistration: criSocket: /run/containerd/containerd.sock --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 controlPlaneEndpoint: 192.168.10.11:6443 networking: dnsDomain: cluster.local podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12几个关键参数的说明:
- advertiseAddress:控制节点监听API请求的IP,一定要填内网实际IP,不要填默认路由IP。
- kubernetesVersion:必须和本地导入的镜像版本完全一致。
- podSubnet:Pod地址段,我建议用10.244.0.0/16,和Flannel默认保持一致会省很多事。
- serviceSubnet:Service虚拟IP段,10.96.0.0/12是默认值,没有特殊规划可以不变。
关于Pod网段的选择有个实际经验:开发测试环境经常存在多个集群,如果每个集群的Pod网段都相同,未来做多集群联邦或者集群迁移时冲突会非常明显。建议从一开始就在内网IP段规划中预留不同集群的不同网段,避免后续返工。
4.3 初始化控制平面节点
在控制节点上执行:
kubeadm init --config /opt/k8s-offline/configs/kubeadm-config.yaml --upload-certs这一步有几个可能的结局:顺利通过,或者卡在等待kubelet启动、拉取镜像失败等位置。如果卡住,不要反复重试同一命令,应该先查kubelet日志:
journalctl -u kubelet -f初始化成功后会输出一大段提示,其中包含加入工作节点的join命令,一定保存好。然后按提示配置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 kubectl get pods -A此时node状态大概率是NotReady,原因是还没有安装CNI网络插件,这是正常的。
4.4 添加Worker节点到集群
工作节点只需要安装kubeadm、kubelet和containerd,并导入相同的一套镜像,然后执行control plane初始化后输出的join命令:
kubeadm join 192.168.10.11:6443 --token xxxxx --discovery-token-ca-cert-hash sha256:xxxxx如果token过期,在控制节点上重新生成:
kubeadm token create --print-join-command实操心得:开发测试环境经常遇到worker节点重新初始化的情况,旧的K8s残留目录会导致加入失败。重置节点时执行kubeadm reset -f后,记得手动清理/etc/cni/net.d和/var/lib/kubelet等目录。不清理干净,新加入节点的CNI配置会乱,Pod网络会彻底瘫痪。
5. 开发测试常用组件离线安装
集群初步跑起来之后,离真正能用还有一段距离。这一节覆盖开发测试环境最常用的几个组件的离线安装方式,包括CNI网络插件、资源监控、Web管理界面,以及离线业务镜像导入能力。
5.1 安装CNI网络插件(Calico/Flannel)
没有网络插件,集群节点会一直保持NotReady状态。开发测试环境我推荐优先使用Flannel,因为配置简单、资源占用低,适合性能不高的测试机和虚拟机。Calico则适合需要网络策略(NetworkPolicy)的测试场景,比如你想验证Pod级别的访问控制规则。
以Flannel为例,先在联网环境准备镜像和yaml文件:
docker pull flannelcni/flannel:v0.22.0 docker save flannelcni/flannel:v0.22.0 -o /opt/k8s-offline/images/flannel.tar # 下载kube-flannel.yml离线机器上导入镜像并应用:
ctr -n=k8s.io images import /opt/k8s-offline/images/flannel.tar kubectl apply -f /opt/k8s-offline/configs/kube-flannel.yml检查Pod状态:
kubectl get pods -A | grep flannel kubectl get nodes几分钟后节点应该都变为Ready。如果Calico,思路一致:提前下载calico.yaml,替换其中的镜像地址为本地或内网仓库地址,导入镜像后kubectl apply即可。
5.2 安装metrics-server实现资源监控
开发测试环境里,查看Pod和节点的CPU、内存使用情况是一个非常高频的需求,kubectl top命令依赖metrics-server。这个组件在离线安装时有一个常见坑:默认配置下它要求节点间的kubelet连接必须走TLS证书校验,开发测试环境的证书配置很难满足,需要给metrics-server加上--kubelet-insecure-tls参数。
先下载metrics-server的yaml(官方components.yaml),修改Deployment部分:
containers: - args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-insecure-tls - --kubelet-preferred-address-types=InternalIP离线机器导入镜像并部署:
ctr -n=k8s.io images import /opt/k8s-offline/images/metrics-server.tar kubectl apply -f /opt/k8s-offline/configs/metrics-server-components.yaml kubectl top nodes5.3 安装Kubernetes Dashboard可视化界面
对于开发测试环境,Dashboard能明显降低团队的使用门槛。一个没接触过K8s的同事,通过网页看Pod日志、看Deployment状态,比教他敲kubectl命令要高效得多。
先导入Dashboard相关镜像:
ctr -n=k8s.io images import /opt/k8s-offline/images/dashboard.tar ctr -n=k8s.io images import /opt/k8s-offline/images/metrics-scraper.tar kubectl apply -f /opt/k8s-offline/configs/dashboard.yaml创建管理员账号并获取登录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-dashboardkubectl -n kubernetes-dashboard create token admin-user访问方式上,开发测试环境最省事的是把Service改成NodePort:
kubectl -n kubernetes-dashboard edit svc kubernetes-dashboard # 将type改为NodePort,保存后查看端口 kubectl -n kubernetes-dashboard get svc通过节点IP加NodePort就能访问Dashboard,这个方式在断网环境里完全够用。
5.4 镜像导入的通用能力:支撑离线业务部署
开发测试环境里,K8s集群本身不是目的,跑业务才是。很多团队在离线集群上要部署大模型推理服务(比如最近很火的DeepSeek 8B)、开源数据平台、低代码工具等。这类应用的镜像动辄几个GB到几十GB,怎么在离线环境把它变成K8s能用的工作负载,本质还是走镜像导入这条路。
通用流程是这样的:在能联网的环境里docker pull业务镜像,然后docker save成tar包,传导到离线机器后,用ctr命令导入到k8s.io命名空间,再接Deployment或Helm Chart部署。业务镜像导入和K8s系统镜像导入,在流程上没有任何区别,只是体积更大。
# 联网机器上 docker pull deepseek-ai/DeepSeek-R1-Distill-Qwen-8B:latest(示意) docker save deepseek-ai/DeepSeek-R1-Distill-Qwen-8B -o /opt/k8s-offline/images/deepseek-8b.tar # 离线机器上 ctr -n=k8s.io images import /opt/k8s-offline/images/deepseek-8b.tar注意大镜像导入耗时较长,ctr命令可能看起来像卡住了,实际上是在解包写入。建议先用sha256校验文件确认完整性,再导入。K8s侧只要有GPU驱动(如果需要),就可以通过kubectl apply部署推理服务。
6. 常见问题与排障速查表
离线部署K8s的坑,从操作系统层到Pod运行层都有。我把这几年实际遇到的高频问题整理成一张速查表,每个问题都是排查过的,不是网上抄来的。
| 问题现象 | 根因分析 | 解决方案 |
|---|---|---|
| kubeadm init卡在等待kubelet启动 | 镜像没导入或cgroup驱动配置错误 | 检查crictl images确认镜像存在;核对containerd SystemdCgroup配置 |
| 初始化后node处于NotReady | CNI网络插件未安装或pause镜像缺失 | 安装Flannel/Calico;确认pause镜像存在且镜像tag与k8s版本一致 |
| Pod状态一直是ContainerCreating | CNI插件没有就绪或镜像拉取失败 | kubectl describe pod查看事件,确认镜像是否导入到k8s.io命名空间 |
| CoreDNS一直CrashLoopBackOff | 网络插件与Pod网段冲突 | 检查podSubnet配置,避免与主机网段重叠 |
| kubectl top没有数据 | metrics-server未安装或安全参数缺失 | 确认metrics-server已启动且配置--kubelet-insecure-tls |
| 工作节点join后无法Ready | token过期或CNI配置残留 | kubeadm token create重新生成;kubeadm reset后清理/var/lib/kubelet与/etc/cni/net.d |
| Dashboard无法访问 | Service类型为ClusterIP | 改为NodePort或用kubectl proxy访问 |
| 容器内时间不准 | 开发测试环境无NTP外网源 | 集群内搭建内网NTP服务,或部署时将宿主时区与time同步挂载进容器 |
6.1 初始化失败:镜像拉取是最常见的拦路虎
离线机器上执行kubeadm init,如果在输出中看到Failed to pull image、EOF等字样,不用慌,这基本就是两种原因:镜像没有导入到containerd,或者导入时没有指定k8s.io这个命名空间。有一种情况是镜像明明导入了,crictl images也能看到,但kubeadm还是提示拉取失败,原因是tag不一致。你本地导入的是v1.28.2,但kubeadm配置里写的kubernetesVersion是v1.28.1,它会按照v1.28.1去找镜像,自然找不到。
规避方法是先执行一遍:
kubeadm config images list --image-repository registry.k8s.io --kubernetes-version v1.28.2逐行对比本地crictl images,确保tag完全一致,再改配置执行init。
6.2 节点NotReady的排查思路
如果你确认CNI插件已经安装,但节点还是NotReady,按这个顺序排查:先看系统组件Pod是否正常运行,再看网络插件Pod日志,最后检查主机防火墙和节点IP配置。开发测试环境很多NotReady的根因不是K8s本身,而是系统防火墙没有放行Pod网段流量。
systemctl status firewalld # 开发测试环境可以直接关闭,或者放行必要端口 systemctl disable firewalld --now注意:关闭防火墙后要确认主机本身没有单独的安全组或网络安全策略,比如虚拟化平台的security group。Pod的Overlay网络走的是VXLAN或host-gw模式,如果底层网络策略限制UDP端口,flannel流量会被丢弃,表现为跨节点Pod不通但单节点内正常。
6.3 证书有效期与Token过期问题
K8s的kubeadm默认签发的证书有效期是一年。开发测试环境里集群用一年以上很常见,一旦证书过期,kubectl会直接报证书过期或Unauthorized。解决办法是每年续期一次:
kubeadm certs renew all systemctl restart kubelet如果是生产级高可用环境,还需要替换kubeconfig文件。开发测试环境在证书到期前几天处理就行,续期后重启各控制组件,通常不会影响已有数据。如果发现apiserver等静态Pod没有被更新,可以直接删除对应的Pod让kubelet重建:
kubectl -n kube-system delete pod kube-apiserver-$(hostname)6.4 大镜像导入变慢与存储空间不足
开发测试环境跑大模型相关应用时,镜像导入动辄几十GB,整个过程对磁盘空间要求很高。ctr导入镜像会先解包到本地,再写入到containerd的存储目录,因此需要的临时空间是镜像tar包的1.5到2倍。建议先检查磁盘空间:
df -h /var/lib/containerd如果空间不够,先把tar包放在独立磁盘或从共享存储读取,导入完成后删除tar包释放空间。另外,containerd的snapshotter对空间占用比较敏感,如果频繁导入删除大镜像导致空间不回收,可以执行gc:
ctr -n=k8s.io content gc ctr -n=k8s.io snapshots gc这个操作在业务低峰期做,gc过程会占用一些CPU和磁盘IO。
7. 实操心得与后续扩展建议
最后聊一些我自己的经验总结。多次离线部署K8s之后,我最大的体会是:离线部署最难的不是安装过程本身,而是版本一致性管理。镜像版本、kubeadm版本、系统依赖版本、网络插件版本,任何一个环节的版本漂移都会导致连锁失败。所以强烈建议把制品清单做成一个版本化文件,放进Git仓库,每次迭代都记录变更。这样哪怕半年后需要重建一套集群,翻一下仓库历史就能按当时的版本完整复原。
另外一个实操心得是:把重复的安装过程写成脚本。虽然kubeadm命令本身不复杂,但准备镜像、导入镜像、配内核参数这些环节完全适合脚本化。建议至少把镜像导入、基础环境配置、离线yum仓库配置这三部分写成Shell脚本,传到离线机器上一条命令执行,能大幅减少人工操作失误。
后续如果有余力,可以往这几个方向扩展:一是搭建内网Harbor镜像仓库,配合containerd的registry配置,让离线机器从内网镜像仓库拉取镜像,比反复导入tar包更贴近生产使用方式;二是引入GitLab Runner实现离线环境下的CI/CD;三是把Chart包离线化,让团队通过Helm快速部署中间件。这些都是在“离线K8s集群可用”之后,开发测试环境真正发挥价值的方向。
这套流程做完之后,你会发现离线部署K8s没有想象中那么玄学,核心就是三件事:把依赖准备好、把版本锁住、把日志看仔细。做到这三点,任何断网环境里你都能从容地搭出一套可用的Kubernetes开发测试环境。