#!/bin/bash set -eo pipefail # 脚本遇到任何错误立即退出,未捕捉的管道错误也退出 # ============================================================================== # 配置区域 # ============================================================================== OFFLINE_ASSETS_DIR="/root/k8s-offline-bundle" K8S_VERSION="v1.28.2" CALICO_VERSION="v3.26.1" KUBEVIRT_VERSION="v1.1.0" NFS_PROVISIONER_VERSION="v4.0.2" # 镜像标签 NFS_CHART_VERSION="4.0.18" # Helm Chart 版本 LOCAL_REGISTRY_IP="192.168.16.5" LOCAL_REGISTRY_PORT="5000" LOCAL_REGISTRY="${LOCAL_REGISTRY_IP}:${LOCAL_REGISTRY_PORT}" K8S_APISERVER_ADVERTISE_ADDRESS="${LOCAL_REGISTRY_IP}" POD_CIDR="192.168.0.0/16" SERVICE_CIDR="10.96.0.0/12" NFS_SERVER="192.168.16.2" NFS_PATH="/d/share/101206" NFS_STORAGE_CLASS_NAME="nfs-client" TEMP_DIR="/tmp/k8s-master-setup" NAMESPACE="default" LOCAL_REGISTRY_IP="192.168.16.5" LOCAL_REGISTRY_PORT="5000" LOCAL_REGISTRY_ADDR="${LOCAL_REGISTRY_IP}:${LOCAL_REGISTRY_PORT}" CONTAINERD_CONFIG="/etc/containerd/config.toml" CERTS_D_PATH="/etc/containerd/certs.d" CALICO_YAML_PATH="$OFFLINE_ASSETS_DIR/manifests/calico.yaml" # 请确认这个路径 CALICO_VERSION="v3.26.1" mkdir -p ${TEMP_DIR} echo "==================================================" echo " Kubernetes 控制节点离线安装脚本 " echo "==================================================" echo "配置参数:" echo " K8s 版本: ${K8S_VERSION}" echo " 本地镜像仓库: ${LOCAL_REGISTRY_ADDR}" echo " K8s API Server IP: ${K8S_APISERVER_ADVERTISE_ADDRESS}" echo " Pod CIDR: ${POD_CIDR}" echo " NFS Server: ${NFS_SERVER}:${NFS_PATH}" echo "--------------------------------------------------" # ============================================================================== # 通用函数 (common.sh 中的内容,为简化,直接内联到这里) # ============================================================================== log_info() { echo -e "\e[32m[INFO] $(date +'%Y-%m-%d %H:%M:%S') $1\e[0m" } log_error() { echo -e "\e[31m[ERROR] $(date +'%Y-%m-%d %H:%M:%S') $1\e[0m" >&2 exit 1 } command_exists() { command -v "$1" >/dev/null 2>&1 } check_root() { if [[ $EUID -ne 0 ]]; then log_error "此脚本必须以 root 用户或使用 sudo 运行。" fi } configure_sysctl() { log_info "配置系统内核参数..." cat < | grep "^\w" | sort -u) # 但目前我们只有你提供的列表,直接全部安装 sudo dpkg -i *.deb || true # 第一次安装可能因为依赖失败,忽略错误 # 再次尝试,确保所有包尽可能安装 sudo dpkg -i *.deb || true # 离线环境apt install -f 无法工作,这里假设所有必要依赖都在debs目录中 # 如果有未满足的依赖,这里会显示错误 log_info "已尝试安装所有DEB包。请检查上述输出是否有未满足的依赖。" cd - > /dev/null # ============================================================================== # 2. 安装 Docker (仅用于本地镜像仓库) # ============================================================================== log_info "安装 Docker daemon (仅用于本地镜像仓库) ..." # 由于你的DEB包列表中没有docker-ce或docker.io的deb包 # 假设docker已经通过其他方式安装,或者这里需要补充下载docker的deb包 # 暂时跳过docker的deb包安装,直接检查docker命令是否存在 if ! command_exists docker; then log_error "未检测到 Docker CLI。请确保已安装 Docker (或其他兼容的容器引擎如Podman)。" log_info "如果在离线环境中,请将 docker-ce 及其依赖的 .deb 包下载并放在 debs 目录中进行安装。" fi # 配置 Docker daemon 以信任本地仓库 (针对非 HTTPS) log_info "配置 Docker daemon 信任本地仓库 ${LOCAL_REGISTRY_ADDR} (针对非 HTTPS)..." sudo mkdir -p /etc/docker cat < /dev/null fi # --- 2. 创建必要的目录结构 --- echo "Creating necessary directory structure under ${CERTS_D_PATH}" sudo mkdir -p "${CERTS_D_PATH}/${LOCAL_REGISTRY_ADDR}" sudo mkdir -p "${CERTS_D_PATH}/registry.k8s.io" # --- 3. 生成 hosts.toml 文件 --- # 为本地 Registry 配置 hosts.toml (http, skip_verify) echo "Creating ${CERTS_D_PATH}/${LOCAL_REGISTRY_ADDR}/hosts.toml" sudo tee "${CERTS_D_PATH}/${LOCAL_REGISTRY_ADDR}/hosts.toml" > /dev/null < /dev/null < /dev/null # ============================================================================== # 7. 导入并推送到本地镜像仓库 (使用 Docker CLI,因为目标是 Docker Registry) # ============================================================================== log_info "导入并推送到本地镜像仓库 (使用 Docker CLI)..." IMAGE_TAR_FILES=$(find "${OFFLINE_ASSETS_DIR}/images" -name "*.tar") echo "### Cleaning up local Docker Registry and containerd storage ###" # 1. 清理本地 Docker Registry (停止并删除容器及数据卷) echo " Stopping and removing local Docker Registry container: ${LOCAL_REGISTRY_ADDR}" # 假设 Registry 容器名为 'registry' sudo docker stop registry || true # 停止容器,如果不存在则忽略错误 sudo docker rm -v registry || true # 删除容器及其匿名数据卷,如果不存在则忽略错误 # 如果你的 Registry 使用了具名数据卷 (named volume),需要额外删除它 # 例如:sudo docker volume rm my-registry-volume || true echo " Restarting a fresh local Docker Registry container." # 重新启动一个干净的 Registry 容器 # 确保你的 Registry 容器名是 'registry',如果不是请修改 sudo docker run -d -p 5000:5000 --restart=always --name registry registry:2 # 稍等片刻,确保 Registry 完全启动 sleep 5 echo " Local Docker Registry is ready." # 2. 清理 containerd 本地存储中的所有镜像 echo " Cleaning up existing images from containerd local storage..." # 获取所有非 且不带 LOCAL_REGISTRY_ADDR 前缀的镜像,以及带 LOCAL_REGISTRY_ADDR 前缀的镜像 # 这里的目的是删除所有可能由之前操作留下的镜像,包括原始的和打了本地标签的。 # 排除掉正在被 kubelet 启动的 pause 镜像等,以防万一 ctr -n "$NAMESPACE" images ls | awk 'NR>1 {print $1}' | while read -r image_ref; do # 避免删除 LOCAL_REGISTRY_ADDR 容器本身(如果它也被导入了) if [[ "$image_ref" == "${LOCAL_REGISTRY_ADDR}/registry:2" || "$image_ref" == "docker.io/library/registry:2" ]]; then echo " Skipping deletion of registry image: $image_ref" continue fi if [[ "$image_ref" == "" ]]; then continue # 跳过 镜像,它们通常是悬空层 fi echo " Deleting containerd image: $image_ref" ctr -n "$NAMESPACE" images rm "$image_ref" || true # || true 避免因为镜像正在被使用而中断 done echo "### Finished cleaning up local environment. ###" IMAGE_DIR=$OFFLINE_ASSETS_DIR/images echo "=== Importing images from $IMAGE_DIR to local registry $LOCAL_REGISTRY_ADDR ===" for tarfile in "$IMAGE_DIR"/*.tar; do [ -e "$tarfile" ] || continue echo "" echo ">>> Processing $tarfile" # 1️⃣ 获取导入前的镜像列表 IMAGES_BEFORE=$(mktemp) # ctr images ls 的第一列就是 REF (镜像名称),使用 awk 提取 if ! ctr -n "$NAMESPACE" images ls | awk 'NR>1 {print $1}' | sort > "$IMAGES_BEFORE"; then echo "❌ Failed to get images list before import." continue fi # Debug: # echo "Images BEFORE import for $tarfile:" # cat "$IMAGES_BEFORE" # 2️⃣ 导入镜像 if ! ctr -n "$NAMESPACE" images import "$tarfile"; then echo "❌ Failed to import image from $tarfile." rm -f "$IMAGES_BEFORE" # 清理临时文件 continue fi # 3️⃣ 获取导入后的镜像列表 IMAGES_AFTER=$(mktemp) if ! ctr -n "$NAMESPACE" images ls | awk 'NR>1 {print $1}' | sort > "$IMAGES_AFTER"; then echo "❌ Failed to get images list after import." rm -f "$IMAGES_BEFORE" # 清理临时文件 continue fi # Debug: # echo "Images AFTER import for $tarfile:" # cat "$IMAGES_AFTER" # echo "Raw difference (comm -13):" # comm -13 "$IMAGES_BEFORE" "$IMAGES_AFTER" # 4️⃣ 找出新增的镜像 (即原始镜像)。排除掉带有本地Registry前缀的镜像本身。 # 过滤条件:排除本地 registry 已存在的镜像,以及 引用。 # 因为导入的 tarfile 可能会包含多个 tag,我们只取第一个符合条件的 ORIGIN_IMG=$(comm -13 "$IMAGES_BEFORE" "$IMAGES_AFTER" | grep -vE "${LOCAL_REGISTRY_ADDR}|" | head -n1) rm -f "$IMAGES_BEFORE" "$IMAGES_AFTER" # 清理临时文件 if [[ -z "$ORIGIN_IMG" ]]; then echo "❌ Failed to detect original image name, skipping..." continue fi echo "Original image: $ORIGIN_IMG" NEW_IMG="" if [[ "$ORIGIN_IMG" == "registry.k8s.io/"* ]]; then if [[ "$ORIGIN_IMG" == "registry.k8s.io/coredns/"* ]]; then NEW_IMG="${LOCAL_REGISTRY_ADDR}/${ORIGIN_IMG#registry.k8s.io/coredns/}" else NEW_IMG="${LOCAL_REGISTRY_ADDR}/${ORIGIN_IMG#registry.k8s.io/}" fi elif [[ "$ORIGIN_IMG" == "ghcr.io/"* ]]; then NEW_IMG="${LOCAL_REGISTRY_ADDR}/${ORIGIN_IMG#ghcr.io/}" elif [[ "$ORIGIN_IMG" == "quay.io/"* ]]; then NEW_IMG="${LOCAL_REGISTRY_ADDR}/${ORIGIN_IMG#quay.io/}" elif [[ "$ORIGIN_IMG" == "nvcr.io/"* ]]; then NEW_IMG="${LOCAL_REGISTRY_ADDR}/${ORIGIN_IMG#nvcr.io/}" elif [[ "$ORIGIN_IMG" == "docker.io/"* ]]; then if [[ "$ORIGIN_IMG" == "docker.io/library/"* ]]; then NEW_IMG="${LOCAL_REGISTRY_ADDR}/${ORIGIN_IMG#docker.io/library/}" else NEW_IMG="${LOCAL_REGISTRY_ADDR}/${ORIGIN_IMG#docker.io/}" fi else echo "Warning: Unknown original registry prefix for $ORIGIN_IMG. Directly prepending LOCAL_REGISTRY_ADDR." NEW_IMG="${LOCAL_REGISTRY_ADDR}/${ORIGIN_IMG}" fi echo "Retag as: $NEW_IMG" # 4️⃣ 打 tag ctr -n "$NAMESPACE" images tag "$ORIGIN_IMG" "$NEW_IMG" # 5️⃣ 推送到本地 registry ctr -n "$NAMESPACE" images push --plain-http "$NEW_IMG" echo "tarfile=$tarfile ORIGIN_IMG=$ORIGIN_IMG NEW_IMG=$NEW_IMG" echo "✅ Done: $NEW_IMG" done log_info "所有镜像已导入并推送到本地镜像仓库。" cd - > /dev/null # ============================================================================== # 8. 初始化 Kubernetes 控制平面 # ============================================================================== log_info "初始化 Kubernetes 控制平面..." # 生成 kubeadm 配置 cat <