文章目录
- 背景
- 问题现象
- 为什么不能直接改
- 解决步骤
-
- [1. 找到并备份卷的参数文件](#1. 找到并备份卷的参数文件)
- [2. 先手动验证新密码能不能挂上(建议)](#2. 先手动验证新密码能不能挂上(建议))
- [3. 修改 opts.json 中的密码](#3. 修改 opts.json 中的密码)
- [4. 重启 Docker(关键!)](#4. 重启 Docker(关键!))
- [5. 启动原来的容器](#5. 启动原来的容器)
- [重点:Portainer 里的显示不会更新](#重点:Portainer 里的显示不会更新)
- 其他可选方案
- 防患于未然:别把密码写在卷参数里
- 总结
背景
NAS 那边把 SMB 共享账号的密码改了,然后服务器上一个挂载了这个共享目录的容器就怎么都起不来了。一开始我以为改个卷参数就行,结果发现 Docker 根本不让改卷的参数;要删卷重建,它又提示卷正在被容器使用,一个个删容器太麻烦。折腾了一圈,最后找到了不删容器、不删卷 的改法,顺手记录一下。特别要提醒的是,改成功以后 Portainer 里显示的还是旧密码,别被它骗了。
我的环境如下(IP、账号、卷名均已脱敏):
| 配置项 | 详情 |
|---|---|
| 宿主机 | Ubuntu 服务器,192.168.1.101 |
| Docker | 24.0.2(Standalone) |
| 管理工具 | Portainer |
| NAS | //192.168.1.10/myshare,SMB 协议 vers=2.0 |
| Docker 卷 | myshare,local 驱动,type=cifs |
问题现象
当初这个卷是用下面这样的命令创建的,SMB 的账号密码直接写在卷参数里:
bash
docker volume create --driver local \
--opt type=cifs \
--opt device=//192.168.1.10/myshare \
--opt o=username=smbuser,password=OLD_PASSWORD,vers=2.0 \
myshare
NAS 改密码后,在 Portainer 里启动容器,直接报错:
error while mounting volume '/var/lib/docker/volumes/myshare/_data': failed to mount local volume:
mount //192.168.1.10/myshare:/var/lib/docker/volumes/myshare/_data,
data: username=smbuser,password=********,vers=2.0: permission denied
原因很好理解:Docker 每次启动容器都要先用卷参数里的账号密码去 mount 这个 SMB 共享,而参数里还是旧密码,NAS 当然不认。
为什么不能直接改
- Docker 没有修改 volume 参数的命令,Portainer 里卷详情页的 Volume options 也只能看不能改;
- 标准做法是
docker volume rm后重建,但只要还有容器引用这个卷(哪怕容器是停止状态 ),就会报volume is in use,得先把相关容器全删掉。
好在 local 驱动会把每个卷的挂载参数存成宿主机上的一个文件,改它就行。
解决步骤
1. 找到并备份卷的参数文件
local 驱动的卷参数保存在 /var/lib/docker/volumes/<卷名>/opts.json,先看一眼、再备份:
bash
sudo cat /var/lib/docker/volumes/myshare/opts.json
sudo cp /var/lib/docker/volumes/myshare/opts.json /root/myshare-opts.json.bak
正常会看到一行 JSON:
json
{"MountType":"cifs","MountOpts":"username=smbuser,password=OLD_PASSWORD,vers=2.0","MountDevice":"//192.168.1.10/myshare"}
2. 先手动验证新密码能不能挂上(建议)
改配置之前先确认新密码本身是对的,免得白忙活:
bash
sudo mkdir -p /mnt/test
sudo mount -t cifs //192.168.1.10/myshare /mnt/test -o 'username=smbuser,password=NEW_PASSWORD,vers=2.0'
ls /mnt/test # 能看到共享里的文件就说明密码没问题
sudo umount /mnt/test
如果手动挂载也报错,执行
sudo dmesg | tail -20看内核日志:出现STATUS_LOGON_FAILURE说明账号/密码不对;如果是协议相关报错,可以试试把vers=2.0换成vers=3.0或加上sec=ntlmssp。
3. 修改 opts.json 中的密码
用 sed 一行替换:
bash
sudo sed -i 's/password=OLD_PASSWORD/password=NEW_PASSWORD/' /var/lib/docker/volumes/myshare/opts.json
sudo cat /var/lib/docker/volumes/myshare/opts.json
确认输出里 MountOpts 已经是新密码,并且 JSON 是完整的一行。
⚠️ 我在这里踩过坑 :一开始用的是
sudo vim编辑,保存时提示:
/var/lib/docker/volumes/myshare/opts.json [+][RO] E45: 'readonly' option is set (add ! to override)明明加了 sudo 还是只读。这是 vim 把文件以只读方式打开了(文件本身没有写权限,或者之前残留了
.opts.json.swp交换文件、打开时选了 Read-Only)。root 下用:wq!强制保存即可;实在不放心就:q!退出,改用上面的 sed。另外,如果新密码里有
/、&这类字符,sed 需要转义,不如用 vim 手动改;密码里不能有逗号,因为逗号是 mount 参数的分隔符。
4. 重启 Docker(关键!)
这一步最容易漏。 我改完 opts.json 直接去启动容器,还是一样的 permission denied。原因是 dockerd 只在启动时把 opts.json 读进内存,运行中改文件它是不知道的。
bash
sudo systemctl restart docker
⚠️ 重启 Docker 会让这台机器上的所有容器跟着停止/重启,建议挑空闲时间操作,并提前通知同事。
5. 启动原来的容器
bash
docker start <容器名>
docker ps | grep <容器名>
看到容器状态是 Up 就成功了,原来的容器、原来的卷都不用动。
重点:Portainer 里的显示不会更新
问题解决后,我回到 Portainer 打开卷详情页一看,Volume options 里显示的居然还是旧密码:
device //192.168.1.10/myshare
o username=smbuser,password=OLD_PASSWORD,vers=2.0
type cifs
而宿主机上 cat opts.json 明明已经是新密码了,容器也确实是用新密码挂上的。
原因是这两处读的根本不是同一个地方:
| 位置 | 数据来源 | 作用 |
|---|---|---|
opts.json |
/var/lib/docker/volumes/myshare/opts.json |
local 驱动实际 mount 时使用的参数 |
Portainer / docker volume inspect 中的 Options |
Docker 卷元数据库 /var/lib/docker/volumes/metadata.db |
创建卷时记录下来的参数,只用于展示 |
我们只改了 opts.json,没动 metadata.db,所以:
- 显示是旧的,但实际挂载用的是新的,这只是展示不一致,不影响使用;
- 判断是否改成功,以容器能否正常启动、宿主机
mount | grep myshare的结果为准,不要看 Portainer; - metadata.db 是二进制的 BoltDB 文件,不建议手动修改。如果一定要让界面显示也一致,只能删容器 → 删卷 → 按原名重建卷 → 重建容器。
另外还有个小细节:Portainer 卷详情里的 Created 时间会变成你编辑文件的时间。这是因为 local 卷的"创建时间"取的是卷目录的修改时间,编辑 opts.json 刷新了它,并不代表卷被重建了,不用慌。
其他可选方案
-
方案 A:删卷重建(标准做法),能删容器的话最干净,Portainer 显示也会同步:
bashdocker rm <所有引用该卷的容器> docker volume rm myshare docker volume create --driver local \ --opt type=cifs \ --opt device=//192.168.1.10/myshare \ --opt o=username=smbuser,password=NEW_PASSWORD,vers=2.0 \ myshare # 然后按原参数重建容器;用 compose/stack 部署的重新 deploy 即可 -
方案 B:让 NAS 管理员把密码改回去,如果允许的话,这是最省事的。
防患于未然:别把密码写在卷参数里
这次的坑本质是密码明文硬编码在 Docker 卷参数里,带来两个问题:一是 Portainer 里有权限的人都能看到明文密码;二是改密码时卷参数改不了。
更好的做法是在宿主机上放一个凭据文件:
bash
sudo tee /root/.smbcred > /dev/null <<'EOF'
username=smbuser
password=YOUR_PASSWORD
EOF
sudo chmod 600 /root/.smbcred
创建卷时引用它:
bash
docker volume create --driver local \
--opt type=cifs \
--opt device=//192.168.1.10/myshare \
--opt o=credentials=/root/.smbcred,vers=2.0 \
myshare
以后 NAS 再改密码,只需要改 /root/.smbcred 这个文件,Docker 那边什么都不用动,Portainer 里也不会再暴露密码。
总结
Docker CIFS 卷的 SMB 密码变了,不想删容器的话,改 /var/lib/docker/volumes/<卷名>/opts.json 里的密码,然后一定要 systemctl restart docker ,原来的容器就能直接起来。改完后 Portainer 里显示的仍是旧密码,那是元数据库里的展示数据,不影响实际挂载,以容器能否启动为准。长期来看,建议把 SMB 凭据挪到 credentials= 文件里,下次改密码就不用再折腾 Docker 了。