#!/bin/bash # 移除 set -e,改为手动控制错误,防止意外退出 set -o pipefail # ================= 1. 加载环境变量 (.env) ================= SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${SCRIPT_DIR}/.env" load_env_file() { if [ -f "$ENV_FILE" ]; then while IFS= read -r line || [[ -n "$line" ]]; do # 去除首尾空白 line=$(echo "$line" | xargs) # 跳过空行和注释 [[ -z "$line" || "$line" == \#* ]] && continue # 简单的格式校验 (KEY=VALUE) if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)= ]]; then key="${BASH_REMATCH[1]}" # 只有当该变量尚未在环境中设置时,才从文件加载 (支持命令行覆盖) if [ -z "${!key}" ]; then export "$line" fi fi done < "$ENV_FILE" echo -e "\033[0;36m[CONFIG]\033[0m 已加载配置:$ENV_FILE" else # 如果文件不存在,不报错,依赖后续检查或命令行传入 : fi } # 执行加载 load_env_file # ================= 2. 颜色与日志 ================= RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_step() { echo -e "${BLUE}[STEP]${NC} $1"; } log_success() { echo -e "${CYAN}[OK]${NC} $1"; } log_header() { echo "" echo "========================================" echo -e "${BLUE}$1${NC}" echo "========================================" } # ================= 3. 配置与初始化 ================= HOST_FILE="${SCRIPT_DIR}/host_list.txt" SCRIPT_DISK="${SCRIPT_DIR}/2.1.prepare_disk.sh" SCRIPT_MINIO="${SCRIPT_DIR}/2.2.deploy_cluster.sh" # 核心二进制文件和服务文件列表 CORE_FILES=("minio" "mc" "minio.service") # ================= 4. 关键变量预检 ================= log_header " MinIO 集群全自动部署启动" # 检查必要的环境变量 if [ -z "$MINIO_ROOT_USER" ]; then log_error "❌ 缺少环境变量 MINIO_ROOT_USER" log_info "请在 ${ENV_FILE} 中配置,或使用命令运行: MINIO_ROOT_USER=admin ./2.deploy_minio.sh" exit 1 fi if [ -z "$MINIO_ROOT_PASSWORD" ]; then log_error "❌ 缺少环境变量 MINIO_ROOT_PASSWORD" log_info "请在 ${ENV_FILE} 中配置,或使用命令运行: MINIO_ROOT_PASSWORD=你的密码 ./2.deploy_minio.sh" exit 1 fi log_info "已加载账号:MINIO_ROOT_USER=$MINIO_ROOT_USER" log_info "已加载密码:MINIO_ROOT_PASSWORD=****** (长度:${#MINIO_ROOT_PASSWORD})" # ================= 5. 前置文件检查 ================= # 1. 检查 host_list.txt if [ ! -f "$HOST_FILE" ]; then log_error "找不到主机列表文件:$HOST_FILE" exit 1 fi log_info "主机列表:$HOST_FILE" log_info "工作目录:$SCRIPT_DIR" # ================= 6. 按区块解析主机列表 ================= log_step "解析 [minio] 区块主机列表..." # 定义函数:提取指定区块的节点 get_section_nodes() { local target_section="$1" local current_section="" local in_target=false while IFS= read -r line || [[ -n "$line" ]]; do # 跳过空行 [[ -z "${line// /}" ]] && continue # 跳过注释行 [[ "$line" =~ ^[[:space:]]*# ]] && continue # 检测区块标记 if [[ "$line" =~ ^\[([a-zA-Z0-9_-]+)\]$ ]]; then current_section="${BASH_REMATCH[1]}" if [ "$current_section" == "$target_section" ]; then in_target=true else in_target=false fi continue fi # 只在目标区块内输出数据 if [ "$in_target" == true ]; then echo "$line" fi done < "$HOST_FILE" } declare -a NODE_NAMES declare -a NODE_IPS declare -a NODE_USERS declare -a NODE_PASSES declare -a NODE_DEVICES COUNT=0 while IFS= read -r line || [[ -n "$line" ]]; do # 使用 awk 提取,避免 shell 词法解析特殊字符的问题 name=$(echo "$line" | awk '{print $1}') ip=$(echo "$line" | awk '{print $2}') user=$(echo "$line" | awk '{print $3}') pass=$(echo "$line" | awk '{print $4}') devices=$(echo "$line" | awk '{print $5}') if [ -z "$name" ] || [ -z "$ip" ] || [ -z "$user" ] || [ -z "$pass" ] || [ -z "$devices" ]; then log_warn "跳过格式错误的行 (缺少列): $line" continue fi # 存入数组 NODE_NAMES+=("$name") NODE_IPS+=("$ip") NODE_USERS+=("$user") NODE_PASSES+=("$pass") NODE_DEVICES+=("$devices") ((COUNT++)) done < <(get_section_nodes "minio") if [ $COUNT -eq 0 ]; then log_error "未找到 [minio] 区块的有效节点配置,请检查 $HOST_FILE 格式" exit 1 fi log_success "共解析到 $COUNT 个 MinIO 节点" # 显示解析到的节点 echo "" log_info "MinIO 节点列表:" for i in "${!NODE_NAMES[@]}"; do echo " $i: ${NODE_NAMES[$i]} (${NODE_IPS[$i]}) - 设备:${NODE_DEVICES[$i]}" sed -i s#MINIO_IP"$i"#"${NODE_IPS[$i]}"#g haproxy.cfg log_info "正在处理haproxy.cfg配置文件..." done echo "" TOTAL_NODES=$COUNT FAILED_COUNT=0 # ================= 阶段 1: 磁盘准备 ================= log_header "阶段 1/3: 磁盘格式化与挂载" for i in "${!NODE_NAMES[@]}"; do name="${NODE_NAMES[$i]}" user="${NODE_USERS[$i]}" devices="${NODE_DEVICES[$i]}" log_step "正在准备磁盘:$name (设备:$devices)" TARGET="${user}@${name}" if "$SCRIPT_DISK" "$TARGET" "$devices"; then log_success "$name 磁盘准备完成" else log_error "$name 磁盘准备失败" ((FAILED_COUNT++)) fi done # ================= 阶段 2: MinIO 部署 ================= log_header "阶段 2/3: 部署 MinIO 服务" for i in "${!NODE_NAMES[@]}"; do name="${NODE_NAMES[$i]}" user="${NODE_USERS[$i]}" log_step "正在部署 MinIO: $name" TARGET="${user}@${name}" # 传递从 .env 或命令行获取的账号密码 if "$SCRIPT_MINIO" "$TARGET" "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"; then log_success "$name MinIO 服务启动成功" else log_error "$name MinIO 部署失败" ((FAILED_COUNT++)) fi done # ================= 阶段 3: 集群验收 (本地执行版) ================= log_header "阶段 3/3: 集群状态验收" FIRST_NAME="${NODE_NAMES[0]}" # 注意:这里直接使用 FIRST_NAME 作为访问地址,确保 deploy 机能解析该主机名或 IP MINIO_URL="http://${FIRST_NAME}:9000" log_info "部署控制节点:$(hostname)" log_info "MinIO 入口地址:$MINIO_URL" log_info "预期账号:$MINIO_ROOT_USER" # --------------------------------------------------------- # 步骤 1: 配置 mc 别名 (在 deploy 本地执行) # --------------------------------------------------------- log_step "配置 mc 别名 (本地)..." # 如果之前存在同名 alias,先删除以防冲突 (可选,增加健壮性) mc alias rm mycluster >/dev/null 2>&1 CMD_ALIAS="mc alias set mycluster '${MINIO_URL}' '${MINIO_ROOT_USER}' '${MINIO_ROOT_PASSWORD}'" if eval "$CMD_ALIAS"; then log_success "mc 别名配置成功" else log_error "配置 mc 别名失败 (请检查网络连通性、服务状态或密码)" # 如果是 "Server not initialized",我们可以在后面的重试循环中再次尝试设置, # 但为了逻辑清晰,这里先报错,后面重试循环里会再次尝试 alias set ((FAILED_COUNT++)) fi # --------------------------------------------------------- # 步骤 2: 检查集群状态 (带重试机制,本地执行) # --------------------------------------------------------- log_step "检查集群健康状态 (最多等待 90 秒)..." MAX_RETRIES=18 # 90 秒 SLEEP_INTERVAL=5 # 5 秒 RETRY_COUNT=0 CLUSTER_READY=false OUTPUT="" while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do log_info "第 $((RETRY_COUNT + 1)) 次尝试连接..." # 【关键修改】直接在本地执行 mc admin info # 即使 alias 设置失败,也可以直接用完整 URL+ 账号密码方式查询,防止 alias 未生效导致循环死锁 # 语法:mc admin info --json # 为了稳妥,我们优先使用已设置的 alias,如果失败则尝试直接传参 OUTPUT=$(mc admin info --json mycluster 2>&1) EXIT_CODE=$? # 如果 alias 方式失败(可能因为还没设好,或者服务未就绪),尝试直接传参方式 if [ $EXIT_CODE -ne 0 ] || [ -z "$OUTPUT" ]; then # 尝试直接通过 URL+ 凭证查询,绕过 alias 依赖 OUTPUT=$(mc admin info --json "${MINIO_URL}" "${MINIO_ROOT_USER}" "${MINIO_ROOT_PASSWORD}" 2>&1) EXIT_CODE=$? fi if [ $EXIT_CODE -ne 0 ]; then # 分析错误原因 if echo "$OUTPUT" | grep -q "Server not initialized"; then log_warn "服务未初始化完成 (Formatting/Healing),等待中..." elif echo "$OUTPUT" | grep -q "Connection refused"; then log_warn "端口未监听或服务未启动,等待中..." elif echo "$OUTPUT" | grep -q "Unable to initialize new alias"; then log_warn "Alias 未就绪,等待服务初始化..." else log_warn "连接失败:$OUTPUT" fi sleep $SLEEP_INTERVAL ((RETRY_COUNT++)) continue fi # 验证 JSON 合法性 if ! echo "$OUTPUT" | jq -e '.status' > /dev/null 2>&1; then log_warn "返回数据非 JSON 格式,可能是中间日志干扰,等待中..." # 调试用:echo "$OUTPUT" sleep $SLEEP_INTERVAL ((RETRY_COUNT++)) continue fi # 解析状态 ONLINE_COUNT=$(echo "$OUTPUT" | jq '[.info.servers[] | select(.state == "online")] | length' 2>/dev/null) TOTAL_COUNT=$(echo "$OUTPUT" | jq '.info.servers | length' 2>/dev/null) ONLINE_COUNT=${ONLINE_COUNT:-0} TOTAL_COUNT=${TOTAL_COUNT:-0} if [ "$TOTAL_COUNT" -eq 0 ]; then log_warn "未检测到服务器节点信息,等待中..." sleep $SLEEP_INTERVAL ((RETRY_COUNT++)) continue fi log_info "状态更新:$ONLINE_COUNT / $TOTAL_COUNT 节点在线" if [ "$ONLINE_COUNT" -ne "$TOTAL_COUNT" ]; then echo "$OUTPUT" | jq -r '.info.servers[] | " - \(.endpoint): \(.state)"' | head -n 5 fi if [ "$ONLINE_COUNT" -eq "$TOTAL_COUNT" ]; then CLUSTER_READY=true break fi sleep $SLEEP_INTERVAL ((RETRY_COUNT++)) done if [ "$CLUSTER_READY" = true ]; then log_success "✅ 集群已就绪!确认 mc 别名配置..." # 再次确保 alias 是最新的(虽然前面可能成功了,但双重保险) if ! mc alias list | grep -q "mycluster"; then mc alias set mycluster "${MINIO_URL}" "${MINIO_ROOT_USER}" "${MINIO_ROOT_PASSWORD}" >/dev/null 2>&1 fi echo -e "${CYAN}--- 节点详情 ---${NC}" echo "$OUTPUT" | jq -r '.info.servers[] | " \(.endpoint): \(.state) | 硬盘:\(.drives | length) 盘"' echo -e "${CYAN}------------------${NC}" else log_error "❌ 集群未在预期时间内完全启动 (当前:$ONLINE_COUNT / $TOTAL_COUNT)!" echo "" echo -e "${YELLOW}========================================${NC}" echo -e "${YELLOW} 手动排查建议:${NC}" echo -e "${YELLOW}========================================${NC}" echo "1. 在 deploy 机器上查看 MinIO 实时日志 (需 SSH):" echo " ssh ${ENTRY_NODE} 'journalctl -u minio -f --no-pager | tail -n 50'" echo "" echo "2. 手动尝试连接 (确认 deploy 机网络是否通畅):" echo " mc admin info --json '${MINIO_URL}' '${MINIO_ROOT_USER}' '${MINIO_ROOT_PASSWORD}'" echo "" echo "3. 检查 deploy 机到目标节点的端口连通性:" echo " telnet ${FIRST_NAME} 9000" echo "" ((FAILED_COUNT++)) fi # ================= 总结 ================= echo "" echo "========================================" if [ $FAILED_COUNT -eq 0 ]; then log_success " 恭喜!MinIO 集群部署全部成功!" log_info " 访问控制台:http://${FIRST_NAME}:9001" log_info " 账号:${MINIO_ROOT_USER}" log_info " 密码:${MINIO_ROOT_PASSWORD}" log_info " 快速开始 (已在当前 shell 生效):" echo " export MC_HOST_mycluster='http://${MINIO_ROOT_USER}:${MINIO_ROOT_PASSWORD}@${FIRST_NAME}:9000'" echo " mc mb mycluster/mybucket" else log_error " 部署完成,但有 $FAILED_COUNT 处异常/失败,请检查上方日志。" exit 1 fi echo "========================================"