Jul 04 2020

Register a Jenkins Slave with Ansible

Published by at 7:21 pm under 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.

Cafe Morning Coffee Drink Drinking  - 453169 / Pixabay
453169 / Pixabay


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.


No responses yet

Trackback URI | Comments RSS

Leave a Reply