Skip to content

Latest commit

 

History

History
132 lines (90 loc) · 6.43 KB

5.how-to-keep-data.md

File metadata and controls

132 lines (90 loc) · 6.43 KB

5.数据持久化

容器是无状态的,volumes 才能持久化,挂载后面说。这里先说下 docker volumes 相关的,前面Dockerfile 的 VOLUME指令现在来说。

实际上 VOLUME 声明的就是需要挂载的地方,例如 mysql 的官方 Dockerfile https://github.com/docker-library/mysql/blob/bb7ea52db4e12d3fb526450d22382d5cd8cd41ca/5.7/Dockerfile#L73 里有

VOLUME /var/lib/mysql

我们可以看看官方的 readme 文件会说变量 MYSQL_ROOT_PASSWORD 必须传入,不然会退出。下面截图里带上这个必要的环境变量运行下看看

实际上docker的 volume 是通过命令管理,默认是 local 类型,默认存在于/var/lib/docker/volumes目录下。我们可以看到 mysql 启动后就多了个一大串名字的卷。就是因为 Dockerfile 里的 VOLUME 字段。

很多进程服务的数据或者说用户在使用中一定会添加插件的,为了让用户只拷贝数据目录来方便备份,也防止用户忘记挂载。VOLUME字段的路径没被用户 run 的时候挂载再宿主机路径上去的话,docker 会在宿主机创建一个匿名卷(docker volume,随机名所以叫匿名卷),匿名卷会被自动挂载到VOLUME的路径点上。

备份角度来讲就是把数据目录拷贝过去,然后把目录挂载了启动。但是观察上面的图,好像mysql的数据跑出来了写到了 volume 目录上,那我们备份的时候挂载进去 mysql 容器对我们的挂载目录是如何处理的呢?

其实可以看看 mysql 镜像的entrypoint.sh启动逻辑,它启动主进程之前会判断数据目录是否为空,为空则初始化出图上面的文件,不为空则使用用户挂载的之前运行产生的数据文件:

if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then
	# still need to check config, container may have started with --user
	_check_config "$@"
	# Get config
	DATADIR="$(_get_config 'datadir' "$@")"

	if [ ! -d "$DATADIR/mysql" ]; then
		file_env 'MYSQL_ROOT_PASSWORD'
		if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
			echo >&2 'error: database is uninitialized and password option is not specified '
			echo >&2 '  You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD'
			exit 1
		fi

		mkdir -p "$DATADIR"

		echo 'Initializing database'
		"$@" --initialize-insecure
		echo 'Database initialized'

它还支持挂载 init 的 sql 文件去帮你初始化:

# 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
}

docker run -v 挂载

我们可以利用目录去挂载,会发现根本没有匿名卷生成

我们也可以 run 的时候创建匿名卷,-v 的时候只声明需要匿名卷的路径即可:

匿名卷不方便长久的管理,我们也可以创建命名卷用命名卷挂载

也可以挂载单独一个文件,例如我们挂载自定的:

上面没映射,但是能访问是留个悬念后面讲容器网络的时候讲。同时上面这个index的例子继续一下,在它的基础上我们接下来讲

为何宿主机修改文件容器内不变化?

这里方便截图我用sed不是vim修改文件

我们可以看到修改了文件后访问还是原来的,ls -i是列出文件的inode,实际上文件内容没变是因为docker 挂载文件就是用的inode,vim 和 sed 啥的都是编辑的时候会产生一个临时文件,临时文件去替换原文件导致inode变了,如果是在我 sed 修改之前用重定向这种修改的话则不会修改 inode

为了避免这种问题一般是挂载配置目录,配置文件存放在这个配置目录里面

最后,如果docker run的时候带上--rm,那容器停止会被删除的时候匿名卷也会被删除!!!

-v 挂载的一个小 tips

$ tree
.
├── conf
│   └── my.cnf
├── data
└── init
    └── init.sql

3 directories, 2 files

当前目录为上面所示,要在当前目录启动一个 mysql 容器,需要三个挂载,data 和 init 都是挂载到容器里的对应目录,而 my.cnf 是文件挂载(-v $PWD/conf/my.conf:/etc/mysql/my.cnf)。这样是能正常启动和运行的。

但是如果 conf 目录下没有my.cnf,挂载点不存在下docker会把它创建成目录,也就是 mkdir -p $PWD/conf/my.conf,然后启动报错,翻译下就是说你在尝试把一个目录挂载到容器里的文件。这个注意下就行了。

然后就是这个目录问题引申出一个问题。如果你运行了一个 restart 挂载了/var/run/docker.sock 的容器,docker停止的时候会移除掉/var/run/docker.sock。所以在启动的时候有很小的几率,你挂载该文件的容器会比docker daemon 创建docker.sock文件快。然后导致把/var/run/docker.sock变成了目录,然后docker daemon创建这个sock文件报错。整个docker daemon 退出。可以在 systemd 层面解决这个问题(前面的步骤安装docker的话不用执行下面的,已经有了):

mkdir -p /etc/systemd/system/docker.service.d/
cat >>/etc/systemd/system/docker.service.d/10-docker.conf<<EOF
[Service]
ExecStartPre=/bin/bash -c 'test -d /var/run/docker.sock && rmdir /var/run/docker.sock || true'
EOF

其实是推荐使用 --mount 的,-v vs --mount 的差异, --mount 不会创建 src 的路径

   --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock