离线单节点部署

Kubernetes 离线单节点部署

本章介绍如何在无互联网连接的环境中部署单节点 Kubernetes 集群,适用于内网开发和测试环境。

部署概述

离线单节点部署需要提前在联网环境准备好所有必需的安装包,然后在目标服务器上进行离线安装。

流程图

联网环境(准备)          目标服务器(部署)
┌─────────────┐           ┌─────────────┐
│ 下载二进制   │           │ 传输离线包   │
│ 导出镜像     │  ──────>  │ 安装组件     │
│ 打包资源     │           │ 初始化集群   │
└─────────────┘           └─────────────┘

特点

  • ✅ 不依赖外网
  • ✅ 部署速度快
  • ✅ 版本可控
  • ✅ 适合内网环境
  • ❌ 准备工作较复杂

适用场景

  • 🔒 内网隔离的开发环境
  • 🧪 安全测试环境
  • 📚 培训和演示(无网络)

一、环境要求

1.1 硬件要求

配置级别 CPU 内存 磁盘 适用场景
最低配置 2 核 4GB 30GB 学习
推荐配置 4 核 8GB 60GB 开发
高配 8 核 16GB 120GB 测试

1.2 软件要求

  • 操作系统:Ubuntu 22.04 / CentOS 8+ / RHEL 8+
  • 内核版本:>= 4.19
  • Python:>= 3.6(用于运行部署脚本)

二、准备离线资源包(联网环境)

2.1 创建工作目录

mkdir -p ~/k8s-offline/{images,packages,scripts,configs}
cd ~/k8s-offline

2.2 设置版本变量

# 定义版本号
export K8S_VERSION=1.30.0
export CONTAINERD_VERSION=1.7.11
export RUNC_VERSION=1.1.10
export CNI_VERSION=1.4.0
export CRICTL_VERSION=1.30.0
export CALICO_VERSION=3.27.0

# 保存版本信息
cat <<EOF > versions.txt
K8S_VERSION=$K8S_VERSION
CONTAINERD_VERSION=$CONTAINERD_VERSION
RUNC_VERSION=$RUNC_VERSION
CNI_VERSION=$CNI_VERSION
CRICTL_VERSION=$CRICTL_VERSION
CALICO_VERSION=$CALICO_VERSION
EOF

2.3 下载二进制文件

cd packages

echo "下载 Kubernetes 组件..."
wget https://dl.k8s.io/release/v${K8S_VERSION}/bin/linux/amd64/kubeadm
wget https://dl.k8s.io/release/v${K8S_VERSION}/bin/linux/amd64/kubelet
wget https://dl.k8s.io/release/v${K8S_VERSION}/bin/linux/amd64/kubectl

echo "下载 containerd..."
wget https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/containerd-${CONTAINERD_VERSION}-linux-amd64.tar.gz

echo "下载 runc..."
wget https://github.com/opencontainers/runc/releases/download/v${RUNC_VERSION}/runc.amd64

echo "下载 CNI plugins..."
wget https://github.com/containernetworking/plugins/releases/download/v${CNI_VERSION}/cni-plugins-linux-amd64-v${CNI_VERSION}.tgz

echo "下载 crictl..."
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v${CRICTL_VERSION}/crictl-v${CRICTL_VERSION}-linux-amd64.tar.gz

echo "下载 systemd 服务文件..."
wget -O containerd.service https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
wget -O kubelet.service https://raw.githubusercontent.com/kubernetes/release/master/cmd/krel/templates/latest/kubelet/kubelet.service
wget -O 10-kubeadm.conf https://raw.githubusercontent.com/kubernetes/release/master/cmd/krel/templates/latest/kubeadm/10-kubeadm.conf

cd ..
echo "✅ 二进制文件下载完成"

2.4 导出容器镜像

cd images

# 生成镜像列表
echo "生成镜像列表..."
kubeadm config images list --kubernetes-version v${K8S_VERSION} > image-list.txt

# 添加网络插件镜像
cat <<EOF >> image-list.txt
docker.io/calico/cni:v${CALICO_VERSION}
docker.io/calico/node:v${CALICO_VERSION}
docker.io/calico/kube-controllers:v${CALICO_VERSION}
quay.io/tigera/operator:v1.32.0
EOF

echo "镜像列表:"
cat image-list.txt

# 拉取并导出镜像
echo "开始拉取并导出镜像..."
while IFS= read -r image; do
    echo "处理镜像: $image"
    
    sudo crictl pull "$image" || {
        echo "警告: $image 拉取失败,跳过"
        continue
    }
    
    image_file=$(echo "$image" | tr '/:' '_').tar
    sudo ctr -n k8s.io image export "$image_file" "$image" || {
        echo "警告: $image 导出失败"
        continue
    }
    
    echo "✅ $image 已导出"
done < image-list.txt

cd ..
echo "✅ 镜像导出完成"
ls -lh images/*.tar

2.5 下载网络插件配置

cd configs

echo "下载 Calico 配置文件..."
wget https://raw.githubusercontent.com/projectcalico/calico/v${CALICO_VERSION}/manifests/calico.yaml

cd ..

2.6 创建安装脚本

系统准备脚本

cat <<'EOF' > scripts/prepare-system.sh
#!/bin/bash
# 系统准备脚本

set -e

GREEN='\033[0;32m'
NC='\033[0m'

echo -e "${GREEN}[1/6] 禁用 swap...${NC}"
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

echo -e "${GREEN}[2/6] 加载内核模块...${NC}"
cat <<EOFMOD | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOFMOD

modprobe overlay
modprobe br_netfilter

echo -e "${GREEN}[3/6] 配置内核参数...${NC}"
cat <<EOFSYS | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOFSYS

sysctl --system > /dev/null

echo -e "${GREEN}[4/6] 关闭防火墙...${NC}"
if command -v ufw &> /dev/null; then
    ufw disable || true
elif command -v systemctl &> /dev/null; then
    systemctl stop firewalld || true
    systemctl disable firewalld || true
fi

echo -e "${GREEN}[5/6] 禁用 SELinux...${NC}"
if [ -f /etc/selinux/config ]; then
    setenforce 0 || true
    sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
fi

echo -e "${GREEN}[6/6] 验证配置...${NC}"
lsmod | grep br_netfilter
lsmod | grep overlay

echo -e "${GREEN}✅ 系统准备完成${NC}"
EOF

chmod +x scripts/prepare-system.sh

安装 containerd 脚本

cat <<'EOF' > scripts/install-containerd.sh
#!/bin/bash
# 安装 containerd

set -e

WORK_DIR=/root/k8s-offline
GREEN='\033[0;32m'
NC='\033[0m'

echo -e "${GREEN}[1/7] 解压 containerd...${NC}"
tar Cxzvf /usr/local "${WORK_DIR}/packages/containerd-"*.tar.gz

echo -e "${GREEN}[2/7] 安装 runc...${NC}"
install -m 755 "${WORK_DIR}/packages/runc.amd64" /usr/local/sbin/runc

echo -e "${GREEN}[3/7] 安装 CNI plugins...${NC}"
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin "${WORK_DIR}/packages/cni-plugins-linux-amd64-"*.tgz

echo -e "${GREEN}[4/7] 安装 crictl...${NC}"
tar -xzvf "${WORK_DIR}/packages/crictl-"*.tar.gz -C /usr/local/bin/

echo -e "${GREEN}[5/7] 创建 containerd 配置...${NC}"
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml > /dev/null

echo -e "${GREEN}[6/7] 配置 systemd cgroup 驱动...${NC}"
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

echo -e "${GREEN}[7/7] 启动 containerd...${NC}"
cp "${WORK_DIR}/packages/containerd.service" /etc/systemd/system/
systemctl daemon-reload
systemctl enable containerd
systemctl start containerd

echo -e "${GREEN}✅ containerd 安装完成${NC}"
systemctl status containerd --no-pager
EOF

chmod +x scripts/install-containerd.sh

安装 Kubernetes 组件脚本

cat <<'EOF' > scripts/install-k8s.sh
#!/bin/bash
# 安装 Kubernetes 组件

set -e

WORK_DIR=/root/k8s-offline
GREEN='\033[0;32m'
NC='\033[0m'

echo -e "${GREEN}[1/4] 安装 kubeadm、kubelet、kubectl...${NC}"
install -m 755 "${WORK_DIR}/packages/kubeadm" /usr/local/bin/
install -m 755 "${WORK_DIR}/packages/kubelet" /usr/local/bin/
install -m 755 "${WORK_DIR}/packages/kubectl" /usr/local/bin/

echo -e "${GREEN}[2/4] 创建 kubelet 目录...${NC}"
mkdir -p /etc/systemd/system/kubelet.service.d
mkdir -p /var/lib/kubelet

echo -e "${GREEN}[3/4] 安装 systemd 服务...${NC}"
cp "${WORK_DIR}/packages/kubelet.service" /etc/systemd/system/
cp "${WORK_DIR}/packages/10-kubeadm.conf" /etc/systemd/system/kubelet.service.d/

# 修复路径
sed -i 's|/usr/bin|/usr/local/bin|g' /etc/systemd/system/kubelet.service
sed -i 's|/usr/bin|/usr/local/bin|g' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

echo -e "${GREEN}[4/4] 启动 kubelet...${NC}"
systemctl daemon-reload
systemctl enable kubelet

echo -e "${GREEN}✅ Kubernetes 组件安装完成${NC}"
kubeadm version
kubelet --version
kubectl version --client
EOF

chmod +x scripts/install-k8s.sh

导入镜像脚本

cat <<'EOF' > scripts/load-images.sh
#!/bin/bash
# 导入容器镜像

set -e

WORK_DIR=/root/k8s-offline
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

cd "${WORK_DIR}/images"

echo -e "${GREEN}开始导入镜像...${NC}"
total=$(ls -1 *.tar 2>/dev/null | wc -l)
count=0

for tar_file in *.tar; do
    count=$((count + 1))
    echo -e "${YELLOW}[$count/$total] 导入 $tar_file...${NC}"
    ctr -n k8s.io image import "$tar_file" || {
        echo "警告: $tar_file 导入失败"
    }
done

echo -e "${GREEN}✅ 所有镜像已导入${NC}"
echo "已导入的镜像列表:"
crictl images
EOF

chmod +x scripts/load-images.sh

一键部署脚本

cat <<'EOF' > scripts/deploy-single.sh
#!/bin/bash
# 单节点离线部署一键脚本

set -e

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
echo_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
echo_error() { echo -e "${RED}[ERROR]${NC} $1"; }

# 检查 root 权限
if [ "$EUID" -ne 0 ]; then
    echo_error "请使用 root 用户运行此脚本"
    exit 1
fi

echo ""
echo "==================================="
echo "  Kubernetes 单节点离线部署"
echo "==================================="
echo ""

read -p "请输入节点 IP 地址: " NODE_IP
if [ -z "$NODE_IP" ]; then
    echo_error "IP 地址不能为空"
    exit 1
fi

read -p "请输入节点主机名 [k8s-single]: " HOSTNAME
HOSTNAME=${HOSTNAME:-k8s-single}

echo ""
echo_info "配置信息:"
echo_info "  节点 IP: $NODE_IP"
echo_info "  主机名: $HOSTNAME"
echo ""
read -p "确认无误?(y/n): " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    exit 0
fi

WORK_DIR=/root/k8s-offline
cd "$WORK_DIR"

# 步骤 1: 系统准备
echo ""
echo_info "====== 步骤 1/6: 系统准备 ======"
./scripts/prepare-system.sh

# 设置主机名
hostnamectl set-hostname $HOSTNAME
echo "127.0.0.1 $HOSTNAME" >> /etc/hosts

# 步骤 2: 安装 containerd
echo ""
echo_info "====== 步骤 2/6: 安装 containerd ======"
./scripts/install-containerd.sh

# 步骤 3: 导入镜像
echo ""
echo_info "====== 步骤 3/6: 导入容器镜像 ======"
./scripts/load-images.sh

# 步骤 4: 安装 Kubernetes 组件
echo ""
echo_info "====== 步骤 4/6: 安装 Kubernetes 组件 ======"
./scripts/install-k8s.sh

# 步骤 5: 初始化集群
echo ""
echo_info "====== 步骤 5/6: 初始化集群 ======"

kubeadm init \
  --kubernetes-version=v1.30.0 \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=$NODE_IP | tee /tmp/kubeadm-init.log

# 配置 kubectl
mkdir -p /root/.kube
cp -i /etc/kubernetes/admin.conf /root/.kube/config

# 移除 Master 污点
kubectl taint nodes --all node-role.kubernetes.io/control-plane-

# 步骤 6: 安装网络插件
echo ""
echo_info "====== 步骤 6/6: 安装网络插件 ======"
kubectl apply -f configs/calico.yaml

echo ""
echo_info "========================================="
echo_info "        单节点部署完成!"
echo_info "========================================="
echo ""
echo_info "集群信息:"
echo_info "  kubectl get nodes"
echo ""
kubectl get nodes
echo ""
echo_info "系统 Pod 状态:"
kubectl get pods -n kube-system
echo ""
echo_info "部署完成!请等待所有 Pod 启动(约 2-3 分钟)"
EOF

chmod +x scripts/deploy-single.sh

2.7 打包离线资源

cd ~

# 创建 README
cat <<EOF > k8s-offline/README.md
# Kubernetes ${K8S_VERSION} 离线单节点部署包

## 版本信息
$(cat k8s-offline/versions.txt)

## 目录结构
- images/    - 容器镜像文件
- packages/  - 二进制文件
- scripts/   - 安装脚本
- configs/   - 配置文件

## 快速部署
\`\`\`bash
tar -xzf k8s-offline-single-v${K8S_VERSION}.tar.gz
cd k8s-offline
sudo ./scripts/deploy-single.sh
\`\`\`

## 手动部署
\`\`\`bash
sudo ./scripts/prepare-system.sh
sudo ./scripts/install-containerd.sh
sudo ./scripts/load-images.sh
sudo ./scripts/install-k8s.sh
# 然后手动初始化集群
\`\`\`
EOF

# 打包
echo "正在打包离线资源..."
tar -czf k8s-offline-single-v${K8S_VERSION}.tar.gz k8s-offline/

# 生成校验和
md5sum k8s-offline-single-v${K8S_VERSION}.tar.gz > k8s-offline-single-v${K8S_VERSION}.tar.gz.md5
sha256sum k8s-offline-single-v${K8S_VERSION}.tar.gz > k8s-offline-single-v${K8S_VERSION}.tar.gz.sha256

echo ""
echo "✅ 离线包制作完成!"
echo ""
ls -lh k8s-offline-single-v${K8S_VERSION}.tar.gz*
echo ""
echo "MD5: $(cat k8s-offline-single-v${K8S_VERSION}.tar.gz.md5)"

三、离线部署(目标服务器)

3.1 传输离线包

# 方式1: USB 存储
# 直接复制到 U 盘

# 方式2: 内网 SCP
scp k8s-offline-single-v1.30.0.tar.gz root@192.168.1.10:/root/

# 方式3: NFS 共享
# mount -t nfs 192.168.1.100:/share /mnt
# cp /mnt/k8s-offline-single-v1.30.0.tar.gz /root/

3.2 验证完整性

cd /root

# MD5 校验
md5sum -c k8s-offline-single-v1.30.0.tar.gz.md5

# SHA256 校验
sha256sum -c k8s-offline-single-v1.30.0.tar.gz.sha256

# 解压
tar -xzf k8s-offline-single-v1.30.0.tar.gz
cd k8s-offline

3.3 一键部署

# 执行一键部署脚本
sudo ./scripts/deploy-single.sh

# 脚本会提示输入:
# 1. 节点 IP 地址
# 2. 节点主机名(可选)
# 3. 自动执行所有安装步骤

部署过程输出

===================================
  Kubernetes 单节点离线部署
===================================

[INFO] ====== 步骤 1/6: 系统准备 ======
[INFO] [1/6] 禁用 swap...
[INFO] [2/6] 加载内核模块...
...

[INFO] ====== 步骤 2/6: 安装 containerd ======
[INFO] [1/7] 解压 containerd...
...

[INFO] ====== 步骤 3/6: 导入容器镜像 ======
[INFO] 开始导入镜像...
[1/10] 导入 registry.k8s.io_kube-apiserver_v1.30.0.tar...
...

[INFO] ====== 步骤 4/6: 安装 Kubernetes 组件 ======
[INFO] [1/4] 安装 kubeadm、kubelet、kubectl...
...

[INFO] ====== 步骤 5/6: 初始化集群 ======
[init] Using Kubernetes version: v1.30.0
...

[INFO] ====== 步骤 6/6: 安装网络插件 ======
daemonset.apps/calico-node created
deployment.apps/calico-kube-controllers created

[INFO] =========================================
[INFO]        单节点部署完成!
[INFO] =========================================

3.4 手动部署(可选)

如果需要更细粒度的控制:

# 步骤 1: 系统准备
sudo ./scripts/prepare-system.sh

# 步骤 2: 安装 containerd
sudo ./scripts/install-containerd.sh

# 步骤 3: 导入镜像
sudo ./scripts/load-images.sh

# 步骤 4: 安装 Kubernetes 组件
sudo ./scripts/install-k8s.sh

# 步骤 5: 初始化集群
sudo kubeadm init \
  --kubernetes-version=v1.30.0 \
  --pod-network-cidr=10.244.0.0/16

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

# 移除 Master 污点
kubectl taint nodes --all node-role.kubernetes.io/control-plane-

# 步骤 6: 安装网络插件
kubectl apply -f configs/calico.yaml

四、验证部署

4.1 检查节点状态

# 查看节点
kubectl get nodes

# 输出示例
NAME         STATUS   ROLES           AGE   VERSION
k8s-single   Ready    control-plane   5m    v1.30.0

4.2 检查系统 Pod

# 查看所有 Pod
kubectl get pods --all-namespaces

# 等待所有 Pod Running
kubectl wait --for=condition=Ready pods --all -n kube-system --timeout=300s

4.3 部署测试应用

# 创建 Deployment
kubectl create deployment nginx --image=nginx:1.25 --replicas=2

# 查看 Pod
kubectl get pods -o wide

# 暴露服务
kubectl expose deployment nginx --port=80 --type=NodePort

# 测试访问
kubectl get svc nginx
curl http://192.168.1.10:<node-port>

# 清理
kubectl delete deployment nginx
kubectl delete service nginx

五、常见问题排查

5.1 镜像导入失败

# 检查镜像文件
ls -lh images/*.tar

# 手动导入单个镜像
sudo ctr -n k8s.io image import images/<image-file>.tar

# 查看已导入的镜像
crictl images

5.2 containerd 启动失败

# 查看日志
journalctl -u containerd -f

# 检查配置
cat /etc/containerd/config.toml

# 重新生成配置
sudo rm /etc/containerd/config.toml
containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd

5.3 Pod 一直 Pending

# 检查节点状态
kubectl describe node

# 检查污点
kubectl describe node | grep Taints

# 确认已移除污点
kubectl taint nodes --all node-role.kubernetes.io/control-plane-

六、离线升级

6.1 准备新版本离线包

在联网环境重复第二章步骤,使用新版本号。

6.2 升级流程

# 1. 传输新版本离线包
scp k8s-offline-single-v1.30.1.tar.gz root@server:/root/

# 2. 解压
tar -xzf k8s-offline-single-v1.30.1.tar.gz
cd k8s-offline

# 3. 导入新镜像
sudo ./scripts/load-images.sh

# 4. 升级 kubeadm
sudo install -m 755 packages/kubeadm /usr/local/bin/

# 5. 查看升级计划
sudo kubeadm upgrade plan

# 6. 执行升级
sudo kubeadm upgrade apply v1.30.1

# 7. 升级 kubelet 和 kubectl
sudo install -m 755 packages/kubelet /usr/local/bin/
sudo install -m 755 packages/kubectl /usr/local/bin/
sudo systemctl daemon-reload
sudo systemctl restart kubelet

# 8. 验证
kubectl get nodes

七、维护管理

7.1 备份离线包

# 保存多个版本
/backup/
├── k8s-offline-single-v1.30.0.tar.gz
├── k8s-offline-single-v1.30.1.tar.gz
└── k8s-offline-single-v1.31.0.tar.gz

7.2 备份集群配置

# 备份 kubectl 配置
cp ~/.kube/config ~/.kube/config.backup

# 备份所有资源
kubectl get all --all-namespaces -o yaml > cluster-backup.yaml

# 备份 etcd(重要)
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

7.3 重置集群

# 重置 kubeadm
sudo kubeadm reset

# 清理配置
sudo rm -rf /etc/cni/net.d
sudo rm -rf ~/.kube/config

# 清理 iptables
sudo iptables -F && sudo iptables -t nat -F && sudo iptables -X

八、最佳实践

8.1 离线包管理

  • ✅ 使用版本号命名离线包
  • ✅ 保留多个历史版本
  • ✅ 定期更新离线包
  • ✅ 验证校验和确保完整性
  • ✅ 制作详细的部署文档

8.2 部署规范

  • ✅ 测试离线包后再用于生产
  • ✅ 记录部署日志
  • ✅ 制定回滚计划
  • ✅ 保持版本一致性

8.3 安全建议

  • ✅ 离线包加密存储
  • ✅ 限制访问权限
  • ✅ 审计部署操作
  • ✅ 定期安全扫描

九、故障恢复

9.1 快速恢复脚本

cat <<'EOF' > /usr/local/bin/k8s-recover.sh
#!/bin/bash
# Kubernetes 快速恢复脚本

set -e

echo "开始恢复 Kubernetes 集群..."

# 1. 检查服务状态
systemctl restart containerd
systemctl restart kubelet

# 2. 等待服务启动
sleep 10

# 3. 检查集群状态
kubectl get nodes
kubectl get pods -A

echo "✅ 恢复完成"
EOF

chmod +x /usr/local/bin/k8s-recover.sh

小结

本章介绍了离线单节点部署:

完整离线包:包含所有必需组件
一键部署:30 分钟完成部署
不依赖网络:适合内网环境
版本可控:稳定可靠
易于维护:脚本化管理
支持升级:离线升级流程

准备时间:1-2 小时(联网环境)
部署时间:30 分钟(目标服务器)
适用场景:内网开发、测试环境

下一步