一份手记,记录我在使用 rclone、FTP、SMB 和 Docker 时踩过的坑与解决方案。

最近给公司搭建共享文件服务,遇到一大堆坑。记录一下,查了一堆办法,没有完美解决问题。mac上无法读写ftp,所以想着把ftp挂载到目录,然后把目录用samba共享出去。几个服务都在docker环境中运行。

1. Linux 挂载 FTP 的传统方式

1.1 为什么 mount 不能直接挂载 FTP?

mount 命令是为内核支持的文件系统(如 ext4、NFS、CIFS)设计的,而 FTP 是一个应用层协议,并非文件系统。因此,执行 mount -t ftp ... 会直接报错,也无法在 /etc/fstab 中直接配置。

1.2 curlftpfs:基于 FUSE 的挂载工具

curlftpfs 利用 FUSE(用户态文件系统)将 FTP 映射为本地目录,用法简单:

# 安装(Ubuntu/Debian)
sudo apt install curlftpfs

# 挂载(带用户名密码)
curlftpfs ftp://user:pass@host /mnt/ftp

# 推荐使用 .netrc 安全存储凭据
echo “machine host login user password pass” > ~/.netrc
chmod 600 ~/.netrc
curlftpfs ftp://host /mnt/ftp

限制与风险: - 只读为主:写入不稳定,易出错。 - 项目已停止维护(2008 年后无更新),不推荐生产环境。 - 密码明文传输(FTP 本身不安全)。


2. rclone:现代化的全能云存储客户端

2.1 为什么选择 rclone?

特性 curlftpfs rclone
维护状态 停滞 活跃更新
性能 较差 优秀
写入支持 不稳定 支持(需 VFS 缓存)
协议支持 FTP 基本 FTP/SFTP/FTPS/HTTP/云存储等
高级功能 加密、校验、带宽限制

2.2 使用 rclone 挂载 FTP

第一步:配置远程存储

rclone config
# 交互式创建 new remote,选择 ftp,填写主机、用户名、密码等

第二步:挂载到本地

mkdir -p ~/ftp_mount
rclone mount myftp: /home/user/ftp_mount \
    --daemon \
    --vfs-cache-mode writes \
    --allow-other

常用参数说明: - --daemon:后台运行 - --vfs-cache-mode writes/full:启用缓存,保证写入行为 - --allow-other:允许其他用户访问挂载点 - --dir-cache-time 5m:目录缓存时间,提升响应速度

卸载

fusermount -u /home/user/ftp_mount

3. 在 Docker 容器中挂载并共享给宿主机

3.1 核心原理:挂载传播(Mount Propagation)

Docker 容器拥有独立的挂载命名空间,默认容器内的挂载对宿主机不可见。通过 绑定挂载(bind mount)+ 共享传播(shared propagation),可以将容器内的挂载点“投射”到宿主机。

3.2 实战:rclone 容器挂载 FTP 并共享

启动 rclone 容器

docker run -d \
  --name rclone-ftp \
  --restart unless-stopped \
  --device /dev/fuse \
  --cap-add SYS_ADMIN \
  --security-opt apparmor:unconfined \
  -v /share/mnt:/data:shared \
  rclone/rclone:latest \
  mount remote_name:path /data --allow-other --vfs-cache-mode writes

关键点: - --device /dev/fuse:让容器能使用 FUSE。 - --cap-add SYS_ADMIN:允许修改挂载命名空间。 - -v /share/mnt:/data:shared:shared 是关键,表示该绑定挂载启用共享传播。 - --allow-other:允许宿主机用户访问。

此时,宿主机上的 /share/mnt 目录即可看到远程 FTP 内容,并且可以读写(取决于权限)。

3.3 常见错误:directory already mounted, use --allow-non-empty

原因:目标目录非空(可能宿主机目录有文件,或有残留挂载记录)。

解决方案

  1. 快速修复:在 rclone mount 命令中添加 --allow-non-empty
  2. 彻底清理
    • 检查容器内是否有残留挂载:docker exec rclone-ftp fusermount -uz /data
    • 确保宿主机 /share/mnt 为空目录。

建议:保持挂载点绝对干净,避免因非空导致的数据写入歧义。


4. 容器停止后挂载点无法卸载:Transport endpoint is not connected

4.1 问题现象

容器停止后,宿主机上的 /share/mnt 目录无法访问,执行 lsumount 报错:Transport endpoint is not connected

4.2 原因分析

后端连接(FTP 会话)已断开,但内核的挂载点仍保留,处于“僵尸”状态。

4.3 应急处理流程

按顺序尝试以下命令(在宿主机上执行):

  1. 首选sudo fusermount -uz /share/mnt(针对 FUSE 的强力卸载)
  2. 若无效:sudo umount -l /share/mnt(懒惰卸载,立即从树中移除)
  3. 若仍失败:sudo systemctl restart docker(重启 Docker 守护进程释放引用)
  4. 最后手段:docker rm -f rclone-ftp(强制删除容器)

4.4 预防措施:优雅停止

在容器停止前主动卸载,避免残留。利用 rclone 的 RC(远程控制)API:

  • 启动时加上 --rc 参数。
  • 在停止钩子中调用 rclone rc/unmount

例如在 systemd 服务中定义 ExecStop,或在容器编排工具的 pre-stop 钩子中实现。


5. 通过 SMB 共享 rclone 挂载目录

5.1 问题描述

将宿主机的 /share 目录(包含 /share/mnt rclone 挂载点)通过 Samba 容器共享给 Mac 的 Finder。结果: - /share 下的普通目录读写删除正常。 - /share/mnt 内的文件删除失败,报 Operation not supported

5.2 根因分析

这是一个多层协议转换问题,数据流为:

Mac (SMB Client) → Samba 容器 → 宿主机 /share/mnt → rclone 容器 → FTP Server

删除操作失败的原因可能出自任意一层,但线索是只有 /share/mnt 失败,说明问题不在 Samba 层,而在于 rclone → FTP 后端 对删除语义的支持或权限配置。

5.3 排查与解决清单

排查方向 操作/检查项
权限映射 确保 Samba 容器与 rclone 容器使用相同的 UID/GID;rclone 挂载加 --allow-other;Samba 可设 force user = root 测试。
rclone 挂载参数 添加 --vfs-cache-mode writesfull,加 --umask 000 放宽权限。
FTP 服务器权限 lftpftp 命令直接登录 FTP,手动执行 delete 看是否成功。检查 FTP 日志。
SELinux/AppArmor 临时 setenforce 0 测试,若有效则调整策略。
Samba VFS 模块 smb.conf 中注释 vfs objects = recycle 等模块,看是否干扰。
Mac Finder 行为 Finder 删除时可能先移动到 .Trash,涉及重命名,若 FTP 不支持重命名则失败。可用 smbclient 命令行测试,排除 Finder 特性。
rclone 版本 升级到最新版,修复已知 FTP 删除 bug。

5.4 最佳实践建议

  • 优先使用 SFTP 替代 FTP:SFTP 是加密协议,语义更完整,兼容性更好。
  • 避免多层嵌套挂载:尽量将远程存储直接挂载到 Samba 共享的根目录,减少中间层。
  • 启用详细日志:在 rclone 挂载时加 -vv,Samba 日志级别调高,便于定位。

6. 总结与备忘

需求场景 推荐方案
临时挂载 FTP,只读访问 curlftpfs(简单)或 rclone mount(更稳)
需写入、长期使用、支持多种后端 rclone 是首选
在 Docker 中挂载并共享给宿主机 使用 :shared 传播模式 + --allow-other
容器停止后清理挂载点 fusermount -uz 优先
通过 SMB 共享 rclone 挂载目录 确保权限一致,开启缓存,排查 FTP 服务器是否支持删除

一句话口诀

挂载用 rclone,容器加 shared,停止前先卸载,SMB 查权限。


用到的相关命令

# rclone 挂载
rclone mount remote:path /local/mount --daemon --allow-other --vfs-cache-mode writes

# 卸载 FUSE 挂载
fusermount -uz /local/mount

# Docker 运行 rclone 容器(共享挂载)
docker run -d –device /dev/fuse –cap-add SYS_ADMIN -v /host/path:/container/path:shared rclone/rclone mount remote:path /container/path –allow-other

# 强制卸载(懒卸载)
sudo umount -l /host/path

# 查看挂载点占用进程
lsof /host/path
fuser -v /host/path