This document describes a getting started guide for Jenkins Operator and an additional configuration.
- First Steps
- Deploy Jenkins
- Configure Seed Jobs and Pipelines
- Install Plugins
- Configure Backup & Restore
- AKS
- Jenkins login credentials
- Override default Jenkins container command
- Debugging
Prepare your Kubernetes cluster and set up access. Once you have running Kubernetes cluster you can focus on installing Jenkins Operator according to the Installation guide.
Once jenkins-operator is up and running let's deploy actual Jenkins instance. Create manifest ie. jenkins_instance.yaml with following data and save it on drive.
apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
name: example
spec:
master:
containers:
- name: jenkins-master
image: jenkins/jenkins:lts
imagePullPolicy: Always
livenessProbe:
failureThreshold: 12
httpGet:
path: /login
port: http
scheme: HTTP
initialDelaySeconds: 80
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
failureThreshold: 3
httpGet:
path: /login
port: http
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
limits:
cpu: 1500m
memory: 3Gi
requests:
cpu: "1"
memory: 500Mi
seedJobs:
- id: jenkins-operator
targets: "cicd/jobs/*.jenkins"
description: "Jenkins Operator repository"
repositoryBranch: master
repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git
Deploy Jenkins to K8s:
kubectl create -f jenkins_instance.yaml
Watch Jenkins instance being created:
kubectl get pods -w
Get Jenkins credentials:
kubectl get secret jenkins-operator-credentials-<cr_name> -o 'jsonpath={.data.user}' | base64 -d
kubectl get secret jenkins-operator-credentials-<cr_name> -o 'jsonpath={.data.password}' | base64 -d
Connect to Jenkins (minikube):
minikube service jenkins-operator-http-<cr_name> --url
Connect to Jenkins (actual Kubernetes cluster):
kubectl port-forward jenkins-<cr_name> 8080:8080
Then open browser with address http://localhost:8080
.
Jenkins operator uses job-dsl and kubernetes-credentials-provider plugins for configuring jobs and deploy keys.
First you have to prepare pipelines and job definition in your GitHub repository using the following structure:
cicd/
├── jobs
│ └── build.jenkins
└── pipelines
└── build.jenkins
cicd/jobs/build.jenkins it's a job definition:
#!/usr/bin/env groovy
pipelineJob('build-jenkins-operator') {
displayName('Build jenkins-operator')
definition {
cpsScm {
scm {
git {
remote {
url('https://github.com/jenkinsci/kubernetes-operator.git')
credentials('jenkins-operator')
}
branches('*/master')
}
}
scriptPath('cicd/pipelines/build.jenkins')
}
}
}
cicd/pipelines/build.jenkins it's an actual Jenkins pipeline:
#!/usr/bin/env groovy
def label = "build-jenkins-operator-${UUID.randomUUID().toString()}"
def home = "/home/jenkins"
def workspace = "${home}/workspace/build-jenkins-operator"
def workdir = "${workspace}/src/github.com/jenkinsci/kubernetes-operator/"
podTemplate(label: label,
containers: [
containerTemplate(name: 'jnlp', image: 'jenkins/jnlp-slave:alpine'),
containerTemplate(name: 'go', image: 'golang:1-alpine', command: 'cat', ttyEnabled: true),
],
envVars: [
envVar(key: 'GOPATH', value: workspace),
],
) {
node(label) {
dir(workdir) {
stage('Init') {
timeout(time: 3, unit: 'MINUTES') {
checkout scm
}
container('go') {
sh 'apk --no-cache --update add make git gcc libc-dev'
}
}
stage('Dep') {
container('go') {
sh 'make dep'
}
}
stage('Test') {
container('go') {
sh 'make test'
}
}
stage('Build') {
container('go') {
sh 'make build'
}
}
}
}
}
Jenkins Seed Jobs are configured using Jenkins.spec.seedJobs
section from your custom resource manifest:
apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
name: example
spec:
seedJobs:
- id: jenkins-operator
targets: "cicd/jobs/*.jenkins"
description: "Jenkins Operator repository"
repositoryBranch: master
repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git
Jenkins Operator will automatically discover and configure all seed jobs.
You can verify if deploy keys were successfully configured in Jenkins Credentials tab.
You can verify if your pipelines were successfully configured in Jenkins Seed Job console output.
If your GitHub repository is private you have to configure SSH or username/password authentication.
There are two methods of SSH private key generation:
$ openssl genrsa -out <filename> 2048
or
$ ssh-keygen -t rsa -b 2048
$ ssh-keygen -p -f <filename> -m pem
Then copy content from generated file.
If you want to upload your public key to your Git server you need to extract it.
If key was generated by openssl
then you need to type this to extract public key:
$ openssl rsa -in <filename> -pubout > <filename>.pub
If key was generated by ssh-keygen
the public key content is located in .pub and there is no need to extract public key
Configure seed job like:
apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
name: example
spec:
seedJobs:
- id: jenkins-operator-ssh
credentialType: basicSSHUserPrivateKey
credentialID: k8s-ssh
targets: "cicd/jobs/*.jenkins"
description: "Jenkins Operator repository"
repositoryBranch: master
repositoryUrl: git@github.com:jenkinsci/kubernetes-operator.git
and create Kubernetes Secret(name of secret should be the same from credentialID
field):
apiVersion: v1
kind: Secret
metadata:
name: k8s-ssh
stringData:
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAxxDpleJjMCN5nusfW/AtBAZhx8UVVlhhhIKXvQ+dFODQIdzO
oDXybs1zVHWOj31zqbbJnsfsVZ9Uf3p9k6xpJ3WFY9b85WasqTDN1xmSd6swD4N8
...
username: github_user_name
Configure seed job like:
apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
name: example
spec:
seedJobs:
- id: jenkins-operator-user-pass
credentialType: usernamePassword
credentialID: k8s-user-pass
targets: "cicd/jobs/*.jenkins"
description: "Jenkins Operator repository"
repositoryBranch: master
repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git
and create Kubernetes Secret(name of secret should be the same from credentialID
field):
apiVersion: v1
kind: Secret
metadata:
name: k8s-user-pass
stringData:
username: github_user_name
password: password_or_token
Jenkins can be customized using groovy scripts or configuration as code plugin. All custom configuration is stored in the jenkins-operator-user-configuration-<cr_name> ConfigMap which is automatically created by Jenkins Operator.
Jenkins Operator creates jenkins-operator-user-configuration-<cr_name> secret where user can store sensitive
information used for custom configuration. If you have entry in secret named PASSWORD
then you can use it in
Configuration as Plugin as adminAddress: "${PASSWORD}"
.
kubectl get secret jenkins-operator-user-configuration-<cr_name> -o yaml
kind: Secret
apiVersion: v1
type: Opaque
metadata:
name: jenkins-operator-user-configuration-<cr_name>
namespace: default
data:
SECRET_JENKINS_ADMIN_ADDRESS: YXNkZgo=
kubectl get configmap jenkins-operator-user-configuration-<cr_name> -o yaml
apiVersion: v1
data:
1-configure-theme.groovy: |2
import jenkins.*
import jenkins.model.*
import hudson.*
import hudson.model.*
import org.jenkinsci.plugins.simpletheme.ThemeElement
import org.jenkinsci.plugins.simpletheme.CssTextThemeElement
import org.jenkinsci.plugins.simpletheme.CssUrlThemeElement
Jenkins jenkins = Jenkins.getInstance()
def decorator = Jenkins.instance.getDescriptorByType(org.codefirst.SimpleThemeDecorator.class)
List<ThemeElement> configElements = new ArrayList<>();
configElements.add(new CssTextThemeElement("DEFAULT"));
configElements.add(new CssUrlThemeElement("https://cdn.rawgit.com/afonsof/jenkins-material-theme/gh-pages/dist/material-light-green.css"));
decorator.setElements(configElements);
decorator.save();
jenkins.save()
1-system-message.yaml: |2
jenkins:
systemMessage: "Configuration as Code integration works!!!"
adminAddress: "${SECRET_JENKINS_ADMIN_ADDRESS}"
kind: ConfigMap
metadata:
name: jenkins-operator-user-configuration-<cr_name>
namespace: default
When jenkins-operator-user-configuration-<cr_name> ConfigMap is updated Jenkins automatically runs the jenkins-operator-user-configuration Jenkins Job which executes all scripts then runs the jenkins-operator-user-configuration-casc Jenkins Job which applies Configuration as Code configuration.
Edit CR under spec.master.plugins
:
apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
name: example
spec:
master:
plugins:
- name: simple-theme-plugin
version: 0.5.1
Then Jenkins Operator will automatically install plugins after Jenkins master pod restart.
Backup and restore is done by container sidecar.
Save to file pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: <pvc_name>
namespace: <namespace>
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Gi
Run command:
$ kubectl -n <namespace> create -f pvc.yaml
apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
name: <cr_name>
namespace: <namespace>
spec:
master:
securityContext:
runAsUser: 1000
fsGroup: 1000
containers:
- name: jenkins-master
image: jenkins/jenkins:lts
- name: backup # container responsible for backup and restore
env:
- name: BACKUP_DIR
value: /backup
- name: JENKINS_HOME
value: /jenkins-home
- name: BACKUP_COUNT
value: "3" # keep only the 2 most recent backups
image: virtuslab/jenkins-operator-backup-pvc:v0.0.6 # look at backup/pvc directory
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /jenkins-home # Jenkins home volume
name: jenkins-home
- mountPath: /backup # backup volume
name: backup
volumes:
- name: backup # PVC volume where backups will be stored
persistentVolumeClaim:
claimName: <pvc_name>
backup:
containerName: backup # container name is responsible for backup
action:
exec:
command:
- /home/user/bin/backup.sh # this command is invoked on "backup" container to make backup, for example /home/user/bin/backup.sh <backup_number>, <backup_number> is passed by operator
interval: 30 # how often make backup in seconds
makeBackupBeforePodDeletion: true # make backup before pod deletion
restore:
containerName: backup # container name is responsible for restore backup
action:
exec:
command:
- /home/user/bin/restore.sh # this command is invoked on "backup" container to make restore backup, for example /home/user/bin/restore.sh <backup_number>, <backup_number> is passed by operator
#recoveryOnce: <backup_number> # if want to restore specific backup configure this field and then Jenkins will be restarted and desired backup will be restored
Azure AKS managed Kubernetes service adds to every pod the following envs:
- name: KUBERNETES_PORT_443_TCP_ADDR
value:
- name: KUBERNETES_PORT
value: tcp://
- name: KUBERNETES_PORT_443_TCP
value: tcp://
- name: KUBERNETES_SERVICE_HOST
value:
The operator is aware of it and omits these envs when checking if Jenkins pod envs have been changed. It prevents restart Jenkins pod over and over again.
The operator automatically generate Jenkins user name and password and stores it in Kubernetes secret named
jenkins-operator-credentials-<cr_name>
in namespace where Jenkins CR has been deployed.
If you want change it you can override the secret:
apiVersion: v1
kind: Secret
metadata:
name: jenkins-operator-credentials-<cr-name>
namespace: <namespace>
data:
user: <base64-encoded-new-username>
password: <base64-encoded-new-password>
If needed Jenkins Operator will restart Jenkins master pod and then you can login with the new user and password credentials.
The default command for the Jenkins master container jenkins/jenkins:lts
looks like:
command:
- bash
- -c
- /var/jenkins/scripts/init.sh && /sbin/tini -s -- /usr/local/bin/jenkins.sh
The script/var/jenkins/scripts/init.sh
is provided be the operator and configures init.groovy.d(creates Jenkins user)
and installs plugins.
The /sbin/tini -s -- /usr/local/bin/jenkins.sh
command runs the Jenkins master main process.
You can overwrite it in the following pattern:
command:
- bash
- -c
- /var/jenkins/scripts/init.sh && <custom-code-here> && /sbin/tini -s -- /usr/local/bin/jenkins.sh
Turn on debug in Jenkins Operator deployment:
sed -i 's|\(args:\).*|\1\ ["--debug"\]|' deploy/operator.yaml
kubectl apply -f deploy/operator.yaml
Watch Kubernetes events:
kubectl get events --sort-by='{.lastTimestamp}'
Verify Jenkins master logs:
kubectl logs -f jenkins-<cr_name>
Verify jenkins-operator logs:
kubectl logs deployment/jenkins-operator
Delete Jenkins master pod and wait for the new one to come up:
kubectl delete pod jenkins-<cr_name>