
简介这是一份面向 Kubernetes 运维与部署人员的 k8s 1.18.8 离线安装资源包适合无外网环境下搭建生产或测试集群的读者。包内共 21 个文件整体约 610.15MB涵盖 kubeadm、kubectl、kubelet、crictl、sealos 等核心二进制kubelet.service 与 docker.service 服务文件以及 init.sh、master.sh、docker.sh 部署脚本并包含 calico.yaml 网络插件和 images.tar 镜像包覆盖环境初始化到集群加入的主要环节。yaml、conf 文件用于组件配置md 文档附使用说明。目前已有 290 人学习下载适合离线部署准备不熟的初、中级 k8s 用户可省去逐一下载官方组件的繁琐过程直接获得一套可落地的离线安装素材。1. kube1.18.8.tar.gz 是什么一个离线部署包的背后是一整套交付逻辑kube1.18.8.tar.gz 这个名字内行人一眼就能读出两层意思Kubernetes 1.18.8 这个具体版本以及一个被 tar 打包压缩后的离线安装包。生产环境里碰到它潜台词通常是——你的目标服务器上不了公网拉不了镜像连不上 apt 源而你还得在上面装出一套能跑的 Kubernetes。这个包就是为了解决这件事而存在的把 Kubernetes 的核心组件、依赖镜像和部署脚本一次性塞进一个压缩文件拷到内网机器上解压就能开工。它的价值不在于压缩技术而在于把复杂的集群交付过程收敛成几个固定动作。适合谁适合那些在政企内网、IDC 隔离区、机房离线环境里干活的人以及刚接手这类环境、面对一台上网受限的裸机不知道从哪里下手的运维和交付工程师。这套东西的核心思路和你在公网用一条 curl 脚本装集群完全不同它讲究的是资源台账清晰、步骤可回滚、出错看得见。2. 拆开压缩包看家底离线包目录规划与组件清单2.1 一个典型离线包的目录结构长什么样先别急着解压就开装。拿到 kube1.18.8.tar.gz 之后我一般会让磁盘上先出一个干净的交付目录然后用tar -tvf先看清单而不是直接一次性解开。原因很简单离线包可能在传输过程中被截断也可能在打包时漏了镜像归档先看列表能省掉后面一半的排查时间。mkdir -p /opt/k8s-offline cd /opt/k8s-offline tar -tzvf kube1.18.8.tar.gz | head -30-t表示仅查看列表而不解开包-z解压缩 gzip 格式-v输出详细权限和大小信息-f指定包文件名。head 限制只打印前 30 行目的是先确认包内顶层目录结构是否完整。正常来说这样一个离线包内部会分成几个固定的功能区域bin/放 kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy、kubectl 这些二进制images/放所有需要离线导入的容器镜像归档文件config/放集群初始化需要用到的配置文件、证书、kubeadm 配置模板packages/放操作系统层面的依赖比如 cri-tools、conntrack、ipvsadm。不同团队打的包布局会有差异但“二进制 镜像 配置模板”这个三角结构几乎是通用的。2.2 镜像清单是整包的生命线为什么核心组件必须提前进镜像仓库如果你tar -tzvf之后发现 images 目录是空的这个包基本是个废包。Kubernetes 1.18.8 时代控制面组件 kube-apiserver、kube-controller-manager、kube-scheduler 全部以容器方式运行etcd 也是容器形态网络插件、CoreDNS、kube-proxy 全部依赖镜像。公网安装时会通过 registry.k8s.io当时还叫 k8s.gcr.io现场拉取离线环境没有这个条件所以这些镜像必须提前 export 成 tar 文件放到包内。把包解开后第一件要做的事是把 images 目录下的 tar 文件逐批导入到节点本地的容器运行时里。cd /opt/k8s-offline tar -xzf kube1.18.8.tar.gz -C /opt/k8s-offline for img in /opt/k8s-offline/images/*.tar; do docker load -i $img || ctr -n k8s.io images import $img done这个循环是常见的双运行时兼容写法docker load面向 Docker 作为容器运行时的情况ctr -n k8s.io images import面向 containerd 直接对接 kubelet 的情况。-n k8s.io指定命名空间kubelet 默认从这个命名空间读取镜像如果导错了命名空间kubelet 会报告镜像不存在另一个深坑后面避坑章会专门讲到。镜像导入完成后用docker images或crictl images验证清单数量和包内文件数量做个对账。对不上的时候不要继续往下走说明有镜像没导进去后面 kubeadm init 一定失败且失败信息会很绕。2.3 二进制版本核对1.18.8 对应关系与 cgroup driver 选型二进制不是解压完就能直接用先检查版本一致性。离线交付最常见的翻车现场就是包里的 kubectl 是 1.20kubelet 是 1.18apiserver 镜像 tag 却是 1.18.8——混版本集群在公网环境也会出问题离线环境出了问题更难查。/opt/k8s-offline/bin/kubelet --version /opt/k8s-offline/bin/kubeadm version /opt/k8s-offline/bin/kubectl version --client输出里三个版本号必须都是 v1.18.8 或对应的补丁版本。我一般要求完全一致除非你要做跨版本升级实验。版本确认后紧接着要看 CRI 运行时的 cgroup driver。1.18.8 时代默认还是 systemd 和 cgroupfs 之争kubelet 的--cgroup-driver参数必须和容器运行时保持一致。Docker 默认 cgroupfs近些版本的系统初始化工具又偏好 systemd这样暧昧的配置经常是 kubelet 启动即崩溃的元凶。3. 搭建最小离线集群系统初始化到控制面可用3.1 三台机器还是单台离线部署拓扑决定后续所有参数kube1.18.8.tar.gz 适合先搭一个控制面单节点集群后续再扩容节点。控制面单节点意味着 etcd、apiserver、controller-manager、scheduler 都落在同一台机器上生产环境不推荐但离线交付第一步需要它验证镜像、配置和网络插件是否匹配而不是一次性铺开高可用。建议先用一台 4C8G 的机器做控制面一台 2C4G 的机器做 worker验证通过后再按同样步骤横向加机器。3.2 系统准备swap、内核参数、iptables 一个都不能少Kubernetes 对节点系统有硬性要求。1.18.8 的 kubelet 默认情况下遇到 swap 开启会直接拒绝工作虽然可以设置--fail-swap-onfalse绕过但我不建议在生产里这么干。swapoff -a sed -i / swap / s/^/#/ /etc/fstab cat EOF /etc/modules-load.d/kubernetes.conf br_netfilter ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack_ipv4 EOF systemctl restart systemd-modules-load交换分区禁用后还要修改 fstab 防止重启后 swap 回来否则节点重启后 kubelet 还是起不来。内核模块里 br_netfilter 是必须的没有它 iptables 规则对网桥流量不生效跨 Pod 通信会表现为时通时断ip_vs 系列模块是 kube-proxy 工作在 ipvs 模式时才需要如果你打算用 iptables 模式可以少加载两个模块但离线场景下我习惯一次性全部加载避免后续切换模式时再来补。加载模块后还要确认内核参数cat EOF /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 --systemnet.bridge.bridge-nf-call-iptables不设为 1NodePort 访问和 Service 转发都会出现“能 ping 通 Pod IP 但访问 Service 超时”的诡异现象。net.ipv4.ip_forward为 0 时 Pod 内发起的出站访问会失败这类问题排查起来非常容易误导人。3.3 用 kubeadm init 初始化控制面离线场景的三个必改参数系统准备完成后进入核心初始化环节。公共环境里跑kubeadm init它会自动拉取镜像离线环境必须用配置文件显式指定镜像仓库和版本。cat kubeadm-config.yaml EOF apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration kubernetesVersion: v1.18.8 controlPlaneEndpoint: 192.168.1.10:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubeadm.k8s.io/v1beta2 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.10 nodeRegistration: criSocket: /var/run/containerd/containerd.sock EOF /opt/k8s-offline/bin/kubeadm init --configkubeadm-config.yaml \ --ignore-preflight-errorsSwap,FileContent--proc-sys-net-bridge-bridge-nf-call-iptables这个配置的两个部分要区分清楚ClusterConfiguration 控制集群级参数InitConfiguration 控制本节点初始化行为。podSubnet选 10.244.0.0/16 是给 flannel 用的标准网段如果换 calico 则可以保持这个网段但换其他插件要确认网段兼容。advertiseAddress必须改成你的控制面机器实际 IP写错会导致 kubelet 无法注册节点、kubectl get nodes 永远看不到这台机器。criSocket 指向 containerd 的 socket说明这台机器用 containerd 做运行时。1.18.8 的 kubeadm 默认还偏好 docker socket必须显式指定。如果 kubeadm 报错找不到 containerd.sock先确认 containerd 是否启动。初始化成功后会输出一串 token、证书 hash 和 joining 命令我们需要把这些完整保存下来worker 节点加入时全部用得上建议直接存成文件/opt/k8s-offline/bin/kubeadm init --configkubeadm-config.yaml \ --ignore-preflight-errorsSwap,FileContent--proc-sys-net-bridge-bridge-nf-call-iptables \ | tee kubeadm-init.log用 tee 把完整输出存进日志后加入节点时无需再回想证书 hash。在初始化过程走完以后记得执行输出里的 kubectl 配置命令把集群连接凭证放好不然 kubectl 会说 connection refused其实是没找到集群信息。4. 打通工作节点与网络插件从加入集群到 Pod 就绪4.1 worker 节点加入集群token 失效前完成操作控制面初始化完成后worker 节点按同样的系统准备步骤执行然后执行控制面输出的kubeadm join命令。离线环境最大的问题是这条命令里的 token 和证书 hash 经常因为拖延而过期。/opt/k8s-offline/bin/kubeadm join 192.168.1.10:6443 \ --token your-token \ --discovery-token-ca-cert-hash sha256:your-hash \ --cri-socket /var/run/containerd/containerd.socktoken 默认有效期为 24 小时如果打包的交付过程隔了一天才装 worker必须重新生成。离线包里不应该包含 fabric 条件判断就用最朴素的方式/opt/k8s-offline/bin/kubeadm token create --print-join-command kubeadm init 的输出会自动打印完整的 join 命令。这个命令在 1.18.8 里可以直接在 worker 上执行不需要额外文件。如果不想用 token 方式也可以把控制面 /etc/kubernetes/pki/ca.crt 拷贝到 worker 节点用 --discovery-file 方式做发现这里两种方式选一种即可token 方式更短平快。worker 节点 join 完成后控制面上执行kubectl get nodes状态大概率是 NotReady。这是正常现象因为此时还没有网络插件所有节点包括控制面都处于 NotReady 状态。接下来部署网络插件网络插件也依赖镜像直接使用离线包里的网络插件清单文件。4.2 部署 flannel为什么 1.18.8 的 Pod 网段不能改很多人在公网环境里装集群网络插件是临时找一个最新版清单文件直接用这在离线环境会出大问题。离线环境下网络插件的镜像必须在离线包内所以这里保持一个基础原则包内没有的镜像版本就不要在清单文件里写那个版本。# 在控制面节点执行 kubectl apply -f /opt/k8s-offline/config/flannel/kube-flannel.ymlflannel 清单文件里有一段 configmap 的配置net-conf.json: | { Network: 10.244.0.0/16, Backend: { Type: vxlan } }这段配置里的Network必须和 kubeadm-config.yaml 里的 podSubnet 完全一致否则 flannel 建出来的网段跟 kubelet 分配的 CIDR 对不上Pod 会一直 ContainerCreating 并报分配 IP 失败的错。flannel 后端默认是 vxlan如果你在内网环境里追求低延迟可以改用 host-gateway但那就需要把所有节点 IP 与网段的静态路由配好交付复杂度会上升不建议离线场景第一次就这么干。网络插件部署后用kubectl get pods -n kube-system -w观察 CoreDNS、flannel 和 kube-proxy 的 Pod 状态。正常情况下几十秒内从 Pending 变为 Running。如果 flannel Pod 卡在 CrashLoopBackOff常见原因是节点之间 8472 端口的 UDP 不通内网环境防火墙经常拦这个端口。CoreDNS 从 Pending 变 Running 之后DNS 就通了Service 名称解析可用。此时验证一个基础 Pod 调度情况kubectl run test-pod --imagebusybox:1.31 -- sleep 3600 kubectl exec -it test-pod -- nslookup kubernetes.default.svc.cluster.local这条 nslookup 能返回 IP说明集群的 DNS 链路已经打通ClusterIP Service 的解析工作正常。如果提示 server 不解析先查 CoreDNS Pod 是否真的 Running再查 kube-proxy 的 iptables/ipvs 规则是否存在不要一上来就怀疑离线包有问题大部分时候是 CNI 和 Service 链路的经典失误。4.3 验证控制面组件健康状态不看整体状态只看关键 pod节点 Ready 后不看全局看关键组件这是一个很实用的交付习惯。跑一次kubectl get pods -n kube-system -o wide kubectl get nodes -o wide kubectl cluster-infokubectl cluster-info返回的信息最直接——它告诉你控制面地址和 CoreDNS 地址如果返回空响应说明 cert 或 apiserver 的 IP 有问题。控制面单节点的集群在 worker 上也执行kubectl cluster-info会返回 worker 上 kubectl 配置对应的集群信息这里的重点其实是确认 apiserver 地址从 worker 能访问。如果证书 hash 或 token 有问题worker join 阶段就会报错网络不通join 会持续超时这里直接看组件状态方法上这是最稳定的。5. 离线部署避坑指南我在 kube1.18.8 交付中踩过的五个大坑5.1 现象containerd 报“拉取镜像超时”但明明导入了镜像现象kubeadm init 阶段一直卡在 Pulling images日志报拉取 registry.k8s.io/pause:3.2 超时。原因kubelet 在启动静态 Pod 前会优先保证 pause 镜像存在而离线导入时把 pause 导入到了错误的 containerd 命名空间或者根本没有导入 pause 镜像。pause 镜像虽然不起业务作用但每个 Pod 都需要它不存在的表现就是所有 Pod 都创建不了网络命名空间。解决确认 images 目录里有 pause-***.tar用ctr -n k8s.io images list | grep pause检查没有就重新导入。5.2 现象kubelet 反复重启日志报 cgroup driver 不匹配现象kubelet 服务启动后存活几秒就退出journalctl 里看到failed to run Kubelet errfailed to validate kubelet flags或 cgroup driver 相关报错。原因1.18.8 的 kubelet 默认 cgroup driver 是 cgroupfs而 containerd 的配置在多数系统初始化工具下被改成 systemd两者不一致。解决改 containerd 的配置文件让它与 kubelet 保持一致通常把 systemd 改成 cgroupfs或者反过来改 kubelet 的启动参数为--cgroup-driversystemd改完后必须重启 containerd 和 kubelet顺序是先 crictl 确认运行时连接正常再重启 kubelet。5.3 现象worker 节点 join 成功但状态一直 NotReady日志刷 CNI 错误现象worker 节点能出现在控制面节点列表里但 Status 始终 NotReadykubelet 日志频繁出现Error adding pod to CNI network或网络命名空间创建失败。原因flannel 网络插件的镜像在 worker 节点上没有导入只有控制面导入了Pod 调度到 worker 后无法创建网络。解决确认离线包的 images 目录在 worker 节点上也完整导入导入后重启 kubelet让 Pod 重新调度。这是离线交付特有的坑——公网环境里 worker 会自己拉镜像离线环境少一步导入就全断。5.4 现象NodePort 服务无法从外部访问curl 超时现象部署了一个 NodePort 服务集群内 curl 正常集群外访问节点 IP 端口超时。原因Netfilter 的 bridge-nf-call-iptables 没有生效或者节点防火墙拦了 NodePort 端口段。解决重新执行 sysctl 设置并sysctl --system确认net.bridge.bridge-nf-call-iptables 1在内网安全组和节点 iptables 放行 30000-32767 端口段。还有一个不起眼的坑检查/proc/sys/net/bridge/bridge-nf-call-iptables是否存在如果这个文件不存在说明 br_netfilter 模块没加载文件路径都不存在参数设置了也白设。5.5 现象kubeadm init 提示 ignore-preflight-errors 参数不被识别现象1.18.8 的 kubeadm 对部分 preflight 错误项名称特别敏感比如提示unknown preflight error或找不到对应检查项。原因kubeadm 在不同版本对 preflight 错误名称的拼写有细微差别1.18.8 中部分要忽略的项不支持直接--ignore-preflight-errorsSwap的写法。解决先不带参数跑一次kubeadm init让它把不通过的项全部列出来然后按提示逐项修改系统无法快速修改的项再逐个抄进--ignore-preflight-errors。最省事的方法是提前把 swap 和系统参数全部改好尽量做到零忽略。6. 验证与进阶用 Nginx 部署走一遍集群全链路顺手检查证书期限刚搭好的离线集群不要急着交付先用一个 nginx Deployment 把调度、拉起、Service、NodePort、DNS 解析全链路走通这比任何检查脚本都可靠。# nginx-offline-test.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-test spec: replicas: 2 selector: matchLabels: app: nginx-test template: metadata: labels: app: nginx-test spec: containers: - name: nginx image: nginx:1.17.10 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-test spec: type: NodePort selector: app: nginx-test ports: - port: 80 targetPort: 80 nodePort: 30080这个清单里有两个关键第一是image: nginx:1.17.10必须确认离线包 images 目录里有这个镜像没有就需要额外导入第二是nodePort: 30080手工指定端口避免自动分配的端口在后续防火墙配置里又漏掉。应用它kubectl apply -f nginx-offline-test.yaml kubectl rollout status deployment/nginx-test等 Pod 变成 Running用curl http://任意节点IP:30080验证返回 Nginx 欢迎页再用kubectl exec进任意一个 Pod 内部访问 Service 名称nginx-test验证 DNS 解析。两层验证都通过集群才算真正可用。然后检查证书离线部署最大的隐性风险是证书过期了没人知道。1.18.8 的 kubeadm 默认生成的证书有效期是一年/opt/k8s-offline/bin/kubeadm certs check-expiration执行后注意看CERTIFICATE EXPIRES列。交付时我会把证书到期时间写进交接文档里提前一个月设个日历提醒。如果交付周期拉得长还可以顺手配置自动续期机制但升级 kubeadm 版本会引入额外的变动因此我通常只做监控和提醒。这一套全部走完回到开头那个问题kube1.18.8.tar.gz 的离线部署本质上是在资源受限的环境里把公网安装过程的每一个隐式依赖显性化。镜像、网络插件、cgroup driver、内核参数缺一环都要花几倍的排查时间去填。我的经验是永远先检查镜像导入是否完整——任何“超时”“NotReady”“拉取失败”的报错都把这条放第一位排查能省掉大半查日志的时间。希望这份实战笔记帮你在离线环境里少走几趟弯路。本文还有配套的精品资源点击获取