Docker中的Docker无法安装卷

时间:2015-07-13 10:47:11

标签: linux jenkins docker containers

我正在运行一个Jenkins集群,在Master和Slave中,它们都作为Docker容器运行。

主机是在MacOS上运行的最新boot2docker VM。

为了让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容器中运行另一个Container,我会执行以下操作: -

sudo docker run -v $JENKINS_CONTAINER/deploy.json:/root/deploy.json $CONTAINER_REPO/$CONTAINER_IMAGE 

上面运行容器,但文件" deploy.json"不是作为文件挂载,而是作为"目录&#34 ;.即使我将目录作为卷安装,我也无法查看生成的容器中的文件。

这是一个问题,因为Docker案例中Docker的文件权限是什么?

任何指针都很有用!

谢谢!

9 个答案:

答案 0 :(得分:61)

Docker容器中的Docker容器使用父HOST的Docker守护程序,因此,任何安装在" docker-in-docker" case仍然是从HOST引用的,而不是从Container引用的。

因此,从Jenkins容器"安装的实际路径不存在"在主持人。因此,在" docker-in-docker"中创建了一个新目录。容器是空的。当目录安装到Container内的新Docker容器时,同样适用。

我错过了非常基本和明显的事情,但很快我就输入了问题。

答案 1 :(得分:9)

另一种方法是使用命名卷或数据卷容器。这样,内部容器不必知道有关主机的任何信息,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工作空间目录的问题。

答案 2 :(得分:8)

关于与Jenkins相关的用例,您可以通过在主机上创建符号链接来伪造路径:

ln -s $HOST_JENKINS_DATA_DIRECTORY/jenkins_data /var/jenkins_home

答案 3 :(得分:3)

这些文章中有很多不错的信息,但我发现他们都不清楚他们指的是哪个容器。因此,我们将3种环境标记为:

  • 主持人:H
  • 在H:D上运行的docker容器
  • 在D:D2中运行的docker容器

我们都知道如何将文件夹从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装入D2时,我们都被咬了,因为我们从

开始了D2。
path-on-H

当您与D共享H上的docker套接字时,然后在D中运行docker命令实际上就是在H上运行它们。的确,如果像这样启动D2,所有的工作(一开始都是意外的,但是当考虑):

docker run ... -v <path-on-D>:<path-on-D2> ...

下一个棘手的问题是,对于我们许多人来说,docker run ... -v <path-on-H>:<path-on-D2> ... 会根据运行它的人而改变。有很多方法可以将数据传递到D,因此它知道将path-on-H使用什么,但是最简单的方法可能是环境变量。为了使此类var更清晰,我以path-on-H开头。然后从 H 这样开始D:

DIND_

,然后从 D 像这样开始D2:

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 ...

答案 4 :(得分:2)

这也可以通过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

答案 5 :(得分:1)

我在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}

答案 6 :(得分:0)

如果您像我一样,不想与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

  

因此,变量ABSOLUTE_WORKSPACE = / home / ubuntu / volumes / jenkins-data + / workspace

  • JOB_WORKSPACE命令为我们提供了代码所在的当前工作空间目录。这也是代码库的根目录。请紧跟this answer以供参考。

这是如何工作的?

这是非常简单的,正如@ZephyrPLUSPLUS(归因于)所述,在Jenkins管道中运行的Docker容器的源路径不是当前容器中的路径,而是采用的路径是主机的路径。我们在这里所做的只是构建运行Jenkins管道的路径。并将其安装到我们的容器上。瞧!

这里有一个小例子可以帮助澄清...  enter image description here

答案 7 :(得分:0)

您可以解决在环境变量中传递的问题。 示例:

.
├── 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

答案 8 :(得分:0)

根据@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。 现在可以从/home/vagrant/dir-new访问vagrant3644bfdac636中的内容。

我认为是因为docker-in-docker不是孩子,而是兄弟姐妹。并且您指定的路径必须是父路径,而不是兄弟姐妹的路径。因此,无论您进行vagrant的深度如何,任何挂载仍将引用来自docker-in-docker的路径。