Jenkins Operator Kubernetes Install
Jenkins stateless deployments
I hear a lot of people complain about maintaining a Jenkins instance with all the updates, plugins as well as setting up jobs. What if I told you that you could deploy an entire Jenkins instance with just a few yaml files?
This will be a quick tutorial on how you can do that. I’ll go over importing jobs, setting up build agents and authentication. The way that this works is that the operator will manage the lifecycle of our instance as well as back up and restore build logs. Our instance is stateless after all and gets rebuilt every single time the pod restarts.
The Configuration As Code plugin is what will be setting all of our settings once the instance gets started. Do note, the documentation is terrible. It took a few days of trial and error to get this working like I wanted.
Lets start for the top
Jenkins Operator
The Helm chart that will allow us to create jenkins instances is Jenkins Operator. I’m not going to jump into too much details about the project. The operator itself will supply a few things for us
The Instance
We can deploy an operator that will manage a single Jenkins instance. You can deploy multiple operators in order to create multiple instances. The Operator will instance plugins needed for bootstrapping the instance via Jenkins plugins CLI and then install what they call user plugins after the instance is all set up such as popular Blue Ocean plugin
Seeding Jobs
The next thing that it does create a few seed jobs that will create our build structure. This part is rather straight forward if you use keep a Jenkinsfile in each repository that you want to build. This is done with the job dsl
Backup and Restore
By nature of it being stateless, once the instance is gone you lose all build logs and history. If you want to keep this then the operator does regular scheduled backups which it will restore on the creation of a new instance. Do be warned that it truly only backs up build logs and not any settings
Installing the operator
Before we install our operator we want to setup all of our config for our instance. I’ll give you a full working copy and then go over the individual pieces
jenkins:
namespace: jenkins
image: jenkins/jenkins:lts # can be any official jenkins image
env:
- name: TZ # Timezone
value: America/New_York # TZ took forever to figure out
backup: # Backup Settings
enabled: true
image: virtuslab/jenkins-operator-backup-pvc
interval: 3600
makeBackupBeforePodDeletion: true
pvc: # PVC you want to use
enabled: true
className: nfs-client # This is my pvc class for my nfs fileshare
# Optional, this is my configure as code configmap
configuration:
configurationAsCode:
configurations:
configMapName: jenkins-config
secretRefName: jenkins-config-secrets # Secret storing secrets for my jenkins-config
latestPlugins: true
basePlugins: # Plugins don't auto update, you have to specify exact versions and there is not a forced version scheme
- name: kubernetes
version: "4054.v2da_8e2794884"
- name: workflow-job
version: "1360.vc6700e3136f5"
- name: workflow-aggregator
version: "596.v8c21c963d92d"
- name: git
version: "5.2.0"
- name: job-dsl
version: "1.87"
- name: configuration-as-code
version: "1714.v09593e830cfa"
- name: kubernetes-credentials-provider
version: "1.258.v95949f923a_a_e"
# This section is optional, this is for user defined plugins. I'll add a few just to get you going
plugins:
- name: github-branch-source
version: "1741.va_3028eb_9fd21"
- name: blueocean
version: "1.27.9"
# Resource limits. IMO cpu should never be limited and memory requests should always equal limit
limits:
memory: 4Gi
requests:
memory: 4Gi
# My seed jobs are stored in a gitea repository. I'll go over the seed jobs after. Similar syntax can be used for githubm just pick an appropiate credentialID
seedJobs:
- id: jenkins-org-scans
credentialType: usernamePassword
credentialID: gitea-login
targets: "jobs/*.jenkins"
description: "Jenkins org repository"
repositoryBranch: main
repositoryUrl: https://git.mydomain.com/devops/jenkins-configuration.git
# Backup volume mentioned earlier in the code
volumes:
- name: backup
persistentVolumeClaim:
claimName: jenkins-backup
- name: jenkins-secrets
persistentVolumeClaim:
claimName: jenkins-secrets
volumeMounts:
- mountPath: /var/lib/jenkins/secrets
name: jenkins-secrets
# The amount of operators we want to depoy
operator:
replicaCount: 1
Configuration as code
One thing to note, all of the secrets listed in this file as saved in the secret we referenced in the configureAsCode section of the operators value file
# Stores all of the credentials we want to add to the instance
credentials:
system:
domainCredentials:
- credentials:
- string:
description: "Github PAT for Github access"
id: "github-pat"
scope: SYSTEM
secret: ${GITHUB_PAT}
- gitHubApp:
appID: "428894"
scope: GLOBAL
description: "GitHub app"
id: "github-app"
privateKey: ${GITHUB_APP_KEY}
jenkins:
systemMessage: ${SYSTEM_MESSAGE}
labelAtoms:
- name: "Linux"
nodes: # Adds a linux node from the cluster. I'll go over this when we add the node to the cluster
- permanent:
labelString: "Linux"
launcher:
inbound:
webSocket: true
workDirSettings:
disabled: false
failIfWorkDirIsMissing: false
internalDir: "remoting"
mode: EXCLUSIVE
name: "Arthas"
remoteFS: "/home/jenkins/agent"
retentionStrategy: "always"
# This is my config for Authentik Open ID Connect
securityRealm:
oic:
authorizationServerUrl: "https://auth.mydomain.com/application/o/authorize/"
automanualconfigure: "auto"
clientId: ${AUTHENTIK_CLIENT_ID}
clientSecret: ${AUTHENTIK_CLIENT_SECRET}
disableSslVerification: false
endSessionEndpoint: "https://auth.mydomain.com/application/o/jenkins/end-session/"
overrideScopes: "email profile openid"
overrideScopesDefined: true
scopes: "openid profile email"
tokenAuthMethod: "client_secret_post"
tokenServerUrl: "https://auth.mydomain.com/application/o/token/"
userInfoServerUrl: "https://auth.mydomain.com/application/o/userinfo/"
wellKnownOpenIDConfigurationUrl: "https://auth.mydomain.com/application/o/jenkins/.well-known/openid-configuration"
emailFieldName: "email"
fullNameFieldName: "given_name"
groupsFieldName: "groups"
Building your seed job
I’ll give you a sample seed job. This one is fairly straightforward. It will scan the github org or username’s repos and add any repo to Jenkins that contains a Jenkinsfile
organizationFolder('my-github-username') {
organizations {
github {
repoOwner('my-github-username')
credentialsId('github-app')
traits {
gitHubBranchDiscovery {
strategyId(0)
}
gitHubPullRequestDiscovery {
strategyId(1)
}
gitHubExcludeArchivedRepositories()
gitHubTagDiscovery()
}
}
}
orphanedItemStrategy {
discardOldItems {
daysToKeep(60)
}
}
triggers {
periodicFolderTrigger {
interval('15m')
}
}
}
Adding a build agent
You can dabble with Kubernetes agents and to be honest thats most likely the best approach since you would then be running a full stateless infrastructure. I’m just a guy and I used what was already there which was my old pod deployment. Do note that I did something hacky and saved the keys that Jenkins creates for agents and as part of my redeployment add those keys back to the instance. Instead of going with that hack you’re most likely better off just using the built in Kubernetes plugin. The operator already sets this up for and it is how its seeding the jobs
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins-arthas
namespace: jenkins
labels:
app.kubernetes.io/name: jenkins-agent
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: jenkins-agent
template:
metadata:
labels:
app.kubernetes.io/name: jenkins-agent
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- jenkins-agent
topologyKey: "kubernetes.io/hostname"
containers:
- name: jenkins-arthas
image: jenkins/inbound-agent@sha256:c4df2a7c2779f185a2d1014d0a4d684fbca9dbc26f5425714af7423b57e7e3f2
imagePullPolicy: Always
resources:
limits:
memory: "1Gi"
requests:
memory: "1Gi"
Start the deployment
All thats left is to deploy!
- Deploy your build agent
- Deploy the configure as code config map
- Create your seed job files and save it to a repo that you can access with the credentials you set up in your configure as code
- Run the following below to deploy!
Save that file to your current directory as ./values/jenkins-operator.yaml
helm repo add jenkins https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/chart
helm repo update
helm upgrade --install --namespace jenkins-operator jenkins-operator jenkins/jenkins-operator --values=.\values\jenkins-operator.yaml
Closing
This is a lot, this wasn’t easy. This should get you pretty far, if you have any questions feel free to reach out. I did not use this setup for an extended period of time. I ended up moving to BuildKite. Its fun, has an amazing test suite analyzer and is designed for me to provide compute so I don’t have to do all of this hacky things that I’ve done with TeamCity, Github Actions and Jenkins