盒子
盒子
文章目录
  1. 0.前言
    1. 0.1 缺点的发现
  2. 0.2 Dockerfile中的关键点
  3. 1.方式一:基于MySQL官方Dockerfile改造
  4. 2.方式二:基于MySQL官方镜像改造
  5. 3.结语

Dockerfile构建支持定时备份的MySQL镜像

0.前言

  承接上一篇文章,手上的项目选择使用Docker进行部署,其方便快捷的特性深深吸引了我。但是,最近实验室某项目遭受黑客攻击,数据丢失,给我们敲响了警钟,数据备份的重要性不言而喻。于是,我想到如何让Docker镜像自动定期备份,成为了我的研究重点。参考了鲸临于空的Dockerfile实现MySQL定时备份一文,利用Dockerfile基于官方的MySQL镜像构建自定义的镜像,从而实现更加简单的crontab定时任务。

0.1 缺点的发现

  鲸临于空所写的教程和建的镜像,大体上已完成了定期数据备份。但是,最大的问题在于,鲸临于空所构建的镜像仅对新数据库有效,对已存在的数据库,并不能定期备份。其原因在于MySQL官方镜像构建时,Entrypoint所使用的shell脚本中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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
}

  该方法仅在if [ ! -d "$DATADIR/mysql" ];判断MySQL文件夹不存在时才执行,所有当数据库已存在时,便不会再次构建数据库,更不会执行/docker-entrypoint-initdb.d目录下的shell脚本、sql文件等。参考上述资料之后,我们可以明白,要想构建的镜像支持已存在的数据库,方便使用者更加自由的从官方镜像切换过来,就必须改变Entrypoint所使用的shell脚本。于是,我尝试了两种不同的方法构建目标镜像。经过验证,ENTRYPOINT每个Dockerfile文件可以有多条,但一个镜像只有最后一条ENTRYPOINT有效。

0.2 Dockerfile中的关键点

  Dockerfile有很多需要我们去学习的东西,这里推荐yeasy的Docker文档。这里我只列出本文的关键点:ENTRYPOINT 入口点ENTRYPOINT的格式和RUN指令格式一样,分为exec格式和shell格式。ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及参数。而我们这里需要用到的场景便是应用运行前的准备工作

1.方式一:基于MySQL官方Dockerfile改造

  参考MySQL官方开放在Gihub上的Dockerfiledocker-entrypoint.sh文件,进行如何更改:
  - Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
FROM debian:stretch-slim
# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r mysql && useradd -r -g mysql mysql
RUN apt-get update && apt-get install -y --no-install-recommends gnupg dirmngr && rm -rf /var/lib/apt/lists/*
# add gosu for easy step-down from root
ENV GOSU_VERSION 1.7
RUN set -x \
&& apt-get update && apt-get install -y --no-install-recommends ca-certificates wget && rm -rf /var/lib/apt/lists/* \
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" \
&& wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
&& gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
&& gpgconf --kill all \
&& rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true \
&& apt-get purge -y --auto-remove ca-certificates wget
RUN mkdir /docker-entrypoint-initdb.d
RUN apt-get update && apt-get install -y --no-install-recommends \
# for MYSQL_RANDOM_ROOT_PASSWORD
pwgen \
# FATAL ERROR: please install the following Perl modules before executing /usr/local/mysql/scripts/mysql_install_db:
# File::Basename
# File::Copy
# Sys::Hostname
# Data::Dumper
perl \
# mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory
libaio1 \
# mysql: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory
libncurses5 \
&& rm -rf /var/lib/apt/lists/*
ENV MYSQL_MAJOR 5.5
ENV MYSQL_VERSION 5.5.62
RUN apt-get update && apt-get install -y ca-certificates wget --no-install-recommends && rm -rf /var/lib/apt/lists/* \
&& wget "https://cdn.mysql.com/Downloads/MySQL-$MYSQL_MAJOR/mysql-$MYSQL_VERSION-linux-glibc2.12-x86_64.tar.gz" -O mysql.tar.gz \
&& wget "https://cdn.mysql.com/Downloads/MySQL-$MYSQL_MAJOR/mysql-$MYSQL_VERSION-linux-glibc2.12-x86_64.tar.gz.asc" -O mysql.tar.gz.asc \
&& apt-get purge -y --auto-remove ca-certificates wget \
&& export GNUPGHOME="$(mktemp -d)" \
# gpg: key 5072E1F5: public key "MySQL Release Engineering <[email protected]>" imported
&& gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5 \
&& gpg --batch --verify mysql.tar.gz.asc mysql.tar.gz \
&& gpgconf --kill all \
&& rm -rf "$GNUPGHOME" mysql.tar.gz.asc \
&& mkdir /usr/local/mysql \
&& tar -xzf mysql.tar.gz -C /usr/local/mysql --strip-components=1 \
&& rm mysql.tar.gz \
&& rm -rf /usr/local/mysql/mysql-test /usr/local/mysql/sql-bench \
&& rm -rf /usr/local/mysql/bin/*-debug /usr/local/mysql/bin/*_embedded \
&& find /usr/local/mysql -type f -name "*.a" -delete \
&& apt-get update && apt-get install -y binutils && rm -rf /var/lib/apt/lists/* \
&& { find /usr/local/mysql -type f -executable -exec strip --strip-all '{}' + || true; } \
&& apt-get purge -y --auto-remove binutils
ENV PATH $PATH:/usr/local/mysql/bin:/usr/local/mysql/scripts
# replicate some of the way the APT package configuration works
# this is only for 5.5 since it doesn't have an APT repo, and will go away when 5.5 does
RUN mkdir -p /etc/mysql/conf.d \
&& { \
echo '[mysqld]'; \
echo 'skip-host-cache'; \
echo 'skip-name-resolve'; \
echo 'datadir = /var/lib/mysql'; \
echo '!includedir /etc/mysql/conf.d/'; \
} > /etc/mysql/my.cnf
RUN mkdir -p /var/lib/mysql /var/run/mysqld \
&& chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \
# ensure that /var/run/mysqld (used for socket and lock files) is writable regardless of the UID our mysqld instance ends up having at runtime
&& chmod 777 /var/run/mysqld
###################### Crontab Initialize ###############################
# 将任务脚本复制进容器
COPY j-entrypoint/cron-shell/ /j-entrypoint/cron-shell/
COPY j-entrypoint/init-shell/ /j-entrypoint/init-shell/
# 修正时区
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 /j-entrypoint
VOLUME /var/lib/mysql
COPY docker-entrypoint.sh /usr/local/bin/
RUN dos2unix /usr/local/bin/docker-entrypoint.sh \
&& chmod 777 /usr/local/bin/docker-entrypoint.sh \
&& ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 3306
CMD ["mysqld"]

  - docker-entrypoint.sh

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
######################## Crontab Initialize #######################
for f in /j-entrypoint/init-shell/*; do
case "$f" in
*.sh) echo "running $f"; . "$f";;
*) echo "ignoring $f";;
esac
echo
done
######################## MySQL Official Initialize #######################
############## 此处省略MySQ官方的docker-entrypoint.sh剩余内容 ##############

  可以看到我仅对官方Dockerfile文件末尾部分和docker-entrypoint.sh的开头部分做了修改,学习鲸临于空将crondos2unix安装到镜像中,并赋予相关权限。值得注意的是:

1
2
3
RUN dos2unix /usr/local/bin/docker-entrypoint.sh \
&& chmod 777 /usr/local/bin/docker-entrypoint.sh \
&& ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat

  由于我重写了docker-entrypoint.sh,所以这里重新格式化并附于执行权限,保证镜像的正常构建。由于每次启动容器时,都会执行docker-entrypoint.sh,于是在其开头部分加入执行/j-entrypoint/init-shell/*目录下所有shell文件,便可实现定时任务的自动设定。
  这里给出/j-entrypoint目录下所有文件内容:
  - /j-entrypoint/init-shell/目录下的start.sh。该文件是对相关文件进行赋权,并加载crontab文件内的定时任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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 /j-entrypoint/cron-shell/*; do
sudo dos2unix "$f"
done
# 确保结尾换行,避免出现错误:`new crontab file is missing newline before EOF, can't install.`
echo "" >> /j-entrypoint/cron-shell/crontab
#以/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 /j-entrypoint/cron-shell/crontab

  - /j-entrypoint/cron-shell/目录下的backup.sh。这个文件是可以自定义的,不限于备份数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
#作为crontab运行的脚本,需特别注意环境变量问题,指令写成绝对路径
#读取环境变量
. /etc/profile
#如果目录不存在则新建
DIR=/var/lib/mysql/backup
if [ ! -e $DIR ]
then
/bin/mkdir -p $DIR
fi
#将所有数据库导出并按日期命名保存成sql.gz
/usr/local/mysql/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 {} \;

  - /j-entrypoint/cron-shell/目录下的crontab。此文件必须名为crontab,否则无法正常载入定时任务。

1
* 4 * * * /j-entrypoint/cron-shell/backup.sh

2.方式二:基于MySQL官方镜像改造

  不可否认,以上操作实现了我们需要的功能,但太过复杂,于是我想到通过重新拷贝docker-entrypoint.sh到镜像中,并重新链接到/entrypoint.sh的方式,简化了Dockfile文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
FROM mysql:5.5.62
MAINTAINER Jiacy
#将任务脚本复制进容器
COPY j-entrypoint/cron-shell/ /j-entrypoint/cron-shell/
COPY j-entrypoint/init-shell/ /j-entrypoint/init-shell/
#将修改后的docker-entrypoint.sh脚本复制到/usr/local/bin/目录
COPY docker-entrypoint.sh /usr/local/bin/
#修正时区
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 /j-entrypoint
#更新docker-entrypoint.sh脚本
RUN dos2unix /usr/local/bin/docker-entrypoint.sh \
&& chmod 777 /usr/local/bin/docker-entrypoint.sh \
&& rm /entrypoint.sh \
&& ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat

  通过这样简短的方式,将修改后的docker-entrypoint.sh拷贝进镜像,并移除原有的entrypoint.sh文件,最后重新进行软链接,便可实现跟方法一相同的功能。

3.结语

  经历千辛万苦,构建出了符合自己需求的镜像,同样我也上传到了Dockerhub中,使用时仅需要把自定义的shell文件和一个必须名为crontab的任务表文件挂载到/j-entrypoint/cron-shell/目录下。详情可参考我提供的Dockerhub页面。

参考贴来源:
Dockerfile实现MySQL定时备份 作者:鲸临于空

转载说明

转载请注明出处,无偿提供。

支持一下
感谢大佬们的支持