简介
使用Docker搭建MySQL服务时,考虑到MySQL存储的数据较大,希望存放在空间较大的nas上。于是通过容器目录映射的方式将数据通过smb的方式储存,但是报错 Errcode: 13 - Permission denied。
下面记录下排查过程和解决方案
问题描述
My Yaml
version: "2"
services:
mysql:
container_name: mysql
image: mysql:5.7.23
privileged: true
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8 --collation-server=utf8_general_ci
volumes:
- cifs_mount:/var/lib/mysql
ports:
- 3306:3306
environment:
- MYSQL_USER=root
- MYSQL_ROOT_PASSWORD=123
- MYSQL_DATABASE=db
volumes:
cifs_mount:
driver: local
driver_opts:
type: cifs
device: //${SMB_HOST}/nas/home/mysql
o: "username=${SMB_USER},password=${SMB_PWD}"
Errcode: 13 - Permission denied
$ docker-compose up
mysql_future | Initializing database
mysql_future | mysqld: Can't create/write to file '/var/lib/mysql/is_writable' (Errcode: 13 - Permission denied)
mysql_future | 2024-05-26T16:25:35.482190Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
mysql_future | 2024-05-26T16:25:35.491790Z 0 [ERROR] --initialize specified but the data directory exists and is not writable. Aborting.
mysql_future | 2024-05-26T16:25:35.491839Z 0 [ERROR] Aborting
由于出现了 Errcode: 13 - Permission denied 的报错,于是认为一定是权限给少了,那么如何添加权限,最简单的方式是直接映射一个 root 进容器。
排查过程
Try: 设置进程ID为root
environment:
- MYSQL_USER=root
- MYSQL_ROOT_PASSWORD=123
- MYSQL_DATABASE=db
+ - UID=0
+ - PID=0
在Docker容器中,通过设置环境变量可以控制容器中进程的运行环境。在这个例子中,设置了环境变量:
- PUID=0:指定了容器中进程的用户ID(PUID)。在这里设置为0,表示将容器中的进程以root用户的身份运行。这样设置可以让容器内的进程具有root用户的权限,可以执行一些需要特权的操作。
- PGID=0:指定了容器中进程的组ID(PGID)。同样设置为0,表示将容器中的进程所属的组设置为root组。
结果是报错依旧同上,那么我们就会很疑惑,为什么还会出现权限问题。猜测一是文件夹不允许写操作,猜测二是文件夹的用户和进程的用户不一致。
文件夹的用户和权限是什么
登录nas查看文件夹权限,结果发现文件夹的用户为 ppsteven ,用户组为 user 。文件夹 mysql 是我在mac上使用用户ppsteven 连接smb手动创建的,故该文件夹下会存在 .DS_Store 。用户为我smb的账号ppsteven。
root@ppnas:/srv/mergerfs/nas/nas/home/mysql# ls -al
总用量 12
drwxrwsr-x 3 ppsteven users 76 5月 27 00:25 .
drwxrwsr-x 6 ppsteven users 128 5月 26 02:40 ..
-rw-rw-r-- 1 ppsteven users 4096 5月 26 02:41 ._.DS_Store
-rw-rw-r-- 1 ppsteven users 6148 5月 27 00:25 .DS_Store
drwxrwsr-x 2 ppsteven users 10 10月 16 2018 mysql_data
根据提供的输出,可以看到mysql_data文件夹的权限为drwxrwsr-x,所有者为ppsteven,所属组为users。这里有一个特殊的权限位S,它表示设置了粘滞位(Sticky Bit)。
根据权限表示法:
d表示这是一个目录rwx表示所有者(ppsteven)具有读取、写入和执行权限rws表示所属组(users)具有读取、写入和执行权限,并且设置了粘滞位r-x表示其他用户具有读取和执行权限
粘滞位在目录上的作用是,只有目录的所有者、文件的所有者或者root用户才能删除目录中的文件,即使其他用户有写权限。这有助于防止普通用户误删其他用户的文件。因此,根据权限设置,ppsteven用户可以读取、写入和执行mysql_data目录,users组的用户也可以读取、写入和执行该目录,并且设置了粘滞位。
结论:文件夹用户为 ppsteven,用户组为user,权限为读写
操作文件夹的用户是谁?
目前我们可以知道文件夹有读写权限,那么 Errcode: 13 - Permission denied 的问题大概率是由于用户组的不匹配造成的了,下面我们看下 目前 大概有哪些用户。
文件夹权限:
- ppsteven:smb 注册账户
- root:nas root 账户
进程:
- ppsteven:Ubuntu 账户
- root:Ubuntu账户
现在走进了一个死胡同,按一般来说,当进程为root时,拥有全部文件夹的访问权限。而出现上述权限不足的问题时,只有可能出现以下情形:
- 用户为ppsteven,文件夹权限为root
- 用户为 X,文件夹权限为 ppsteven 或 root
自然就容易产生的疑惑是,明明已经在 Try中显式指定了用户为root,以上两种问题都不应该发生。
最直接的方式就是启动容器,查看用户
docker run --rm -t mysql:5.7.23 sh -c 'id'
uid=0(root) gid=0(root) groups=0(root)
结论:操作文件夹的用户就是root,而且无需显式指定 UID 和 PID。
这个结论和上面的两个推测是相冲突的,只能说明一点,容器当前用户确实是root,但是执行mysql进程的另有其人。具体是谁,需要进入容器排查。
容器中排查
1. 容器持久运行不退出
version: "2"
services:
mysql:
container_name: mysql
image: mysql:5.7.23
privileged: true
- command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8 --collation-server=utf8_general_ci
+ command: tail -f /dev/null
2. 找到容器默认执行的命令
在 dockerhub-mysql:5.7.23 中找到了Dockerfile 的命令
16 ENTRYPOINT ["docker-entrypoint.sh"]
17 EXPOSE 3306/tcp 33060/tcp
18 CMD ["mysqld"]
即 docker-entrypoint.sh mysqld
3. 执行mysqld
果然出现了之前看到的错误
root@ba9456632723:/# docker-entrypoint.sh mysqld
Initializing database
mysqld: Can't create/write to file '/var/lib/mysql/is_writable' (Errcode: 13 - Permission denied)
2024-05-26T17:24:44.679361Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2024-05-26T17:24:44.687552Z 0 [ERROR] --initialize specified but the data directory exists and is not writable. Aborting.
2024-05-26T17:24:44.687578Z 0 [ERROR] Aborting
4. 确认文件夹的用户和权限
刚刚在 nas 上看到的文件夹的用户是 ppsteven,进入容器后再次查看,用户变为了 root。
root@ba9456632723:/# ls -al /var/lib/
total 36
drwxr-xr-x 1 root root 4096 Oct 16 2018 .
drwxr-xr-x 1 root root 4096 Oct 11 2018 ..
drwxr-xr-x 1 root root 4096 Oct 16 2018 apt
drwxr-xr-x 1 root root 4096 Oct 16 2018 dpkg
drwxr-xr-x 2 root root 4096 Jun 26 2018 misc
drwxr-xr-x 2 root root 0 Oct 16 2018 mysql
drwxrwx--- 2 mysql mysql 4096 Oct 16 2018 mysql-files
drwxr-x--- 2 mysql mysql 4096 Oct 16 2018 mysql-keyring
drwxr-xr-x 2 root root 4096 Oct 11 2018 pam
drwxr-xr-x 1 root root 4096 Oct 11 2018 systemd
结论:容器卷挂载的用户和文件系统中的并不一样,查询资料:
默认情况下,容器卷的device的默认用户组是root。这是因为在Docker中,容器卷的权限是由Docker引擎所控制的。当容器卷被挂载到容器中时,Docker引擎会将该卷所属的用户和用户组设置为root。这是为了确保容器在访问卷中的文件时具有足够的权限。如果需要更改默认的用户组,可以在容器启动命令中使用
--user选项来指定用户和用户组。
5. 确认真实执行的用户
root@ba9456632723:/# whereis docker-entrypoint.sh
docker-entrypoint: /usr/local/bin/docker-entrypoint.sh
root@ba9456632723:/# cat /usr/local/bin/docker-entrypoint.sh
发现脚本对root有特殊处理
# allow the container to be started with `--user`
if [ "$1" = 'mysqld' -a -z "$wantHelp" -a "$(id -u)" = '0' ]; then
_check_config "$@"
DATADIR="$(_get_config 'datadir' "$@")"
mkdir -p "$DATADIR"
chown -R mysql:mysql "$DATADIR"
exec gosu mysql "$BASH_SOURCE" "$@"
fi
这部分代码主要是用于允许容器以--user参数启动。如果脚本的第一个参数是mysqld,且$wantHelp为空,并且当前用户的id为0(即root用户),则执行以下操作:
- 调用
_check_config函数检查配置。 - 获取MySQL的数据目录路径,并创建该目录。
- 使用
chown命令将数据目录的所有权设置为mysql:mysql。 - 使用
gosu命令以mysql用户身份重新执行脚本($BASH_SOURCE表示当前脚本文件),并传递之前的参数。
这段代码的作用是在容器启动时,如果以root用户身份启动,并且参数是mysqld,则将执行权限切换为mysql用户,并重新执行脚本以启动MySQL服务。这样可以避免以root用户身份运行MySQL服务,提高安全性。
结论:真实执行的用户是 mysql。在 dockerhub-mysql:5.7.23 中存在一个命令
/bin/sh -c groupadd -r mysql && useradd -r -g mysql mysql创建了mysql的用户。
6. 疑惑 chown -R mysql:mysql "$DATADIR" 无效
可以看到 bash 脚本中当发现当前用户为root时,会将文件的用户和用户组改为 mysql 并使用 mysql 去执行。但是从最终的效果中发现并没有成功更改,我后续多次执行此命令,发现是无效,但是没有报错。如下
root@ba9456632723:/# id
uid=0(root) gid=0(root) groups=0(root)
root@ba9456632723:/# chown -R mysql:mysql /var/lib/mysql
root@ba9456632723:/# ls -al /var/lib/
total 36
drwxr-xr-x 1 root root 4096 Oct 16 2018 .
drwxr-xr-x 1 root root 4096 Oct 11 2018 ..
drwxr-xr-x 1 root root 4096 Oct 16 2018 apt
drwxr-xr-x 1 root root 4096 Oct 16 2018 dpkg
drwxr-xr-x 2 root root 4096 Jun 26 2018 misc
drwxr-xr-x 2 root root 0 Oct 16 2018 mysql
drwxrwx--- 2 mysql mysql 4096 Oct 16 2018 mysql-files
drwxr-x--- 2 mysql mysql 4096 Oct 16 2018 mysql-keyring
drwxr-xr-x 2 root root 4096 Oct 11 2018 pam
drwxr-xr-x 1 root root 4096 Oct 11 2018 systemd
这个留待后续验证…
解决方案
我们可以看到本质上是由于数据目录 mysql 归属用户为 root,但是执行为 mysql 报的权限不足的错误。
那么我们可以直接将容器映射时直接指定归属即可。
1. 查看mysql用户id
root@ba9456632723:/# cat /etc/passwd | grep mysql
mysql:x:999:999::/home/mysql:
2. 设置目录id
volumes:
cifs_mount:
driver: local
driver_opts:
type: cifs
device: //${SMB_HOST}/nas/home/mysql
- o: "username=${SMB_USER},password=${SMB_PWD}"
+ o: "username=${SMB_USER},password=${SMB_PWD},uid=999,gid=999"
成功运行
root@fc502a823b1e:/# ls -al /var/lib/
total 36
drwxr-xr-x 1 root root 4096 Oct 16 2018 .
drwxr-xr-x 1 root root 4096 Oct 11 2018 ..
drwxr-xr-x 1 root root 4096 Oct 16 2018 apt
drwxr-xr-x 1 root root 4096 Oct 16 2018 dpkg
drwxr-xr-x 2 root root 4096 Jun 26 2018 misc
drwxr-xr-x 2 mysql mysql 0 Oct 16 2018 mysql
drwxrwx--- 2 mysql mysql 4096 Oct 16 2018 mysql-files
drwxr-x--- 2 mysql mysql 4096 Oct 16 2018 mysql-keyring
drwxr-xr-x 2 root root 4096 Oct 11 2018 pam
drwxr-xr-x 1 root root 4096 Oct 11 2018 systemd
root@fc502a823b1e:/# docker-entrypoint.sh mysqld
Initializing database
2024-05-26T17:51:53.023965Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2024-05-26T17:51:55.708097Z 0 [Warning] InnoDB: New log files created, LSN=45790
2024-05-26T17:51:56.325563Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
2024-05-26T17:51:56.495066Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: a4ba4e3a-1b88-11ef-b584-0242ac1e0002.
2024-05-26T17:51:56.555978Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
2024-05-26T17:51:56.564034Z 1 [Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
结论
本次排查 MySQL 容器化部署时,遇到将mysql数据挂载至SMB上遇到的权限不足的问题。
通过排查问题发现,容器运行用户为root,文件夹权限为读写,所属用户组为root。但是依旧出现权限不足的问题时,mysql 容器在运行时会针对当前用户为root的情形进行处理,会将文件夹用户和用户组更改为mysql,并使用mysql 用户执行命令。
在此过程中,chown 更改用户组的行为不知什么原因没有成功改动,导致了权限不足的情况。
解决方案是,在挂载时指定目录权限为 mysql后可成功运行。
参考
拓展知识
1. 如果不显式指定 PID=0,UID=0,那么容器中真实运行的用户是谁?
在Dockerfile中未指定用户时,默认情况下容器中的进程会以root用户身份运行。如果指定了USER,则以该用户执行。查看当前用户的方式如下:
- 首先进入容器内部:
docker exec -it [容器名称] /bin/bash
- 然后使用以下命令查看当前用户的 UID 和 GID:
id