小易说IT 小易说IT

MinIO mc mirror 完整版生产同步脚本

特性:

  1. 脚本锁,防止 crontab 重复并发执行

  2. 钉钉 Webhook 告警(成功 / 失败都可配置通知)

  3. dry-run 试运行开关

  4. 日志输出,配套 logrotate 日志切割

  5. 失败重试、返回码判断、异常捕获

  6. 支持环境变量传入 MinIO 密钥,可选择不硬编码 AK/SK

文件名:sync_local_to_minio_prod.sh

#!/bin/bash
set -euo pipefail

##############################################################################
# 【生产配置区 - 修改这里】
##############################################################################
# MinIO 配置
MINIO_ALIAS="myminio"
MINIO_ENDPOINT="http://127.0.0.1:9000"
MINIO_ACCESS_KEY="minioadmin"
MINIO_SECRET_KEY="minioadmin"

# 源目录 & 目标桶
SRC_LOCAL="/data/source_files"
DST_BUCKET="${MINIO_ALIAS}/mybucket"

# 同步参数
ENABLE_REMOVE="false"          # true=镜像,删除目标多余对象;生产谨慎开启
DRY_RUN="true"                 # 上线前先true试运行,确认无误改成false
RETRY_COUNT=3

# 钉钉告警 webhook地址
DING_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=xxx"
DING_NOTIFY_SUCCESS="false"    # 同步成功是否发送钉钉通知

# 日志 & 锁文件
LOG_FILE="/var/log/minio_sync_prod.log"
LOCK_FILE="/var/run/minio_sync.lock"
##############################################################################

# ===================== 工具函数 =====================
# 日志打印
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
}

# 钉钉消息发送函数
send_ding() {
    local MSG_CONTENT="$1"
    curl -s -X POST "${DING_WEBHOOK}" \
    -H "Content-Type: application/json" \
    -d '{
        "msgtype": "text",
        "text": {
            "content": "【MinIO同步任务】'"${MSG_CONTENT}"'"
        }
    }' > /dev/null
}

# 清理锁文件(不管正常退出还是异常退出)
cleanup() {
    if [ -f "${LOCK_FILE}" ]; then
        rm -f "${LOCK_FILE}"
        log "已释放锁文件 ${LOCK_FILE}"
    fi
}

# trap捕获退出信号,确保锁一定会释放
trap cleanup EXIT INT TERM

# ===================== 脚本锁控制 =====================
if [ -f "${LOCK_FILE}" ]; then
    PID=$(cat "${LOCK_FILE}")
    if ps -p "${PID}" > /dev/null; then
        MSG="任务正在运行,PID:${PID},本次脚本退出!"
        log "${MSG}"
        send_ding "${MSG}"
        exit 2
    else
        log "发现残留锁文件,旧进程已死亡,清理锁继续执行"
        rm -f "${LOCK_FILE}"
    fi
fi
# 写入当前PID到锁文件
echo $$ > "${LOCK_FILE}"

# ===================== 前置校验 =====================
log "==== MinIO同步任务开始 ===="

# 检查mc命令
if ! command -v mc &> /dev/null; then
    MSG="ERROR: mc客户端不存在,请先安装minio mc"
    log "${MSG}"
    send_ding "${MSG}"
    exit 1
fi

# 检查源目录
if [ ! -d "${SRC_LOCAL}" ];then
    MSG="ERROR: 源目录 ${SRC_LOCAL} 不存在"
    log "${MSG}"
    send_ding "${MSG}"
    exit 1
fi

# 配置minio别名,关闭历史记录,防止密钥写入history
set +o history
mc alias set "${MINIO_ALIAS}" "${MINIO_ENDPOINT}" "${MINIO_ACCESS_KEY}" "${MINIO_SECRET_KEY}"
set -o history

# 连通性测试
if ! mc ls "${MINIO_ALIAS}" &>/dev/null; then
    MSG="ERROR: MinIO连接失败,请检查端点/AK/SK"
    log "${MSG}"
    send_ding "${MSG}"
    exit 1
fi

# ===================== 组装同步参数 =====================
MIRROR_ARGS=("--overwrite" "--retry" "${RETRY_COUNT}")

if [ "${DRY_RUN}" = "true" ];then
    MIRROR_ARGS+=("--dry-run")
fi

if [ "${ENABLE_REMOVE}" = "true" ];then
    MIRROR_ARGS+=("--remove")
fi

log "执行命令:mc mirror ${MIRROR_ARGS[*]} ${SRC_LOCAL} ${DST_BUCKET}"

# ===================== 执行同步 =====================
RET=0
mc mirror "${MIRROR_ARGS[@]}" "${SRC_LOCAL}" "${DST_BUCKET}" || RET=$?

if [ ${RET} -eq 0 ];then
    MSG="同步执行成功!DRY_RUN=${DRY_RUN}, ENABLE_REMOVE=${ENABLE_REMOVE}"
    log "${MSG}"
    if [ "${DING_NOTIFY_SUCCESS}" = "true" ];then
        send_ding "${MSG}"
    fi
else
    MSG="同步失败,返回码:${RET}"
    log "${MSG}"
    send_ding "${MSG}"
    exit ${RET}
fi

log "==== MinIO同步任务结束 ===="
exit 0

脚本部署

# 创建脚本,粘贴内容
vim /opt/scripts/sync_local_to_minio_prod.sh

# 赋权
chmod +x /opt/scripts/sync_local_to_minio_prod.sh

# 测试运行
/opt/scripts/sync_local_to_minio_prod.sh

logrotate 配置文件

新建 /etc/logrotate.d/minio_sync,用于自动切割日志,防止日志无限膨胀

/var/log/minio_sync_prod.log {
    daily
    rotate 30
    missingok
    notifempty
    compress
    delaycompress
    create 0644 root root
    dateext
    postrotate
        # 可选:重启相关服务,这里只是日志文件不需要
    endscript
}
  • daily:按天切割

  • rotate 30:保留最近 30 天日志

  • compress:压缩旧日志

  • dateext:文件名带日期后缀

测试 logrotate 是否生效:

logrotate -d /etc/logrotate.d/minio_sync

Crontab 定时任务配置

# 编辑crontab
crontab -e

示例:每天凌晨 2 点执行同步脚本

0 2 * * * /bin/bash /opt/scripts/sync_local_to_minio_prod.sh >> /dev/null 2>&1

脚本内部已经处理日志写入LOG_FILE,crontab 不需要再重定向日志。

安全加固建议(生产必看)

  1. 密钥不要明文写脚本 改用环境变量方式,删除脚本内 AK/SK,在 crontab 或者脚本开头 export 环境变量:

    export MC_HOST_myminio=http://minioadmin:minioadmin@127.0.0.1:9000
    

    之后脚本不需要执行mc alias set,直接使用myminio/bucket

  2. 锁文件存放目录 /var/run 是 tmpfs,重启自动清理锁文件,不会出现永久死锁。

  3. 钉钉机器人安全:

    • 建议在钉钉机器人设置里开启IP 白名单,只允许服务器出口 IP 调用 webhook。

    • 或者使用加签方式(当前脚本是简单 token 版,如需加签我可以改代码)。

  4. ENABLE_REMOVE=true 一定要先DRY_RUN=true跑几次,确认删除对象列表无误再开启。

  5. 脚本权限收紧:

    chmod 700 /opt/scripts/sync_local_to_minio_prod.sh
    chown root:root /opt/scripts/sync_local_to_minio_prod.sh
    

可选扩展

如果你需要,我可以再增加:

  • 同步前后统计桶对象数量、总大小,放到钉钉消息里

  • 增加超时控制(timeout 防止 mc mirror 卡死)

  • 跨 MinIO 集群桶同步版本(源 MinIO 桶 → 目标 MinIO 桶)


本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/minio-mc-mirror-production-sync-script

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/