Docker 中的 Docker 无法挂载卷
Posted
技术标签:
【中文标题】Docker 中的 Docker 无法挂载卷【英文标题】:Docker in Docker cannot mount volume 【发布时间】:2015-10-01 14:19:10 【问题描述】:我正在运行一个 Jenkins 集群,其中主服务器和从服务器都作为 Docker 容器运行。
主机是在 MacOS 上运行的最新 boot2docker 虚拟机。
为了让 Jenkins 能够使用 Docker 执行部署,我已将 docker.sock 和 docker 客户端从主机安装到 Jenkins 容器,如下所示:-
docker run -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):/usr/bin/docker -v $HOST_JENKINS_DATA_DIRECTORY/jenkins_data:/var/jenkins_home -v $HOST_SSH_KEYS_DIRECTORY/.ssh/:/var/jenkins_home/.ssh/ -p 8080:8080 jenkins
我在将卷安装到在 Jenkins 容器内运行的 Docker 容器时遇到问题。例如,如果我需要在 Jenkins 容器中运行另一个容器,我会执行以下操作:-
sudo docker run -v $JENKINS_CONTAINER/deploy.json:/root/deploy.json $CONTAINER_REPO/$CONTAINER_IMAGE
上面运行容器,但文件“deploy.json”不是作为文件挂载,而是作为“目录”。即使我将目录挂载为卷,我也无法查看生成的容器中的文件。
这是因为 Docker 案例中 Docker 文件权限的问题吗?
【问题讨论】:
我在 EC2 主机上运行 Docker 时遇到了同样的问题,安装了docker.sock
以便容器可以使用主机 Docker。看起来您在下面的答案是正确的 - 出现在最里面的容器中的卷包含来自 EC2 主机的文件。
【参考方案1】:
Docker 容器中的 Docker 容器使用父 HOST 的 Docker 守护程序,因此,在“docker-in-docker”案例中挂载的任何卷仍然从 HOST 引用,而不是从 Container 引用。
因此,从 Jenkins 容器挂载的实际路径在 HOST 中“不存在”。因此,在空的“docker-in-docker”容器中创建了一个新目录。当目录挂载到 Container 内的新 Docker 容器时,同样适用。
我错过了非常基本和明显的事情,但我一输入问题就意识到了。
【讨论】:
那么解决方案是什么?因为 docker 文档引用了jpetazzo.github.io/2015/09/03/…,它说以这种方式使用 docker。但是这种方式无法将卷从 docker 容器安装到另一个容器。数据量? @ZephyrPLUSPLUS 你能把你有什么和你改成什么张贴出来,以便其他人可以从你的回答中受益吗? 很高兴知道您解决了问题,但实际的解决方案是什么。你是如何挂载这个文件夹的? @JulioGuerra 我们还想知道,我们已承诺采用该博客文章中推荐的方法,该文章称“看起来像 Docker-in-Docker [并且] 感觉像 Docker-in-Docker”,但没有提到这个巨大的警告! 这篇文章实际上并没有解决问题。它只是进一步解释了问题。【参考方案2】:解决此问题的另一种方法是使用命名卷或数据卷容器。这样,里面的容器就不必知道主机的任何信息,Jenkins 容器和构建容器都以相同的方式引用数据量。
我尝试做一些类似于你正在做的事情,除了使用代理而不是使用 Jenkins 主服务器。问题是一样的,因为我无法在内部容器中安装 Jenkins 工作区。对我有用的是使用数据卷容器方法,并且工作区文件对代理容器和内部容器都是可见的。我喜欢这种方法的是两个容器都以相同的方式引用数据量。使用内部容器挂载目录会很棘手,因为内部容器现在需要了解运行其父容器的主机的一些信息。
我在这里有关于我的方法的详细博客文章:
http://damnhandy.com/2016/03/06/creating-containerized-build-environments-with-the-jenkins-pipeline-plugin-and-docker-well-almost/
以及这里的代码:
https://github.com/damnhandy/jenkins-pipeline-docker
在我的具体情况下,就 Jenkins Pipeline 插件而言,并非一切都按照我希望的方式运行。但它确实解决了内部容器能够访问 Jenkins 工作区目录的问题。
【讨论】:
我不敢相信有人否决了这个答案。这太棒了,直击问题的核心。这是一个解决方案,感觉就像是在使用 docker 的功能,因为它们存在的原因。 另一个相关的博客文章可以找到here(不是我的)。 这很好,除了我需要一个解决方案来运行 docker-compose。有什么线索吗?【参考方案3】:关于与 Jenkins 相关的用例,您可以通过在主机上创建符号链接来简单地伪造路径:
ln -s $HOST_JENKINS_DATA_DIRECTORY/jenkins_data /var/jenkins_home
【讨论】:
我很想使用这个解决方案。但是我不确定如何使用它。这应该在哪个主机上运行?它是如何解决问题的? @InbarRose,这个命令应该在运行 docker 守护进程的 host 机器上运行。之后,您将在主机和 Jenkins 容器中拥有两个“目录”/var/jenkins_home
(具有相同的名称和内容),因此您可以使用该目录名称将数据挂载到启动的“docker-in-docker”容器中由 Jenkins 工作。【参考方案4】:
解决此问题的一种方法是使用与其目的地完全相同的路径来安装目录(在您安装 docker 套接字的 docker 容器内)。然后,当您在该容器内运行容器时,您可以使用 docker -v
将该挂载路径中的任何内容挂载到新容器中。
举个例子:
# Spin up your container from which you will use docker
docker run -v /some/dir:/some/dir -v /var/run/docker.sock:/var/run.docker.sock docker:latest
# Now spin up a container from within this container
docker run -v /some/dir:/usr/src/app $CONTAINER_IMAGE
文件夹/some/dir
现在已安装在您的主机、中间容器以及目标容器中。由于挂载的路径作为“nearly docker-in-docker”容器存在于两个主机上,因此您可以按预期使用docker -v
。
这有点类似于在主机上创建符号链接的建议,但我发现这个(至少在我的情况下)是一个更清洁的解决方案。只是不要忘记之后清理主机上的目录! ;)
【讨论】:
【参考方案5】:这也适用于docker-compose
和/或命名卷,因此您无需创建仅数据容器,但您仍需要在主机上拥有空目录。
主机设置
制作主机端目录并设置权限以允许 Docker 容器访问
sudo mkdir -p /var/jenkins_home/workspace,builds,jobs && sudo chown -R 1000 /var/jenkins_home && sudo chmod -R a+rwx /var/jenkins_home
docker-compose.yml
version: '3.1'
services:
jenkins:
build: .
image: jenkins
ports:
- 8080:8080
- 50000:50000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- workspace:/var/jenkins_home/workspace/
# Can also do builds/jobs/etc here and below
jenkins-lts:
build:
context: .
args:
versiontag: lts
image: jenkins:lts
ports:
- 8081:8080
- 50001:50000
volumes:
workspace:
driver: local
driver_opts:
type: none
o: bind
device: /var/jenkins_home/workspace/
当您docker-compose up --build jenkins
(您可能希望将其合并到一个准备运行的示例中,例如https://github.com/thbkrkr/jks,其中 .groovy 脚本预先配置 Jenkins 以在启动时有用)然后您将能够拥有您的工作克隆到 $JENKINS_HOME/workspace 目录,并且不应该收到有关丢失文件/等的错误,因为主机和容器路径将匹配,然后从 Docker-in-Docker 中运行更多容器应该也可以工作。
Dockerfile(用于 Jenkins 和 Docker 中的 Docker)
ARG versiontag=latest
FROM jenkins/jenkins:$versiontag
ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false"
COPY jenkins_config/config.xml /usr/share/jenkins/ref/config.xml.override
COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt
USER root
RUN curl -L http://get.docker.io | bash && \
usermod -aG docker jenkins
# Since the above takes a while make any other root changes below this line
# eg `RUN apt update && apt install -y curl`
# drop back to the regular jenkins user - good practice
USER jenkins
EXPOSE 8080
【讨论】:
【参考方案6】:如果您和我一样不想弄乱 Jenkins 设置,或者懒得解决所有这些麻烦,这里有一个简单的解决方法,我可以让它为我工作。
第 1 步 - 将以下变量添加到管道的环境部分
environment
ABSOLUTE_WORKSPACE = "/home/ubuntu/volumes/jenkins-data/workspace"
JOB_WORKSPACE = "\$PWD##*/"
第 2 步 - 使用以下命令 Jenkins 管道运行容器,如下所示。
steps
sh "docker run -v $ABSOLUTE_WORKSPACE/$JOB_WORKSPACE/my/dir/to/mount:/targetPath imageName:tag"
注意上面语句中的双引号,如果引号格式不正确或添加了单引号,Jenkins将不会转换env变量。
每个变量的含义是什么?
ABSOLUTE_WORKSPACE 是我们在启动 Jenkins Docker 容器时挂载的 Jenkins 卷的路径。就我而言, docker run 命令如下。
sudo docker run \
-p 80:8080 \
-v /home/ubuntu/volumes/jenkins-data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-d -t jenkinsci/blueocean
JOB_WORKSPACE 命令为我们提供了代码所在的当前工作区目录。这也是代码库的根目录。已关注this answer 供参考。因此变量 ABSOLUTE_WORKSPACE=/home/ubuntu/volumes/jenkins-data + /workspace
这是如何工作的?
正如@ZephyrPLUSPLUS ( credits where due ) 回答中提到的那样,这非常简单,我们在 Jenkins 管道中运行的 docker 容器的源路径不是当前容器中的路径,而是采用的路径是主机的路径.我们在这里所做的只是构建运行 Jenkins 管道的路径。并将其安装到我们的容器中。瞧!!
这里有一个小插图来帮助澄清......
【讨论】:
【参考方案7】:我在 Gitlab CI 中遇到了同样的问题,我通过使用 docker cp
执行类似 mount 的操作解决了这个问题
script:
- docker run --name $CONTAINER_NAME $API_TEST_IMAGE_NAME
after_script:
- docker cp $CONTAINER_NAME:/code/newman ./
- docker rm $CONTAINER_NAME
【讨论】:
【参考方案8】:你可以通过一个环境变量来解决这个问题。 示例:
.
├── docker-compose.yml
└── my-volume-dir
└── test.txt
在 docker-compose.yml 中
version: "3.3"
services:
test:
image: "ubuntu:20.04"
volumes:
- $REPO_ROOT-./my-volume-dir:/my-volume
entrypoint: ls /my-volume
试运行
docker run -e REPO_ROOT=$PWD \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $PWD:/my-repo \
-w /my-repo \
docker/compose \
docker-compose up test
你应该在输出中看到:
test_1 | test.txt
【讨论】:
【参考方案9】:这些帖子中有很多很好的信息,但我发现他们都不清楚他们指的是哪个容器。所以让我们标记这 3 个环境:
主持人:H 在 H:D 上运行的 docker 容器 docker 容器在 D:D2 中运行我们都知道如何将一个文件夹从 H 挂载到 D 中:以 D 开头
docker run ... -v <path-on-H>:<path-on-D> -v /var/run/docker.sock:/var/run/docker.sock ...
挑战在于:您希望 path-on-H
在 D2 中以 path-on-D2
的形式出现。
但是当我们尝试将相同的path-on-H
挂载到 D2 时,我们都被咬了,因为我们用
docker run ... -v <path-on-D>:<path-on-D2> ...
当您与 D 共享 H 上的 docker 套接字时,在 D 中运行 docker 命令本质上是在 H 上运行它们。确实,如果您像这样启动 D2,一切正常(起初非常出乎意料,但当您考虑时才有意义它):
docker run ... -v <path-on-H>:<path-on-D2> ...
下一个棘手的问题是,对于我们中的许多人来说,path-on-H
会根据谁运行它而改变。有很多方法可以将数据传递到 D 中,因此它知道 path-on-H
使用什么,但可能最简单的是环境变量。为了使此类 var 的目的更明确,我以 DIND_
开头。然后从 H 开始 D 像这样:
docker run ... -v <path-on-H>:<path-on-D> --env DIND_USER_HOME=$HOME \
--env DIND_SOMETHING=blabla -v /var/run/docker.sock:/var/run/docker.sock ...
从 D 开始 D2 如下:
docker run ... -v $DIND_USER_HOME:<path-on-D2> ...
【讨论】:
与最受好评的答案相比,最有用的答案【参考方案10】:根据@ZephyrPLUSPLUS 提到的描述 这是我设法解决这个问题的方法:
vagrant@vagrant:~$ hostname
vagrant
vagrant@vagrant:~$ ls -l /home/vagrant/dir-new/
total 4
-rw-rw-r-- 1 vagrant vagrant 10 Jun 19 11:24 file-new
vagrant@vagrant:~$ cat /home/vagrant/dir-new/file-new
something
vagrant@vagrant:~$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock docker /bin/sh
/ # hostname
3947b1f93e61
/ # ls -l /home/vagrant/dir-new/
ls: /home/vagrant/dir-new/: No such file or directory
/ # docker run -it --rm -v /home/vagrant/dir-new:/magic ubuntu /bin/bash
root@3644bfdac636:/# ls -l /magic
total 4
-rw-rw-r-- 1 1000 1000 10 Jun 19 11:24 file-new
root@3644bfdac636:/# cat /magic/file-new
something
root@3644bfdac636:/# exit
/ # hostname
3947b1f93e61
/ # vagrant@vagrant:~$ hostname
vagrant
vagrant@vagrant:~$
所以docker
安装在Vagrant
机器上。让我们称之为vagrant
。您要挂载的目录在/home/vagrant/dir-new
中的vagrant
中。
它启动一个容器,主机为3947b1f93e61
。请注意,/home/vagrant/dir-new/
没有为 3947b1f93e61
安装。
接下来,我们使用来自vagrant
的确切位置,即/home/vagrant/dir-new
作为挂载源并指定我们想要的任何挂载目标,在本例中为/magic
。还要注意/home/vagrant/dir-new
在3947b1f93e61
中不存在。
这将启动另一个容器3644bfdac636
。
现在可以从3644bfdac636
访问vagrant
中来自/home/vagrant/dir-new
的内容。
我认为是因为 docker-in-docker 不是孩子,而是兄弟姐妹。并且您指定的路径必须是父路径而不是兄弟路径。因此,无论您对docker-in-docker
执行多深,任何挂载仍将引用来自vagrant
的路径。
【讨论】:
以上是关于Docker 中的 Docker 无法挂载卷的主要内容,如果未能解决你的问题,请参考以下文章
docker中启用挂载卷,docker中的新文件会不会同步到