《离线部署 Kubernetes 全攻略:适配麒麟 V10、CentOS 7/8/9、Ubuntu》证书100年版本!!!
·
1.脚本及tar包在同目录操作----
1.1 .主页资源下载离线tar包至服务器
2.
vim deploy-k8s.sh
#!/bin/bash
#===============================================================================
# K8s v1.30.14 All-in-One 离线部署脚本 (证书100年版)
#===============================================================================
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"
CNI_MODE="${CNI_MODE:-auto}"
# ==========================================================
# ========================= 颜色定义 =========================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
DIM='\033[2m'
BOLD='\033[1m'
NC='\033[0m'
# ========================= 日志 & 装饰函数 =========================
log_info() {
printf "\r\033[K"
echo -e "${GREEN}${BOLD}[INFO]${NC} ${DIM}$(date '+%H:%M:%S')${NC} $1"
}
log_warn() {
printf "\r\033[K"
echo -e "${YELLOW}${BOLD}[WARN]${NC} ${DIM}$(date '+%H:%M:%S')${NC} $1"
}
log_error() {
printf "\r\033[K"
echo -e "${RED}${BOLD}[ERROR]${NC} ${DIM}$(date '+%H:%M:%S')${NC} $1"
}
# ★ 炫彩标题框(自适应宽度)★
log_step() {
local title="$1"
local term_cols
term_cols=$(tput cols 2>/dev/null || echo 80)
local width=$((term_cols - 4))
[[ $width -gt 54 ]] && width=54
[[ $width -lt 40 ]] && width=40
local inner=$((width - 2))
local tlen=${#title}
local lpad=$(( (inner - tlen) / 2 ))
[[ $lpad -lt 1 ]] && lpad=1
local rpad=$((inner - tlen - lpad))
[[ $rpad -lt 1 ]] && rpad=1
local lp="" rp=""
for ((i=0; i<lpad; i++)); do lp+=" "; done
for ((i=0; i<rpad; i++)); do rp+=" "; done
local -a rainbow=(
'\033[38;5;196m' '\033[38;5;202m' '\033[38;5;208m' '\033[38;5;214m'
'\033[38;5;220m' '\033[38;5;226m' '\033[38;5;190m' '\033[38;5;154m'
'\033[38;5;118m' '\033[38;5;82m' '\033[38;5;46m' '\033[38;5;47m'
'\033[38;5;48m' '\033[38;5;49m' '\033[38;5;50m' '\033[38;5;51m'
'\033[38;5;45m' '\033[38;5;39m' '\033[38;5;33m' '\033[38;5;27m'
'\033[38;5;57m' '\033[38;5;93m' '\033[38;5;129m' '\033[38;5;165m'
'\033[38;5;201m' '\033[38;5;200m' '\033[38;5;199m' '\033[38;5;198m'
'\033[38;5;197m' '\033[38;5;196m'
)
local rc=${#rainbow[@]}
echo ""
local border_top="╔"
for ((i=0; i<inner; i++)); do border_top+="═"; done
border_top+="╗"
for ((i=0; i<${#border_top}; i++)); do
printf "%b%s%b" "${rainbow[$((i % rc))]}" "${border_top:$i:1}" "$NC"
sleep 0.004
done
echo ""
printf "%b%s%b" "${rainbow[0]}" "║" "$NC"
printf "%s" "$lp"
printf "\033[1;97m%s\033[0m" "$title"
printf "%s" "$rp"
printf "%b%s%b\n" "${rainbow[$(( (inner + 1) % rc ))]}" "║" "$NC"
local border_bot="╚"
for ((i=0; i<inner; i++)); do border_bot+="═"; done
border_bot+="╝"
for ((i=0; i<${#border_bot}; i++)); do
local ci=$(( (rc - 1 - (i % rc)) ))
printf "%b%s%b" "${rainbow[$ci]}" "${border_bot:$i:1}" "$NC"
sleep 0.004
done
echo ""
}
# ★ 烟花特效加载动画 ★
run_with_spinner() {
local msg="$1"; shift
local -a fw_frames=(
' · ' ' ··· ' ' ··*·· ' ' ··***·· '
'··*****··' '·*******·' ' ***·*** ' ' *·*·* '
' ··* ' ' · ' ' ' ' ✦ '
' ✦✦ ' ' ✦*✦✦ ' ' ✦✦***✦✦ ' '✦*****✦✦'
'✦*******✦' ' ***·*** ' ' *·*·* ' ' ··* '
' · ' ' ' ' · ' ' ··· '
' ··⁺·· ' ' ··⁺*⁺·· ' '··⁺***⁺··' '·⁺*****⁺·'
' ⁺*****⁺ ' ' ***·*** ' ' *·*·* ' ' ··⁺ '
)
local fc=${#fw_frames[@]}
local -a fw_colors=(
'\033[38;5;226m' '\033[38;5;220m' '\033[38;5;214m' '\033[38;5;208m'
'\033[38;5;202m' '\033[38;5;196m' '\033[38;5;201m' '\033[38;5;213m'
'\033[38;5;231m' '\033[38;5;226m' '\033[38;5;220m' '\033[38;5;214m'
'\033[38;5;51m' '\033[38;5;45m' '\033[38;5;39m' '\033[38;5;33m'
)
local cc=${#fw_colors[@]}
(
local i=0
while true; do
local fi=$((i % fc))
local ci=$((i % cc))
printf "\r${fw_colors[$ci]}${BOLD}%s${NC} ${WHITE}%s${NC}" "${fw_frames[$fi]}" "$msg"
sleep 0.06
i=$((i + 1))
done
) &
local disp_pid=$!
disown $disp_pid 2>/dev/null || true
set +e
"$@"
local rc=$?
set -e
kill $disp_pid 2>/dev/null || true
wait $disp_pid 2>/dev/null || true
printf "\r\033[K"
return $rc
}
# ★ 黄色进度条 ★
show_progress() {
local cur=$1 max=$2 desc="$3"
local pct=0
[[ $max -gt 0 ]] && pct=$((cur * 100 / max))
[[ $pct -gt 100 ]] && pct=100
local filled=$((pct / 2))
local empty=$((50 - filled))
local bar_filled="" bar_empty=""
for ((i=0; i<filled; i++)); do bar_filled+="█"; done
for ((i=0; i<empty; i++)); do bar_empty+="░"; done
printf "\r ${YELLOW}[${YELLOW}%s${DIM}%s${NC}${YELLOW}]${NC} ${YELLOW}%3d%%${NC} %s" \
"$bar_filled" "$bar_empty" "$pct" "$desc"
}
# =====================================================================
# ↓↓↓ 业务逻辑 ↓↓↓
# =====================================================================
#===============================================================================
# 第0步:检测操作系统
#===============================================================================
log_step "第0步:检测操作系统"
if [[ ! -f /etc/os-release ]]; then
log_error "无法检测操作系统类型(缺少 /etc/os-release)"
exit 1
fi
. /etc/os-release
OS_ID="${ID,,}"
OS_VER="$VERSION_ID"
OS_PRETTY="$PRETTY_NAME"
OS_ID_LIKE="${ID_LIKE,,}"
OS_FAMILY=""
OS_DISTRO="$OS_ID"
case "$OS_ID" in
centos|rhel|rocky|alma|ol|fedora|alinux|tencentos|openEuler|openuler)
OS_FAMILY="rpm" ;;
ubuntu|debian)
OS_FAMILY="deb" ;;
kylin)
if echo "$OS_ID_LIKE" | grep -qi "debian\|ubuntu"; then
OS_FAMILY="deb"; OS_DISTRO="kylin-desktop"
elif echo "$OS_ID_LIKE" | grep -qi "rhel\|centos\|fedora"; then
OS_FAMILY="rpm"; OS_DISTRO="kylin-server"
else
if command -v dpkg &>/dev/null; then
OS_FAMILY="deb"; OS_DISTRO="kylin-desktop"
elif command -v rpm &>/dev/null; then
OS_FAMILY="rpm"; OS_DISTRO="kylin-server"
fi
fi ;;
neokylin)
OS_FAMILY="rpm"; OS_DISTRO="neokylin" ;;
*)
if echo "$OS_ID_LIKE" | grep -qi "rhel\|fedora\|centos"; then
OS_FAMILY="rpm"
elif echo "$OS_ID_LIKE" | grep -qi "debian\|ubuntu"; then
OS_FAMILY="deb"
else
if command -v dpkg &>/dev/null; then OS_FAMILY="deb"
elif command -v rpm &>/dev/null; then OS_FAMILY="rpm"
else log_error "不支持的操作系统: $OS_ID ($OS_PRETTY)"; exit 1; fi
fi ;;
esac
if [[ -z "$OS_FAMILY" ]]; then
log_error "无法确定系统族,请手动指定"; exit 1
fi
log_info "操作系统: $OS_PRETTY"
log_info "系统ID: $OS_ID (VERSION=$OS_VER)"
log_info "系统族: $OS_FAMILY (发行版标识: $OS_DISTRO)"
log_info "包管理器: $( [[ $OS_FAMILY == rpm ]] && echo 'rpm/yum/dnf' || echo 'dpkg/apt' )"
#===============================================================================
# 第1步:自动查找并解压离线包
#===============================================================================
log_step "第1步:自动查找并解压离线包"
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 "未找到离线包!请将 k8s-offline-local-*.tar.gz 放到 ${OFFLINE_BASE}/ 下"
exit 1
fi
log_info "找到离线包: $(basename "$ARCHIVE")"
log_info "文件大小: $(du -h "$ARCHIVE" | awk '{print $1}')"
run_with_spinner "正在解压离线包..." tar -xzf "$ARCHIVE" -C "$OFFLINE_BASE"
log_info "解压完成"
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/"
#===============================================================================
# 第2步:前置检查
#===============================================================================
log_step "第2步:前置检查"
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 "内 核: $(uname -r)"
log_info "CPU: $(nproc) 核"
log_info "内存: $(free -h | awk '/Mem:/{print $2}')"
#===============================================================================
# 第3步:安装系统依赖包
#===============================================================================
log_step "第3步:安装系统依赖包"
if [[ "$OS_FAMILY" == "rpm" ]]; then
if [[ -d "$OFFLINE_DIR/rpms" ]] && ls "$OFFLINE_DIR/rpms"/*.rpm &>/dev/null; then
run_with_spinner "正在安装 RPM 依赖包..." \
bash -c 'rpm -ivh --force --nodeps "$OFFLINE_DIR"/rpms/*.rpm 2>&1 | tail -5'
log_info "RPM 依赖安装完成 ✓"
else
log_warn "rpms/ 目录为空或不存在,跳过 RPM 依赖安装"
MISSING_DEPS=""
for cmd in conntrack socat iptables; do
command -v "$cmd" &>/dev/null || MISSING_DEPS="$MISSING_DEPS $cmd"
done
[[ -n "$MISSING_DEPS" ]] && log_warn "缺少:$MISSING_DEPS (如系统已内置则无影响)"
fi
elif [[ "$OS_FAMILY" == "deb" ]]; then
DEB_VER="$OS_VER"
if [[ "$OS_DISTRO" == "kylin-desktop" ]]; then
if [[ -d "$OFFLINE_DIR/debs/$OS_VER" ]]; then DEB_VER="$OS_VER"
elif [[ -d "$OFFLINE_DIR/debs/20.04" ]]; then DEB_VER="20.04"; log_info "麒麟桌面版 → 映射到 Ubuntu 20.04"
elif [[ -d "$OFFLINE_DIR/debs/22.04" ]]; then DEB_VER="22.04"; log_info "麒麟桌面版 → 映射到 Ubuntu 22.04"
fi
fi
DEB_DIR="$OFFLINE_DIR/debs/$DEB_VER"
if [[ -d "$DEB_DIR" ]] && ls "$DEB_DIR"/*.deb &>/dev/null; then
log_info "检测到依赖包目录: $DEB_DIR ($(ls "$DEB_DIR"/*.deb | wc -l) 个)"
run_with_spinner "正在安装 DEB 依赖包..." \
bash -c 'dpkg -i "$DEB_DIR"/*.deb 2>&1 | grep -E "(正在|设置|Unpacking|Setting|Selecting)" || true; apt-get install -f -y --no-download 2>/dev/null || true'
log_info "DEB 依赖安装完成 ✓"
else
log_warn "未找到 $DEB_DIR,检查系统是否已有依赖..."
MISSING_DEPS=""
for cmd in conntrack socat iptables ip; do
command -v "$cmd" &>/dev/null || MISSING_DEPS="$MISSING_DEPS $cmd"
done
if [[ -n "$MISSING_DEPS" ]]; then
log_error "缺少关键依赖:$MISSING_DEPS"
log_error "请确保离线包中包含 debs/$DEB_VER/ 目录"
exit 1
fi
log_info "系统已有必要依赖,跳过"
fi
fi
#===============================================================================
# 第4步:系统基础配置
#===============================================================================
log_step "第4步:系统基础配置"
run_with_spinner "关闭 swap..." bash -c 'swapoff -a; sed -i "/swap/s/^/#/" /etc/fstab'
log_info "swap 已关闭"
if [[ "$OS_FAMILY" == "rpm" ]]; then
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 已关闭"
if command -v setstatus &>/dev/null; then
setstatus disable 2>/dev/null || true
log_info "KYSEC 已关闭"
fi
if [[ -f /etc/kysec/kysec.conf ]]; then
sed -i 's/^KYSEC_ENABLE=yes/KYSEC_ENABLE=no/' /etc/kysec/kysec.conf 2>/dev/null || true
sed -i 's/^kysec_enable=1/kysec_enable=0/' /etc/kysec/kysec.conf 2>/dev/null || true
log_info "KYSEC 配置文件已修改"
fi
else
systemctl stop apparmor 2>/dev/null || true
systemctl disable apparmor 2>/dev/null || true
log_info "AppArmor 已关闭"
fi
if [[ "$OS_FAMILY" == "rpm" ]]; then
systemctl stop firewalld 2>/dev/null || true
systemctl disable firewalld 2>/dev/null || true
log_info "firewalld 已关闭"
else
ufw disable 2>/dev/null || true
systemctl stop ufw 2>/dev/null || true
systemctl disable ufw 2>/dev/null || true
log_info "ufw 已关闭"
fi
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"
#===============================================================================
# 第5步:安装核心组件
#===============================================================================
log_step "第5步:安装核心组件"
INSTALLED_FROM=""
if [[ "$OS_FAMILY" == "rpm" ]]; then
if [[ -d "$OFFLINE_DIR/rpms" ]] && ls "$OFFLINE_DIR/rpms"/*.rpm &>/dev/null; then
set +e
rpm -ivh --force --nodeps "$OFFLINE_DIR"/rpms/*.rpm 2>/dev/null
_rpm_rc=$?
set -e
if [[ $_rpm_rc -eq 0 ]]; then
INSTALLED_FROM="rpms"
log_info "RPM 安装完成"
else
log_warn "RPM 安装失败(rc=$_rpm_rc),切换到二进制方式"
fi
fi
fi
if [[ -z "$INSTALLED_FROM" && -d "$OFFLINE_DIR/binaries" ]]; then
BIN_DIR="$OFFLINE_DIR/binaries"
log_info "从 $BIN_DIR 安装二进制文件..."
_BIN_TOTAL=0
for bin in kubelet kubeadm kubectl containerd ctr crictl runc containerd-shim-runc-v2; do
[[ -f "$BIN_DIR/$bin" ]] && _BIN_TOTAL=$((_BIN_TOTAL + 1))
done
_BIN_CUR=0
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
_BIN_CUR=$((_BIN_CUR + 1))
show_progress $_BIN_CUR $_BIN_TOTAL "$bin → /usr/bin/"
fi
done
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
_BIN_CUR=$((_BIN_CUR + 1))
show_progress $_BIN_CUR $_BIN_TOTAL "$bin → /usr/local/bin/"
fi
done
echo ""
export PATH="/usr/local/bin:$PATH"
cat > /etc/profile.d/k8s-path.sh <<'EOF'
export PATH="/usr/local/bin:$PATH"
EOF
chmod +x /etc/profile.d/k8s-path.sh
INSTALLED_FROM="binaries"
log_info "二进制安装完成"
fi
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
fi
MISSING=""
for cmd in kubelet kubeadm kubectl containerd ctr crictl; do
command -v "$cmd" &>/dev/null || MISSING="$MISSING $cmd"
done
if [[ -n "$MISSING" ]]; then
log_error "以下命令未找到:$MISSING"
ls -la "$OFFLINE_DIR/binaries/" 2>/dev/null || echo " (binaries/ 目录不存在)"
exit 1
fi
log_info "安装来源: $INSTALLED_FROM"
log_info "kubelet/kubeadm/kubectl/containerd/ctr/crictl 均已就绪 ✓"
#===============================================================================
# 第6步:安装 CNI plugins
#===============================================================================
log_step "第6步:安装 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) 个"
else
log_warn "cni-plugins 目录为空,跳过"
fi
#===============================================================================
# 第7步:配置 containerd
#===============================================================================
log_step "第7步:配置 containerd"
mkdir -p /etc/containerd
_CONTAINERD_BIN="$(command -v containerd 2>/dev/null || echo /usr/local/bin/containerd)"
if [[ ! -x "$_CONTAINERD_BIN" ]]; then
log_error "containerd 二进制未找到或不可执行"
ls -la /usr/local/bin/containerd* 2>/dev/null || echo " /usr/local/bin/ 下无 containerd"
exit 1
fi
log_info "containerd 路径: $_CONTAINERD_BIN ($("$_CONTAINERD_BIN" --version 2>&1 || echo 'unknown'))"
set +e
"$_CONTAINERD_BIN" config default > /etc/containerd/config.toml 2>/tmp/containerd-config.err
_crd_rc=$?
set -e
if [[ $_crd_rc -ne 0 ]]; then
log_error "containerd config default 失败 (rc=$_crd_rc):"
cat /tmp/containerd-config.err
exit 1
fi
sed -i "s|sandbox_image = .*|sandbox_image = \"${IMAGE_REPO}/pause:3.9\"|" /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sed -i 's/snapshotter = "native"/snapshotter = "overlayfs"/' /etc/containerd/config.toml 2>/dev/null || true
log_info "config.toml 已修改 (sandbox_image/systemd cgroup/overlayfs)"
systemctl daemon-reload
systemctl enable containerd
run_with_spinner "正在启动 containerd..." bash -c '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
#===============================================================================
# 第8步:导入容器镜像
#===============================================================================
log_step "第8步:导入容器镜像"
IMAGE_DIR="$OFFLINE_DIR/images"
IMPORTED=0; SKIPPED=0; FAILED=0
if [[ -d "$IMAGE_DIR" ]] && ls "$IMAGE_DIR"/*.tar &>/dev/null; then
_IMG_TOTAL=0
for tar_file in "$IMAGE_DIR"/*.tar; do
fn="$(basename "$tar_file")"
echo "$fn" | grep -qE "_sha256_" || _IMG_TOTAL=$((_IMG_TOTAL + 1))
done
_IMG_CUR=0
for tar_file in "$IMAGE_DIR"/*.tar; do
filename="$(basename "$tar_file")"
if echo "$filename" | grep -qE "_sha256_"; then
SKIPPED=$((SKIPPED + 1)); continue
fi
_IMG_CUR=$((_IMG_CUR + 1))
show_progress $_IMG_CUR $_IMG_TOTAL "导入 $filename"
if ctr -n k8s.io images import "$tar_file" > /dev/null 2>&1; then
IMPORTED=$((IMPORTED + 1))
else
FAILED=$((FAILED + 1))
fi
done
echo ""
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
#===============================================================================
# 第9步:创建 kubelet drop-in
#===============================================================================
log_step "第9步:创建 kubelet drop-in"
mkdir -p /etc/systemd/system/kubelet.service.d
if [[ "$OS_FAMILY" == "rpm" ]]; then
KUBELET_ENV_FILE="/etc/sysconfig/kubelet"
else
KUBELET_ENV_FILE="/etc/default/kubelet"
fi
touch "$KUBELET_ENV_FILE"
cat > /etc/systemd/system/kubelet.service.d/10-kubeadm.conf <<EOF
[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=-${KUBELET_ENV_FILE}
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 已创建 (EnvFile=$KUBELET_ENV_FILE) ✓"
#===============================================================================
# 第10步:清理旧状态
#===============================================================================
log_step "第10步:清理旧状态"
run_with_spinner "正在清理旧集群状态..." bash -c '
kubeadm reset -f --cri-socket "'"$CRI_SOCKET"'" 2>/dev/null || true
rm -rf /var/lib/etcd/* /etc/kubernetes/manifests /etc/kubernetes/pki
rm -rf /var/lib/kubelet/config.yaml /var/lib/kubelet/kubeadm-flags.env
rm -rf /etc/cni/net.d/* "$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 "清理完成 ✓"
#===============================================================================
# 第10.5步:生成 kubeadm 配置文件(证书有效期 100 年)
#===============================================================================
log_step "第10.5步:生成 kubeadm 配置"
cat > /tmp/kubeadm-config.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "${K8S_VERSION}"
imageRepository: "${IMAGE_REPO}"
networking:
serviceSubnet: "${SERVICE_CIDR}"
podSubnet: "${POD_CIDR}"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: "${MASTER_IP}"
nodeRegistration:
criSocket: "${CRI_SOCKET}"
ignorePreflightErrors:
- all
EOF
log_info "kubeadm 配置文件已生成: /tmp/kubeadm-config.yaml"
#===============================================================================
# 第11步:kubeadm init
#===============================================================================
log_step "第11步: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 " OS: $OS_PRETTY ($OS_FAMILY)"
echo " CNI模式: $CNI_MODE"
echo " 证书有效期: 100年 (876000h)"
echo ""
_INIT_LOG="/tmp/kubeadm-init.log"
_sp_frames=('⠋' '' '⠹' '' '⠼' '⠴' '⠦' '' '⠇' '⠏')
(
_i=0
while true; do
printf "\r${CYAN}${BOLD}%s${NC} ${WHITE}kubeadm init 执行中(预计 1~3 分钟)...${NC}" "${_sp_frames[$((_i % ${#_sp_frames[@]}))]}"
sleep 0.08
_i=$((_i + 1))
done
) &
_SP_PID=$!
disown $_SP_PID 2>/dev/null || true
set +e
kubeadm init \
--config=/tmp/kubeadm-config.yaml \
> "$_INIT_LOG" 2>&1
_INIT_RC=$?
set -e
kill $_SP_PID 2>/dev/null || true
wait $_SP_PID 2>/dev/null || true
printf "\r\033[K"
if [[ $_INIT_RC -ne 0 ]]; then
log_error "kubeadm init 失败 (exit code=$_INIT_RC),最后 30 行日志:"
tail -30 "$_INIT_LOG" | sed 's/^/ /'
log_error "完整日志: $_INIT_LOG"
exit 1
fi
grep -E "(Your Kubernetes control-plane|kubeadm join|To start using)" "$_INIT_LOG" | sed 's/^/ /' || true
log_info "kubeadm init 成功 ✓ (完整日志: $_INIT_LOG)"
#===============================================================================
# 第11.5步:重签证书为100年有效期
#===============================================================================
log_step "第11.5步:重签证书 → 100年有效期"
set +e
PKI_DIR="/etc/kubernetes/pki"
DAYS=36500
mkdir -p /tmp/pki-backup
cp -a "$PKI_DIR"/* /tmp/pki-backup/ 2>/dev/null || true
log_info "原始证书已备份到 /tmp/pki-backup/"
# 通用重签函数:从原证书提取SAN,用CA重签为100年
renew_leaf() {
local cert="$1" key="$2" ca_cert="$3" ca_key="$4"
local name
name=$(basename "$cert")
if [[ ! -f "$cert" || ! -f "$key" || ! -f "$ca_cert" || ! -f "$ca_key" ]]; then
log_warn "跳过 $name (文件缺失)"
return 0
fi
# 1) 提取CN
local cn
cn=$(openssl x509 -in "$cert" -noout -subject -nameopt multiline 2>/dev/null | grep commonName | sed 's/.*= //')
[[ -z "$cn" ]] && cn="unknown"
# 2) 提取SAN(关键:去掉前导空格,保留原始逗号分隔格式)
local san=""
san=$(openssl x509 -in "$cert" -noout -ext subjectAltName 2>/dev/null | tail -n +2 | sed 's/^[[:space:]]*//' | sed 's/IP Address:/IP:/g')
# 3) 写扩展配置文件
local ext="/tmp/_ext_${name}.cnf"
cat > "$ext" <<EXTEOF
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
EXTEOF
if [[ -n "$san" ]]; then
echo "subjectAltName = ${san}" >> "$ext"
fi
# 4) 生成CSR并重签
local csr="/tmp/_csr_${name}.pem"
openssl req -new -key "$key" -subj "/CN=${cn}" -out "$csr" 2>/dev/null
local errfile="/tmp/_err_${name}.txt"
if openssl x509 -req -in "$csr" \
-CA "$ca_cert" -CAkey "$ca_key" -CAcreateserial \
-days "$DAYS" -extfile "$ext" \
-out "$cert" 2>"$errfile"; then
log_info "✓ $name → 100年 (CN=$cn)"
else
log_warn "重签失败 $name: $(cat "$errfile")"
cp -f "/tmp/pki-backup/$name" "$cert" 2>/dev/null || true
fi
rm -f "$csr" "$ext" "$errfile"
}
log_info "── 重签叶子证书 ──"
renew_leaf "$PKI_DIR/apiserver.crt" "$PKI_DIR/apiserver.key" "$PKI_DIR/ca.crt" "$PKI_DIR/ca.key"
renew_leaf "$PKI_DIR/apiserver-kubelet-client.crt" "$PKI_DIR/apiserver-kubelet-client.key" "$PKI_DIR/ca.crt" "$PKI_DIR/ca.key"
renew_leaf "$PKI_DIR/apiserver-etcd-client.crt" "$PKI_DIR/apiserver-etcd-client.key" "$PKI_DIR/etcd/ca.crt" "$PKI_DIR/etcd/ca.key"
renew_leaf "$PKI_DIR/front-proxy-client.crt" "$PKI_DIR/front-proxy-client.key" "$PKI_DIR/front-proxy-ca.crt" "$PKI_DIR/front-proxy-ca.key"
renew_leaf "$PKI_DIR/etcd/server.crt" "$PKI_DIR/etcd/server.key" "$PKI_DIR/etcd/ca.crt" "$PKI_DIR/etcd/ca.key"
renew_leaf "$PKI_DIR/etcd/peer.crt" "$PKI_DIR/etcd/peer.key" "$PKI_DIR/etcd/ca.crt" "$PKI_DIR/etcd/ca.key"
renew_leaf "$PKI_DIR/etcd/healthcheck-client.crt" "$PKI_DIR/etcd/healthcheck-client.key" "$PKI_DIR/etcd/ca.crt" "$PKI_DIR/etcd/ca.key"
# 更新 kubeconfig
# ... 前面 renew_leaf 和 PKI 叶子证书重签保持不变 ...
# 更新 kubeconfig
log_info "── 更新 kubeconfig ──"
rm -f /etc/kubernetes/admin.conf /etc/kubernetes/controller-manager.conf \
/etc/kubernetes/scheduler.conf /etc/kubernetes/kubelet.conf \
/etc/kubernetes/super-admin.conf "$HOME/.kube/config" 2>/dev/null || true
kubeadm init phase kubeconfig all --config=/tmp/kubeadm-config.yaml >/dev/null 2>&1 || true
mkdir -p "$HOME/.kube"
cp -f /etc/kubernetes/admin.conf "$HOME/.kube/config" 2>/dev/null || true
chown "$(id -u):$(id -g)" "$HOME/.kube/config" 2>/dev/null || true
export KUBECONFIG=/etc/kubernetes/admin.conf
log_info "kubeconfig 已重新生成 ✓"
# ========== 新增:重签 kubeconfig 内嵌客户端证书为100年 ==========
log_info "── 重签 kubeconfig 内嵌客户端证书 ──"
renew_kubeconfig_cert() {
local conf_path="$1" cn="$2" org="$3" ca_cert="$4" ca_key="$5"
local conf_name
conf_name=$(basename "$conf_path")
if [[ ! -f "$conf_path" || ! -f "$ca_cert" || ! -f "$ca_key" ]]; then
log_warn "跳过 $conf_name (文件缺失)"
return 0
fi
# 生成新密钥和证书
local tmp_key="/tmp/_kc_${cn}.key"
local tmp_csr="/tmp/_kc_${cn}.csr"
local tmp_crt="/tmp/_kc_${cn}.crt"
openssl genrsa -out "$tmp_key" 2048 2>/dev/null
openssl req -new -key "$tmp_key" -subj "/O=${org}/CN=${cn}" -out "$tmp_csr" 2>/dev/null
openssl x509 -req -in "$tmp_csr" \
-CA "$ca_cert" -CAkey "$ca_key" -CAcreateserial \
-days "$DAYS" \
-out "$tmp_crt" 2>/dev/null
# Base64 编码并嵌入 kubeconfig
local cert_b64 key_b64
cert_b64=$(base64 -w0 "$tmp_crt")
key_b64=$(base64 -w0 "$tmp_key")
kubectl --kubeconfig="$conf_path" config set users."${cn}".client-certificate-data "${cert_b64}" 2>/dev/null
kubectl --kubeconfig="$conf_path" config set users."${cn}".client-key-data "${key_b64}" 2>/dev/null
log_info "✓ $conf_name → 100年 (CN=$cn)"
rm -f "$tmp_key" "$tmp_csr" "$tmp_crt"
}
renew_kubeconfig_cert "/etc/kubernetes/admin.conf" "kubernetes-admin" "system:masters" "$PKI_DIR/ca.crt" "$PKI_DIR/ca.key"
renew_kubeconfig_cert "/etc/kubernetes/controller-manager.conf" "system:kube-controller-manager" "system:kube-controller-manager" "$PKI_DIR/ca.crt" "$PKI_DIR/ca.key"
renew_kubeconfig_cert "/etc/kubernetes/scheduler.conf" "system:kube-scheduler" "system:kube-scheduler" "$PKI_DIR/ca.crt" "$PKI_DIR/ca.key"
renew_kubeconfig_cert "/etc/kubernetes/super-admin.conf" "kubernetes-super-admin" "system:masters" "$PKI_DIR/ca.crt" "$PKI_DIR/ca.key"
# 同步更新 ~/.kube/config(admin.conf 的副本)
cp -f /etc/kubernetes/admin.conf "$HOME/.kube/config" 2>/dev/null || true
log_info "kubeconfig 内嵌证书重签完成 ✓"
# ========== 新增结束 ==========
# 重启控制平面
log_info "── 重启控制平面 ──"
# ... 后面重启和验证逻辑保持不变 ...
# 重启控制平面
log_info "── 重启控制平面 ──"
if command -v crictl &>/dev/null; then
crictl stopp "$(crictl pods -q --namespace kube-system 2>/dev/null)" 2>/dev/null || true
crictl rmp "$(crictl pods -q --namespace kube-system 2>/dev/null)" 2>/dev/null || true
fi
sleep 5
run_with_spinner "等待 API Server 就绪..." bash -c '
export KUBECONFIG=/etc/kubernetes/admin.conf
for i in $(seq 1 90); do
kubectl get --raw /healthz &>/dev/null && exit 0
sleep 2
done
exit 1
' || { log_error "API Server 未就绪"; exit 1; }
log_info "控制平面已重启 ✓"
# 验证
log_info "── 证书到期时间 ──"
for crt in ca.crt apiserver.crt apiserver-kubelet-client.crt apiserver-etcd-client.crt \
front-proxy-ca.crt front-proxy-client.crt \
etcd/ca.crt etcd/server.crt etcd/peer.crt etcd/healthcheck-client.crt; do
exp=$(openssl x509 -in "$PKI_DIR/$crt" -noout -enddate 2>/dev/null | cut -d= -f2)
printf " %-38s %s\n" "$crt" "$exp"
done
echo ""
log_info "所有证书重签完成 ✓"
set -e
#===============================================================================
# 第12步:配置 kubectl
#===============================================================================
log_step "第12步:配置 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 配置完成 ✓"
#===============================================================================
# ★★★ 第13步:CNI 自动选型 & 部署网络 ★★★
#===============================================================================
log_step "第13步:CNI 自动选型 & 部署网络"
if [[ -t 0 && "$CNI_MODE" == "auto" ]]; then
echo ""
echo -e "${CYAN}${BOLD}请选择 CNI 网络插件:${NC}"
echo -e " ${WHITE}1)${NC} Auto - 自动评分选型(推荐)"
echo -e " ${WHITE}2)${NC} Calico - 功能全面,支持 NetworkPolicy/eBPF/BGP"
echo -e " ${WHITE}3)${NC} Flannel - 轻量简单,适合小规模集群"
echo ""
read -r -p "$(echo -e "${GREEN}${BOLD}请输入选项 [1/2/3] (默认 1):${NC} ")" _cni_choice
case "$_cni_choice" in
2|calico|Calico) CNI_MODE="calico" ;;
3|flannel|Flannel) CNI_MODE="flannel" ;;
*) CNI_MODE="auto" ;;
esac
log_info "用户选择: CNI_MODE=${CNI_MODE}"
echo ""
fi
choose_cni() {
local FLANNEL_SCORE=0 CALICO_SCORE=0
local -a REASONS=()
local NODE_COUNT=1
local TOTAL_CPU TOTAL_MEM_KI KERNEL_MAJOR KERNEL_MINOR
TOTAL_CPU=$(nproc)
TOTAL_MEM_KI=$(free -k 2>/dev/null | awk '/^Mem:/{print int($2/1024/1024)}')
[[ -z "$TOTAL_MEM_KI" || ! "$TOTAL_MEM_KI" =~ ^[0-9]+$ ]] && TOTAL_MEM_KI=16
KERNEL_MAJOR=$(uname -r | awk -F. '{print $1}')
KERNEL_MINOR=$(uname -r | awk -F. '{print $2}')
local HAS_BGP_PEER="no"
pgrep -x "frr\|bird\|bgpd" &>/dev/null && HAS_BGP_PEER="yes"
local NETPOL_COUNT=0
NETPOL_COUNT=$(kubectl get networkpolicy -A --no-headers 2>/dev/null | wc -l || echo 0)
local L2_REACHABLE="no"
local GW_NET MY_NET
GW_NET=$(ip route show default 2>/dev/null | awk '{print $3}' | cut -d. -f1-3)
MY_NET=$(hostname -I | awk '{print $1}' | cut -d. -f1-3)
[[ "$GW_NET" == "$MY_NET" ]] && L2_REACHABLE="yes"
local CLOUD_PROVIDER="bare-metal"
curl -s --connect-timeout 2 http://100.100.100.200/latest/meta-data/ &>/dev/null && CLOUD_PROVIDER="aliyun"
curl -s --connect-timeout 2 http://169.254.169.254/latest/meta-data/ &>/dev/null && CLOUD_PROVIDER="aws/gcp/openstack"
log_info "── CNI 选型检测数据 ──"
log_info " 节点数: ${NODE_COUNT} | CPU: ${TOTAL_CPU} | MEM: ${TOTAL_MEM_KI}Gi"
log_info " 内核: ${KERNEL_MAJOR}.${KERNEL_MINOR} | BGP: ${HAS_BGP_PEER} | L2: ${L2_REACHABLE}"
log_info " 云平台: ${CLOUD_PROVIDER} | NetPol数: ${NETPOL_COUNT}"
echo ""
if [ "$NODE_COUNT" -le 5 ]; then
((FLANNEL_SCORE += 3)); REASONS+=("节点数 ≤5,Flannel 足够轻量")
elif [ "$NODE_COUNT" -le 20 ]; then
((FLANNEL_SCORE += 1)); ((CALICO_SCORE += 1)); REASONS+=("节点数 ${NODE_COUNT},两者皆可")
else
((CALICO_SCORE += 3)); REASONS+=("节点数 ${NODE_COUNT} > 20,Calico 更适合大规模")
fi
[ "$NETPOL_COUNT" -gt 0 ] && { ((CALICO_SCORE += 5)); REASONS+=("已有 NetworkPolicy,Calico 原生支持"); }
[[ "$TOTAL_MEM_KI" -le 8 && "$TOTAL_CPU" -le 4 ]] && { ((FLANNEL_SCORE += 2)); REASONS+=("资源受限(CPU≤4,MEM≤8Gi),Flannel 更轻量"); }
[ "$KERNEL_MAJOR" -ge 5 ] && [ "$KERNEL_MINOR" -ge 4 ] 2>/dev/null && { ((CALICO_SCORE += 1)); REASONS+=("内核 ≥5.4,Calico 可启用 eBPF"); }
[ "$HAS_BGP_PEER" = "yes" ] && { ((CALICO_SCORE += 4)); REASONS+=("检测到 BGP 环境,Calico 原生 BGP 路由最优"); }
[ "$L2_REACHABLE" = "yes" ] && { ((FLANNEL_SCORE += 1)); REASONS+=("节点二层可达,Flannel host-gw 零封装高性能"); }
[ "$CLOUD_PROVIDER" != "bare-metal" ] && { ((FLANNEL_SCORE += 1)); REASONS+=("云环境(${CLOUD_PROVIDER}),Flannel 通常足够"); }
((FLANNEL_SCORE += 1)); REASONS+=("Flannel 运维复杂度更低")
echo ""
echo "┌─────────────────────────────────────────┐"
echo "│ CNI 选型评分结果 │"
echo "├─────────────────────────────────────────┤"
printf "│ Flannel 得分: %-23s│\n" "$FLANNEL_SCORE"
printf "│ Calico 得分: %-23s│\n" "$CALICO_SCORE"
echo "└─────────────────────────────────────────┘"
echo ""
log_info "决策依据:"
for r in "${REASONS[@]}"; do echo " • $r"; done
echo ""
if [ "$CALICO_SCORE" -gt "$FLANNEL_SCORE" ]; then CHOSEN_CNI="calico"
elif [ "$FLANNEL_SCORE" -gt "$CALICO_SCORE" ]; then CHOSEN_CNI="flannel"
else CHOSEN_CNI="calico"; log_warn "评分持平,默认选择 Calico(功能更全面)"
fi
log_info "✅ CNI 选型结果: ${CHOSEN_CNI^^}"
}
case "$CNI_MODE" in
calico) CHOSEN_CNI="calico"; log_info "CNI_MODE=calico,强制使用 Calico" ;;
flannel) CHOSEN_CNI="flannel"; log_info "CNI_MODE=flannel,强制使用 Flannel" ;;
auto|*) choose_cni ;;
esac
CALICO_YAML="$OFFLINE_DIR/configs/calico.yaml"
FLANNEL_YAML="$OFFLINE_DIR/configs/flannel.yaml"
if [[ "$CHOSEN_CNI" == "calico" && ! -f "$CALICO_YAML" ]]; then
if [[ -f "$FLANNEL_YAML" ]]; then
log_warn "离线包中无 calico.yaml,fallback 到 Flannel"; CHOSEN_CNI="flannel"
else log_error "离线包中既无 calico.yaml 也无 flannel.yaml!"; exit 1; fi
fi
if [[ "$CHOSEN_CNI" == "flannel" && ! -f "$FLANNEL_YAML" ]]; then
if [[ -f "$CALICO_YAML" ]]; then
log_warn "离线包中无 flannel.yaml,fallback 到 Calico"; CHOSEN_CNI="calico"
else log_error "离线包中既无 flannel.yaml 也无 calico.yaml!"; exit 1; fi
fi
case "$CHOSEN_CNI" in
calico)
log_info "部署 Calico 网络..."
kubectl apply -f "$CALICO_YAML"
KERNEL_VER=$(uname -r | cut -d. -f1-2 | tr -d '.')
if [ "$KERNEL_VER" -lt 54 ]; then
log_warn "内核版本低于 5.4 ($(uname -r)),禁用 Calico eBPF"
WAIT_DS=0
while [[ $WAIT_DS -lt 60 ]]; do
kubectl get daemonset calico-node -n kube-system &>/dev/null && break
sleep 3; WAIT_DS=$((WAIT_DS + 3))
done
if kubectl get daemonset calico-node -n kube-system &>/dev/null; then
kubectl set env daemonset/calico-node -n kube-system FELIX_BPFENABLED=false
kubectl delete pod -n kube-system -l k8s-app=calico-node
log_info "Calico eBPF 已禁用,Pod 已重启 ✓"
else log_warn "calico-node DaemonSet 尚未创建,eBPF 禁用跳过"; fi
else log_info "内核版本 $(uname -r) >= 5.4,保留 Calico eBPF 默认配置"; fi
;;
flannel)
log_info "部署 Flannel 网络..."
kubectl apply -f "$FLANNEL_YAML"
;;
esac
log_info "等待 ${CHOSEN_CNI^^} & 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|flannel)' || true)"
TOTAL="$(kubectl get pods -A 2>/dev/null | grep -cE '(coredns|calico|flannel)' || true)"
if [[ "$READY" -ge 3 && "$READY" == "$TOTAL" ]]; then
printf "\r\033[K"
log_info "${CHOSEN_CNI^^} & CoreDNS 全部就绪 ✓ (${WAIT}s)"
break
fi
sleep 5; WAIT=$((WAIT + 5))
show_progress $WAIT $MAX_WAIT "Running: ${READY}/${TOTAL}"
done
echo ""
[[ $WAIT -ge $MAX_WAIT ]] && log_warn "超时!部分 Pod 可能仍在启动中,请手动检查"
#===============================================================================
# 第14步:验证集群
#===============================================================================
log_step "第14步:验证集群"
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 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 异常"
#===============================================================================
# 第15步:验证证书有效期
#===============================================================================
log_step "第15步:验证证书有效期(100年)"
log_info "证书过期时间:"
kubeadm certs check-expiration 2>/dev/null | sed 's/^/ /' || true
echo ""
log_info "CA 证书详情:"
openssl x509 -in /etc/kubernetes/pki/ca.crt -noout -subject -enddate 2>/dev/null | sed 's/^/ /' || true
#===============================================================================
# 完成
#===============================================================================
log_step "🎉 K8s $K8S_VERSION 部署完成"
JOIN_CMD="$(kubeadm token create --print-join-command 2>/dev/null || echo "N/A")"
cat <<EOF
┌─────────────────────────────────────────────────┐
│ 部 署 信 息 汇 总 │
├─────────────────────────────────────────────────┤
│ K8s 版本: $K8S_VERSION
│ 操作系统: $OS_PRETTY
│ 系统族: $OS_FAMILY ($OS_DISTRO)
│ Master IP: $MASTER_IP
│ Pod CIDR: $POD_CIDR
│ Service CIDR: $SERVICE_CIDR
│ 网络插件: ${CHOSEN_CNI^^} (选型模式: $CNI_MODE)
│ 容器运行时: containerd
│ 证书有效期: 100年 (876000h)
├─────────────────────────────────────────────────┤
│ kubectl 使用:
│ export KUBECONFIG=/etc/kubernetes/admin.conf
│ kubectl get nodes -o wide
│ kubectl get pods -A
├─────────────────────────────────────────────────┤
│ 添加 Worker 节点:
│ $JOIN_CMD
└─────────────────────────────────────────────────┘
EOF
log_info "部署日志已保存至 /tmp/kubeadm-init.log"
echo "============================================================"
bash deploy-k8s.sh
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)