Run Jenkins for CI/CD
Jenkins¶
Jenkins is a popular CI/CD tool with a rich history in the industry. Compared to newer products like GitHub Actions, it has much less complexity, and when used with Slicer, jobs launch almost instantly.
There are two use-cases for Jenkins with Slicer:
- Run a Jenkins master and add build-slaves separately or via a public cloud plugin (EC2, GCE, etc)
- Add ephemeral build slaves to a Jenkins master (existing, or hosted in Slicer)
Learn more¶
Read how Slicer works and compares to the Docker( Docker In Docker / Docker Socket), Kubernetes (privileged Pod), or EC2 (long start-up times), in our announcement on the Actuated blog
Watch a video walkthrough on our YouTube channel.
1. Run a Jenkins master¶
You can run a Jenkins master within a Slicer microVM, and have everything set up for you automatically via a userdata script.
Create setup-master.sh:
#!/usr/bin/env bash
set -euxo pipefail
# --- Tunables (override via env if you like) ---
: "${ADMIN_USER:=admin}"
: "${ADMIN_PASS:=$(openssl rand -base64 18)}"
: "${JENKINS_HTTP_PORT:=8080}"
# Best-guess URL (update later if you put it behind a domain/reverse-proxy)
DEFAULT_IP="$(hostname -I | awk '{print $1}')"
: "${JENKINS_URL:=http://${DEFAULT_IP}:${JENKINS_HTTP_PORT}/}"
export DEBIAN_FRONTEND=noninteractive
if command -v apt-get >/dev/null 2>&1; then
# ----- Ubuntu/Debian path -----
apt-get update && \
apt-get install -qy --no-install-recommends \
curl gnupg ca-certificates openjdk-17-jdk git
# Add Jenkins apt repo
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | tee /etc/apt/keyrings/jenkins-keyring.asc >/dev/null
chmod a+r /etc/apt/keyrings/jenkins-keyring.asc
echo "deb [signed-by=/etc/apt/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/" > /etc/apt/sources.list.d/jenkins.list
apt-get update && \
apt-get install -qy --no-install-recommends \
jenkins
else
echo "This setup script targets Debian/Ubuntu (apt). If you need RHEL/Alma/Oracle/Rocky Linux, reach out to us via Discord."
exit 1
fi
# ----- Plugins configuration -----
# List of plugins to install (name[:version], one per line)
cat > /var/lib/jenkins/plugins.txt <<'EOF'
configuration-as-code
git
workflow-aggregator
credentials
ssh-credentials
EOF
# ----- JCasC config -----
CCFG_DIR="/var/lib/jenkins/casc_configs"
mkdir -p "$CCFG_DIR"
cat > "${CCFG_DIR}/jenkins.yaml" <<'YAML'
jenkins:
systemMessage: "Jenkins bootstrapped by SlicerVM.com\n"
mode: EXCLUSIVE
numExecutors: 0
remotingSecurity:
enabled: true
labelString: "system"
securityRealm:
local:
allowsSignup: false
users:
- id: "${ADMIN_USER}"
password: "${ADMIN_PASS}"
authorizationStrategy:
loggedInUsersCanDoAnything:
allowAnonymousRead: false
unclassified:
location:
adminAddress: "address not configured yet <nobody@nowhere>"
url: "${JENKINS_URL}"
YAML
# Substitute vars into JCasC (simple inline envsubst using bash)
sed -i "s|\${ADMIN_USER}|${ADMIN_USER}|g" "${CCFG_DIR}/jenkins.yaml"
sed -i "s|\${ADMIN_PASS}|${ADMIN_PASS}|g" "${CCFG_DIR}/jenkins.yaml"
sed -i "s|\${JENKINS_URL}|${JENKINS_URL}|g" "${CCFG_DIR}/jenkins.yaml"
# Ensure Jenkins uses our JCasC and skips the setup wizard
install -d -m 0755 /etc/systemd/system/jenkins.service.d
cat > /etc/systemd/system/jenkins.service.d/override.conf <<EOF
[Service]
Environment=JENKINS_HOME=/var/lib/jenkins
Environment=CASC_JENKINS_CONFIG=${CCFG_DIR}/jenkins.yaml
Environment=JAVA_OPTS=-Djenkins.install.runSetupWizard=false
Environment=JENKINS_PORT=${JENKINS_HTTP_PORT}
EOF
# Download and use Jenkins Plugin Manager
PLUGIN_MANAGER_JAR="/tmp/jenkins-plugin-manager.jar"
curl -L -o "$PLUGIN_MANAGER_JAR" "https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/2.13.2/jenkins-plugin-manager-2.13.2.jar"
# Install plugins using the plugin manager
java -jar "$PLUGIN_MANAGER_JAR" -d /var/lib/jenkins/plugins --plugin-file /var/lib/jenkins/plugins.txt --verbose
# Clean up
rm -f "$PLUGIN_MANAGER_JAR"
# Permissions & start
chown -R jenkins:jenkins /var/lib/jenkins
systemctl daemon-reload
systemctl enable --now jenkins
# Wait for it to be reachable
tries=60
until curl -fsS "http://127.0.0.1:${JENKINS_HTTP_PORT}/login" >/dev/null 2>&1 || [ $tries -le 0 ]; do
sleep 2; tries=$((tries-1))
done
# Save creds somewhere easy to grab
cat > /home/ubuntu/jenkins-admin.txt <<CREDS
Jenkins URL: ${JENKINS_URL}
Username: ${ADMIN_USER}
Password: ${ADMIN_PASS}
(saved by user-data at $(date -u +"%Y-%m-%dT%H:%M:%SZ"))
CREDS
sudo chown ubuntu:ubuntu /home/ubuntu/jenkins-admin.txt
echo "======================================================"
echo " Jenkins is up at: ${JENKINS_URL}"
echo " Admin credentials are in: /home/ubuntu/jenkins-admin.txt"
echo "======================================================"
You can customise the script as you wish, and the admin password will be printed out to the console during VM creation.
Now create a jenkins-master.yaml file:
config:
host_groups:
- name: jenkins-master
userdata_file: ./setup-master.sh
storage: image
storage_size: 25G
count: 1
vcpu: 2
ram_gb: 4
network:
bridge: brvm0
tap_prefix: vmtap
gateway: 192.168.137.1/24
api:
enabled: true
port: 8080
bind_address: "0.0.0.0:"
ssh:
bind_address: "127.0.0.1:"
port: 2222
github_user: alexellis
image: "ghcr.io/openfaasltd/slicer-systemd:5.10.240-x86_64-latest"
hypervisor: firecracker
The disk size, vCPU and RAM can be adjusted as needed.
You can provide one or more SSH keys via
github_user- your GitHub username, used to fetch your public SSH keys from your profilessh_keys- an array of public keys one per line
Now create the VM:
sudo -E slicer up ./jenkins-master.yaml
You can run an interactive shell into the VM and read the password file directly from /home/ubuntu/jenkins-admin.txt:
$ sudo -E ./bin/slicer vm exec jenkins-master-1
Connecting to VM: jenkins-master-1
Connected! Press Ctrl+] to exit.
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.10.240 x86_64)
root@jenkins-master-1:/root# cat /home/ubuntu/jenkins-admin.txt
Jenkins URL: http://192.168.137.2:8080/
Username: admin
Password: ......
Open the URL in a browser and go to the Settings page.
Add any plugins you want, and create jobs as needed.
At a minimum, you should install the Pipeline plugin to allow pipeline jobs to run.
Go to "Manage Jenkins" -> "Manage Plugins" and install the "Pipeline" plugin.
Then, make sure the URL is set for Jenkins itself:
- Go to "Manage Jenkins" -> "Configure System"
- Scroll down to "Jenkins Location"
- Set the "Jenkins URL" to the same URL you used to access Jenkins in your browser
- Save the configuration.
If you're able, it's also worth disabling the built-in system executors:
- Go to "Manage Jenkins" -> "Configure System"
- Scroll down to "Number of executors"
- Set it to
0 - Add a label of "system"
- Under "Usage" select "Only build jobs with label expressions matching this node"
- Save the configuration.
This ensures that no jobs run on the master itself, which is a best practice for Jenkins installations.
2. Run ephemeral Jenkins build slaves in Slicer via API¶
In Jenkins, a "Cloud" is simply an API that can add and remove build agents (slaves) on demand.
We built one that can talk to a slicer API endpoint to create and destroy VMs as needed.
Set up another Slicer instance, ideally on another machine on the same network, or with access via an overlay network like a VPN (Wireguard/OpenVPN).
config:
host_groups:
- name: slave
storage: zfs
count: 0
vcpu: 2
ram_gb: 8
network:
bridge: sbrvm0
tap_prefix: svmtap
gateway: 192.168.138.1/24
ssh_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCxtkOu6f/UwZP1ITq7dvXUNCX1gZLuf/ReNXEUXFuPumVGuCWVVLQf5zGDKK+BEi0lPBP6F5Pr2XjF84JlQXELkhbp2WczXGzWdZN0EvYcCD3jHk3saeHPK21ndc/on8/FHVZgF5IO444UP1vwpi3jzuNwbxxM3VH1xqH1F/71Zc7VzU+qcsHqA+rWbQX6vFIVhbS9NQKa/OXgxnyTNstTobLMYIy310tDg1/nzhfXK1gmxFTTDlMT2RFi8Gfo/t2pHzKDZCqZnltrcXtrRy9wqKnA6nqJ6dsT3V04IlG+LGTbBGtIxO+x5LQfw0kKnF31uudkL5wgBEgOSK5nwsTp alex@alex-nuc8
image: "ghcr.io/openfaasltd/slicer-systemd:5.10.240-x86_64-latest"
hypervisor: firecracker
graceful_shutdown: false
api:
enabled: true
port: 8080
bind_address: "0.0.0.0:"
ssh:
bind_address: "127.0.0.1:"
port: 2222
We recommend that you use ZFS-backed storage for the slaves because with ZFS, a snapshot is unpacked when Slicer starts up and it can be cloned instantly.
If you don't have time to set up ZFS and are only experimenting, then you can make the following change (but it may add a few seconds to each launch time):
- storage: zfs
+ storage: image
+ storage_size: 20G
If you're running on the same host as the Jenkins master, ensure that the API and SSH ports don't conflict with the master instance.
api:
enabled: true
+ port: 8081
bind_address: "0.0.0.0:"
ssh:
bind_address: "127.0.0.1:"
+ port: 2223
count- set to0because slaves are added via the Java Plugin as requiredstorage- usezfsfor best performance when starting up new slaves, orimagefor quick experimentationssh_keys- add any public keys you want to be able to SSH into the slave VMs - we recommend this overgithub_usersince it's much faster than querying GitHub for every launchgraceful_shutdown: false- slaves are ephemeral so we don't need to wait for a graceful shutdown - it will be destroyed immediately when the job is done
2.1 Create a custom image for the Jenkins slaves¶
If you want to pre-install any software on the slave images, you can create your own Dockerfile that extends the base Slicer image.
By pre-loading the JRE, we can speed up Jenkins slave startup times by skipping an installation of Java on every boot. We may produce an image with the JRE pre-installed in a future release, but most teams tend to want to add something like Docker or Terraform into their slave images.
This image is available via CI/CD as: ghcr.io/openfaasltd/slicer-slave:latest
FROM ghcr.io/openfaasltd/slicer-systemd:5.10.240-x86_64-latest
RUN apt-get update -qy && \
apt-get install -qy --no-install-recommends \
curl \
ca-certificates \
openjdk-17-jre-headless
RUN useradd -m -s /bin/bash jenkins && \
echo 'jenkins ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/jenkins && \
chmod 440 /etc/sudoers.d/jenkins
If you also wanted Docker, and basic Kubernetes tooling, you can add the following extra lines, which is also available via CI/CD as: ghcr.io/openfaasltd/slicer-slave-docker:latest
RUN arkade get \
--path=/usr/local/bin/ \
--quiet \
k3sup \
kubectl \
helm \
kubectx \
kind
RUN curl -sLS https://get.docker.com | sh && \
usermod -aG docker jenkins && \
chmod +x /usr/local/bin/*
RUN systemctl disable regen-ssh-host-keys && \
systemctl disable ssh && \
systemctl disable sshd && \
systemctl disable docker
We disable docker in order to make the slave start faster, however Docker can still be activated via systemd's socket activation when needed.
Then build and publish the image to your own container registry.
Edit your jenkins-slaves.yaml file to use your custom image:
- image: "ghcr.io/openfaasltd/slicer-systemd:5.10.240-x86_64-latest"
+ image: "ghcr.io/openfaasltd/slicer-slave-docker:latest"
Then you only need to relaunch the Slicer slave instance for the new packages to be available.
2.2 Start up the Slicer slave instance¶
Create the Slicer instance for the slaves:
sudo -E slicer up ./jenkins-slaves.yaml
If your master and slave are running on different hosts, make sure you run the routing commands (ip route add) that are printed out on start up.
- Master range: 192.168.131.0/24 (255 IP addresses, only one is needed)
- Slave range: 192.168.138.0/24 (255 IP addresses)
If they are both on the same host, then you must not run those commands.
2.3 Add the Slicer Cloud to Jenkins¶
Add the "Slicer VM Cloud" plugin to your Jenkins master via the plugins page.
Upload the slicer-vm-cloud.hpi file you received from our team.
Then go to "Manage Jenkins" -> "Configure System" and scroll down to the "Cloud" section.
Add a new Cloud, pick "Slicer VM Cloud".
Enter all the details including the API endpoint for the Slicer instance running the slaves, and its API token (the location is printed upon start-up).
2.4 Example Pipeline jobs¶
For every pipeline job you create, you need the following directive:
pipeline {
agent { label 'slicer'}
options { timeout(time: 2, unit: 'MINUTES') }
Now run a quick test job:
pipeline {
agent { label 'slicer'}
options { timeout(time: 2, unit: 'MINUTES') }
stages {
stage('Build') { steps { sh '''
cat /etc/hostname
''' } }
}
}
If you see the VM launch, and the runner get created in Jenkins, but it's not able to connect, check the log file for the VM itself under /var/log/slicer/. It should be called slave-1.txt if it's the first launch so far.
Here's an example that runs a container via Docker:
pipeline {
agent { label 'slicer'}
options { timeout(time: 2, unit: 'MINUTES') }
stages {
stage('Build') { steps { sh '''
sudo systemctl start docker
docker run -i alpine:latest ping -c 4 google.com
''' } }
}
}
Another example that installs Kubernetes via K3s and installs OpenFaaS Community Edition (CE):
pipeline {
agent { label 'slicer'}
options { timeout(time: 2, unit: 'MINUTES') }
stages {
stage('Build') { steps { sh '''
export PATH=$PATH:$HOME/.arkade/bin
arkade get k3sup kubectl --progress=false
export KUBECONFIG=`pwd`/kubeconfig
k3sup install --local --no-extras
k3sup ready --attempts 5 --pause 100ms
kubectl get nodes -o wide
kubectl get pods -A -o wide
arkade install openfaas
kubectl get deploy -n openfaas -o wide
''' } }
}
}
The rest is up to you.
3. Questions and support¶
If you have any questions or notice anything unexpected, reach out to us via your support channels. Discord for Home Edition, email for Commercial, and Slack/Email for Enterprise.
Is the Jenkins URL empty or nil?
You can then navigate to the Jenkins URL in your web browser to access the Jenkins master using the IP address from the YAML file i.e.
http://192.168.137.2:8080/
Even if you already see this string, re-enter it and hit Save. Otherwise Jenkins won't be able to generate correct links later on for build slaves.
Are you having issues with the build slaves?
Find out whether there are networking or other issues with the following
- Check the logs of the jenkins server on the master
sudo -E slicer vm exec jenkins-master-1
sudo journalctl -u jenkins.service -f
- Check the logs of the slave VM if it is booting
sudo cat /var/log/slicer/slave-1.txt
Check networking/routing
If your two Slicer instances are running on different machines, make sure you ran the ip route commands as directed.
To verify the routes on either machine use ping between the two .1 IP addresses i.e. ping 192.168.138.1 from the master host, and ping 192.168.137.1 from the slave host.
Check the proper Slicer URL was given for the Slicer Cloud
So long as you haven't changed anything in the tutorial, the Slicer URL should be http://192.168.141.1:8082.
The token is read from /var/slicer/auth/token.