Jul 04 2020
Register a Jenkins Slave with Ansible
We saw how to register a Jenkins slave with a REST API. Let’s go one step further: make Ansible do that for you.
You will first need a Jenkins user and associated token with the proper rights. Agent connect and create permissions should be sufficient. I’ll simply call that user “node”. Log on Jenkins with the “node” account and add a new token.
The new node details need to be passed on to the REST url. We’ll squeeze these settings into a Jinja2 template as follow:
{
"name": "{{ jenkins_node }}",
"nodeDescription": "slave {{ jenkins_node }}",
"numExecutors": "{{ jenkins_numExecutors }}",
"remoteFS": "{{ jenkins_remoteFS }}",
"labelString": "{{ jenkins_label }}",
"mode": "EXCLUSIVE",
"": [
"hudson.slaves.JNLPLauncher",
"hudson.slaves.RetentionStrategy$Always"
],
"launcher": {
"stapler-class": "hudson.slaves.JNLPLauncher",
"$class": "hudson.slaves.JNLPLauncher",
"workDirSettings": {
"disabled": true,
"workDirPath": "",
"internalDir": "remoting",
"failIfWorkDirIsMissing": false
},
"tunnel": "",
"vmargs": ""
},
"retentionStrategy": {
"stapler-class": "hudson.slaves.RetentionStrategy$Always",
"$class": "hudson.slaves.RetentionStrategy$Always"
},
"nodeProperties": {
"stapler-class-bag": "true",
"hudson-slaves-EnvironmentVariablesNodeProperty": {
"env": [
{
"key": "JAVA_HOME",
"value": "{{ java_home }}"
}
]
},
"_comment:": {
"hudson-tools-ToolLocationNodeProperty": {
"locations": [
{
"key": "hudson.model.JDK$DescriptorImpl@JAVA-8",
"home": "/usr/bin/java"
}
]
}
}
}
}
Adapt the template to your needs, if you’d like a SSH agent for instance.
The variables that will be replaced in the template can be defined in the following “default” file:
jenkins_slave_user: jenkins-slave
jenkins_token: xxxxxxde2152xxxxxxaa339exxxxxx48d6
jenkins_user: node
jenkins_url: https://jenkins.domain.lan
jenkins_node: "{{ansible_hostname}}"
jenkins_numExecutors: 4
jenkins_remoteFS: /home/jenkins-slave
jenkins_label: "label_1 label_2 label_3"
java_home: /usr/lib/jvm/java-8-openjdk-amd64/
jenkins_user will connect to the master and create the new node, authenticating with jenkins_token created beforehand.
jenkins_slave_user is the system user that will launch Jenkins service on the node.
We can now add Ansible tasks to build our role. We first call the REST API:
name: create node on Jenkins master
uri:
url: "{{jenkins_url}}/computer/doCreateItem?name={{ jenkins_node }}&type=hudson.slaves.DumbSlave"
method: POST
body_format: form-urlencoded
force_basic_auth: yes
user: "{{ jenkins_user }}"
password: "{{jenkins_token }}"
body: "json={{ lookup('template', 'node.json.j2', convert_data=False) }}"
return_content: yes
status_code: 200, 302, 400
register: webpage
I added return code 400 in case the node already exists but you may remove it if you think it’s best. Then I make it fail if the error is anything else than ‘already exists’:
name: Go through if agent already exists error
fail:
when: >
webpage.status == '400'
and 'already exists' not in webpage.x_error
The Jenkins agent service needs a secret from the master to get started. The secret is embedded in the agent’s page in XML format so you can easily retrieve it.
name: get node page content
uri:
url: "{{jenkins_url}}/computer/{{jenkins_node}}/slave-agent.jnlp"
method: POST
body_format: form-urlencoded
force_basic_auth: yes
user: "{{ jenkins_user }}"
password: "{{ jenkins_token }}"
return_content: yes
status_code: 200
register: slavepage
name: get secret from xml
xml:
xmlstring: "{{slavepage.content}}"
xpath: /jnlp/application-desc/argument
content: text
register: secretxml
It will be stored in the system user’s default file /etc/default/jenkins-slave that is loaded by the startup script.
Here’s the template:
JENKINS_USER="jenkins-slave"
JENKINS_WORKDIR=$(eval echo "~$JENKINS_USER")
JENKINS_URL={{ jenkins_url }}
JENKINS_NODENAME=$(hostname)
JENKINS_SECRET={{ jenkins_secret }}
JAVA_ARGS="-Xmx6g"
Here comes the init script. We can now upload these two files:
name: Copy jenkins-slave default file template
template:
src: jenkins-slave-default.j2
dest: /etc/default/jenkins-slave
owner: jenkins-slave
group: jenkins-slave
mode: 0600
vars:
jenkins_secret: "{{secretxml.matches[0].argument}}"
register: jenkins_config
name: Copy jenkins-slave init file
copy:
src: jenkins-slave-init
dest: /etc/init.d/jenkins-slave
owner: root
group: root
mode: 0755
Final step, we make sure the service is started:
name: restart and enable jenkins-slave service if needed
service:
name: jenkins-slave
enabled: yes
state: restarted
when: jenkins_config.changed
name: start and enable jenkins-slave service
service:
name: jenkins-slave
enabled: yes
state: started
These steps were the basics but you can do a lot more like adding the Jenkins system user creation, adding your own CA certificate if you’re on a private IP, and so on
Also check how to speed up Ansible and save a lot of time when deploying.
This is great, thank you for this post!