那天下午,我正在用Docker快速部署一个MySQL测试环境。像往常一样输入docker run命令,满心以为几秒钟后就能愉快地敲SQL了。结果docker ps -a给我泼了盆冷水——容器状态赫然显示Exited (1),就像个闹脾气的服务员直接甩手不干了。
这种情况太常见了。根据我的经验,80%的Docker新手都会在这个坑里摔跤。那个冷冰冰的Exited状态其实在说:"老兄,你给的权限不够啊!"但问题远不止加个--privileged参数那么简单。让我们先做个快速检查:
bash复制# 查看容器死亡原因的金钥匙
docker logs <container_id>
如果日志里出现"Can't create/write to file"或者"Permission denied"之类的字眼,那基本可以确定是权限问题在作祟。有意思的是,这个问题在MySQL容器里特别常见,因为MySQL服务启动时需要大量文件操作权限。
Docker默认运行在安全沙箱中,就像给容器戴上了手套——能防止它乱动系统文件,但也限制了正常操作。这就是为什么我们需要--privileged这个"万能钥匙":
bash复制docker run --privileged=true ...
这个参数相当于把容器的权限提到和宿主机root同级。但别急着用,这就像给你的实习生CEO权限——能解决问题,但风险巨大。我曾在测试环境随手加了这参数,结果容器里的进程差点把宿主机的关键目录删了。
当你用-v参数映射宿主机目录时,权限问题就变得更复杂了。比如:
bash复制-v /mydata/mysql/data:/var/lib/mysql
这里有个隐藏坑:容器内MySQL默认以mysql用户(UID 999)运行,而宿主机目录可能属于root或其他用户。我遇到过最诡异的情况是宿主机目录权限明明是777,容器还是报权限错误——后来发现是SELinux在作怪。
即使容器权限没问题,MySQL服务启动时还要:
这些操作需要精确的权限配置。有次我在阿里云ECS上部署,所有权限都检查了还是启动失败,最后发现是云主机的磁盘挂载参数少了nosuid选项。
与其简单粗暴地用--privileged,不如精准授权。比如只需要文件系统权限时:
bash复制docker run --cap-add SYS_ADMIN ...
如果需要修改系统时间:
bash复制docker run --cap-add SYS_TIME ...
这是我常用的权限组合,适合大多数MySQL容器:
bash复制docker run \
--cap-add SYS_NICE \
--cap-add DAC_READ_SEARCH \
--security-opt seccomp=unconfined
对于映射目录,我总结出这个万能公式:
bash复制mkdir -p /mydata/mysql/{data,conf,log}
chown -R 999:999 /mydata/mysql
chmod -R 755 /mydata/mysql
然后在docker run时指定用户:
bash复制docker run -u 999 ...
注意:999是官方MySQL镜像的默认用户UID,其他镜像可能不同。
这是我经过多次踩坑优化的MySQL 5.7启动命令:
bash复制docker run -d \
--name mysql \
-p 3306:3306 \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql/conf.d \
-v /etc/localtime:/etc/localtime:ro \
-e MYSQL_ROOT_PASSWORD=yourpassword \
--restart=unless-stopped \
--security-opt="no-new-privileges:true" \
--cap-drop ALL \
--cap-add SETGID \
--cap-add SETUID \
--cap-add CHOWN \
mysql:5.7 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci
关键改进:
unless-stopped替代always更合理就算容器跑起来了,远程连接还可能遇到:
解决方案:
sql复制-- 进入容器
docker exec -it mysql mysql -uroot -p
-- 授权远程访问
CREATE USER 'remote'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'remote'@'%';
FLUSH PRIVILEGES;
-- 检查绑定地址
SHOW VARIABLES LIKE 'bind_address';
如果bind_address是127.0.0.1,需要在my.cnf中添加:
ini复制[mysqld]
bind-address = 0.0.0.0
当问题特别棘手时,我会祭出这些工具:
bash复制docker top mysql
bash复制docker exec -it mysql bash
bash复制journalctl -u docker --no-pager | grep mysql
如果权限问题实在搞不定,可以考虑:
bash复制dockerd --userns-remap=default
bash复制podman run --security-opt label=disable mysql:5.7
有一次在客户生产环境,MySQL容器频繁崩溃。排查三天后发现是宿主机磁盘inode用尽——这种问题--privileged也救不了。还有次遇到CentOS 7上的老版本Docker与Overlay2驱动不兼容导致权限错误。
最坑的是某次Ubuntu系统上,AppArmor默认配置阻止了MySQL写入日志。解决方案是在/etc/apparmor.d/docker中添加:
apparmor复制profile docker-default flags=(attach_disconnected,mediate_deleted) {
# 允许MySQL相关操作
/var/log/mysql/** rw,
/var/lib/mysql/** rw,
}
这些经验告诉我:Docker权限问题从来不是简单的加参数就能解决,需要理解整个技术栈的运作机制。现在每当我看到Exited (1)状态,反而会兴奋——又一个深入了解系统原理的机会来了。