Dockerfile实现MySQL定时备份

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/alinyua/article/details/82532988

本文利用自定制Dockerfile实现在mysql容器内自动定时备份,避免了在宿主机设置cron定时任务所带来的高耦合操作,更易于部署和搬迁.项目最新版已上传到githubDockerHub,建议直接到DockerHub查看使用.

网上其他教程大多都是利用宿主机crontab定期执行docker exec实现,很简单但每次部署mysql都需要设置,本教程则是将其封装起来,避免重复性工作.踩坑不易,请大家多多支持,未经本人允许禁止转载.

一. 文件结构

这里写图片描述

二. 文件介绍

  • cron-shell文件夹
    放置cron任务表文件crontab.bak和相关任务脚本,对应容器内文件夹为/cron-shell
    • backup.sh
      实现备份功能,用户可替换掉该文件以实现自己的备份逻辑
    • crontab.bak
      crontab任务表,要求结尾必须换行,且文件为Unix格式
  • init-shell文件夹
    init-shell文件夹下的脚本在容器启动后由mysql用户执行(注意不是root用户)
    • start.sh
      功能:
      1. 将mysql用户的环境变量写入/etc/default/locale文件,使得这些环境变量对cron运行的脚本(如backup.sh)可见
      2. 启动cron
      3. 载入/cron-shell/crontab.bak定时任务列表文件
  • init-sql文件夹
    inti-sql文件夹下的sql脚本(.sql)或者sql的压缩文件(.sql.gz)在容器启动后由msql运行,通常用来实现数据库初始化,需要注意的是这些sql的执行顺序不固定,故如果对sql脚本执行顺序有要求的话应自己使用shell脚本实现
    • data_20180909.sql.gz
      数据库初始化sql的压缩包
  • Dockerfile
    将文件复制进镜像文件,并安装sudo,cron工具,同时授予mysql组用户免密码使用sudo的权限.
  • docker-compose.yml
    非必须,该文件中所有功能可通过docker run携带参数代替,但本人习惯使用docker-compose.yml文件将docker的启动参数记录下来,使用时直接docker-compose up -d就行

三. 实现思路

通过Dockerfile安装所需cron,利用docker-entrypoint.sh中自动执行/docker-entrypoint-initdb.d文件夹下shell和sql脚本的功能完成cron自启动和任务制定

四. 文件实现

1. baskup.sh

#!/bin/bash
#作为crontab运行的脚本,需特别注意环境变量问题,指令写成绝对路径

#读取环境变量
. /etc/profile
#如果目录不存在则新建
DIR=/var/lib/mysql/backup
if [ ! -e $DIR ]
then
/bin/mkdir -p $DIR
fi
#将所有数据库导出并按日期命名保存成sql文件并压缩
/usr/bin/mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"   | gzip > "$DIR/data_`date +%Y%m%d`.sql.gz"
#查找更改时间在7日以前的sql备份文件并删除
/usr/bin/find $DIR -mtime +7 -name 'data_[1-9].sql' -exec rm -rf {} \;

2. crontab.bak

*/1 * * * * /cron-shell/backup.sh

注意结尾要换行,并且文件格式要是Unix(其他所有sh脚本也是)

3. start.sh

#!/bin/bash

#此脚本由mysql用户执行,故需要加sudo避免权限不够,同时,新建文件一般放在/var/lib/mysql目录下,否则同样权限不够

#修改文件夹权限,否则无法在该目录下创建文件
sudo chmod 777 /etc/default
#将docker的环境变量输出到locale,使得cron定期运行的脚本可以使用这些环境变量,否则执行备份脚本时可能提示密码为空
env >> /etc/default/locale

#启动cron并将其启动结果写入文件
# 这里需要使用sudo,否则会提示cron: can't open or create /var/run/crond.pid: Permission denied
# 这个问题无法通过修改/var/run/crond或/run文件夹权限解决
# 需要注意的是,使用sudo后cron是在root用户下运行的,root用户下使用`service cron status`会出现` [ ok ] cron is running. `
# 而mysql执行`service cron status`则会出现` [FAIL] cron is not running ... failed! `
# 虽然如此,mysql用户身份制定的定时任务还是会执行的
sudo /usr/sbin/service cron start &>> /var/lib/mysql/cron-start.log

#授予权限
sudo chmod 777 -R /cron-shell

#修正文件格式,这里dos2unix的执行也需要sudo,否则会报错`Failed to change the owner and group of temporary output file /cron-shell/d2utmpKfjPMF: Operation not permitted`
for f in /cron-shell/*; do
    sudo dos2unix "$f"
done

# 确保结尾换行,避免出现错误:`new crontab file is missing newline before EOF, can't install.`
echo "" >> /cron-shell/crontab.bak

#以/cron-shell/crontab.bak作为crontab的任务列表文件并载入
# 因为执行的定时任务一般是数据库相关的,mysql用户就可以了,如果使用root用户可能会报错:`Got error: 1045: Access denied for user 'root'@'localhost' (using password: YES) when trying to connect`
# 所以这里使用mysql用户载入定时任务表,任务脚本也将以mysql用户执行,需注意权限问题
crontab /cron-shell/crontab.bak

4. Dockerfile

FROM mysql:5.7

MAINTAINER LinShen

#将任务脚本复制进容器,需要注意不能放到/var/lib/mysql目录下,该目录随mysql初始化会被清空造成原文件丢失
COPY cron-shell/ /cron-shell/
#将int-shell中的脚本都复制到初始化文件夹中
COPY init-shell/ /docker-entrypoint-initdb.d/

#修正时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
      && echo 'Asia/Shanghai' >/etc/timezone \
#更新源
      && apt-get update \
#安装cron
      && apt-get install -y  --no-install-recommends cron \
#安装dos2unix工具
      && apt-get install -y  dos2unix \
#安装sudo
      && apt-get install sudo \
#授予mysql组用户sudo免密码
      && echo '%mysql ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers \
#减小镜像的体积
      && rm -rf /var/lib/apt/lists/*  \
      && apt-get clean \
#赋予脚本可执行权限
      && chmod a+x -R /docker-entrypoint-initdb.d

5. docker-compose.yml

这个文件自由实现,这里可以注意一下command的写法

version: '3.1'

services:
 mysql-db:
  build: ./mysql
  restart: always
  command:
   - --character-set-server=utf8mb4
   - --collation-server=utf8mb4_unicode_ci
  environment:
   MYSQL_ROOT_PASSWORD: hdcpptd123.
  ports:
  - "3306:3306"
  volumes:
  - /my/own/datadir:/var/lib/mysql

五. 问题

(1) 在什么时候启动cron(在哪里执行service cron start)

我本来是想在Dockerfile里完成启动cron工作的,Dockerfile里执行语句无外乎RUN,CMDENTRYPOINT三种指令,但很可惜这三种指令都不行,最后只能利用docker-entrypoint.sh,下面进行分析说明:

1. RUN

RUN的特点是可以执行多条,每执行一条docker镜像就会多构建一层.
需要注意的是RUN通常只用来构建镜像,而镜像是不带有运行状态的.故RUN可完成安装软件,修改文件等操作,但使用service start是无意义的.
而CMD和ENTRYPOINT则是等到容器启动后才运行.

2. CMD

CMD每个Dockerfile文件可以有多条,但一个镜像只有最后一条CMD有效
MySQL官方镜像已经有用到CMD和ENTRYPOINT指令了,分别是CMD ["mysqld"]ENTRYPOINT ["docker-entrypoint.sh"],当ENTRYPOINT和CMD同时存在时,CMD通常是作为ENTRYPOINT的参数,其内容视ENTRYPOINT的处理而执行.
而docker-entrypoint.sh中对cmd内容的分类有如下:

  1. 开头是 - , 认为是参数的情况
    可携带参数,如–character-set-server=utf8mb4–collation-server=utf8mb4_unicode_ci
  2. 开头是 mysqld, 且用户 id 为0 (root 用户) 的情况
  3. 开头是 mysqld 的情况
  4. 其他情况
    由docker-entrypoint.sh最后一句的exec "$@"负责执行用户的自定义语句

特别注意:前3种情况在docker-entrypoint.sh都有对应的处理,最终都会启动mysql,而最后一种情况不会 !

在前3种情况中CMD无法携带service cron start,会被视为参数忽略或报错,而最后一种启动cron的语句如下CMD service cron start && tail -n 1 -f somefile,其中tail语句是为了让容器在前台运行而不会自动退出.但是使用docker exec 进入容器就可以发现,mysql根本没有运行.而如果想在CMD里面同时启动cron和mysql就更麻烦了,几乎不可能(因为有很多mysql的初始化工作要做,这些本来是docker-entrypoint帮你做好的),而且这样就失去了使用docker-entrypoint.sh的意义,得不偿失.

3. ENTRYPOINT

ENTRYPOINT每个Dockerfile文件可以有多条,但一个镜像只有最后一条ENTRYPOINT有效
MySQL官方镜像的ENTRYPOINT是执行docker-entrypoint.sh,所以这一句是改不了的,要改不如直接改docker-entrypoint.sh.

以上,可以看出来,想要在Dockerfile里执行service cron start是不可能的,唯一的办法在docker-entrypoint.sh文件.

4. docker-entrypoint.sh

该文件的详细解读网上有比较多,可以直接把执行语句加在这里,毕竟这个文件就是来做这种事的,这也确实是个实现思路.但这么做修改了原来的文件,很不优雅,不符合开闭原则,还好MySQL镜像的设计者考虑到了这点,留下了个/docker-entrypoint-initdb.d文件夹
docker-entrypoint中相关语句如下

        for f in /docker-entrypoint-initdb.d/*; do
            process_init_file "$f" "${mysql[@]}"
        done

如上,会遍历/docker-entrypoint-initdb.d中的每个文件作为参数调用process_init_file方法,该方法如下

# usage: process_init_file FILENAME MYSQLCOMMAND...
#    ie: process_init_file foo.sh mysql -uroot
# (process a single initializer file, based on its extension. we define this
# function here, so that initializer scripts (*.sh) can use the same logic,
# potentially recursively, or override the logic used in subsequent calls)
process_init_file() {
    local f="$1"; shift
    local mysql=( "$@" )

    case "$f" in
        *.sh)     echo "$0: running $f"; . "$f" ;;
        *.sql)    echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;;
        *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;;
        *)        echo "$0: ignoring $f" ;;
    esac
    echo
}

如上,需要执行的shell脚本或者sql脚本,可放到/docker-entrypoint-initdb.d/目录下,如果是sql.gz的压缩包,容器会自动解压再执行.
需要注意的是,只能保证遍历全部执行而不保证顺序,所以有顺序需求的需要利用脚本自己实现.

很明显,cron的启动脚本应该放在这个文件夹下

(2) sh脚本文件须为Unix格式(Windows下默认为dos),同时注意提供可执行权限

dos转unix格式通常有以下几种方式:
1. windows:使用notepad++
依次点击”编辑”->”文档格式转换” ->”转换为UNIX格式”
2. linux:使用vim
在命令模式输入set ff=unix回车即可,set ff可以查看格式
3. linux:使用dos2unix
dos2unix后直接加文件名即可

猜你喜欢

转载自blog.csdn.net/alinyua/article/details/82532988