跳过正文
  1. 博客文章/

使用 Kubernetes CronJob 自动备份 MySQL 数据到 MinIO

·1656 字·8 分钟·
Kubernetes 数据库 Mysql 备份 MinIO Cronjob
Zayn
作者
Zayn
专注 Kubernetes、CI/CD、可观测性等云原生技术栈,记录生产环境中的实战经验与踩坑复盘。
目录

这套方案用 CronJob 定时执行 mysqldump,再把结果上传到 MinIO。重点是让备份任务能连到 MySQL 和 MinIO,并且能从日志、对象存储和恢复测试三个方面确认备份可用。

方案概述
#

备份架构
#

flowchart LR
    MySQL[MySQL Pod
Database] -->|导出数据| CronJob[CronJob Pod
mysqldump] CronJob -->|上传备份| MinIO[MinIO Server
Bucket]

备份文件放在独立的对象存储中,是否需要多数据库、监控和更复杂的保留策略,按实际环境决定。

环境准备
#

前置条件
#

组件版本要求说明
Kubernetesv1.18+支持 CronJob v1 API
MySQL5.7+ / 8.0+运行在 K8s 集群中
MinIO任意版本对象存储服务
网络连通性-CronJob Pod 能访问 MySQL 和 MinIO

自定义镜像
#

本方案使用了专门构建的备份镜像,包含了 mysqldump 和 MinIO 客户端工具。

镜像仓库: kube-mysqldump-tominio-cron

该镜像包含以下组件:

  • MySQL 客户端工具(mysqldump)
  • MinIO 客户端(mc)
  • 备份脚本和错误处理逻辑

详细配置指南
#

MySQL 数据库准备
#

在开始配置备份任务之前,确保您的 MySQL 数据库已正确部署在 Kubernetes 集群中:

# 检查 MySQL 服务状态
kubectl get pods -l app=mysql
kubectl get svc mysql-server

# 验证数据库连接
kubectl exec -it mysql-pod -- mysql -u root -p -e "SHOW DATABASES;"

MinIO 对象存储准备
#

创建存储桶
#

在 MinIO 中创建专用的备份存储桶:

# 使用 MinIO 客户端创建存储桶
mc mb minio/mysql-backups

# 设置存储桶策略(可选)
mc policy set download minio/mysql-backups

配置生命周期策略
#

为了管理存储成本,建议配置自动清理策略:

{
  "Rules": [
    {
      "ID": "mysql-backup-lifecycle",
      "Status": "Enabled",
      "Expiration": {
        "Days": 30
      }
    }
  ]
}

Kubernetes 配置详解
#

Secret 配置(敏感信息)
#

创建包含 MinIO 连接信息的 Secret:

环境变量说明示例值安全建议
MINIO_SERVERMinIO 服务器地址http://minio.example.com:9000使用内部服务地址
MINIO_ACCESS_KEYMinIO 访问密钥backup-user创建专用备份用户
MINIO_SECRET_KEYMinIO 密钥strong-password-123使用强密码
MINIO_BUCKET存储桶路径mysql-backups/prod按环境分目录

ConfigMap 配置(非敏感信息)
#

环境变量说明示例值配置说明
dbhostMySQL 服务地址mysql-server.default.svc.cluster.local使用完整的 DNS 名称
all_databases备份范围true / falsetrue=所有数据库,false=指定数据库
backup_schedule备份计划0 2 * * *Cron 表达式格式
backup_retention保留天数30本地备份文件保留时间

MySQL 连接配置
#

环境变量说明获取方式
DB_USER数据库用户名通常使用 root 用户
DB_PASS数据库密码从 MySQL Secret 中获取
DB_PORT数据库端口默认 3306

Cron 表达式说明
#

表达式说明执行时间
0 2 * * *每天凌晨2点适合生产环境
0 */6 * * *每6小时一次高频备份
0 2 * * 0每周日凌晨2点周备份
0 2 1 * *每月1号凌晨2点月备份

配置注意事项:

  • MinIO 存储桶已经创建,并具有写入权限。
  • CronJob Pod 可以访问 MySQL 和 MinIO 服务。
  • 备份用户具有足够的数据库读取权限。
  • CronJob 使用 UTC 时间,配置计划时要换算时区。
  • 备份任务设置了合适的 CPU 和内存限制。

完整部署配置
#

第一步:创建 ConfigMap
#

创建包含备份配置的 ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-backup-config
  namespace: default
  labels:
    app: mysql-backup
    component: config
data:
  # MySQL 连接配置
  dbhost: "mysql-server.default.svc.cluster.local"
  dbport: "3306"

  # 备份策略配置
  all_databases: "true"                    # 是否备份所有数据库
  backup_retention_days: "7"              # 本地备份保留天数
  compression_enabled: "true"             # 是否启用压缩

  # 备份文件命名规则
  backup_prefix: "mysql-backup"           # 备份文件前缀
  date_format: "%Y%m%d_%H%M%S"            # 时间戳格式

  # 高级配置
  mysqldump_options: "--single-transaction --routines --triggers --events"
  parallel_jobs: "1"                      # 并行备份任务数

第二步:创建 Secret
#

创建包含敏感信息的 Secret:

apiVersion: v1
kind: Secret
metadata:
  name: mysql-backup-secrets
  namespace: default
  labels:
    app: mysql-backup
    component: secrets
type: Opaque
stringData:
  # MinIO 连接信息
  MINIO_SERVER: "http://minio.minio-system.svc.cluster.local:9000"
  MINIO_ACCESS_KEY: "backup-user"
  MINIO_SECRET_KEY: "your-secure-password-here"
  MINIO_BUCKET: "mysql-backups/production"

  # MySQL 连接信息
  MYSQL_ROOT_PASSWORD: "your-mysql-root-password"

  # 可选:备份加密密钥
  BACKUP_ENCRYPTION_KEY: "your-encryption-key-for-backups"

第三步:创建 CronJob
#

创建执行备份任务的 CronJob:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: mysql-backup-cronjob
  namespace: default
  labels:
    app: mysql-backup
    component: cronjob
spec:
  # 备份计划:每天凌晨 2 点执行
  schedule: "0 2 * * *"

  # 时区设置(Kubernetes 1.24+)
  timeZone: "Asia/Shanghai"

  # 历史记录保留
  successfulJobsHistoryLimit: 3           # 保留最近 3 次成功的任务
  failedJobsHistoryLimit: 1               # 保留最近 1 次失败的任务

  # 并发策略
  concurrencyPolicy: Forbid               # 禁止并发执行

  # 启动截止时间
  startingDeadlineSeconds: 300            # 5分钟内必须启动

  # 任务模板
  jobTemplate:
    metadata:
      labels:
        app: mysql-backup
        component: job
    spec:
      # 任务完成后保留时间
      ttlSecondsAfterFinished: 86400      # 24小时后清理

      # 重试策略
      backoffLimit: 2                     # 最多重试 2 次

      template:
        metadata:
          labels:
            app: mysql-backup
            component: pod
        spec:
          # 重启策略
          restartPolicy: OnFailure

          # 服务账户(如需要特殊权限)
          # serviceAccountName: mysql-backup-sa

          containers:
          - name: mysql-backup
            image: cdryzun/kube-mysqldump-tominio-cron:v0.2.0
            imagePullPolicy: IfNotPresent

            # 资源限制
            resources:
              requests:
                memory: "256Mi"
                cpu: "100m"
              limits:
                memory: "1Gi"
                cpu: "500m"

            # 环境变量配置
            env:
            # 命名空间信息(通过 Downward API 注入)
            - name: NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace

            # Pod 名称(用于日志标识)
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name

            # MySQL 连接配置
            - name: DB_HOST
              valueFrom:
                configMapKeyRef:
                  name: mysql-backup-config
                  key: dbhost
            - name: DB_PORT
              valueFrom:
                configMapKeyRef:
                  name: mysql-backup-config
                  key: dbport
            - name: DB_USER
              value: "root"
            - name: DB_PASS
              valueFrom:
                secretKeyRef:
                  name: mysql-backup-secrets
                  key: MYSQL_ROOT_PASSWORD

            # 备份策略配置
            - name: ALL_DATABASES
              valueFrom:
                configMapKeyRef:
                  name: mysql-backup-config
                  key: all_databases
            - name: BACKUP_RETENTION_DAYS
              valueFrom:
                configMapKeyRef:
                  name: mysql-backup-config
                  key: backup_retention_days
            - name: MYSQLDUMP_OPTIONS
              valueFrom:
                configMapKeyRef:
                  name: mysql-backup-config
                  key: mysqldump_options

            # MinIO 存储配置
            - name: MINIO_SERVER
              valueFrom:
                secretKeyRef:
                  name: mysql-backup-secrets
                  key: MINIO_SERVER
            - name: MINIO_ACCESS_KEY
              valueFrom:
                secretKeyRef:
                  name: mysql-backup-secrets
                  key: MINIO_ACCESS_KEY
            - name: MINIO_SECRET_KEY
              valueFrom:
                secretKeyRef:
                  name: mysql-backup-secrets
                  key: MINIO_SECRET_KEY
            - name: MINIO_BUCKET
              valueFrom:
                secretKeyRef:
                  name: mysql-backup-secrets
                  key: MINIO_BUCKET

            # 备份文件配置
            - name: BACKUP_PREFIX
              valueFrom:
                configMapKeyRef:
                  name: mysql-backup-config
                  key: backup_prefix
            - name: DATE_FORMAT
              valueFrom:
                configMapKeyRef:
                  name: mysql-backup-config
                  key: date_format

            # 存储卷挂载
            volumeMounts:
            - name: backup-workspace
              mountPath: /tmp/backup
            - name: mysql-backup-script
              mountPath: /scripts
              readOnly: true

            # 健康检查
            livenessProbe:
              exec:
                command:
                - /bin/sh
                - -c
                - "ps aux | grep -v grep | grep mysqldump || exit 0"
              initialDelaySeconds: 30
              periodSeconds: 60
              timeoutSeconds: 10
              failureThreshold: 3

          # 存储卷定义
          volumes:
          - name: backup-workspace
            emptyDir:
              sizeLimit: 10Gi                # 限制临时存储大小
          - name: mysql-backup-script
            configMap:
              name: mysql-backup-script
              defaultMode: 0755

第四步:创建备份脚本 ConfigMap(可选)
#

如果需要自定义备份逻辑,可以创建包含备份脚本的 ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-backup-script
  namespace: default
data:
  backup.sh: |
    #!/bin/bash
    set -euo pipefail

    # 备份脚本配置
    TIMESTAMP=$(date +${DATE_FORMAT:-"%Y%m%d_%H%M%S"})
    BACKUP_FILE="${BACKUP_PREFIX:-mysql-backup}_${TIMESTAMP}.sql"

    # 日志函数
    log() {
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    }

    # 错误处理
    error_exit() {
        log "ERROR: $1"
        exit 1
    }

    # 执行备份
    log "开始 MySQL 数据库备份..."

    if [[ "${ALL_DATABASES:-true}" == "true" ]]; then
        log "备份所有数据库..."
        mysqldump -h${DB_HOST} -P${DB_PORT:-3306} -u${DB_USER} -p${DB_PASS} \
                  --all-databases ${MYSQLDUMP_OPTIONS:-} > /tmp/backup/${BACKUP_FILE} \
                  || error_exit "数据库备份失败"
    else
        log "备份指定数据库: ${TARGET_DATABASES}"
        mysqldump -h${DB_HOST} -P${DB_PORT:-3306} -u${DB_USER} -p${DB_PASS} \
                  --databases ${TARGET_DATABASES} ${MYSQLDUMP_OPTIONS:-} > /tmp/backup/${BACKUP_FILE} \
                  || error_exit "数据库备份失败"
    fi

    # 压缩备份文件
    if [[ "${COMPRESSION_ENABLED:-true}" == "true" ]]; then
        log "压缩备份文件..."
        gzip /tmp/backup/${BACKUP_FILE}
        BACKUP_FILE="${BACKUP_FILE}.gz"
    fi

    # 上传到 MinIO
    log "上传备份文件到 MinIO..."
    mc alias set minio ${MINIO_SERVER} ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
    mc cp /tmp/backup/${BACKUP_FILE} minio/${MINIO_BUCKET}/${BACKUP_FILE} \
       || error_exit "备份文件上传失败"

    log "备份完成: ${BACKUP_FILE}"

    # 清理本地文件
    rm -f /tmp/backup/${BACKUP_FILE}
    log "本地临时文件已清理"

部署和验证
#

部署步骤
#

按照以下顺序部署备份系统:

# 1. 创建 ConfigMap
kubectl apply -f mysql-backup-configmap.yaml

# 2. 创建 Secret(注意修改敏感信息)
kubectl apply -f mysql-backup-secret.yaml

# 3. 创建备份脚本 ConfigMap(如果使用自定义脚本)
kubectl apply -f mysql-backup-script-configmap.yaml

# 4. 创建 CronJob
kubectl apply -f mysql-backup-cronjob.yaml

# 5. 验证部署
kubectl get cronjob mysql-backup-cronjob
kubectl describe cronjob mysql-backup-cronjob

手动触发备份测试
#

在等待定时任务执行之前,可以手动创建一个 Job 来测试备份功能:

# 基于 CronJob 创建一次性 Job
kubectl create job mysql-backup-test --from=cronjob/mysql-backup-cronjob

# 查看 Job 状态
kubectl get job mysql-backup-test

# 查看 Pod 日志
kubectl logs -f job/mysql-backup-test

验证备份结果
#

# 检查 MinIO 中的备份文件
mc ls minio/mysql-backups/

# 验证备份文件完整性
mc cat minio/mysql-backups/mysql-backup_20231024_020000.sql.gz | gunzip | head -20

监控和告警
#

监控指标
#

通过以下方式监控备份任务的执行情况:

1. Kubernetes 原生监控
#

# 查看 CronJob 状态
kubectl get cronjob mysql-backup-cronjob -o wide

# 查看最近的 Job 执行历史
kubectl get jobs -l app=mysql-backup --sort-by=.metadata.creationTimestamp

# 查看失败的 Job
kubectl get jobs -l app=mysql-backup --field-selector status.successful!=1

2. 日志监控
#

# 查看最近备份任务的日志
kubectl logs -l app=mysql-backup --tail=100

# 实时监控备份过程
kubectl logs -f -l app=mysql-backup

3. Prometheus 监控指标
#

如果集群中部署了 Prometheus,可以监控以下指标:

# 示例 PrometheusRule
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: mysql-backup-alerts
spec:
  groups:
  - name: mysql-backup
    rules:
    - alert: MySQLBackupJobFailed
      expr: kube_job_status_failed{job_name=~"mysql-backup-.*"} > 0
      for: 0m
      labels:
        severity: critical
      annotations:
        summary: "MySQL 备份任务失败"
        description: "MySQL 备份任务 {{ $labels.job_name }} 执行失败"

    - alert: MySQLBackupJobMissing
      expr: time() - kube_cronjob_next_schedule_time{cronjob="mysql-backup-cronjob"} > 3600
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "MySQL 备份任务超时"
        description: "MySQL 备份任务超过预期时间未执行"

告警配置
#

1. 邮件告警
#

在备份脚本中添加邮件通知功能:

# 在备份脚本中添加
send_notification() {
    local status=$1
    local message=$2

    if [[ -n "${SMTP_SERVER:-}" ]]; then
        echo "$message" | mail -s "MySQL Backup $status" \
                              -S smtp="${SMTP_SERVER}" \
                              -S smtp-auth=login \
                              -S smtp-auth-user="${SMTP_USER}" \
                              -S smtp-auth-password="${SMTP_PASS}" \
                              "${NOTIFICATION_EMAIL}"
    fi
}

# 使用示例
send_notification "SUCCESS" "MySQL 备份成功完成: ${BACKUP_FILE}"
send_notification "FAILED" "MySQL 备份失败: ${ERROR_MESSAGE}"

2. Slack 通知
#

# Slack Webhook 通知
send_slack_notification() {
    local status=$1
    local message=$2
    local color="good"

    [[ "$status" == "FAILED" ]] && color="danger"

    curl -X POST -H 'Content-type: application/json' \
         --data "{
             \"attachments\": [{
                 \"color\": \"$color\",
                 \"title\": \"MySQL Backup $status\",
                 \"text\": \"$message\",
                 \"ts\": $(date +%s)
             }]
         }" \
         "${SLACK_WEBHOOK_URL}"
}

故障排除
#

常见问题及解决方案
#

1. 连接问题
#

问题: 无法连接到 MySQL 数据库

# 诊断步骤
kubectl exec -it mysql-backup-test-pod -- mysql -h${DB_HOST} -u${DB_USER} -p${DB_PASS} -e "SELECT 1"

# 检查网络连通性
kubectl exec -it mysql-backup-test-pod -- nc -zv mysql-server 3306

# 检查 DNS 解析
kubectl exec -it mysql-backup-test-pod -- nslookup mysql-server

解决方案:

  • 验证 MySQL 服务名称和端口
  • 检查网络策略是否阻止连接
  • 确认数据库用户权限

2. MinIO 上传失败
#

问题: 备份文件无法上传到 MinIO

# 测试 MinIO 连接
kubectl exec -it mysql-backup-test-pod -- mc alias set test ${MINIO_SERVER} ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
kubectl exec -it mysql-backup-test-pod -- mc ls test/

# 检查存储桶权限
kubectl exec -it mysql-backup-test-pod -- mc stat test/${MINIO_BUCKET}

解决方案:

  • 验证 MinIO 凭据和服务地址
  • 检查存储桶是否存在
  • 确认网络连通性

3. 资源不足
#

问题: 备份任务因资源不足而失败

# 检查节点资源使用情况
kubectl top nodes

# 检查 Pod 资源限制
kubectl describe pod mysql-backup-test-pod

# 查看事件日志
kubectl get events --sort-by=.metadata.creationTimestamp

解决方案:

  • 调整资源请求和限制
  • 优化备份策略(分批备份)
  • 增加集群资源

4. 备份文件过大
#

问题: 备份文件太大导致存储或传输问题

解决方案:

# 启用压缩
COMPRESSION_ENABLED=true

# 分库备份
ALL_DATABASES=false
TARGET_DATABASES="db1 db2"

# 使用增量备份(需要自定义脚本)
BACKUP_TYPE=incremental

日志分析
#

查看详细日志
#

# 查看 CronJob 事件
kubectl describe cronjob mysql-backup-cronjob

# 查看最近的 Job 日志
kubectl logs $(kubectl get pods -l app=mysql-backup --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')

# 查看失败的 Job 详情
kubectl describe job $(kubectl get jobs -l app=mysql-backup --field-selector status.successful!=1 -o jsonpath='{.items[0].metadata.name}')

常见错误信息
#

错误信息可能原因解决方案
Access denied for user数据库权限不足检查用户权限和密码
Can't connect to MySQL server网络连接问题检查服务名称和网络策略
The specified bucket does not existMinIO 存储桶不存在创建存储桶或检查配置
No space left on device磁盘空间不足清理空间或增加存储

运行效果展示
#

成功执行的备份任务
#

备份任务执行状态

图:Kubernetes Dashboard 中显示的备份任务执行状态

MinIO 中的备份文件
#

MinIO 备份文件列表

图:MinIO 对象存储中的备份文件列表,显示了按时间戳命名的备份文件

高级配置和优化
#

多数据库实例备份
#

对于多个 MySQL 实例的备份需求,可以采用以下策略:

方案一:多个 CronJob
#

为每个数据库实例创建独立的 CronJob:

# 为不同环境创建不同的备份任务
kubectl apply -f mysql-backup-production.yaml
kubectl apply -f mysql-backup-staging.yaml
kubectl apply -f mysql-backup-development.yaml

方案二:统一 CronJob 配置
#

使用一个 CronJob 配置多个数据库实例:

env:
- name: DB_INSTANCES
  value: "mysql-prod:3306,mysql-staging:3306,mysql-dev:3306"
- name: BACKUP_STRATEGY
  value: "parallel"  # 或 "sequential"

备份策略优化
#

1. 增量备份
#

实现基于 binlog 的增量备份:

# 在备份脚本中添加增量备份逻辑
if [[ "${BACKUP_TYPE}" == "incremental" ]]; then
    # 获取上次备份的 binlog 位置
    LAST_POSITION=$(cat /tmp/last_backup_position.txt)

    # 执行增量备份
    mysqlbinlog --start-position=${LAST_POSITION} \
                --host=${DB_HOST} \
                --user=${DB_USER} \
                --password=${DB_PASS} \
                > /tmp/backup/incremental_${TIMESTAMP}.sql
fi

2. 并行备份
#

对于大型数据库,可以实现并行备份:

# 并行备份多个数据库
backup_database() {
    local db_name=$1
    mysqldump -h${DB_HOST} -u${DB_USER} -p${DB_PASS} \
              --single-transaction \
              ${db_name} > /tmp/backup/${db_name}_${TIMESTAMP}.sql &
}

# 获取数据库列表并并行备份
for db in $(mysql -h${DB_HOST} -u${DB_USER} -p${DB_PASS} -e "SHOW DATABASES;" | grep -v "Database\|information_schema\|performance_schema\|mysql\|sys"); do
    backup_database $db
done

wait  # 等待所有后台任务完成

安全性增强
#

1. 备份加密
#

# 使用 GPG 加密备份文件
encrypt_backup() {
    local backup_file=$1
    gpg --symmetric --cipher-algo AES256 \
        --passphrase "${BACKUP_ENCRYPTION_KEY}" \
        --batch --yes \
        --output "${backup_file}.gpg" \
        "${backup_file}"
    rm "${backup_file}"  # 删除未加密的文件
}

2. 访问控制
#

# 创建专用的 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: mysql-backup-sa
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: mysql-backup-role
rules:
- apiGroups: [""]
  resources: ["secrets", "configmaps"]
  verbs: ["get", "list"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: mysql-backup-binding
subjects:
- kind: ServiceAccount
  name: mysql-backup-sa
roleRef:
  kind: Role
  name: mysql-backup-role
  apiGroup: rbac.authorization.k8s.io

上线前要验证的事
#

先手动运行一次备份任务,确认 MySQL 导出、MinIO 上传和清理策略都正常,再交给 CronJob 定时执行。备份文件需要定期做完整性和恢复测试,数据库用户按最小权限配置,密码和 MinIO 凭据不要写进公开的 YAML。资源、网络、压缩和保留周期根据数据库大小调整;跨云、PVC 备份和自动恢复属于后续扩展。

相关文章

Kubernetes 部署 Kafka 集群记录
·3073 字·15 分钟
Kafka Helm Kubernetes Zookeeper Streaming
Kubernetes NFS 动态存储类部署记录
·453 字·3 分钟
存储 Nfs Kubernetes Storageclass
Traefik Ingress Controller 完整部署指南
·1278 字·6 分钟
网络 网关 Kubernetes Traefik Ingress