Skip to content

Instantly share code, notes, and snippets.

@abdennour
Created September 5, 2019 21:45
Show Gist options
  • Select an option

  • Save abdennour/4b3a569c05d2bb10b6bfc4e09a370f15 to your computer and use it in GitHub Desktop.

Select an option

Save abdennour/4b3a569c05d2bb10b6bfc4e09a370f15 to your computer and use it in GitHub Desktop.
Secure Docker In Docker

To provide software in the form of Docker Images, it requires a build system and an infrastructure that creates the images as part of a Continuous Integration and, if necessary, stores them in a Docker Registry. For open source projects there are offers in the cloud, from Travis CI to build on Dockerhub itself. For in-house development, the infrastructure for the build is often called Jenkins, and with good reason. To install a Jenkins - whether for the whole company, the team or locally for testing - there are many ways.

An installation of operating system packages or ZIP archives is one possibility, but leaves many things open (eg the Java version) and may depend on the operating system. Obviously - and also very trendy - is starting Jenkins itself as a Docker container. Starting Jenkins in a container is easy. It is possible to run Docker in a container, but it does not matter which way it is connected to some configuration.

jenkins_in_docker_for_docker Jenkins in the container accesses the Docker Executable and the Docker Socket of the host via volumes.

Inspired by, for example, these articles [1] , [2] , [3] , [4] I have built a special Docker Image ( source ) for a Jenkins that runs as a Docker container and can also run Docker by pointing to the Docker Installation of the host is accessed. An installation of Docker in the Jenkins image and execution as a "privileged" container would be another option discussed in [3] and [4]. The execution of Docker as a separate process in privileged containers, but especially in the context of Linux security modules and "nested" file systems is not without problems and therefore not per se easier or better.

If you just want to use the image, you can use the command line

docker run -p 8080:8080 -p 50000:50000 -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /var/lib/jenkins_home:/var/jenkins_home -e "DOCKER_GID_ON_HOST=$(cat /etc/group | grep docker: | cut -d: -f3)" oose/dockerjenkins

quickly start a Jenkins. The prerequisite is a Docker installation on the host or a configured connection to a remote Docker host. If the command is not executed directly on the (Linux) Docker host but, for example, under Windows with a remote connection to Docker, the call must be $(cat /etc/group | grep docker: | cut -d: -f3) replaced by the Group ID of the group dockeron the Docker host. After installing the Docker Toolbox under Windows, this is eg the Group ID 100.

A script to determine the Group ID dynamically via an ssh command from the host, I leave the idea of ​​the inclined reader.

As a further requirement, the directory must be /var/lib/jenkins_homecreated or exist on the host . Here, the Jenkins lays down its configuration or reads it again. For tests or when starting on remote Docker hosts, this volume can simply be omitted or replaced by a volume container.

On localhost: 8080, a Jenkins instance can be reached that can build and push Docker Images.

How does it work? If you want to know how it works and why the command line is so complicated, read on here.

First, let's take a look into Dockerfile . As a basis, the standard Jenkins Image is used.

FROM jenkins:1.625.1

This base will be supplemented or modified in four places.

  1. First of all, an Oracle Java 8 and fakeroot will be installed as user root. fakeroot is needed during the build of my javafx projects.

USER root

Java 8 and fakeroot for javafx builds

RUN echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee /etc/apt/sources.list.d/webupd8team-java.list &&
echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list.d/webupd8team-java.list &&
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886 &&
apt-get update &&
echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections &&
apt-get --no-install-recommends -y install oracle-java8-installer &&
apt-get --no-install-recommends -y install fakeroot &&
rm -rf /var/lib/apt/lists/*

  1. Then, sudo is installed and the jenkinsuser is allowed to do anything without a password as an arbitrary user.

Let Jenkins be sudoer

RUN apt-get update &&
apt-get --no-install-recommends -y install sudo &&
echo "jenkins ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers &&
rm -rf /var/lib/apt/lists/*

This is necessary because the Jenkins process in the container must access the Docker Binary and the Docker Socket of the host on which the container is started, and therefore must run with the "correct" user or group identifier!

  1. Again, as a jenkinsuser, the number of build processors is confi gured via the executors.groovy file and the installation of plugins via the plugins.txt, which simply lists the plugins with their version, is initiated.

USER jenkins COPY executors.groovy /usr/share/jenkins/ref/init.groovy.d/executors.groovy COPY plugins.txt /usr/share/jenkins/plugins.txt RUN /usr/local/bin/plugins.sh /usr/share/jenkins/plugins.txt

  1. The most special adaptation comes in very inconspicuous and at the very end

ENV DOCKER_GID_ON_HOST "" COPY jenkins.sh /usr/local/bin/jenkins.sh

The environment variable DOCKER_GID_ON_HOST is first set to an empty string. When starting the container, this environment variable must be set to the Group ID, which is allowed to run the Docker Binary and access the Docker Socket. In general, the ID of the group dockeron the host (the machine where the Docker daemon is running).

There is no mapping of users and groups between Docker Host and Docker Container! An access from a container to a volume takes place with the User ID and Group ID that the process has in the container, regardless of which user name and which group name correspond to the container and host respectively. For example, it may be that a process in the container is running as user "jenkins" with the group "jenkins", which in the container corresponds to the user ID 1400 and the group ID 1000, and then the files in a host mounted volume also with These IDs (!) are created, which then but for example, the user "klaus" and the group "modem" corresponds to the host. Since the container should access Docker Binary and Socket, the container must have the ID of the groupdockeron the host and running under this group ID. Since the name of the group is known at the time of the image build, but not the ID, the ID is not evaluated until the Container Entrypoint is started.

For this purpose, the entypoint of the image, the script jenkins.sh, is exchanged for a customized version. This custom script evaluates the environment variable DOCKER_GID_ON_HOST. If the variable is set to a value, then the script ensures that the Jenkins process with the Group ID runs in the environment variable, allowing access to the Docker Binary and the Docker Socket.

This snippet in the jenkins.sh :

When a DOCKER_GID_ON_HOST is supplied, run jenkins with this

group id - to access the docker socket shared via volume

dockerGroupName="" if [ -n "$DOCKER_GID_ON_HOST" ]; then

echo "Create group for gid $DOCKER_GID_ON_HOST" sudo groupadd -g $DOCKER_GID_ON_HOST docker && sudo grpconv sudo usermod -a -G $DOCKER_GID_ON_HOST jenkins dockerGroupName=$(cat /etc/group | grep :$DOCKER_GID_ON_HOST: | cut -d: -f1)

fi;

In the container creates a group for the given ID and makes it available in the same session with "grpconv", adds the user jenkinsto the group and finally determines the name of the group with the given ID. This can be the group docker, if no group with the ID existed, or any other group name, if it already existed with the given ID. Finally leads

exec sg $dockerGroupName "$cmdLine"

Jenkins in the context of the "right" group. Important is the "exec" so that the current process (the Entrypoint script) is replaced by the Jenkins process, so that signals from the terminal (eg a Str + C) are forwarded to the Jenkiddens process.

And now… The "totally simple" command line

docker run -p 8080:8080 -p 50000:50000 -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /var/lib/jenkins_home:/var/jenkins_home -e "DOCKER_GID_ON_HOST=$(cat /etc/group | grep docker: | cut -d: -f3)" oose/dockerjenkins

binds the two network ports 8080 and 50000 of the host to the same ports of the container, so that also from the outside on the Jenkins can be accessed, as if the Jenkins really "native" would be installed. The Docker Socket /var/run/docker.sockand the executable file /usr/bin/dockerare made available to the container via volumes. dockerSo, when a build job calls in the Jenkins container , both the binary and the docker daemon are used on the host, not a "custom" instance of it.

Depending on the application, the access of containers to the Docker installation of the host can be disadvantageous! For example, all images that pull or generate build jobs are stored on the host and not in the container! If that is not acceptable, it is of course also possible to refrain from the "Jenkins as a Docker Container" approach and instead, for example, operate with the help of Vagrant a whole virtual machine with Jenkins. The third volume is Jenkins as "home", in which the configuration and the jobs, etc. are stored. The last cryptic expression in the line before the name of the image sets the environment variable DOCKER_GID_ON_HOST on the container to the cat, grep, and cut value for the ID of the group dockeron the host.

Since this is too complicated to remember and since we also have no configuration in Jenkins, I now need a Puppet module that automates me. I will describe this module in the next blog post.

Reference

https://www.oose.de/blogpost/jenkins-in-docker-und-mit-docker-und-fuer-docker/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment