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

适用场景与前置条件
| 项目 | 说明 |
|---|---|
| 适用场景 | 在内网或完全断网的环境中,使用第一篇制作的离线包部署 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 连接异常。正确的做法是:
- 保留默认网关 —— 确保集群内部通信正常
- 使用 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 地址 | 配置建议 |
|---|---|---|---|
| 控制平面 | node1 | 192.168.0.225 | 2C/4G+ |
| 工作节点 | node2 | 192.168.0.226 | 2C/4G+ |
| 工作节点 | node3 | 192.168.0.227 | 2C/4G+ |
请根据您的实际网络环境修改上述 IP 地址。
1.2 配置主机名(所有节点)
bash
# 设置主机名(根据角色执行)
sudo hostnamectl set-hostname node1 # 在 node1 上执行
sudo hostnamectl set-hostname node2 # 在 node2 上执行
sudo hostnamectl set-hostname node3 # 在 node3 上执行
# 验证
hostname1.3 配置静态 IP(所有节点)
- 查看网卡名称:
bash
ip a- 编辑 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- 应用配置:
bash
sudo netplan apply1.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 21.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 cgroupcgroups 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 -l2.3 执行安装脚本(所有节点)
运行 install.sh 安装所有 deb 包并导入镜像。
bash
# 执行离线安装脚本
sudo ./install.sh该脚本会自动完成以下任务:
- 安装 Kubernetes 组件(
kubeadm、kubelet、kubectl) - 安装
containerd运行时 - 加载
k8s-images.tar、calico-images.tar等镜像文件 - 锁定组件版本,防止自动更新
三、配置 containerd
3.1 生成默认配置文件
bash
# 生成默认配置
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml3.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 containerd3.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
EOF3.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 2m4.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.14.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.134.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) 162m6.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.16.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 nginx6.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=50Q2: 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-commandQ5: 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 环境下验证通过。请按顺序执行,确保集群部署成功。如有任何问题,欢迎查阅文档中的常见问题章节。
