Docker in Docker - 卷不工作:第一层容器中的文件已满,第二层中的文件为空

Docker in Docker - volumes not working: Full of files in 1st level container, empty in 2nd tier

我 运行ning Docker 在 Docker 中(特别是 运行 Jenkins 然后 运行s Docker builder containers to build一个项目图像,然后是 运行 这些,然后是测试容器)。

jenkins 镜像是这样构建和启动的:

docker build --tag bb/ci-jenkins .
mkdir $PWD/volumes/
docker run -d --network=host  \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /usr/bin/docker:/usr/bin/docker \
  -v $PWD/volumes/jenkins_home:/var/jenkins_home \
  --name ci-jenkins bb/ci-jenkins

詹金斯工作正常。但是还有一个基于 Jenkinsfile 的工作,运行 是这样的:

docker run -i --rm -v /var/jenkins_home/workspace/forkMV_jenkins-VOLTRON-3057-KQXKVJNXOU4DGSUG3P27IR3QEDHJ6K7HPDEZYN7W6HCOTCH3QO3Q:/tmp/build collab/collab-services-api-mvn-builder:2a074614 mvn -B -T 2C install

最后出现错误:

The goal you specified requires a project to execute but there is no POM in this directory (/tmp/build).

当我对容器执行 docker exec -it sh 时,/tmp/build 是空的。但是当我在 Jenkins 容器中时,路径 /var/jenkins_home/...QO3Q/ 存在并且它包含工作空间,其中包含所有已检出和准备的文件。

所以我想知道 - 如何 Docker 愉快地挂载卷然后它是空的?*

更令人困惑的是,此设置适用于 Mac 上的同事。 我在 Linux、Ubuntu 17.10、Docker 最新。

经过一些研究,静下心来思考,我意识到Docker-in-Docker并不是真的那么“-in-”,而是[=35] =].

使一个容器能够 运行 另一个容器的技巧是通过卷共享 /var/run/docker.sock-v /var/run/docker.sock:/var/run/docker.sock

然后容器中的 docker 客户端实际调用主机上的 Docker。

卷源路径(:左侧)不是指中间容器,而是指主机文件系统!

意识到这一点后,解决方法是使主机文件系统和 Jenkins(中间)容器中的 Jenkins workspace 目录路径相同:

docker run -d --network=host  \
   ...
   -v /var/jenkins_home:/var/jenkins_home

瞧!有用。 (我创建了一个符号链接而不是移动它,似乎也有效。)

如果你正在看同事的 Mac,它会有点复杂,因为 Docker 在那里的实现有点不同 - 它是 运行ning 在 Alpine [=41] =] 基于 VM 但假装不是。 (不是 100% 确定。)在 Windows 上,我读到路径有另一层抽象 - 从 C:/somewhere/... 映射到 Linux-like 路径。

我希望我能节省别人几个小时的时间来弄清楚:)

更好的方法是使用 Jenkins Docker plugin 并让它为您完成所有安装,只需在其 inside 函数参数中添加 -v /var/run/docker.sock:/var/run/docker.sock

例如

docker.build("bb/ci-jenkins")
docker.image("bb/ci-jenkins").inside('-v /var/run/docker.sock:/var/run/docker.sock')

{
 ...
}

Docker cp

的替代解决方案

我在 Kubernetes 的 Jenkins 服务器中的 Docker 容器 运行ning 中遇到了从 运行s 中安装卷的相同问题。当我们使用 docker-in-dockerdind 时,我无法以此处建议的任何一种方式安装该卷。我仍然不确定原因是什么,但我找到了另一种方法:使用 docker cp 复制构建工件。

多阶段Docker测试图像

我正在使用以下 Docker文件阶段进行单元 + 集成测试。

#
# Build stage to for building the Jar
#
FROM maven:3.2.5-jdk-8 as builder
MAINTAINER marcello.desales@gmail.com

# Only copy the necessary to pull only the dependencies from registry
ADD ./pom.xml /opt/build/pom.xml
# As some entries in pom.xml refers to the settings, let's keep it same
ADD ./settings.xml /opt/build/settings.xml

WORKDIR  /opt/build/

# Prepare by downloading dependencies
RUN mvn -s settings.xml -B -e -C -T 1C org.apache.maven.plugins:maven-dependency-plugin:3.0.2:go-offline

# Run the full packaging after copying the source
ADD ./src /opt/build/src
RUN mvn -s settings.xml install -P embedded -Dmaven.test.skip=true -B -e -o -T 1C verify

# Building only this stage can be done with the --target builder switch
# 1. Build: docker build -t config-builder --target builder .
# When running this first stage image, just verify the unit tests
# Overriden them by removing the "!" for integration tests
# 2. docker run --rm -ti config-builder mvn -s settings.xml -Dtest="*IT,*IntegrationTest" test
CMD mvn -s settings.xml -Dtest="!*IT,!*IntegrationTest" -P jacoco test

用于测试的 Jenkins 管道

  • 我的 Jenkins 管道有一个阶段用于 运行 宁并行测试(单元 + 集成)。
  • 我所做的是在一个阶段构建测试图像,运行 并行测试。
  • 我使用 docker cp 从测试 docker 容器中复制构建工件,可以在命名容器中 运行 测试后启动。
    • 或者,您可以使用 Jenkins stash 将测试结果传送到 Post 阶段

在这一点上,我用docker run --name test:SHA解决了问题然后我使用docker start test:SHA然后docker cp test:SHA:/path .,其中.是当前工作区目录,类似于我们需要的 docker 卷挂载到当前目录。

stage('Build Test Image') {
  steps {
    script {
      currentBuild.displayName = "Test Image"
      currentBuild.description = "Building the docker image for running the test cases"
    }
    echo "Building docker image for tests from build stage ${env.GIT_COMMIT}"
    sh "docker build -t tests:${env.GIT_COMMIT} -f ${paas.build.docker.dockerfile.runtime} --target builder ."
  }
}

stage('Tests Execution') {
  parallel {
    stage('Execute Unit Tests') {
      steps {
        script {
          currentBuild.displayName = "Unit Tests"
          currentBuild.description = "Running the unit tests cases"
        }
        sh "docker run --name tests-${env.GIT_COMMIT} tests:${env.GIT_COMMIT}"
        sh "docker start tests-${env.GIT_COMMIT}"
        sh "docker cp tests-${env.GIT_COMMIT}:/opt/build/target ."

        // https://jenkins.io/doc/book/pipeline/jenkinsfile/#advanced-scripted-pipeline#using-multiple-agents
        stash includes: '**/target/*', name: 'build'
      }
    }
    stage('Execute Integration Tests') {
      when {
        expression { paas.integrationEnabled == true }
      }
      steps {
        script {
          currentBuild.displayName = "Integration Tests"
          currentBuild.description = "Running the Integration tests cases"
        }
        sh "docker run --rm tests:${env.GIT_COMMIT} mvn -s settings.xml -Dtest=\"*IT,*IntegrationTest\" -P jacoco test"
      }
    }
  }
}