一键部署K8s v1.30.14全离线解决方案

1.打包所有k8s离线需要的镜像及RPM包

查看主页资源下载即可

包含所有,拷入服务器无需解压,脚本自会解压

2.编辑脚本

bash 复制代码
vim deploy-k8s.sh

以下内容

bash 复制代码
#!/bin/bash
#===============================================================================
# K8s v1.30.14 All-in-One 离线部署脚本
# 整合所有踩坑修复:
#   1. kubelet drop-in 文件缺失(核心问题)
#   2. 镜像 unpack 问题
#   3. containerd 配置
#   4. CNI 网络配置
#   5. 自动查找并解压离线包
#   6. 兼容 RPM 为空时从 binaries/ 安装
#   7. 【修复】镜像导入不再按文件名筛选,全量导入避免遗漏
#===============================================================================
set -e

# ========================= 配置区 =========================
K8S_VERSION="v1.30.14"
IMAGE_REPO="registry.aliyuncs.com/google_containers"
SERVICE_CIDR="10.96.0.0/12"
POD_CIDR="10.244.0.0/16"
CRI_SOCKET="unix:///var/run/containerd/containerd.sock"
OFFLINE_BASE="/root"
OFFLINE_DIR="/root/k8s-offline-local"
# ==========================================================

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

log_info()  { echo -e "${GREEN}[INFO]${NC}  $(date '+%H:%M:%S') $1"; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC}  $(date '+%H:%M:%S') $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%H:%M:%S') $1"; }
log_step()  {
    echo ""
    echo -e "${CYAN}╔══════════════════════════════════════════════════╗${NC}"
    echo -e "${CYAN}║  $1${NC}"
    echo -e "${CYAN}╚══════════════════════════════════════════════════╝${NC}"
}

#===============================================================================
# 第0步:自动查找并解压离线包
#===============================================================================
log_step "第0步:自动查找并解压离线包"

if [[ -d "$OFFLINE_DIR" && -f "$OFFLINE_DIR/MANIFEST.txt" ]]; then
    log_info "已存在解压目录 $OFFLINE_DIR,跳过解压"
else
    ARCHIVE="$(find "$OFFLINE_BASE" -maxdepth 1 -name 'k8s-offline-local-*.tar.gz' -type f 2>/dev/null | sort -r | head -1)"

    if [[ -z "$ARCHIVE" ]]; then
        log_error "未找到离线包!"
        log_error "请将 k8s-offline-local-*.tar.gz 放到 ${OFFLINE_BASE}/ 下"
        exit 1
    fi

    log_info "找到离线包: $(basename "$ARCHIVE")"
    log_info "文件大小: $(du -h "$ARCHIVE" | awk '{print $1}')"
    log_info "正在解压..."

    tar -xzf "$ARCHIVE" -C "$OFFLINE_BASE"

    EXTRACTED="$(find "$OFFLINE_BASE" -maxdepth 1 -type d -name 'k8s-offline-local*' ! -name '*.tar.gz' 2>/dev/null | sort -r | head -1)"
    if [[ -n "$EXTRACTED" && "$EXTRACTED" != "$OFFLINE_DIR" ]]; then
        rm -rf "$OFFLINE_DIR"
        mv "$EXTRACTED" "$OFFLINE_DIR"
    fi

    if [[ ! -f "$OFFLINE_DIR/MANIFEST.txt" ]]; then
        log_error "解压后未找到 MANIFEST.txt,离线包可能不完整"
        exit 1
    fi

    log_info "解压完成 → $OFFLINE_DIR"
fi

log_info "离线包内容:"
ls -la "$OFFLINE_DIR/"

#===============================================================================
# 第1步:前置检查
#===============================================================================
log_step "第1步:前置检查"

if [[ $EUID -ne 0 ]]; then
    log_error "请以 root 用户运行此脚本"
    exit 1
fi

MASTER_IP="$(hostname -I | awk '{print $1}')"
log_info "主机名: $(hostname)"
log_info "IP地址: $MASTER_IP"
log_info "系  统: $(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)"
log_info "内  核: $(uname -r)"
log_info "CPU:    $(nproc) 核"
log_info "内存:   $(free -h | awk '/Mem:/{print $2}')"

#===============================================================================
# 第2步:系统基础配置
#===============================================================================
log_step "第2步:系统基础配置"

swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab
log_info "swap 已关闭"

setenforce 0 2>/dev/null || true
sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config 2>/dev/null || true
sed -i 's/^SELINUX=permissive/SELINUX=disabled/' /etc/selinux/config 2>/dev/null || true
log_info "SELinux 已关闭"

systemctl stop firewalld 2>/dev/null || true
systemctl disable firewalld 2>/dev/null || true
log_info "防火墙已关闭"

cat > /etc/modules-load.d/k8s.conf <<'EOF'
overlay
br_netfilter
EOF
modprobe overlay 2>/dev/null || true
modprobe br_netfilter 2>/dev/null || true

cat > /etc/sysctl.d/k8s.conf <<'EOF'
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
vm.swappiness                       = 0
EOF
sysctl --system > /dev/null 2>&1
log_info "内核模块 & sysctl 已配置"

timedatectl set-timezone Asia/Shanghai 2>/dev/null || true
log_info "时区已设为 Asia/Shanghai"

#===============================================================================
# 第3步:安装核心组件(RPM 优先,fallback 到 binaries/)
#===============================================================================
log_step "第3步:安装核心组件"

INSTALLED_FROM=""

# 方式1:尝试 RPM 安装
if [[ -d "$OFFLINE_DIR/rpms" ]] && ls "$OFFLINE_DIR/rpms"/*.rpm &>/dev/null; then
    log_info "检测到 RPM 包,使用 rpm 安装..."
    rpm -ivh --force --nodeps "$OFFLINE_DIR/rpms"/*.rpm 2>&1 | tail -5
    INSTALLED_FROM="rpms"
    log_info "RPM 安装完成"
else
    log_warn "RPM 目录为空或不存在,尝试从 binaries/ 安装..."
fi

# 方式2:从 binaries/ 安装(RPM 为空或未成功时的 fallback)
if [[ -d "$OFFLINE_DIR/binaries" ]]; then
    BIN_DIR="$OFFLINE_DIR/binaries"
    log_info "从 $BIN_DIR 安装二进制文件..."

    # kubelet / kubeadm / kubectl → /usr/bin/
    for bin in kubelet kubeadm kubectl; do
        if [[ -f "$BIN_DIR/$bin" ]]; then
            cp -f "$BIN_DIR/$bin" /usr/bin/$bin
            chmod +x /usr/bin/$bin
            log_info "  ✓ $bin → /usr/bin/$bin"
        fi
    done

    # containerd / ctr / crictl / runc → /usr/local/bin/ 或 /usr/bin/
    for bin in containerd ctr crictl runc containerd-shim-runc-v2; do
        if [[ -f "$BIN_DIR/$bin" ]]; then
            cp -f "$BIN_DIR/$bin" /usr/local/bin/$bin
            chmod +x /usr/local/bin/$bin
            log_info "  ✓ $bin → /usr/local/bin/$bin"
        fi
    done

    # 确保 PATH 包含 /usr/local/bin
    export PATH="/usr/local/bin:$PATH"
    echo 'export PATH="/usr/local/bin:$PATH"' >> /etc/profile.d/k8s-path.sh
    chmod +x /etc/profile.d/k8s-path.sh

    INSTALLED_FROM="binaries"
    log_info "二进制安装完成"
fi

# 安装 systemd unit 文件(如果离线包里有)
if [[ -d "$OFFLINE_DIR/systemd" ]]; then
    log_info "安装 systemd unit 文件..."
    find "$OFFLINE_DIR/systemd" -name '*.service' -exec cp -f {} /etc/systemd/system/ \; 2>/dev/null || true
    systemctl daemon-reload
    log_info "systemd unit 文件已安装"
fi

# ★ 验证关键命令 ★
MISSING=""
for cmd in kubelet kubeadm kubectl containerd ctr crictl; do
    if ! command -v "$cmd" &>/dev/null; then
        MISSING="$MISSING $cmd"
    fi
done

if [[ -n "$MISSING" ]]; then
    log_error "以下命令未找到:$MISSING"
    log_error "请检查离线包中 rpms/ 或 binaries/ 是否包含这些文件"
    log_error "当前 binaries/ 内容:"
    ls -la "$OFFLINE_DIR/binaries/" 2>/dev/null || echo "  (目录不存在)"
    exit 1
fi

log_info "安装来源: $INSTALLED_FROM"
log_info "kubelet/kubeadm/kubectl/containerd/ctr/crictl 均已就绪 ✓"

#===============================================================================
# 第4步:安装 CNI plugins
#===============================================================================
log_step "第4步:安装 CNI plugins"

mkdir -p /opt/cni/bin
if [[ -d "$OFFLINE_DIR/cni-plugins" ]] && ls "$OFFLINE_DIR/cni-plugins"/* &>/dev/null; then
    cp -f "$OFFLINE_DIR/cni-plugins"/* /opt/cni/bin/
    chmod +x /opt/cni/bin/*
    log_info "CNI plugins 已安装: $(ls /opt/cni/bin/ | wc -l) 个"
    ls /opt/cni/bin/ | tr '\n' ' '
    echo ""
else
    log_warn "cni-plugins 目录为空,跳过"
fi

#===============================================================================
# 第5步:配置 containerd
#===============================================================================
log_step "第5步:配置 containerd"

mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml

# sandbox 镜像 → 阿里云
sed -i "s|sandbox_image = .*|sandbox_image = \"${IMAGE_REPO}/pause:3.9\"|" /etc/containerd/config.toml

# cgroup driver → systemd
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# snapshotter → overlayfs
sed -i 's/snapshotter = "native"/snapshotter = "overlayfs"/' /etc/containerd/config.toml 2>/dev/null || true

systemctl daemon-reload
systemctl enable containerd
systemctl restart containerd
sleep 3

if systemctl is-active --quiet containerd; then
    log_info "containerd 运行正常 ✓"
    crictl info > /dev/null 2>&1 && log_info "crictl 连接正常 ✓"
else
    log_error "containerd 启动失败!"
    journalctl -u containerd --no-pager -n 20
    exit 1
fi

#===============================================================================
# 第6步:导入容器镜像 ★★★ 修复:全量导入,不再按文件名筛选 ★★★
#===============================================================================
log_step "第6步:导入容器镜像"

IMAGE_DIR="$OFFLINE_DIR/images"
IMPORTED=0
SKIPPED=0
FAILED=0

if [[ -d "$IMAGE_DIR" ]] && ls "$IMAGE_DIR"/*.tar &>/dev/null; then
    for tar_file in "$IMAGE_DIR"/*.tar; do
        filename="$(basename "$tar_file")"

        # 跳过 sha256 digest 命名的重复文件(与 tag 命名的是同一镜像)
        if echo "$filename" | grep -qE "_sha256_"; then
            SKIPPED=$((SKIPPED + 1))
            continue
        fi

        log_info "导入: $filename"
        if ctr -n k8s.io images import "$tar_file" > /dev/null 2>&1; then
            IMPORTED=$((IMPORTED + 1))
        else
            log_warn "导入失败: $filename"
            FAILED=$((FAILED + 1))
        fi
    done
else
    log_warn "images 目录为空或不存在"
fi

log_info "导入完成: 成功=$IMPORTED  跳过=$SKIPPED  失败=$FAILED"
log_info "当前 k8s 镜像列表:"
ctr -n k8s.io images ls -q 2>/dev/null | grep -v "^sha256:" | sort | while read -r img; do
    echo "    $img"
done

#===============================================================================
# 第7步:创建 kubelet drop-in ★★★ 核心修复 ★★★
#===============================================================================
log_step "第7步:创建 kubelet drop-in(核心修复)"

mkdir -p /etc/systemd/system/kubelet.service.d

cat > /etc/systemd/system/kubelet.service.d/10-kubeadm.conf <<'EOF'
# Note: This dropin only works with kubeadm and kubelet v1.11+
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
EnvironmentFile=-/etc/sysconfig/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
EOF

systemctl daemon-reload
systemctl enable kubelet
log_info "kubelet drop-in 已创建 ✓"

#===============================================================================
# 第8步:清理旧状态(支持重复执行)
#===============================================================================
log_step "第8步:清理旧状态"

if kubectl get nodes &>/dev/null 2>&1; then
    log_warn "检测到已有集群,执行 kubeadm reset..."
fi

kubeadm reset -f --cri-socket "$CRI_SOCKET" 2>/dev/null || true
rm -rf /var/lib/etcd/*
rm -rf /etc/kubernetes/manifests
rm -rf /etc/kubernetes/pki
rm -rf /var/lib/kubelet/config.yaml
rm -rf /var/lib/kubelet/kubeadm-flags.env
rm -rf /etc/cni/net.d/*
rm -rf "$HOME/.kube"

echo 1 > /proc/sys/net/ipv4/ip_forward

systemctl restart containerd
sleep 2
systemctl restart kubelet 2>/dev/null || true
sleep 3

log_info "清理完成 ✓"

#===============================================================================
# 第9步:kubeadm init
#===============================================================================
log_step "第9步:kubeadm init"

log_info "参数:"
echo "    K8s版本:      $K8S_VERSION"
echo "    镜像仓库:     $IMAGE_REPO"
echo "    Service CIDR: $SERVICE_CIDR"
echo "    Pod CIDR:     $POD_CIDR"
echo "    CRI Socket:   $CRI_SOCKET"
echo ""

kubeadm init \
    --kubernetes-version="${K8S_VERSION}" \
    --image-repository="${IMAGE_REPO}" \
    --service-cidr="${SERVICE_CIDR}" \
    --pod-network-cidr="${POD_CIDR}" \
    --cri-socket="${CRI_SOCKET}" \
    --ignore-preflight-errors=all \
    2>&1 | tee /tmp/kubeadm-init.log

if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    log_error "kubeadm init 失败!查看日志: /tmp/kubeadm-init.log"
    exit 1
fi

log_info "kubeadm init 成功 ✓"

#===============================================================================
# 第10步:配置 kubectl
#===============================================================================
log_step "第10步:配置 kubectl"

mkdir -p "$HOME/.kube"
cp -f /etc/kubernetes/admin.conf "$HOME/.kube/config"
chown "$(id -u):$(id -g)" "$HOME/.kube/config"
export KUBECONFIG=/etc/kubernetes/admin.conf

cat > /etc/profile.d/kubectl.sh <<'EOF'
export KUBECONFIG=/etc/kubernetes/admin.conf
alias k='kubectl'
source <(kubectl completion bash) 2>/dev/null || true
complete -o default -F __start_kubectl k 2>/dev/null || true
EOF
chmod +x /etc/profile.d/kubectl.sh

log_info "kubectl 配置完成 ✓"

#===============================================================================
# 第11步:部署 Flannel 网络 ★★★ 修复:移除冗余的单镜像导入 ★★★
#===============================================================================
#===============================================================================
# 第11步:部署 Calico 网络
#===============================================================================
log_step "第11步:部署 Calico 网络"

CALICO_YAML="$OFFLINE_DIR/configs/calico.yaml"

if [[ -f "$CALICO_YAML" ]]; then
    log_info "找到 Calico YAML: $CALICO_YAML"

    log_info "执行 kubectl apply..."
    kubectl apply -f "$CALICO_YAML"

    log_info "等待 Calico & CoreDNS 就绪(最多 180 秒)..."
    WAIT=0
    MAX_WAIT=180
    while [[ $WAIT -lt $MAX_WAIT ]]; do
        READY="$(kubectl get pods -A --field-selector=status.phase=Running 2>/dev/null | grep -cE '(coredns|calico)' || true )"
        TOTAL="$(kubectl get pods -A 2>/dev/null | grep -cE '(coredns|calico)' || true )"
        if [[ "$READY" -ge 3 && "$READY" == "$TOTAL" ]]; then
            log_info "Calico & CoreDNS 全部就绪 ✓ (${WAIT}s)"
            break
        fi
        sleep 5
        WAIT=$((WAIT + 5))
        printf "\r    已等待 %ds ... (Running: %s/%s)" "$WAIT" "$READY" "$TOTAL"
    done
    echo ""

    if [[ $WAIT -ge $MAX_WAIT ]]; then
        log_warn "超时!部分 Pod 可能仍在启动中,请手动检查"
    fi
else
    log_warn "未找到 $CALICO_YAML,CoreDNS 将无法启动"
fi
#===============================================================================
# 第12步:验证集群
#===============================================================================
log_step "第12步:验证集群"

echo ""
log_info "--- 节点状态 ---"
kubectl get nodes -o wide 2>/dev/null || true

echo ""
log_info "--- 全部 Pod ---"
kubectl get pods -A -o wide 2>/dev/null || true

echo ""
log_info "--- 组件健康 ---"
kubectl get componentstatuses 2>/dev/null || true

echo ""
log_info "--- 集群信息 ---"
kubectl cluster-info 2>/dev/null || true

echo ""
log_info "--- API Server 连通性 ---"
kubectl get --raw /healthz 2>/dev/null && log_info "API Server 健康 ✓" || log_warn "API Server 异常"

#===============================================================================
# 🎉 完成
#===============================================================================
log_step "🎉 K8s $K8S_VERSION 部署完成"

JOIN_CMD="$(kubeadm token create --print-join-command 2>/dev/null || echo "")"

cat <<EOF

  ┌─────────────────────────────────────────────┐
  │          部 署 信 息 汇 总                    │
  ├─────────────────────────────────────────────┤
  │  K8s 版本:     $K8S_VERSION
  │  Master IP:    $MASTER_IP
  │  Pod CIDR:     $POD_CIDR
  │  Service CIDR: $SERVICE_CIDR
  │  网络插件:     Flannel (VXLAN)
  │  容器运行时:   containerd
  ├─────────────────────────────────────────────┤
  │  kubectl 使用:
  │    export KUBECONFIG=/etc/kubernetes/admin.conf
  │    kubectl get nodes -o wide
  │    kubectl get pods -A
  ├─────────────────────────────────────────────┤
  │  添加 Worker 节点:
  │    $JOIN_CMD
  └─────────────────────────────────────────────┘

EOF

echo "============================================================"
log_info "部署日志已保存至 /tmp/kubeadm-init.log"
echo "============================================================"

3.执行脚本

复制代码
bash deploy-k8s.sh
相关推荐
XR风云1 小时前
docker buildx 忽略容器镜像服务的证书校验的方法
运维·docker·容器
circuitsosk3 小时前
不止于API调用:大模型推理加速与云原生服务化部署指南
python·云原生·agent·vllm·推理加速·大模型部署·ensorrt-llm
摇滚侠3 小时前
云原生 Java 架构师的第一课 K8s+Docker+KubeSphere+DevOps 89-129
java·云原生·kubernetes
雨辰AI4 小时前
人大金仓 K8s 密评合规加固|国密全链路加密 + 权限收敛容器化落地(三级密评直通版)
java·云原生·容器·kubernetes·政务
冷莫溪13 小时前
Kubernetes——k8s v1.24离线部署(Containerd + Calico)
云原生·容器·kubernetes
崔亮的博客16 小时前
K8S GPU 调度——Operator 自动管理GPU
云原生·容器·kubernetes
小义_17 小时前
K8s 全网高频常用命令大全
docker·容器·kubernetes
雨辰AI1 天前
K8s 国产数据库慢 SQL 自动监控|Prometheus+Grafana 可视化全落地
数据库·sql·安全·容器·kubernetes·grafana·prometheus
GGG7661 天前
k8s的 Node and Cluster实战
容器·kubernetes