Kubernetes离线部署实战:kubeadm+containerd+Harbor全流程指南
2026/9/15 22:36:51 网站建设 项目流程

搞开发的人基本都遇到过这种场景:公司网闸一拉,服务器只能访问内网,或者项目本身就在一个跟外网彻底隔离的隔离网里,想装个Kubernetes集群,结果连个镜像都拉不下来。上个月我正好在开发测试环境里把整套Kubernetes离线部署流程完整跑了一遍,从物料准备到集群初始化再到后续组件补齐,中间踩了不少坑。这篇文章就是这次实操的完整记录,从制定方案到每一步的原理和命令,都尽量写清楚,给后面需要离线搭K8s的同学省点时间。

如果你正打算在离线网络环境里搭建一套Kubernetes集群,或者已经试过手动部署但卡在某个环节,这篇指南应该能帮上忙。文章会覆盖离线部署的完整流程:方案选型、离线物料的准备、containerd的配置、kubeadm初始化、工作节点接入、常用组件的离线安装,以及整个过程中遇到的高频问题。我不打算讲那种纯理论的东西,全部是实际操作里能直接用的办法。

1. 离线部署的整体思路与方案选型

1.1 三个常见方案的对比

先说说目前离线部署Kubernetes的主流路线。如果你在网上搜“Kubernetes离线部署”,基本会看到三类方案:基于kubeadm加离线镜像包、基于sealos这类打包工具,以及纯手工二进制部署。

第一类是最传统的kubeadm路线:在一台能访问外网的机器上把依赖的rpm包、镜像全部下载好,然后搬到内网,用kubeadm初始化集群。这种方式的好处是每一步都可以控制,出了问题好排查;坏处是准备工作量大,镜像列表要自己整理,容易漏。

第二类是sealos之类的工具。sealos把整个集群的镜像、组件、配置全部打成一个大的OCI镜像,一条命令就能拉起集群,非常快。我在测试环境里试过sealos部署在线集群,速度确实快,几分钟就能看到node处于Ready状态。但离线场景下用sealos有个问题:你需要提前找一台机器把sealos的集群镜像下载下来,然后转成离线包,这中间的依赖关系如果没理清楚,反而比kubeadm更麻烦。另外,sealos封装的K8s版本相对固定,如果你需要某个特定版本,就得自己重新做一个集群镜像,定制成本不低。

第三类纯二进制部署:直接把kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy这些二进制文件下载下来,手动创建证书、配置文件、systemd服务单元。这种方式能让你对K8s内部机制有很深的理解,适合学习,但不适合开发测试环境。原因很简单:手动做证书、配置高可用非常容易出错,而且后续维护成本和排查成本都高。

1.2 为什么我选择kubeadm + containerd这条路线

综合比较下来,我的建议是:开发测试环境的离线部署,首选kubeadm + containerd,镜像通过内网Harbor仓库中转。这套组合有两个核心优势:第一,kubeadm本身就是官方推荐的部署工具,版本兼容性有保障,升级路径清晰;第二,containerd在1.24版本之后已经成为K8s默认的运行时,配置和维护比Docker直连简单,也避免了dockershim被移除后带来的兼容问题。

用kubeadm还有一个实际的考虑:这个工具在初始化的时候会自己检测集群的版本、健康状态,如果有明显配置问题会在初始化前就报错,而不是等集群起来了才出问题。这种“fail fast”的特性在离线环境里特别宝贵,因为你没有外网可以查资料,每节省一次拉锯战都相当于节省了大量时间。

离线部署中需要重点解决的一个问题是“镜像从哪来”。在离线环境里,各节点无法访问Docker Hub或quay.io等公共镜像仓库。我的做法是:准备一台“中转机”,这台机器能访问外网,也有磁盘空间,用它提前把Kubernetes组件镜像、网络插件镜像、存储类镜像全部拉下来,推送到内网Harbor。然后,内网各节点统一从Harbor拉取镜像。这样做的好处是:

  • 镜像只下载一次,后续所有节点复用;
  • Harbor内部可配置为项目公开,节点拉取时不需要额外配置认证;
  • 后续要加节点或升级组件,只需从Harbor拉取,不用再往机器上拷贝tar包。

containerd上的镜像拉取也一样,你需要在/etc/containerd/config.toml里配置好registry的endpoint,让containerd把对registry.k8s.io的请求转发到内网Harbor,这样kubeadm初始化时不需要修改镜像仓库地址参数,所有组件镜像就能直接从内网拉取,既省事又不容易出错。

2. 开工前的资源准备:一件都不能少

2.1 主机规划与系统配置

准备阶段最容易犯的错误是“拿到几台机器就直接开始装”。我在这次部署里一开始就吃了这个亏,三台机器里有两台的hostname重复,结果kubeadm初始化时etcd起不来,排查了半天才发现是主机名冲突。

建议先列一张主机清单,规划好IP、主机名、角色、配置。我这次用的是三台机器:

主机名IP地址角色配置系统
k8s-master01192.168.20.11控制面节点2C4GCentOS 7.9
k8s-node01192.168.20.12工作节点4C8GCentOS 7.9
k8s-node02192.168.20.13工作节点4C8GCentOS 7.9

开发测试环境有一个主节点加两个工作节点完全够用,如果你只是自己验证功能,单节点模式也可以跑,但不太推荐,因为有些调度和网络特性在单节点上验证不出来。

系统配置这一步不能跳。首先是设置主机名,三台机器分别执行:

hostnamectl set-hostname k8s-master01 hostnamectl set-hostname k8s-node01 hostnamectl set-hostname k8s-node02

然后编辑/etc/hosts,把三台机器的IP和主机名映射写进去:

192.168.20.11 k8s-master01 192.168.20.12 k8s-node01 192.168.20.13 k8s-node02

接下来是关闭防火墙和swap。开发测试环境这样处理没问题,但如果是生产环境,建议用安全组策略代替直接关闭防火墙。关闭swap是必须的,因为kubelet默认要求swap处于关闭状态,不关的话初始化时会直接报错。

systemctl stop firewalld && systemctl disable firewalld sed -i '/ swap / s/^/#/' /etc/fstab swapoff -a

还要加载两个内核模块并调整系统参数:

modprobe overlay modprobe br_netfilter 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 --system

注意,net.bridge.bridge-nf-call-iptables这个参数如果不设置,集群内部的ClusterIP访问会不通,表现为Pod能启动但Service无法访问,这个坑后面排查的时候很隐蔽。

2.2 内网Harbor仓库搭建

离线部署里Harbor是一个关键基础设施。我用的版本是harbor-offline-installer-v2.8.4.tgz,大概2GB左右。你可以在中转机上下载Harbor离线安装包,然后传到内网一台独立的服务器上安装。

Harbor安装本身很简单,解压后修改harbor.yml配置文件。比较重要的几项:

  • hostname:改成你的Harbor服务器IP或域名,比如harbor.internal.com
  • port:默认80,如果80端口被占可以改成8080;
  • harbor_admin_password:默认密码是Harbor12345,第一次登录后立即修改;
  • data_volume:镜像数据存储路径,建议单独挂一块大磁盘。

配置完成后执行:

./install.sh

Harbor启动以后,在中转机上用docker登录:

docker login harbor.internal.com

然后创建项目,比如k8slibrary。后面从外网拉镜像,打好tag,再push到Harbor。举例,如果你拉取的是registry.k8s.io/pause:3.9,把它重新打标签为harbor.internal.com/k8s/pause:3.9,然后push。这样处理之后,内网节点在使用registry.k8s.io/pause:3.9时才会自动转发到Harbor的k8s/pause:3.9

Harbor本身可以选registry:2代替,但在有多个开发测试团队共用的情况下,Harbor的镜像仓库管理、项目隔离、审核功能更全。如果团队很小,只求简单,用离线安装的单机registry也能撑住。

2.3 离线物料清单:镜像、软件包一网打尽

软件包的准备是离线部署的重头戏。为了避免漏东西,我列了一张清单,每次部署前都按这个清单核对。

基础软件包

  • containerd安装包(如containerd-1.7.13-linux-amd64.tar.gz
  • runc安装包(部分containerd版本会自带,建议单独准备一份)
  • Kubernetes组件:kubeadmkubeletkubectl(版本保持一致,比如1.28.2)
  • crictl:用于调试containerd中的Pod和容器,非常有用
  • calicoctl:如果网络插件选Calico,可选安装,方便调试网络策略

Kubernetes核心镜像

这组镜像是kubeadm初始化时必需的。你可以手动从registry.k8s.io拉取,也可以进入一个有外网的机器执行kubeadm config images list生成镜像清单,然后脚本循环拉取。

我把1.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-0 registry.k8s.io/coredns/coredns:v1.10.1

网络与存储组件镜像

  • Flannel或Calico镜像(flannel:v0.24.0、flannel-cni-plugin、calico相关)
  • metrics-server镜像(如果想用kubectl top
  • Ingress Controller镜像(如ingress-nginx-controller)
  • 存储相关镜像(如local-path-provisionernfs-subdir-external-provisioner

Helm(可选)

如果你习惯用Helm管理应用,提前准备好Helm二进制。离线环境里Helm本身只是一个二进制文件,解压即可用,不需要额外配置。

所有二进制包和镜像文件准备好之后,统一放到一个目录下打包,比如k8s-offline-package.tar.gz。复制到内网环境时最好用校验和确认文件完整性,避免传输过程中文件损坏导致装到一半报错。

3. 一步步部署:从系统初始化到集群就绪

3.1 containerd的离线安装与配置

containerd在这套方案里承担所有容器的生命周期管理。离线安装containerd时需要注意,它的tar包解压的目标目录是/usr/local,解压后containerdctrcontainerd-shim-runc-v2这些二进制会到/usr/local/bin下。

tar -C /usr/local -xzf containerd-1.7.13-linux-amd64.tar.gz mv containerd.service /etc/systemd/system/ systemctl daemon-reload systemctl enable --now containerd

这里有个容易被忽略的地方:默认的containerd配置是不存在的,最好先执行一次containerd config default生成默认配置,再在里面修改。

mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml

打开/etc/containerd/config.toml,要做三处关键修改。第一处是SystemdCgroup,把它从false改为true。为什么要改?Kubernetes和containerd之间关于cgroup driver必须保持一致,kubelet默认用systemd作为cgroup driver,containerd如果用cgroupfs,两者会对同一个Pod的资源管理竞争,轻则报错,重则节点状态不稳定。

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true

第二处是sandbox_image。这个就是pause镜像地址,默认是registry.k8s.io/pause:3.9。如果你的内网节点无法直接访问这个地址,需要改为Harbor里的地址,比如harbor.internal.com/k8s/pause:3.9。否则创建Pod时会发生"Failed to pull image"错误。

第三处是镜像仓库的endpoint配置。在config.tomlregistry部分增加如下内容,让containerd访问公共仓库时自动转发到Harbor:

[plugins."io.containerd.grpc.v1.cri".registry] [plugins."io.containerd.grpc.v1.cri".registry.mirrors] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"] endpoint = ["http://harbor.internal.com/k8s"] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["http://harbor.internal.com/dockerhub"]

这个mirror配置用起来真的很方便。kubeadm默认会从registry.k8s.io拉镜像,Calico默认从quay.io拉镜像,只要在mirrors里分别定义一个quay.io的转发规则,这些组件就可以用默认地址正常拉取,不用挨个修改yaml文件。

修改完配置后重启containerd:

systemctl restart containerd systemctl status containerd

3.2 kubeadm、kubelet、kubectl的安装

K3s这类轻量发行版我不太建议在脱机环境下做主力集群,因为它的API Server、etcd、scheduler等组件都被封装在一个二进制里,虽然部署简单,但很多组件的独立配置和排障手段跟原生K8s有差异。开发测试环境跟生产环境保持同构,以后有什么问题也能在测试环境里复现出来,少走弯路。

安装kubeadm这套,需要下载三个rpm包或deb包。以CentOS为例,可以从阿里云的K8s仓库下载:

# 在有外网的机器上,把对应版本的kubeadm、kubelet、kubectl下载到本地 wget https://pkgs.k8s.io/core:/stable:/v1.28/rpm/kubeadm-1.28.2-0.x86_64.rpm wget https://pkgs.k8s.io/core:/stable:/v1.28/rpm/kubelet-1.28.2-0.x86_64.rpm wget https://pkgs.k8s.io/core:/stable:/v1.28/rpm/kubectl-1.28.2-0.x86_64.rpm

把这些rpm包拷贝到每台内网节点上,然后:

rpm -ivh kubeadm-1.28.2-0.x86_64.rpm kubelet-1.28.2-0.x86_64.rpm kubectl-1.28.2-0.x86_64.rpm

注意,如果节点已经装了老版本的K8s组件,rpm -ivh会报冲突,需要先卸载或者用rpm -Uvh升级安装。

装完之后,还要把kubelet服务设置成开机自启,但先不要启动它,因为还没有生成kubelet的配置文件,启动会失败:

systemctl enable kubelet

这条命令执行完以后,kubelet会处于未激活状态,这是正常的,等kubeadm init之后它会自动被kubeadm生成的配置文件激活。

3.3 用 kubeadm init 初始化控制面节点

初始化之前,先确认一下镜像是否已经能从Harbor正常拉取。用crictl配合测试:

crictl pull harbor.internal.com/k8s/pause:3.9

如果这一步能成功,那核心镜像基本也没什么问题。

接下来在master节点上执行初始化。初始化命令有几个参数值得仔细说:

kubeadm init \ --kubernetes-version=v1.28.2 \ --control-plane-endpoint=192.168.20.11:6443 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12 \ --apiserver-advertise-address=192.168.20.11 \ --image-repository=harbor.internal.com/k8s \ --upload-certs

这几个参数的作用分别讲一下。--control-plane-endpoint用于指定控制面的访问地址,如果后面要搭高可用,可以填VIP地址或者域名;当前面只有单master,直接用master的IP加6443端口就好。

--pod-network-cidr--service-cidr要提前规划好,因为网络插件(特别是Calico)会依据pod网段下发IP。如果你用Flannel,pod-network-cidr建议设为10.244.0.0/16,因为Flannel默认就装在这段网段;如果用Calico,可以设为172.16.0.0/16或10.42.0.0/16,但要注意不要和你的物理网段冲突。

--image-repository指向Harbor里的k8s项目。注意我们前面在config.toml里做了mirror转发,这里还是显式指定了一下,效果就是把registry.k8s.io/xxx替换为harbor.internal.com/k8s/xxx来拉取镜像。如果config.toml已经配好了mirror,其实可以不指定这个参数,让它默认从registry.k8s.io拉,containerd会自动转发到Harbor;但显式指定可以让日志和排障更清晰,我习惯写上。

--upload-certs只在需要多master高可用时才有用,它会把证书加密后上传到etcd中,其他master节点加入时可以直接拉取。单master环境下可以不加这个参数。

执行初始化时,kubeadm会先做一系列前置检查,包括端口、swap、内核模块、CRI连通性等。如果前面配置有遗漏,它会明确指出来。我这次第一次初始化时就是因为net.bridge.bridge-nf-call-iptables没设成1而报错,可见这一步确实很容易被跳过。

初始化成功后,kubeadm会输出一段提示信息,告诉你接下来要做什么。把它原样记录下来:

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

还要记录下worker节点加入集群的命令,类似这样:

kubeadm join 192.168.20.11:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>

这个token默认有效期是24小时,如果你当时没有记下来,后面可以执行kubeadm token list重新查看。如果token过期,用kubeadm token create --print-join-command重新生成一条join命令。

此时查看节点状态,会看到master处于NotReady状态,这是正常的,因为还没有装网络插件。

3.4 工作节点加入集群

加入集群的方式很简单,在node01和node02上执行刚才记录的kubeadm join命令即可。不过在此之前,node节点上也必须安装好containerd和kubeadm、kubelet,并完成系统初始化。这一步最容易犯的错是“只在master上装了软件包,node上没装就直接join”,结果node一直没法获取到kubelet配置。

加入集群后,在master上执行:

kubectl get nodes

正常情况会看到三个节点,但状态都是NotReady。不要慌,这跟初始化时的master一样,等你把网络插件装上去以后,节点状态会从NotReady变到Ready

如果加入过程报错,优先排查两个方向:一是node节点的kubelet和containerd是否正常运行,二是node节点能不能访问master的6443端口和Harbor仓库。用telnet测一下就知道。

3.5 安装网络插件:Flannel和Calico怎么选

网络插件是整个集群能正常通信的关键。离线环境下,Flannel和Calico是最常见的两个选择,各有利弊。

Flannel的优点是轻量、配置简单、资源占用低,适合开发测试环境。它以VXLAN或者host-gw模式在节点间建立Overlay网络,性能在测试场景下完全够用。缺点是它只解决Pod间通信和Pod到Service的通信,不能做网络策略。

Calico功能更强大,支持网络策略(NetworkPolicy)、BGP路由、更细粒度的流量控制。离线安装时,Calico的镜像会多一些,部署起来也稍重,但对调试和验证K8s的网络能力来说,是个更好的选择。

我在这个测试环境里选了Flannel,原因是测试环境没那么多网络隔离需求,而且Flannel的离线部署只有三四个镜像,操作简单。如果你后面想验证NetworkPolicy,那还是直接上Calico吧。

Flannel的离线安装通常用yaml文件或Helm chart。我在中转机上下载kube-flannel.yml之后,打开这个文件,找到quay.io/coreos/flannel镜像,把它替换为Harbor的对应地址,确保离线环境下能拉取到。如果你在containerd的config.toml里配了quay.io的mirror转发,这里其实不用改,直接kubectl apply -f kube-flannel.yml就行。

kubectl apply -f kube-flannel.yml

等待一两分钟,查看节点状态:

kubectl get nodes -o wide

如果节点全部变成Ready,说明集群容器网络已经正常工作了。

3.6 从metrics-server到Dashboard的组合拳

集群就绪之后,开发测试环境通常会继续装一些配套组件,让日常使用更方便。这里我按从常用到可选排了个序:

第一个是metrics-server。它收集节点和Pod的CPU、内存等指标,装完之后kubectl top nodekubectl top pod才能用。没有metrics-server,HPA(水平自动伸缩)也跑不起来。离线安装metrics-server时,要拉取registry.k8s.io/metrics-server/metrics-server镜像,同样可以通过Harbor转发解决。

第二个是Kubernetes Dashboard。它提供了Web界面,开发同学可以在上面看Pod日志、执行命令、查看资源状态,比命令行直观不少。离线安装时,可以到GitHub下载kubernetes-dashboard.yaml,修改其中的镜像地址为Harbor内地址,然后kubectl apply即可。Dashboard的Service默认是ClusterIP,测试环境可以通过kubectl port-forward代理访问,也可以改成NodePort暴露出去。

第三个是Ingress Controller。如果你要在集群里部署Web应用,或者模拟生产环境的域名访问,Ingress是绕不开的。ingress-nginx的离线安装需要拉取ingress-nginx/controller镜像,同样走Harbor转发。安装完成之后,它会以NodePort或LoadBalancer方式暴露80和443端口,在yaml里配置一下即可。

第四个是存储解决方方案。开发测试环境如果只是跑无状态应用,不需要持久化存储,但很多中间件比如MySQL、Redis都需要PVC。我在测试环境用的方案是local-path-provisioner,它是Rancher团队开源的一个轻量本地存储方案,安装简单,部署之后自动为PVC创建本地目录作为存储。优点是不需要专门的存储服务器,每台节点的本地磁盘都能用;缺点是数据分散在节点本地,不适用于生产环境的高可用要求。

如果团队有NAS或者NFS服务器,用nfs-subdir-external-provisioner也很方便。它把NFS上的一个目录作为动态存储供给池,Pod申请PVC时自动在NFS上创建子目录。需要注意的是,NFS的版本和服务端配置要做好,否则Pod挂载时会报参数错误。

4. 开发测试必须的扩展:存储、Dashboard与监控

4.1 本地存储方案local-path-provisioner的离线部署

这个组件特别适合测试环境。部署方式非常简单,只需要一个yaml文件加上一个镜像。把local-path-provisioner的yaml下载下来,修改其中的镜像地址为Harbor对应地址,然后apply。

本地存储方案的处理流程是这样的:它会创建一个默认的StorageClass,名字叫local-path。当有PVC创建时,provisioner就会在节点上的指定目录(默认是/opt/local-path-provisioner)下创建一块目录,然后通过hostPath方式挂载给Pod。对开发测试来说,用这种方式部署一个单实例的数据库来做验证,非常方便。

有一点要提前提醒:如果你把某个有状态应用的Pod调度到了node01,当Pod被删掉并重建时,新Pod可能会被调度到node02,而node02上没有原来那份数据。所以local-path这种方案只适合测试,不适合需要数据稳定的场景。要么给数据库类应用绑定固定节点,要么直接上NFS。

4.2 让开发更方便:Dashboard与K9s

Dashboard装好之后,默认账号有kubernetes-dashboard这个ServiceAccount,需要给它绑定一个cluster-admin的ClusterRole才能看到集群内容。可以创建一个admin-user并生成token,然后用token方式登录Dashboard。

除了Dashboard,我还建议在本地电脑上装一个k9s。它是一个终端版的K8s管理界面,支持键盘快捷键快速切换namespace、查看Pod日志、进入容器、编辑资源定义。开发测试环境里,我大多数时候是kubectl搭k9s一起用,K9s用于日常巡检和查看状态,kubectl用于写脚本和批量操作。

4.3 监控告警:先上metrics-server,再加kube-prometheus

开发测试环境的监控需求不用一开始就铺很大。先用metrics-server把资源使用情况可视化出来,让kubectl top能工作,大部分情况下就够用了。

如果后面想进一步做告警和可视化大盘,再考虑kube-prometheus-stack,它把Prometheus、Grafana、Alertmanager打包在一起,还带了一组开箱即用的告警规则。它的缺点是镜像数量非常多,离线部署时要把几十个镜像全部拉到Harbor,比较麻烦。我的建议是先把metrics-server用起来,确认有明确需求后再上全套监控,别一上来就把自己卷进镜像搬运的大坑里。

4.4 用Helm简化应用部署

在开发测试环境里,Helm的价值更多体现在“用Chart快速部署一套有依赖的应用”,比如WordPress加MySQL,或者Prometheus全家桶。离线环境下使用Helm,只要提前把需要的Chart打包成tgz文件,传到内网之后用helm install本地文件安装即可。

Helm二进制本身就是一个可执行文件,没有外部依赖,下载后放到/usr/local/bin就能用。Chart包可以从Artifact Hub下载,注意下载时带上依赖,避免安装时提示缺依赖项。

5. 高频排错实录与避坑心得

5.1 我实际遇到的几个典型问题

这次离线部署从头到尾大概花了两个工作日,中间遇到不少问题。挑几个有代表性的记录下来。

问题1:kubeadm init报“Initial timeout of 40s passed”

这个错误一般出现在初始化时kubelet起不起来,原因通常是系统配置未完全到位。我用journalctl -u kubelet查看日志,发现里面有很多关于cgroup的报错。排查思路是:

  • 确认SystemdCgroup是否已改为true;
  • 确认没有其他容器运行时残留在节点上;
  • 确认kubelet的cgroup driver与containerd一致。

这里有个快速验证命令:kubeadm init前先执行crictl info,查看containerd的runtime信息是否符合预期。

问题2:节点一直NotReady,Flannel Pod反复重启

Flannel Pod重启,多半是网络插件与Pod CIDR冲突,或者Flannel的配置无法找到子网。我的排查方法是:

  1. kubectl logs -n kube-flannel <pod-name>查看Flannel日志;
  2. 确认初始化时是否加了--pod-network-cidr=10.244.0.0/16
  3. 如果使用的是Calico,还要查看IP池配置是否与Pod CIDR一致。

还有一次是Flannel的镜像tag拉取错误,导致Pod镜像拉取失败。这种问题通常在日志里表现为ImagePullBackOff,检查一下镜像地址是不是指向Harbor的正确项目就行。

问题3:Service ClusterIP ping不通,但DNS正常

这个现象最迷惑人。后来发现是net.bridge.bridge-nf-call-iptables没开启,导致宿主机iptables的规则没有作用于桥接流量,所以Service的访问被绕过,表现为Pod之间可以通信,但Service无法转发。解决方法是开启内核参数,重启节点或者执行sysctl --system即可。

问题4:token过期,工作节点无法加入

开发测试环境经常要隔几天再加节点,此时你会发现之前记录的kubeadm join命令已经失效,token过期了。重新创建一个:

kubeadm token create --print-join-command

它会生成一条新的join命令,直接执行即可。注意新token也默认是24小时有效期。

5.2 关于离线包的版本管理

离线部署最怕的就是版本不一致,尤其是kubeadm、kubelet、kubectl这三个组件必须严格保持相同版本,否则集群会报"version skew"问题。我的习惯是,每次做的离线包,在包内附带一个manifest.txt,把里面每个镜像、每个二进制文件的版本和来源写清楚,比如:

containerd: 1.7.13 kubeadm/kubelet/kubectl: 1.28.2 flannel: v0.24.0 pause: 3.9 etcd: 3.5.9-0

这样下次再建环境时,直接照着清单准备,不用重新查版本兼容性。

5.3 其他值得注意的实操经验

  • 中转机建议用一台有图形界面的机器,后面如果要调试Harbor、查看镜像列表会方便很多;
  • containerd默认没有docker命令,但ctrcrictl都能操作容器,习惯用docker的人可能会一开始觉得别扭,多试几次就好了;
  • 开发测试环境尽量保持跟生产环境大版本一致,比如你生产上准备用1.30,测试环境就别贪新用1.31,版本差异带来的配置差异有时候非常隐晦;
  • 在离线环境里,官方文档不好直接访问,建议提前把K8s和containerd的离线文档、yaml模板下载到本地,有备无患;
  • 所有节点的时间必须保持同步,否则etcd会因时间偏差导致leader选举异常。离线环境一般没有外网NTP,建议内网搭一个chrony服务器,指向一些或多个可用的上游时间源。

这套离线部署方案我后来又帮团队在其他两套环境里复现过,整体稳定。如果你按照这个流程走,应该能避开大部分我踩过的坑。部署过程中如果遇到其他问题,建议先把kubelet日志和containerd日志拉出来看,大部分问题都会自己暴露身份。离线部署的魅力就在于此,一切都在掌控之中,只要把你需要的那份镜像和二进制准备齐了,剩下的就是按步骤执行。希望这份实操记录能帮你顺利把Kubernetes环境在离线环境下跑起来。

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

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

立即咨询