使用 docker-compose 在派生映像中创建的安装目录时出错

Nag*_*gev 0 docker docker-compose

概括

\n

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

\n
\n

将“newdir”安装到“/usr/share/nginx/html/newdir”处的rootfs导致:\ nmkdir newdir:只读文件系统:未知

\n
\n

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

\n

细节

\n

完整错误如下:

\n
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\n\nERROR: 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.\n
Run Code Online (Sandbox Code Playgroud)\n

这是docker-compose.yml

\n
version: '3'\nservices:\n  nginx:\n    image: local/mynginx:withsubdir\n    volumes:\n      - ./html:/usr/share/nginx/html:ro\n      - ./newdir:/usr/share/nginx/html/newdir\n
Run Code Online (Sandbox Code Playgroud)\n

上面的图像local/mynginx:withsubdir是使用以下Dockerfile构建的:

\n
FROM docker.io/library/nginx:1.21\nRUN  mkdir /usr/share/nginx/html/newdir && ls -lah /usr/share/nginx/html/newdir\n
Run Code Online (Sandbox Code Playgroud)\n

我验证了我可以安装该卷并使用 docker 查看其内容:

\n
$ docker run --rm -ti -v /home/user/test/newdir:/usr/share/nginx/html/newdir local/mynginx:withsubdir bash\nroot@7db709476271:/# ls /usr/share/nginx/html/newdir\nhallo.imhere\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

额外说明

\n

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

\n
docker build . -t local/mynginx:withsubdir\n
Run Code Online (Sandbox Code Playgroud)\n

我在两个不同的环境( Docker 版本 20.10.7,构建 20.10.7-0ubuntu1~20.04.1Docker 版本 20.10.12,构建 e91ed5707e )中遇到相同的错误,无论有或没有:ro.

\n

我的test目录文件:

\n
.\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 docker-compose.yml\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Dockerfile\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 html\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 index.html\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 newdir\n    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 hallo.imhere\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

Dav*_*aze 6

Docker 尝试在只读目录中创建挂载点,但失败。

正常的 Linux mount (8) 调用和底层的mount (2) 系统调用将某些文件系统挂载到某些现有目录上。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
Run Code Online (Sandbox Code Playgroud)

(这与Why does docker createempty node_modules and how to避免它?的机制相同。)

现在,考虑到这些机制,您已经告诉 Docker 将外部目录挂载为只读,并且它确实这样做了。那么newdir容器文件系统中不存在该目录,因此 Docker 尝试将其创建为挂载点,但它不能,因为该目录位于只读文件系统中。这就是你得到的错误。

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

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

    mkdir ./html/newdir
    docker-compose up -d
    
    Run Code Online (Sandbox Code Playgroud)
  2. 不要将外部目录挂载为只读

    volumes:
      - ./html:/usr/share/nginx/html  # not ...:ro
    
    Run Code Online (Sandbox Code Playgroud)
  3. 将内部目录安装在其他地方,就像您在答案中所做的那样

    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./newdir:/usr/share/nginx/newdir:ro  # not under .../html
    
    Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)