nomad

HCL and Docker files for Nomad deployments
git clone https://git.in0rdr.ch/nomad.git
Log | Files | Refs | Pull requests

commit 436e406fba7a982d6c191835681b60f4323d849f
parent c11c10b9f5415a50bca9bb261ed55004925b83eb
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Sat, 25 May 2024 22:13:03 +0200

doc(jenkins): document Jenkins use case

Diffstat:
Adocker/docker-jenkins-inbound-agent/README | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 248 insertions(+), 0 deletions(-)

diff --git a/docker/docker-jenkins-inbound-agent/README b/docker/docker-jenkins-inbound-agent/README @@ -0,0 +1,248 @@ += Jenkins Agent Dockerfile = + +Based on the upstream docker file for Jenkins inbound agents: +* https://github.com/jenkinsci/docker-agent +* https://github.com/jenkinsci/docker-inbound-agents + +Change the uid/gid to be unique. Create a unique user jenkins on the Nomad +agents and assign this user a unique uid/gid that is only used for Jenkins +Docker builds. + +== Jenkins requirements == + +docker-workflow plugin: +* https://plugins.jenkins.io/docker-workflow +* https://docs.cloudbees.com/docs/cloudbees-ci/latest/pipelines/docker-workflow +* https://www.jenkins.io/doc/book/installing/docker +* https://www.jenkins.io/doc/book/pipeline/docker + +Ideally, the Jenkins as code config plugin: +* https://plugins.jenkins.io/configuration-as-code + +Note, that you don't need to install the `docker-plugin` for Jenkins. This +plugin has a different use. It should be used to spawn Jenkins agents on Docker +containers. What we do here is spawning new Nomad jobs instead. The Nomad jobs +(workers with a particular label that signal that they can run Docker workload) +will then launch the Docker container. + +== Nomad requirements == + +Nomad plugin for Jenkins (aka Nomad cloud): +* https://plugins.jenkins.io/nomad +* https://faun.pub/jenkins-build-agents-on-nomad-workers-626b0df4fc57 +* https://github.com/GastroGee/jenkins-nomad + +The Nomad job template for new Jenkins agent nodes can be configured as code +(Jenkins as code configuration plugin): +* https://code.in0rdr.ch/nomad/file/hcl/default/jenkins/templates/jenkins.yaml.tmpl.html + + +== Mount point requirements == + +Because the Nomad jobs launch the Docker containers, it is required to give +access to a Docker socket (tcp or unix). In this example, we can share the unix +socket from the Nomad node with the Nomad job. The YAML template for the new +Jenkins agent (a Nomad job): + +"Tasks": [ + { + "Name": "jenkins-podman-worker", + "Driver": "podman", + "User": "1312", + "Config": { + "volumes": [ + "/run/user/1312/podman/podman.sock:/home/jenkins/agent/podman.sock", + "/home/jenkins/workspace:/home/jenkins/workspace" + ], + "image": "127.0.0.1:5000/jenkins-inbound-agent:latest" +... + + +This can be configured in the Jenkins configuration as code plugin mentioned in +the requirements sections above. + +Note: +* The user UID/GID can be anything here, just make sure to build the Jenkins + inbound agent with the same UID/GID that are used by Nomad to spawn the + Jenkins agent job. +* The Docker socket shared with the Nomad job (the Jenkins agent) here needs to + be activated and run in the background (see enable-linger below) + +Because the Jenkins agent sits in between our Podman host (the Nomad agent) and +the downstream Docker container where we run our app logic, it is essential to +mount the directory from the Nomad agent node to the Jenkins agent. Otherwise, +the downstream container where we run our app logic will always see an empty +directory, because in the end all containers are run (in a flat structure, as +you so will) on the Nomad agent. + +This is also why UID/GID needs to match between the user that runs the Podman +socket on the Nomad node and the user that spawns the Jenkins agent (the Nomad +job). + +More on this relationship between the container, the Jenkins agent and the +Nomad node in the graphic below. + +== Nomad node requirements == + +Because in the end, the Docker containers are spawned on the Nomad nodes (as +Podman containers), the Nomad job (the Jenkins agent) needs direct access to +the filesystem of the Nomad node. Specifically, the Jenkins workspace directory +needs to be mounted to each Jenkins agent. + + +------------------+ + | Docker container |- - - - - - + + +------------------+ | + ^ mounts workspace | + | spawns Docker | + +---------------------------+ | + | Jenkins agent (Nomad job) | | is scheduled + +---------------------------+ | on and writes + ^ | workspace + | /home/jenkins/workspace | + | has user with UID/GID | + +------------+ | + | Nomad node |<- - - - - - - - -+ + +------------+ + +The Docker pipeline (docker-workflow) plugin has the default configuration to +automatically mound a Docker volume in `/home/jenkins/workspace`. + +This can be seen in the log of Jenkins when the first Job with a docker +pipeline configuration starts: + +$ docker run -t -d -u 1312:1312 -u root -w /home/jenkins/workspace/tutut -v +/home/jenkins/workspace/tutut:/home/jenkins/workspace/tutut:rw,z -v +/home/jenkins/workspace/tutut@tmp:/home/jenkins/workspace/tutut@tmp:rw,z -e +******** -e ******** -e ******** -e ******** -e ******** -e ******** -e +******** -e ******** -e ******** -e ******** -e ******** -e ******** -e +******** -e ******** -e ******** -e ******** -e ******** -e ******** -e +******** -e ******** -e ******** -e ******** -e ******** -e ******** -e +******** -e ******** -e ******** docker.io/arm64v8/alpine:latest cat + +Here we see that the default workspace directory (-w flag) is allocated in +`/home/jenkins/workspace` (the directory which is mounted through the Nomad job +from the Nomad node). Also, the plugin automatically mounts a sub-directory in +that folder for purposes of running the particular pipeline. + +If this mount propagation from Nomad node to job to the final container does +not work, there will be error similar to this one in the Jenkins pipeline: + + cp: can't stat '/home/jenkins/workspace/dubi@tmp/durable-9195a799/script.sh': No such file or directory + sh: can't create /home/jenkins/workspace/dubi@tmp/durable-9195a799/jenkins-log.txt: nonexistent directory + sh: can't create /home/jenkins/workspace/dubi@tmp/durable-9195a799/jenkins-result.txt.tmp: nonexistent directory + mv: can't rename '/home/jenkins/workspace/dubi@tmp/durable-9195a799/jenkins-result.txt.tmp': No such file or directory + +Once again, it's critical to mount the Jenkins workspace(s) as volume inside +the container because of how the docker-workflow plugin works: + +> For inside to work, the Docker server and the Jenkins agent must use the same +> filesystem, so that the workspace can be mounted. The easiest way to ensure +> this is for the Docker server to be running on localhost (the same computer +> as the agent). Currently neither the Jenkins plugin nor the Docker CLI will +> automatically detect the case that the server is running remotely; a typical +> symptom would be errors from nested sh commands such as +> +> "cannot create /…@tmp/durable-…/pid: Directory nonexistent" + +https://docs.cloudbees.com/docs/cloudbees-ci/latest/pipelines/docker-workflow#docker-workflow-sect-inside + +The same warning note is also given again here: +* https://www.jenkins.io/doc/book/pipeline/docker/#using-a-remote-docker-server + +Because the `/home/jenkins/workspace` is the default workspace directory of the +plugin, the cleanest approach is probably to to simply create this dedicated +Jenkins service user on all Nomad nodes (with a dedicated UID/GID combination), +then start the Podman socket as systemd user job on the Nomad nodes. + +On all the Nomad clients, prepare the Jenkins user and the workspace directory +(1312 can be any UID/GID combination, it just needs to map with the User in the +Jenkins cloud plugin configuration where the Nomad job is spawned): + + groupadd -g 1312 jenkins + useradd -m -s /bin/bash -u 1312 -g 1312 jenkins + + # keep my podman.socket enabled even if no jenkins user is logged in + loginctl enable-linger jenkins + #loginctl user-status jenkins | grep -i linger + + # https://www.freedesktop.org/software/systemd/man/latest/systemctl.html#-M + systemctl --user -M jenkins@ start podman.socket + systemctl --user -M jenkins@ enable podman.socket + #systemctl --user -M jenkins@ is-enabled podman.socket + + # create the mountpoint for the workspaces, podman does not create it for us + sudo -u jenkins mkdir /home/jenkins/workspace + +<TODO: LINK-TO-PACKER-SCRIPT> + +If you need to redo the jenkins user configuration (e.g., to change the +UID/GID), make sure to stop the systemd service for the user. Otherwise, new +directories will still be created with old GIDs. + + systemctl stop user@1312 # removes /run/user/1312 + rm -rf /run/user/1312 + +== Example build process == + +To configure a different UID/GID for the Jenkins user, it is also required to +rebuild a Jenkins agent image with that particular UID/GID. + +To build the Jenkins agent docker container for the purposes of using it in +Nomad: + + git clone https://github.com/jenkinsci/docker-agent.git docker-agent.git + cd docker-agent.git + + # change the uid/gid + buildah bud --no-cache --arch arm64/v8 --build-arg=JAVA_VERSION=21.0.3_9 \ + --build-arg=uid=1312 --build-arg=gid=1312 -f alpine/Dockerfile \ + -t 127.0.0.1:5000/jenkins-inbound-agent:latest . + + podman push 127.0.0.1:5000/jenkins-inbound-agent:latest + +== Jenkins Pipeline examples == + +The declarative pipeline examples need to ensure that the user (`-u` flag) is +set to the user of the Docker image. Two examples: + + pipeline { + agent { + docker { + image 'docker.io/arm64v8/alpine:latest' + // The Nomad cloud spawns Nomad agents with that label + label 'podman' + // User needs to match the user in the container, otherwise you + // encounter errors while accessing "durable" directories. + args '-u root' + } + } + + stages { + stage('runInDocker') { + steps { + sh 'echo "hello world"' + } + } + } + } + + pipeline { + agent { + docker { + image 'docker.io/hashicorp/vault:latest' + label 'podman' + args '-u root' + } + } + + stages { + stage('runInDocker') { + steps { + sh ''' + vault version + ''' + } + } + } + } +