Jul 04 2020
Register a Jenkins Slave with Ansible
We saw how to add a Jenkins slave automatically in a previous post calling a REST API with Curl. Let’s automate this with Ansible to get a new node ready within minutes!
You will first need a Jenkins user and associated token with the proper rights. Agent connect and create should be sufficient. I’ll simply call that user “node”. Log on Jenkins as this user and add a new token.
The new node details need to be passed on to the REST url. We’ll fit 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. 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.
- 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"
And 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
And 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