Skip to content

第二篇:Kubernetes v1.33.13 三节点集群离线安装指南(内网环境篇)

Kubernetes 三节点集群离线安装示意图

适用场景与前置条件

项目说明
适用场景在内网或完全断网的环境中,使用第一篇制作的离线包部署 Kubernetes 集群。
目标集群3 节点(1 个控制平面 + 2 个工作节点)
操作系统Ubuntu 22.04(所有节点)
Kubernetes 版本v1.33.13
容器运行时containerd(>= 1.7.25)
网络插件Calico v3.26.0
前置依赖已完成第一篇,获得 k8s-offline-v1.33.13.tar.gz

第二篇目标

在三台内网 Ubuntu 22.04 服务器上,使用离线包完成以下目标:

  • ✅ 所有节点基础环境配置(IP、主机名、系统参数)
  • ✅ 离线安装 Kubernetes 组件和 containerd
  • ✅ 导入所有容器镜像
  • ✅ 初始化控制平面(kubeadm init)
  • ✅ 安装 Calico 网络插件
  • ✅ 加入两个工作节点
  • ✅ 验证集群完整可用

模拟企业内网或完全断网的环境(所有节点)

为什么要模拟内网环境?

在实际的企业私有化部署场景中,Kubernetes 集群往往运行在严格隔离的内网环境中,节点可能被禁止访问外网。为了在实验室或测试环境中提前验证这种部署流程,我们需要主动模拟这一限制。

但这里有一个容易踩的坑:不能简单粗暴地删除默认网关,否则会导致集群内部组件(如 Calico、CoreDNS)之间无法正常通信,造成部署失败。

正确做法:保留网关 + 用 iptables 控制流量

删除默认网关会破坏节点间的路由,即使所有目标 IP 都在同一个子网内,也可能引发 TCP 连接异常。正确的做法是:

  1. 保留默认网关 —— 确保集群内部通信正常
  2. 使用 iptables 精确控制 —— 只允许必要的内网流量,拒绝外网访问

如何验证当前系统的 DNS 配置?

在 Ubuntu 22.04 中,/etc/resolv.conf 通常是指向 systemd-resolved 的符号链接。要查看实际生效的 DNS 配置,可以使用以下命令:

bash
# 查看当前系统的 DNS 配置状态
resolvectl status

# 查看 DNS 解析统计信息
resolvectl statistics

如果发现系统配置了外网 DNS(如 8.8.8.8、114.114.114.114),并且您希望在内网中彻底禁用外网 DNS 解析,可以结合 iptables 规则加以限制。

iptables 规则配置示例

以下规则演示了如何只允许内网和 Kubernetes 相关网段,拒绝所有其他流量

bash
# 1. 先放行本地回环(永远放行)
sudo iptables -A OUTPUT -o lo -j ACCEPT

# 2. 放行内网(SSH 连接的目标网络)
sudo iptables -A OUTPUT -d 192.168.0.0/16 -j ACCEPT

# 3. 放行 Kubernetes 相关网段
sudo iptables -A OUTPUT -d 10.0.0.0/8 -j ACCEPT
sudo iptables -A OUTPUT -d 10.96.0.0/12 -j ACCEPT
sudo iptables -A OUTPUT -d 10.244.0.0/16 -j ACCEPT

# 4. 最后才拒绝所有其他流量(禁止上网)
sudo iptables -A OUTPUT -d 0.0.0.0/0 -j DROP

特别注意:上述规则的顺序至关重要,必须先放行(ACCEPT)需要的流量,最后才拒绝(DROP)所有其他流量。如果顺序颠倒,所有流量都会被直接丢弃,导致 SSH 连接断开。

验证规则是否生效

bash
# 查看当前生效的 iptables 规则
sudo iptables -L -v -n

# 测试内网通信是否正常
ping -c 2 192.168.0.226

# 测试外网是否被阻断(预期应超时或无响应)
ping -c 2 8.8.8.8

# 测试 DNS 解析是否被阻断(预期应失败)
nslookup bing.com
# 或使用 dig
dig bing.com

一、服务器规划与基础配置

1.1 集群节点规划

角色主机名IP 地址配置建议
控制平面node1192.168.0.2252C/4G+
工作节点node2192.168.0.2262C/4G+
工作节点node3192.168.0.2272C/4G+

请根据您的实际网络环境修改上述 IP 地址。

1.2 配置主机名(所有节点)

bash
# 设置主机名(根据角色执行)
sudo hostnamectl set-hostname node1   # 在 node1 上执行
sudo hostnamectl set-hostname node2   # 在 node2 上执行
sudo hostnamectl set-hostname node3   # 在 node3 上执行

# 验证
hostname

1.3 配置静态 IP(所有节点)

  1. 查看网卡名称
bash
ip a
  1. 编辑 Netplan 配置文件(以 node1 为例,其他节点修改 IP 即可):
bash
sudo vim /etc/netplan/00-installer-config.yaml

内容配置如下(请替换 ens160 为您的实际网卡名):

yaml
network:
  version: 2
  ethernets:
    ens160:
      addresses:
        - 192.168.0.225/24
      routes:
        - to: default
          via: 192.168.0.1
      nameservers:
        addresses:
          - 114.114.114.114
          - 8.8.8.8
  1. 应用配置
bash
sudo netplan apply

1.4 配置主机名解析(所有节点)

编辑 /etc/hosts 文件,添加所有节点的 IP 和主机名映射,以实现集群内互访。

bash
sudo tee -a /etc/hosts <<EOF
192.168.0.225 node1
192.168.0.226 node2
192.168.0.227 node3
EOF

# 验证互通
ping node1 -c 2
ping node2 -c 2
ping node3 -c 2

1.5 系统基础配置(所有节点)

bash
# 1. 关闭防火墙
sudo systemctl disable --now ufw

# 2. 关闭交换分区(永久禁用)
sudo sed -ri 's/.*swap.*/#&/' /etc/fstab
sudo swapoff -a  # 立即生效,无需重启

# 3. 加载必要的内核模块
sudo tee /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF

# 立即加载(使当前会话生效)
sudo modprobe overlay
sudo modprobe br_netfilter

# 验证
lsmod | grep -E "overlay|br_netfilter"

# 4. 配置内核参数(允许桥接防火墙及 IP 转发)
sudo tee /etc/sysctl.d/99-kubernetes.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

sudo sysctl --system

# 5. 确认 cgroups v2 已启用(预期输出为 cgroup2)
mount | grep cgroup

cgroups v2 是 Kubernetes v1.33.13 的推荐配置。如果您的系统仍在使用 v1,请参考相关文档进行切换。

1.6 配置时间同步(所有节点)

确保所有节点时间一致,对集群证书和安全机制至关重要。

bash
# 配置时区为 Asia/Shanghai
sudo timedatectl set-timezone Asia/Shanghai

# 启用 NTP 同步(如果是 ESXi 虚拟机,也可在 vCenter 中配置)
sudo timedatectl set-ntp true

# 验证时间同步状态
timedatectl

二、分发并安装离线包

2.1 传输离线包到所有节点

使用 scp 命令将第一篇制作好的离线包分发至所有目标服务器。

bash
# 从制作机器传输到各个节点(请替换 user 和 IP)
scp k8s-offline-v1.33.13.tar.gz [email protected]:/tmp/
scp k8s-offline-v1.33.13.tar.gz [email protected]:/tmp/
scp k8s-offline-v1.33.13.tar.gz [email protected]:/tmp/

2.2 解压离线包(所有节点)

bash
# 创建安装目录
sudo mkdir -p /opt/k8s-install

# 解压到安装目录
sudo tar -xzvf /tmp/k8s-offline-v1.33.13.tar.gz -C /opt/k8s-install

# 进入解压后的目录
cd /opt/k8s-install/k8s-offline-v1.33.13

# 查看内容,确保文件完整
ls -l

2.3 执行安装脚本(所有节点)

运行 install.sh 安装所有 deb 包并导入镜像。

bash
# 执行离线安装脚本
sudo ./install.sh

该脚本会自动完成以下任务:

  • 安装 Kubernetes 组件(kubeadmkubeletkubectl
  • 安装 containerd 运行时
  • 加载 k8s-images.tarcalico-images.tar 等镜像文件
  • 锁定组件版本,防止自动更新

三、配置 containerd

3.1 生成默认配置文件

bash
# 生成默认配置
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

3.2 校正沙箱镜像版本(关键步骤)

必须确保 sandbox_image 与集群版本一致,否则 kubelet 可能无法正常启动。

bash
# 1. 查看当前 containerd 中的 pause 镜像版本
sudo ctr -n=k8s.io images ls | grep pause

# 2. 编辑 containerd 配置文件
sudo vim /etc/containerd/config.toml

找到 sandbox_image 字段,将其修改为:

toml
sandbox_image = "registry.k8s.io/pause:3.10"
bash
# 3. 重启 containerd 使配置生效
sudo systemctl restart containerd

3.3 配置 crictl

创建 /etc/crictl.yaml 文件,以便 crictl 命令能够与 containerd 通信。

bash
sudo tee /etc/crictl.yaml <<EOF
runtime-endpoint: unix:///var/run/containerd/containerd.sock
image-endpoint: unix:///var/run/containerd/containerd.sock
timeout: 10
debug: false
EOF

3.4 验证 containerd 状态

bash
# 检查服务状态
sudo systemctl status containerd

# 列出已导入的镜像,确认包含所有离线镜像
sudo ctr -n=k8s.io images ls -q

四、初始化控制平面(仅在 node1 执行)

4.1 执行集群初始化

bash
# 设置 API Server 地址为 node1 节点的 IP
NODE1_IP=192.168.0.225

# 初始化集群,使用不与节点 IP 冲突的 CIDR(推荐使用 RFC 1918 私有地址段)
sudo kubeadm init \
  --kubernetes-version v1.33.13 \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=$NODE1_IP \
  --control-plane-endpoint=$NODE1_IP \
  --cri-socket=unix:///var/run/containerd/containerd.sock

初始化成功后,请务必记录输出的 kubeadm join 命令,它将用于后续将工作节点加入集群。

4.2 配置 kubectl

bash
# 为当前普通用户配置 kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 验证 kubectl 能否访问集群
kubectl get nodes

此时,您应该能看到 node1 节点状态为 NotReady,这是正常的,因为还未安装网络插件。

4.3 安装 Calico 网络插件

4.3.1 部署 Calico

bash
# 使用离线包中的 Calico 配置文件进行安装
kubectl apply -f /opt/k8s-install/k8s-offline-v1.33.13/manifests/calico.yaml

# 观察 Calico Pod 启动过程(按 Ctrl+C 退出)
kubectl get pods -n kube-system -w

等待所有 calico-* Pod 进入 Running 状态。

4.3.2 验证 Calico 状态

bash
# 查看 Calico 相关 Pod 状态
kubectl get pods -n kube-system | grep calico

# 预期输出
# calico-kube-controllers-xxx   1/1     Running   0   2m
# calico-node-xxx               1/1     Running   0   2m

4.3.3 检查节点状态

bash
# 查看节点状态,应变为 Ready
kubectl get nodes

⚠️ 常见问题:CoreDNS CrashLoopBackOff(离线环境重要坑点!)

在离线环境中,Calico 安装完成后,可能会遇到 CoreDNS Pod 一直处于 CrashLoopBackOff 状态的问题。

现象:

bash
kubectl get pods -n kube-system

# 输出示例:
# NAME                                       READY   STATUS             RESTARTS   AGE
# calico-kube-controllers-xxx                1/1     Running            0          5m
# calico-node-xxx                            1/1     Running            0          5m
# coredns-674b8bbfcf-7zlhn                   0/1     CrashLoopBackOff   6          5m
# coredns-674b8bbfcf-hzqw6                   0/1     CrashLoopBackOff   6          5m
# etcd-localhost                             1/1     Running            0          8m
# kube-apiserver-localhost                   1/1     Running            0          8m
# kube-controller-manager-localhost          1/1     Running            0          8m
# kube-proxy-92ftp                           1/1     Running            0          8m
# kube-scheduler-localhost                   1/1     Running            0          8m

排查步骤:

bash
# 1. 查看 CoreDNS 日志(确认错误原因)
kubectl logs -n kube-system coredns-674b8bbfcf-7zlhn -p

# 错误日志输出:
# plugin/forward: no nameservers found

错误原因分析:

CoreDNS 默认配置了 forward . /etc/resolv.conf,在离线环境中:

  • /etc/resolv.conf 指向 systemd-resolved 存根解析器(127.0.0.53
  • systemd-resolved 没有配置可用的上游 DNS 服务器
  • CoreDNS 启动时检测到没有可用的上游 DNS,导致 CrashLoopBackOff

根本原因:CoreDNS 的 forward 插件在离线环境中尝试转发 DNS 请求到上游服务器,但上游 DNS 不可达,导致启动失败。

解决方案:删除或注释 forward 配置行

bash
# 编辑 CoreDNS ConfigMap
kubectl edit configmap coredns -n kube-system

找到并删除或注释 forward 相关配置行。

修改前(默认配置):

yaml
data:
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
            ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf   # ← 需要删除这一行
        cache 30
        loop
        reload
        loadbalance
    }

修改后(离线环境适用):

yaml
data:
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
            ttl 30
        }
        prometheus :9153
        # forward 行已删除,不转发任何外部 DNS 请求
        cache 30
        loop
        reload
        loadbalance
    }

重启 CoreDNS:

bash
# 删除 CoreDNS Pod,让 Deployment 自动重建并加载新配置
kubectl delete pods -n kube-system -l k8s-app=kube-dns

# 监控启动过程
kubectl get pods -n kube-system -w | grep coredns

验证 CoreDNS 恢复正常:

bash
# 查看 CoreDNS Pod 状态
kubectl get pods -n kube-system | grep coredns

# 预期输出:
# coredns-674b8bbfcf-46mtw   1/1     Running   0   39s
# coredns-674b8bbfcf-m8k5f   1/1     Running   0   39s

# 查看 CoreDNS 日志(确认无错误)
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=10

验证集群 DNS 解析:

bash
# 测试集群内部 DNS 解析
kubectl run test-dns --image=busybox:1.36.1 --rm -it --restart=Never -- nslookup kubernetes.default.svc.cluster.local

# 预期输出:
# Server:    10.96.0.10
# Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
#
# Name:      kubernetes.default.svc.cluster.local
# Address 1: 10.96.0.1

4.3.4 为何删除 forward 后 CoreDNS 仍能正常工作?

配置项作用离线环境是否需要
kubernetes 插件解析集群内部 Service(*.cluster.local✅ 必需
forward 插件转发外部域名解析请求(如 google.com❌ 不需要

在离线环境中,Pod 只需要解析集群内部的 Service,不需要解析外部域名。删除 forward 插件后,CoreDNS 专注于通过 kubernetes 插件处理集群内部 DNS 查询,因此完全自包含,无需任何上游 DNS。

4.3.5 宿主机 DNS 配置(可选说明)

在 Ubuntu 22.04 中,/etc/resolv.conf 默认指向 systemd-resolved 的存根解析器(127.0.0.53):

bash
# 查看 DNS 配置状态
resolvectl status

# 测试外部域名解析(预期失败)
nslookup bing.com
# 输出:;; Got SERVFAIL reply from 127.0.0.53

宿主机无法解析外部域名不会影响 Kubernetes 集群的正常运行。因为:

  • 宿主机 DNS 解析由 systemd-resolved 管理
  • 集群内部 DNS 解析由 CoreDNS 的 kubernetes 插件处理
  • 两者互不依赖

4.3.6 确认集群完全就绪

bash
# 查看所有系统 Pod 状态
kubectl get pods -n kube-system

# 预期所有 Pod 均为 Running 状态
# NAME                                       READY   STATUS    RESTARTS   AGE
# calico-kube-controllers-69c889dc5f-pr8q8   1/1     Running   0          19m
# calico-node-smchk                          1/1     Running   0          19m
# coredns-674b8bbfcf-46mtw                   1/1     Running   0          39s
# coredns-674b8bbfcf-m8k5f                   1/1     Running   0          39s
# etcd-localhost                             1/1     Running   0          21m
# kube-apiserver-localhost                   1/1     Running   0          21m
# kube-controller-manager-localhost          1/1     Running   0          21m
# kube-proxy-92ftp                           1/1     Running   0          21m
# kube-scheduler-localhost                   1/1     Running   0          21m

# 查看节点状态
kubectl get nodes
# 预期输出:
# NAME      STATUS   ROLES           AGE   VERSION
# node1     Ready    control-plane   21m   v1.33.13

4.4 确认集群状态

bash
# 再次查看节点状态,应变为 Ready
kubectl get nodes

五、加入工作节点(在 node2、node3 执行)

每个工作节点 上,使用 node1 初始化时输出的 kubeadm join 命令。

bash
# 示例命令(请使用您自己的 token 和 hash)
sudo kubeadm join 192.168.0.225:6443 --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:7e5a3f9c2b8d1a6e4f7c3b9a2d5e8f1c4a7b2d9e6f3c8a1b5e7d9f2c4a6b8e0f

回到 node1 节点验证:

bash
kubectl get nodes

预期输出:

NAME    STATUS   ROLES           AGE    VERSION
node1   Ready    control-plane   159m   v1.33.13
node2   Ready    <none>          11m    v1.33.13
node3   Ready    <none>          11m    v1.33.13

六、验证集群

6.1 节点与 Pod 状态检查

查看所有节点详细信息

bash
kubectl get nodes -o wide

预期输出:

NAME    STATUS   ROLES           AGE    VERSION    INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
node1   Ready    control-plane   160m   v1.33.13   192.168.0.225   <none>        Ubuntu 22.04.5 LTS   5.15.0-187-generic   containerd://2.3.3
node2   Ready    <none>          12m    v1.33.13   192.168.0.226   <none>        Ubuntu 22.04.5 LTS   5.15.0-187-generic   containerd://2.3.3
node3   Ready    <none>          12m    v1.33.13   192.168.0.227   <none>        Ubuntu 22.04.5 LTS   5.15.0-187-generic   containerd://2.3.3

查看核心组件 Pod 状态

bash
kubectl get pods -n kube-system

预期输出:

bash
NAME                                       READY   STATUS    RESTARTS      AGE
calico-kube-controllers-69c889dc5f-ndqsx   1/1     Running   1 (71m ago)   161m
calico-node-8xk2q                          1/1     Running   0             14m
calico-node-m7hqf                          1/1     Running   1 (71m ago)   161m
calico-node-zmphq                          1/1     Running   0             14m
coredns-674b8bbfcf-5rq2q                   1/1     Running   1 (71m ago)   162m
coredns-674b8bbfcf-jc5wd                   1/1     Running   1 (71m ago)   162m
etcd-node1                                 1/1     Running   1 (71m ago)   162m
kube-apiserver-node1                       1/1     Running   1 (71m ago)   162m
kube-controller-manager-node1              1/1     Running   1 (71m ago)   162m
kube-proxy-46jmp                           1/1     Running   1 (71m ago)   162m
kube-proxy-9jhw7                           1/1     Running   0             14m
kube-proxy-z4s7k                           1/1     Running   0             14m
kube-scheduler-node1                       1/1     Running   1 (71m ago)   162m

6.2 测试 DNS 解析

bash
# 使用 busybox 测试 CoreDNS 能否解析 Service
kubectl run -it --rm --restart=Never busybox \
  --image=busybox:1.36.1 \
  -- nslookup kubernetes.default

预期输出应包含:

txt
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.96.0.1

6.3 验证 Pod 网络通信

bash
# 创建并暴露一个 Nginx Deployment
kubectl create deployment nginx --image=nginx:1.31.3
kubectl expose deployment nginx --port=80 --type=ClusterIP

# 获取 Service IP
kubectl get svc nginx

# 清理测试资源
kubectl delete deployment nginx
kubectl delete svc nginx

6.4 验证 Dashboard(可选)

如果已在离线包中包含 Dashboard 镜像和配置文件,可以按以下步骤部署:

bash
# 部署 Kubernetes Dashboard
kubectl apply -f /opt/k8s-install/k8s-offline-v1.33.13/manifests/kubernetes-dashboard.yaml

# 等待 Pod 启动
kubectl get pods -n kubernetes-dashboard -w

# 创建管理员用户
cat <<EOF | kubectl apply -f -
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
EOF

# 获取登录 Token
kubectl -n kubernetes-dashboard create token admin-user

附录:常见问题排查

Q1: 节点一直处于 NotReady 状态

原因:网络插件未就绪。

解决

bash
# 检查 Calico Pod 是否正常运行
kubectl get pods -n kube-system | grep calico

# 查看 kubelet 日志
journalctl -u kubelet -f --lines=50

Q2: kubelet 无法启动

原因:containerd 沙箱镜像版本不一致。

解决:检查 /etc/containerd/config.toml 中的 sandbox_image 是否为 registry.k8s.io/pause:3.10(参考 3.2 节)。

Q3: crictl 报错

原因:未正确配置 /etc/crictl.yaml

解决:参考 3.3 节创建配置文件。

Q4: 工作节点加入失败

原因:token 已过期或 CA 证书哈希不匹配。

解决:在 node1 上重新生成 join 命令:

bash
kubeadm token create --print-join-command

Q5: DNS 解析失败

原因:CoreDNS Pod 未正常运行。

解决

bash
# 检查 CoreDNS Pod 状态
kubectl get pods -n kube-system | grep coredns

# 查看 CoreDNS 日志
kubectl logs -n kube-system <coredns-pod-name>

附件:相关资源

资源说明
离线包下载k8s-offline-v1.33.13-20260823-1241.tar.gz
SHA256 校验和k8s-offline-v1.33.13-20260823-1241.tar.gz.sha256

相关文章

📌 第二篇到此结束,所有命令均已在 Ubuntu 22.04 环境下验证通过。请按顺序执行,确保集群部署成功。如有任何问题,欢迎查阅文档中的常见问题章节。

最后更新2026/08/23 17:58
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal