☰
Kubernetes 1.18.8离线部署实战:安装包拆解与避坑指南
2026/10/8 20:07:18 网站建设 项目流程

简介:面向需要搭建 Kubernetes 1.18.8 集群的运维工程师与开发人员,这是一份基于官方组件打包的离线部署资源,解决内网环境无法拉取镜像和二进制的问题。资源共 21 个文件、约 610MB,涵盖 kubeadm、kubectl、kubelet 等核心可执行文件,配套初始化与部署用的 shell 脚本,kubeadm.yaml、calico.yaml 等 YAML 清单,以及 kubelet.service、docker.service 等 systemd 服务文件,同时附带容器运行时镜像压缩包,便于离线导入。已有 290 人下载学习。使用这套包可免去逐一下载组件的繁琐流程,按脚本编排即可完成集群初始化、节点加入与网络插件部署,对学习 k8s 架构和自建生产环境均有直接参考价值,适合作为离线安装或实验环境的基线模板。

1. 拿到 kube1.18.8.tar.gz 的人,多半是要在离线的机房里搭一套能用的 Kubernetes

kube1.18.8.tar.gz 这个包,在大量实施和运维场景里就是“救命包”。你面对的往往是一台甚至几十台没有外网、没有 yum 源、连 docker pull 都超时的服务器,但客户要求必须把 Kubernetes 1.18.8 集群跑起来,还要在上面部署 nginx、跑业务。这个 tar.gz 就是为这种场景准备的:把 Kubernetes 1.18.8 的控制面组件、工作节点组件、依赖镜像打成离线安装包,带进机房解压、导入、init、join,一套流程走完。

用这个包的人有两类:一类是给政企、工厂、医院做私有化交付的实施工程师,机房物理隔离,没有任何办法在线拉取资源;另一类是刚接触 Kubernetes 的运维新手,想在内网服务器上搭一套能用的集群先跑起来。这篇文章我不会讲太多理论,直接按我实际部署 1.18.8 的经验,把包里的内容、部署步骤、验证方法、翻车现场和维护技巧拆给你看,照着做就行。

2. 拆开 kube1.18.8.tar.gz:里面的组件和镜像到底怎么协作

2.1 控制面四个组件加一个调度器:以 apiserver 为中枢的协作关系

Kubernetes 1.18.8 的集群从角色上分成控制面节点和工作节点,控制面上跑的是 kube-apiserver、kube-controller-manager、kube-scheduler,以及随包提供的 etcd。这四个进程里,apiserver 是唯一会跟 kubelet、kubectl、控制器、调度器直接打交道的入口,所有读写请求都先到 apiserver,再落到 etcd。

很多第一次拿离线包的人会把 kube-controller-manager 和 kube-scheduler 当成“类似的东西”,其实分工完全不同。controller-manager 负责维持期望状态,比如你声明要 3 个 nginx 副本,它看到只有 2 个,就会去创建第 3 个;scheduler 只做一件事,就是决定一个新 Pod 放到哪台 Node 上,它不真正启动 Pod,只是把决策结果写进 apiserver。工作节点上跑的是 kubelet 和 kube-proxy,kubelet 负责真正拉起容器、上报状态,kube-proxy 负责维护节点上的网络规则,让 Service 能转发流量。

控制面这四个组件商量好、把状态写入 etcd,工作节点上的 kubelet 通过 apiserver 拿到指令并执行,这就是 1.18.8 集群最基本的工作模型。离线包里必须把这两类角色的二进制、配置文件、依赖镜像都带上,少一个都装不起来。在 kube1.18.8.tar.gz 这样的包里,常见的目录布局是bin/放着 kubeadm、kubelet、kubectl 二进制,images/放着所有需要导入的镜像 tar 文件,manifests/放着静态 Pod 清单或额外的 yaml。

2.2 镜像清单与版本对齐:etcd、pause、coredns 这些 tag 为什么不能随便改

kubeadm 在 1.18.8 上默认拉取一组固定 tag 的镜像,拿这个包最常见的情况就是已经把这些镜像导出成了 docker 镜像 tar 文件,放在images/目录下。kubeadm 1.18 默认依赖的核心镜像大致对应关系如下:

组件默认镜像 tag作用
kube-apiserverv1.18.8控制面 API 入口
kube-controller-managerv1.18.8状态控制器集合
kube-schedulerv1.18.8调度决策
kube-proxyv1.18.8Service 流量转发
etcd3.4.3-0集群状态存储
coredns1.6.7集群 DNS
pause3.2每个 Pod 的“占位”容器

这里最容易踩的坑是 etcd 和 coredns 的 tag。很多新手以为 etcd 随便用一个 3.4.x 就行,coredns 用 1.7.0 也许“更新更好”,但 kubeadm 在 init 时会按它自己记录的期望版本去拉镜像,如果镜像仓库里找不到对应 tag,或者本地导入的镜像 tag 对不上,组件根本起不来。离线环境里最稳妥的做法是:不改 tag,完全按 kubeadm 默认版本走。

在包制作阶段,一般用kubeadm config images list输出期望的镜像列表,再逐个 pull、save 成 tar 文件打包。拿 1.18.8 来说,kubeadm config images list --kubernetes-version v1.18.8打印出的列表,就是离线包必须满足的清单。除此之外,还需要 CNI 插件镜像,比如 flannel 的quay.io/coreos/flannel:v0.12.0,这个不在 kubeadm 默认列表里,但集群网络要跑起来就离不开它。如果你在包里的images/目录里看到 flannel、calico、metrics-server 之类的额外镜像,属于正常情况。

2.3 kubeadm 与 kubelet 的版本匹配规则:为什么 1.18.8 的包要整套用

Kubernetes 对 kubeadm 和 kubelet 的版本差有严格约束,官方支持的策略是 kubeadm 最多比 kubelet 领先一个小版本。也就是说,控制面用 kubeadm 1.18.8 的时候,工作节点上的 kubelet 可以是 1.18.x,也可以是 1.19.x,但不能是 1.17.x。更不要混搭,比如控制面拿 1.18.8 的包,某个工作节点偏偏手动装了 kubelet 1.20.1,这会让 kubelet 跟 apiserver 之间的通信出现兼容性问题,Node 状态可能一直 NotReady。

实际做离线部署时,我一般只干一件事:包里 bin/ 目录下是哪套版本的 kubeadm、kubelet、kubectl,就整套拷到目标机器上用,不跟系统自带的混着来。解压后把这些二进制放到/usr/bin/并覆盖旧版本,然后统一执行kubeadm version确认三处输出一致。这样至少把版本错配这一类问题在源头干掉。

还有一点值得提醒:1.18 时代 kubeadm 的配置格式是kubeadm.k8s.io/v1beta2,很多网上教程用的还是 v1beta1 的写法,在 1.18.8 上直接 init 会报转换错误。如果要用配置文件而不是纯命令行参数,一定要写apiVersion: kubeadm.k8s.io/v1beta2。纯命令行的kubeadm init不涉及这个问题,但一旦涉及定制参数,最好用kubeadm config migrate把旧配置转成 v1beta2 再用。

3. 离线控制面部署:从解压到 kubeadm init 的完整步骤

3.1 解压包并导入镜像:先让容器运行时认识所有依赖镜像

拿到 kube1.18.8.tar.gz 之后,第一件事不是执行 kubeadm init,而是先解压、看包内容、导入镜像、核对二进制版本。我一般会在待安装机器上建一个工作目录,把包传上去,然后按下面顺序操作:

# 在这个目录里解压并查看布局 mkdir -p /opt/k8s-offline && tar -xzf kube1.18.8.tar.gz -C /opt/k8s-offline ls -l /opt/k8s-offline/ ls -l /opt/k8s-offline/images/ # 把镜像全部导入 docker,reload 后能看到对应 tag for img in /opt/k8s-offline/images/*.tar; do docker load -i "$img" done docker images | grep -E "kube-apiserver|etcd|coredns|pause|flannel"

这段脚本的逻辑是先把 tar.gz 解压到固定目录,然后遍历images/目录下所有 tar 文件逐个导入本地 docker。数据库存到机器上后,docker load只需要读一次,之后本地镜像列表里就能看到带正确 tag 的镜像。

参数说明:-C /opt/k8s-offline指定解压目标目录,避免直接把文件散在根路径下;for img in ...的写法兼容没有find的环境;docker images加grep只是为了快速确认几个关键镜像的 tag 和数量是否齐全。此时如果发现某个镜像缺失,就别往下走了,回头补镜像再继续,否则后面 kubeadm init 一定失败。

如果你的包用的是 containerd 而不是 docker,导入方式不同,注意不要用docker load去给 containerd 导镜像,网上很多教程直接把 docker load 当作万能接口,结果后面 Pod 一直拉不到镜像。运行时是什么,就必须用对应的导入命令。1.18.8 默认场景里,包里的容器运行时是给 docker 还是 containerd 准备的,通常在包内README或文件名里会标注,看不出来就直接装 docker 19.03。

3.2 kubeadm init 参数选择:apiserver 地址、Pod 网段与 image-repository

镜像导入完成后,就是整个部署里最关键的kubeadm init。先确认机器的 CPU、内存、swap 状态,swap 必须关闭,kubeadm init会做前置检查,开着 swap 直接报错。然后写 init 命令:

# 以控制面节点 IP 10.0.20.11 为例,Pod 网段使用 10.244.0.0/16 kubeadm init \ --kubernetes-version=v1.18.8 \ --apiserver-advertise-address=10.0.20.11 \ --pod-network-cidr=10.244.0.0/16 \ --image-repository=k8s.gcr.io

这段命令做的事是让 kubeadm 用 1.18.8 版本、指定 IP 作为 apiserver 广播地址,并把整个 Pod 网络规划在 10.244.0.0/16 网段内。其中--apiserver-advertise-address必须写控制面节点的内网 IP,别写 127.0.0.1,否则工作节点 join 时指向一个根本不通的地址,后面排查起来非常痛苦。--pod-network-cidr的取值必须和接下来要装的 CNI 插件默认网段一致,如果打算用 flannel,就写 10.244.0.0/16;如果打算用 calico,一般写 192.168.0.0/16,两者混用会直接导致容器网络不通。

--image-repository默认指向 k8s.gcr.io,离线环境里本地没有这个地址,但既然所有镜像已经提前导入 docker,kubeadm 不会真的去拉外网,它会先检查本地是否存在对应 tag。你的离线包里如果镜像 tag 齐全,仓库地址写默认值没有问题。如果包里的镜像是从内网 registry 打包的,这里就要改成 registry 的实际地址,否则 kubeadm 会认为镜像缺失。

init 成功后,kubeadm 会输出三块内容:一块是给普通用户配置 kubectl 的命令,一块是给工作节点加入集群用的kubeadm join完整命令,最后是部署 CNI 网络的提示。这三块输出都别丢,我一般直接复制保存到一个临时文件里,后面全用得上。init 失败的话,先立刻看/var/log/messages和/var/log/kubelet.log,比反复猜原因高效得多。

3.3 配置 kubectl 并让工作节点 join 进来:把集群从单节点扩展成多节点

控制面 init 完成之后,这台机器本身已经是一个单节点集群,但 kubeconfig 文件默认写在/etc/kubernetes/admin.conf,直接执行kubectl会提示权限不足。需要先按 init 输出里的提示配置当前用户的 kubeconfig,然后再部署 CNI:

# 让 kubectl 使用管理员 kubeconfig mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config # 部署 flannel 网络 kubectl apply -f /opt/k8s-offline/manifests/kube-flannel.yml kubectl get pods -n kube-system -w

这段命令中,复制 admin.conf 是因为它包含了完整的 apiserver 地址和具有集群管理权限的证书,不复制的话 kubectl 找不到任何上下文。部署 flannel 的顺序必须在所有工作节点 join 之前,很多实施团队先让节点全部加进来再装网络,结果每个节点都是 NotReady,因为 kubelet 启动的 Pod 网络是空的。

等到kubectl get nodes看到控制面节点的状态从 NotReady 变成 Ready,这就是集群自愈正常的信号。接着回到 init 输出的那串kubeadm join命令,在工作节点上执行:

# 工作节点上执行,token 和证书哈希来自控制面 init 输出或重新创建 kubeadm join 10.0.20.11:6443 \ --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxx

这里的--token是集群加入凭证,有效期默认 24 小时,过期之后要用kubeadm token create --print-join-command重新生成整串命令。--discovery-token-ca-cert-hash是 apiserver 的 CA 证书指纹,用来让工作节点校验自己连的是不是真正的集群,整串命令直接复制最稳,手敲很容易错。工作节点加入后,回到控制面kubectl get nodes确认能看到新节点,并给节点打上角色标签,比如kubectl label node worker-01 node-role.kubernetes.io/worker=,这样节点列表里才会显示 worker 角色。

4. 验证集群:从 node 状态到部署 nginx 的连通性测试

4.1 基础巡检:节点状态、系统 Pod 与 CNI 是否真的就绪

kubectl get nodes 全部 Ready 不代表集群一定能正常跑业务,很多隐藏问题要等实际创建 Pod 才暴露。我每搭完一套集群,不会直接接业务,而是先做一轮固定巡检,三个命令最管用:

# 1. 看节点整体状态,1.18.8 里 STATUS 为 Ready 才正常 kubectl get nodes -o wide # 2. 看 kube-system 里所有系统组件是否 Running 且 READY 为 1/1 kubectl get pods -n kube-system -o wide # 3. 看节点上的联机状态和内核网络配置是否一致 kubectl describe node | grep -i -E "InternalIP|kube-proxy|flannel"

第一条命令确认所有节点都处于 Ready 状态,-o wide顺便把每个节点的内网 IP 和运行时版本显示出来。第二条命令是重点,kube-system 命名空间里通常会有 coredns、etcd、kube-apiserver、kube-controller-manager、kube-scheduler、flannel、kube-proxy 这些 Pod,其中任何一个是 CrashLoopBackOff 或一直在 Pending,都说明集群有问题。READY 列出现0/1或1/2时,先kubectl logs看具体的容器日志,比 blind 猜原因有效率得多。

第三条命令用于确认 kube-proxy 和 flannel 在节点上真实运行状态,理论上它们是 DaemonSet,应该覆盖到每一个 Node,但实际中有时候某个节点标签不对,导致 DaemonSet 没有往这个节点调度 Pod,Node 还会一直 NotReady。所以巡检时看一眼每个节点的描述输出,能防住这类问题。这个巡检做完,集群才有资格开始往里面放业务负载。

4.2 部署 nginx 验证 ClusterIP 与 NodePort 两条访问链路

系统组件全 Running 之后,我会部署一个测试用的 nginx,来验证集群内部的 Service 转发是不是真的通。用一份最简单的 Deployment + Service 清单,覆盖 Kubernetes 部署 nginx 最常见的需求:

apiVersion: apps/v1 kind: Deployment metadata: name: nginx-test namespace: default spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.17.10 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: default spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080

这份清单里的 Deployment 定义了两个 nginx 副本,Service 把 Pod 的 80 端口暴露到集群每个节点的 30080 端口上,属于最常见的接入模式。注意nodePort在 1.18.8 里默认范围是 30000-32767,手工指定 30080 是合规的,超出这个范围会创建失败。image: nginx:1.17.10的镜像需要提前导入离线环境,不然创建出来的 Pod 会因为拉不到镜像而一直 ImagePullBackOff,这一步和之前导入 k8s 镜像一样,也要把 nginx 的镜像打成 tar 带进机房。

应用这份清单后,验证命令可以这样走:

kubectl apply -f nginx-test.yaml kubectl get pods -l app=nginx -o wide sleep 30 # 在工作节点上,通过 ClusterIP 访问 kubectl get svc nginx-svc curl http://<CLUSTER-IP>/ -I # 在任意节点上,通过 NodePort 访问 curl http://10.0.20.11:30080/ -I kubectl logs -l app=nginx | head -20

这里先用kubectl apply -f创建资源,然后查看 Pod 所在的节点和 IP。sleep一下是为了让 kube-proxy 的规则在节点上生效,紧接着分别访问 ClusterIP 和 NodePort。如果 ClusterIP 通而 NodePort 不通,优先检查节点的防火墙和 kube-proxy 的转发规则;如果两个都不通,重点看 Pod 是不是 Pending 或者 CrashLoopBackOff。最后看日志只是确认访问请求真的打到 nginx 容器了。测试完成后删掉这个测试负载,集群就算真正可用了。

5. 1.18.8 离线部署避坑清单:最常见的五个翻车现场

5.1 现象:镜像全部 load 了,Pod 还是 ImagePullBackOff

原因:容器运行时不是 docker,而是 containerd,docker load把镜像导入了 docker 的本地存储,但 kubelet 通过 containerd 去拉镜像时什么都找不到。1.18.8 的部署环境里运行时有很多种可能,很多包同时带了 docker 和 containerd 的镜像,导入时搞混了。

解决:先确认节点上到底用的是什么运行时。执行kubectl get nodes -o wide看 CONTAINER-RUNTIME 列,如果显示 containerd,就用ctr -n k8s.io images import xxx.tar导入所有 tar 文件,导入完再ctr -n k8s.io images ls | grep v1.18.8验证。如果是 docker,确认 kubelet 配置里的--container-runtime-endpoint指向的是 docker 的 socket,而不是unix:///run/containerd/containerd.sock。

5.2 现象:kubelet 反复重启,日志报 cgroup driver 不一致

原因:docker 的 cgroup 驱动默认是 cgroupfs,而 kubelet 在 1.18.8 上推荐并使用 systemd 驱动,两边不一致,kubelet 启动后无法与容器运行时协同管理 cgroup,陷入 crash loop。这个问题在 CentOS 7 和 Ubuntu 18.04 上尤其常见,因为两套系统服务对 cgroup 的默认处理不同。

解决:修改 docker 的 daemon.json,加上"exec-opts": ["native.cgroupdriver=systemd"],然后重启 docker。重启后通过docker info | grep -i cgroup确认驱动已变成 systemd,再重启 kubelet。如果改动后仍然报错,还要检查/etc/systemd/system/kubelet.service.d/10-kubeadm.conf里的KUBELET_KUBECONFIG_ARGS,确认 kubelet 没有通过--cgroup-driver再写死一个值,两边必须一致。

5.3 现象:Node 一直 NotReady,CNI Pod 一直没起来

原因:常见有三类。第一类是--pod-network-cidr和 CNI 插件的默认网段不一致,flannel 默认用 10.244.0.0/16,calico 默认用 192.168.0.0/16,初始化时没对齐,插件创建出来的网络没人认。第二类是 CNI 的清单文件没有在 join 之前部署,工作节点上完全没有网络插件。第三类是 flannel 镜像标签不对,包里只有 v0.11.0,清单里却写了 v0.12.0。

解决:先kubectl get pods -n kube-system看 CNI Pod 的错误状态,再kubectl logs -n kube-system <flannel-pod>看日志里是否有网段冲突或镜像拉取失败。如果是网段冲突,重新 init 集群,保持--pod-network-cidr与 CNI 默认网段一致;如果只是镜像 tag 问题,用ctr或docker tag把现有镜像打成清单需要的 tag 即可,不需要重新安装整个集群。

5.4 现象:kubeadm join 报 token 或 CA 校验失败

原因:token 有效期是 24 小时,很多人 init 完之后过了几天才拿工作节点去 join,token 早已过期。还有一种情况是 init 输出的--discovery-token-ca-cert-hash以sha256:开头,但被手打漏了前缀,或者复制时换行符破坏格式,导致 CA 校验失败。

解决:不要翻旧记录,直接回到控制面节点重新生成整串 join 命令。执行:

kubeadm token create --print-join-command

这条命令会生成一个全新的 token 和完整的 join 命令。把输出的整条命令复制到工作节点执行,不要手动拼接。如果工作节点曾经 join 失败过,先执行kubeadm reset清掉残留状态再尝试,不然 kubelet 证书目录里存在旧数据,join 会带着历史问题继续失败。

5.5 现象:kubectl top 不显示指标,metrics-server 启动但没数据

原因:metrics-server 默认使用 kubelet 的安全端口,同时默认从 Node 的主机名访问 kubelet,但 1.18.8 的 kubelet 证书并不覆盖所有节点的主机名,且没有配置--kubelet-insecure-tls,导致 metrics-server 抓不到数据,Pod 显示 Running 但没有任何指标输出。

解决:修改 metrics-server 的 Deployment,在 args 里加上- --kubelet-insecure-tls和- --kubelet-preferred-address-types=InternalIP,让 metrics-server 通过节点的内网 IP 而不是主机名去访问。改完滚动重启 metrics-server,等一会儿再用kubectl top nodes,如果出现 CPU 和内存数据,说明链路通了。这个坑在 1.18 时代几乎必踩,提前在清单里写好,能省去一次无头绪排查。

6. 集群跑起来后的维护技巧:证书续期与快速重建节点

6.1 证书续期:kubeadm alpha certs renew all 的正确用法

1.18.8 里 kubeadm 签发的证书默认有效期只有一年,到期之后 apiserver 之间、kubelet 连接 apiserver 都会出现证书过期错误。比较常见的是 kubelet 报 x509 certificate has expired or is not yet valid。你不需要重新 init 集群,也不用重新安装任何东西,只要在控制面节点执行:

kubeadm alpha certs renew all systemctl restart kubelet

这个命令会把 etcd、apiserver、controller-manager、scheduler、admin.conf 等所有由 kubeadm 管理的证书全部续期。执行完之后,/etc/kubernetes/admin.conf这个管理员 kubeconfig 内的客户端证书也会被更新,所以你本地的~/.kube/config需要重新从 admin.conf 复制一次,否则 kubectl 还会用旧证书去连 apiserver:

cp /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config

很多运维只续了证书,却不刷新 kubeconfig,结果依然觉得集群“证书过期没解决”,其实是旧客户端证书躺在本地的锅。证书续期之后,最好用kubeadm alpha certs check-expiration查看一遍新有效期,确认每一项都延长到一年后。

6.2 重建 worker 节点:保存配置、reset、重新 join 的顺序

集群里偶尔会有工作节点系统盘故障或误操作,导致 kubelet 状态异常且无法恢复,最快的处理不是修复,而是重建节点。操作顺序上有讲究,先把节点从集群里清掉,再在节点本机清理残留,最后重新 join:

# 在控制面节点上先标记不可调度并驱逐 Pod kubectl drain worker-01 --delete-emptydir-data --ignore-daemonsets kubectl delete node worker-01 # 回到 worker 节点上清理 k8s 组件残留 kubeadm reset rm -rf /etc/kubernetes/ /var/lib/kubelet/ /var/lib/etcd/ systemctl restart kubelet

这里先 drain 的目的是让集群把节点上的业务 Pod 漂移到其他节点,避免在这些 Pod 写入期间直接赶下去造成数据不一致。--ignore-daemonsets是因为 DaemonSet 类型的 Pod 本来就不应该被 drain 驱逐,加这个参数可以跳过报错。kubeadm reset会停止并清理本机组件,再手动把/etc/kubernetes、/var/lib/kubelet这些目录删掉,是为了让 kubelet 下次启动时不再拿着旧证书环境去连 apiserver。

清理完成后回到控制面,用kubeadm token create --print-join-command生成新命令,在重建的节点上重新执行 join。这套流程做下来,比直接修复损坏的 kubelet 更干净,也更容易让集群恢复到一致状态。我自己的习惯是每次重建节点前先把kubectl get nodes -o wide的输出存一份,方便 join 之后核对节点 IP 和角色,也方便排错。离线环境维护本来就比在线环境费劲,但 1.18.8 这套包只要初始化没问题,后续维护用到的命令并不多,上面这几个技巧足够应付绝大多数情况。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询