使用 docker-compose 在派生映像中创建的挂载目录出错

Error mounting directory created in derived image with docker-compose

总结

使用 docker-compose,使用 Nginx 基础映像中存在的目标目录安装卷,按预期工作。但是,当挂载目标是在派生映像中创建的子目录时,docker-compose up 出现错误。这是错误的要点:

mounting "newdir" to rootfs at "/usr/share/nginx/html/newdir" caused: mkdir newdir: read-only file system: unknown

我不明白为什么它似乎在尝试创建已经存在的目录 (newdir)!

详情

下面的完整错误:

ERROR: for test_nginx_1  Cannot start service nginx: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: rootfs_linux.go:76: mounting "/home/user/test/newdir" to rootfs at "/usr/share/nginx/html/newdir"  caused: mkdir /var/lib/docker/overlay2/accdaa147d7825a7d2c71b68f76e0b8663d00f6e46fcfb5c0ec32ada8baf85af/merged/usr/share/nginx/html/newdir: read-only file system: unknown

ERROR: for nginx  Cannot start service nginx: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: rootfs_linux.go:76: mounting "/home/user/test/newdir" to rootfs at "/usr/share/nginx/html/newdir" caused: mkdir /var/lib/docker/overlay2/accdaa147d7825a7d2c71b68f76e0b8663d00f6e46fcfb5c0ec32ada8baf85af/merged/usr/share/nginx/html/newdir: read-only file system: unknown.

这里是docker-compose.yml:

version: '3'
services:
  nginx:
    image: local/mynginx:withsubdir
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./newdir:/usr/share/nginx/html/newdir

上图 local/mynginx:withsubdir 是使用以下 Docker 文件:

构建的
FROM docker.io/library/nginx:1.21
RUN  mkdir /usr/share/nginx/html/newdir && ls -lah /usr/share/nginx/html/newdir

并且我验证了我可以挂载该卷并使用 docker 查看其内容:

$ docker run --rm -ti -v /home/user/test/newdir:/usr/share/nginx/html/newdir local/mynginx:withsubdir bash
root@7db709476271:/# ls /usr/share/nginx/html/newdir
hallo.imhere

所以这个问题似乎特定于 docker-compose。如果我删除 docker-compose.yml 中的最后一行,那么 docker-compose up 会成功启动。如何挂载在派生映像中创建的目录?谢谢。

附加说明

顺便说一句,我原来的 docker-compose.yml 更长,它有一个指向 Dockerfile 创建新目录。但是我在排除故障时创建了这个较小的复制器,并且我手动构建了新图像:

docker build . -t local/mynginx:withsubdir

我在两个不同的环境(Docker version 20.10.7, build 20.10.7-0ubuntu1~20.04.1 and Docker 版本 20.10.12,构建 e91ed5707e),有或没有 :ro.

我的test目录文件:

.
├── docker-compose.yml
├── Dockerfile
├── html
│   └── index.html
└── newdir
    └── hallo.imhere

我做了一些在线研究,但找不到任何解决方案。

问题似乎是第二个目标装载是第一个目标的子目录。构建新图像后,我将 docker-compose.yml 更改为此,现在错误消失了:

version: '3'
services:
  nginx:
    image: local/mynginx:withsubdir
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./newdir:/usr/share/nginx/newdir:ro

希望这是我可以解决的问题,但至少谜团似乎已经解开了。

Docker 正在尝试在 read-only 目录中创建挂载点,但失败了。

正常的Linuxmount(8)调用,以及底层的mount(2)系统调用,mount一些一些现有目录上的文件系统。 Linux 绑定挂载是这种情况的一种特殊情况,其中正在挂载的文件系统也是一个现有目录,而 Docker 绑定挂载使用相同的机制。

一般来说,在Docker中,如果任何类型的挂载目标不存在,Docker将创建一个新的空目录。通常这是在容器文件系统中,但如果您尝试将卷安装在其他类型的安装卷中,则安装点将在外部卷中创建。

演示这一点的一个简单方法是使用简单的 docker run 命令,在其中安装外部卷和内部卷,但外部卷实际上是绑定安装:

# Create a new empty local "outer" directory
rm -rf outer
mkdir outer

# Create a new empty container...
docker run --rm \
  -v "$PWD/outer:/outer" \ # bind-mounting the outer directory
  -v inner:/outer/inner  \ # mounting a named volume within that
  busybox                \ # running any image
  ls /outer                # and a command that exits immediately

# Look inside the host directory, and an empty `inner` directory will exist
ls ./outer

# Clean up
docker volume rm inner
rm -rf outer

(这与的机制相同。)

现在,考虑到这些机制,您已经告诉 Docker 将外部目录挂载为 read-only,它确实做到了。然后 newdir 目录在容器文件系统中不存在,所以 Docker 尝试创建它作为挂载点,但它不能,因为该目录在 read-only 文件系统中.这就是你得到的错误。

对于纯 Docker 卷,一些可能的解决方法是:

  1. 在启动容器之前在主机文件系统上创建挂载点

    mkdir ./html/newdir
    docker-compose up -d
    
  2. 不要将外部目录挂载为read-only

    volumes:
      - ./html:/usr/share/nginx/html  # not ...:ro
    
  3. 像在

    中所做的那样,将内部目录安装到其他地方
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./newdir:/usr/share/nginx/newdir:ro  # not under .../html
    

更好的答案仍然是完全避免此处的卷。在 Docker 文件中,您可以 COPY 此内容到您的图像中。由于容器文件系统与主机隔离,这消除了容器修改主机文件的风险,并且您可以根据需要在 Docker 文件中重组内容。这也意味着您可以 运行 另一个系统上的图像,而无需同时拥有要在那里提供的文件的副本。

FROM nginx
COPY html/ /usr/share/nginx/html/
COPY newdir/ /usr/share/nginx/html/newdir/
# no need to repeat base image's EXPOSE or CMD