web-dev-qa-db-de.com

Docker im Docker - Volumes funktionieren nicht: Voll mit Dateien im Container der ersten Ebene, leer in der zweiten Ebene

Ich führe Docker in Docker aus (speziell, um Jenkins auszuführen, das dann Docker-Builder-Container ausführt, um Projektimages zu erstellen, und dann diese und dann die Testcontainer ausführt).

So wird das Jenkins-Image erstellt und gestartet:

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

Jenkins funktioniert gut. Aber dann gibt es einen Job auf der Basis von Jenkinsfile, der Folgendes ausführt:

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

Und das endet mit einem Fehler:

Das von Ihnen angegebene Ziel erfordert die Ausführung eines Projekts, es befindet sich jedoch kein POM in diesem Verzeichnis (/ tmp/build).

Wenn ich docker exec -it sh für den Container mache, ist der /tmp/build leer. Wenn ich mich jedoch im Jenkins-Container befinde, ist der Pfad /var/jenkins_home/...QO3Q/ vorhanden und enthält den Arbeitsbereich mit allen ausgecheckten und vorbereiteten Dateien.

Ich frage mich also - wie kann Docker das Volume glücklich einbinden und es ist dann leer? *

Was noch verwirrender ist, dieses Setup funktioniert für meinen Kollegen auf dem Mac. Ich bin auf Linux, Ubuntu 17.10, Docker aktuell.

5
Ondra Žižka

Nach einigem Nachforschen, Beruhigen und Nachdenken wurde mir klar, dass Docker-in-Docker nicht wirklich so viel "-in" ist, sondern eher "Docker-next-to-Docker".

Der Trick, um einen Container in die Lage zu versetzen, einen anderen Container auszuführen, besteht darin, /var/run/docker.sock über ein Volume gemeinsam zu nutzen: -v /var/run/docker.sock:/var/run/docker.sock

Und dann ruft der docker-Client im Container Docker auf dem Host auf.

Der Volume-Quellpfad (links von :) bezieht sich nicht auf den mittleren Container, sondern auf das Host-Dateisystem!

Nachdem Sie dies erkannt haben, müssen Sie die Pfade zum Verzeichnis Jenkins workspace im Host-Dateisystem und im Container Jenkins (Mitte) identisch machen:

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

Und voilá! Es klappt. (Ich habe einen Symlink erstellt, anstatt ihn zu verschieben. Scheint auch zu funktionieren.)

Es ist etwas kompliziert, wenn Sie sich den Mac eines Kollegen ansehen, da Docker dort etwas anders implementiert ist - es läuft auf einem alpinen Linux-basierten VM, tut aber so, als ob es nicht so wäre. (Da bin ich mir nicht 100% sicher.) Unter Windows habe ich gelesen, dass die Pfade eine andere Abstraktionsebene haben - die Zuordnung von C:/somewhere/... zu einem Linux-ähnlichen Pfad.

Ich hoffe, ich kann jemandem Stunden des Herausfindens ersparen :)

7
Ondra Žižka

Alternative Lösung mit Docker cp

Ich hatte das gleiche Problem beim Mounten von Volumes aus einem Build, der in einem Docker-Container auf einem Jenkins-Server in Kubernetes ausgeführt wird. Da wir docker-in-docker, dind verwenden, konnte ich das Volume auf keine der hier vorgeschlagenen Arten mounten. Ich bin mir immer noch nicht sicher, was der Grund ist, aber ich habe einen alternativen Weg gefunden: Verwenden Sie docker cp, um die Build-Artefakte zu kopieren.

 enter image description here

Mehrstufiges Docker-Image für Tests

Ich verwende die folgende Dockerfile-Stufe für Unit + Integrationstests.

#
# Build stage to for building the Jar
#
FROM maven:3.2.5-jdk-8 as builder
MAINTAINER [email protected]

# 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 Pipeline Für Tests

  • Meine Jenkins-Pipeline verfügt über eine Phase für parallele Tests (Unit + Integration).
  • Ich erstelle das Test-Image in einer Phase und führe die Tests parallel aus.
  • Ich benutze docker cp, um die Build-Artefakte aus dem Test-Docker-Container zu kopieren, die nach dem Ausführen der Tests in einem benannten Container gestartet werden können.
    • Alternativ können Sie Jenkins Stash verwenden, um die Testergebnisse in eine Post Phase zu überführen

Zu diesem Zeitpunkt löste ich das Problem mit einem docker run --name test:SHA und verwende dann docker start test:SHA und dann docker cp test:SHA:/path ., wobei . das aktuelle Arbeitsbereichsverzeichnis ist, ähnlich wie bei einem Docker-Volume, das im aktuellen Verzeichnis angehängt ist.

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"
      }
    }
  }
}
3

Ein besserer Ansatz ist es, das Jenkins Docker-Plugin zu verwenden und es alle Einhängevorgänge für Sie durchführen zu lassen und einfach -v /var/run/docker.sock:/var/run/docker.sock in seine inside-Funktionsargumente einzufügen.

Z.B.

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

{
 ...
}
1
yorammi