本文利用自定制Dockerfile实现在mysql容器内自动定时备份,避免了在宿主机设置cron定时任务所带来的高耦合操作,更易于部署和搬迁.项目最新版已上传到github和DockerHub,建议直接到DockerHub查看使用.
网上其他教程大多都是利用宿主机crontab定期执行docker exec实现,很简单但每次部署mysql都需要设置,本教程则是将其封装起来,避免重复性工作.踩坑不易,请大家多多支持,未经本人允许禁止转载.
docker-compose up -d
就行通过Dockerfile安装所需cron,利用docker-entrypoint.sh中自动执行**/docker-entrypoint-initdb.d**文件夹下shell和sql脚本的功能完成cron自启动和任务制定
#!/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.gz" -exec rm -rf {} \;
*/1 * * * * /cron-shell/backup.sh
注意结尾要换行,并且文件格式要是Unix(其他所有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
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
这个文件自由实现,这里可以注意一下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
service cron start
)我本来是想在Dockerfile里完成启动cron工作的,Dockerfile里执行语句无外乎RUN
,CMD
和ENTRYPOINT
三种指令,但很可惜这三种指令都不行,最后只能利用docker-entrypoint.sh,下面进行分析说明:
RUN的特点是可以执行多条,每执行一条docker镜像就会多构建一层.
需要注意的是RUN通常只用来构建镜像,而镜像是不带有运行状态的.故RUN可完成安装软件,修改文件等操作,但使用service start是无意义的.
而CMD和ENTRYPOINT则是等到容器启动后才运行.
CMD每个Dockerfile文件可以有多条,但一个镜像只有最后一条CMD有效
MySQL官方镜像已经有用到CMD和ENTRYPOINT指令了,分别是CMD ["mysqld"]
和ENTRYPOINT ["docker-entrypoint.sh"]
,当ENTRYPOINT和CMD同时存在时,CMD通常是作为ENTRYPOINT的参数,其内容视ENTRYPOINT的处理而执行.
而docker-entrypoint.sh中对cmd内容的分类有如下:
-
, 认为是参数的情况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的意义,得不偿失.
ENTRYPOINT每个Dockerfile文件可以有多条,但一个镜像只有最后一条ENTRYPOINT有效
MySQL官方镜像的ENTRYPOINT是执行docker-entrypoint.sh,所以这一句是改不了的,要改不如直接改docker-entrypoint.sh.
以上,可以看出来,想要在Dockerfile里执行service cron start是不可能的,唯一的办法在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的启动脚本应该放在这个文件夹下
dos转unix格式通常有以下几种方式: