Jul 04 2020

Register a Jenkins Slave with Ansible

Published by under 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

 

No responses yet

Apr 18 2020

Jenkins Slave Auto Register with REST API

Published by under Jenkins

Create a Jenkins node automatically with a REST API call. Most of the work is to build some json code that describes the new Jenkins slave. The configuration can be slightly different depending on the node settings you are willing to apply. To get exactly what you want, you may create a dummy slave manually and capture the JSON object in your browser developer tool’s network tab while you click on “Save”. In the meantime, here’s mine, assuming agent launches with a startup script:

{
   "name": "my_jenkins_slave",
   "nodeDescription": "my Jenkins slave",
   "numExecutors": "2",
   "remoteFS": "/home/jenkins",
   "labelString": "slave",
   "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": "/usr/lib/jvm/java-8-openjdk-amd64"
            }
         ]
      },
      "_comment:": {
         "hudson-tools-ToolLocationNodeProperty": {
           "locations": [
               {
                  "key": "hudson.model.JDK$DescriptorImpl@JAVA-8",
                  "home": "/usr/bin/java"
               }
            ]
         }
      }
   }
}


All you need is define the following variables:

jenkins_user=my_jenkins_user
jenkins_token=my_jenkins_token
jenkins_url=https://jenkins.mydomain.lan


And post the JSON file with a simple curl:

$ curl -L -s -o /dev/null -w "%{http_code}" -u $jenkins_user:$jenkins_token \
-H "Content-Type:application/x-www-form-urlencoded" -X POST \
-d "json=$(cat /tmp/node.json)" \
"$jenkins_url/computer/doCreateItem?name=my_jenkins_slave&type=hudson.slaves.DumbSlave"; \
echo
200


If you do not get a 200 response code, run the same skipping “-o /dev/null” to troubleshoot.
Some say you may do the same with different tools like Jenkins CLI or some plugins but Jenkins REST API works through any firewall and is pretty easy to set up.
In the next post, I’ll use this method to automate Jenkins agent registration with Ansible

 

No responses yet

May 17 2018

Browser Driver has Received Too many Illegal Datagrams

Published by under AS400

I was getting tons of events ID 8016 labelled “bowser” in the Windows system event log. These are generated by IBM i (AS400) servers:
 
“The browser driver has received too many illegal datagrams from the remote computer AS400 to name DOMAIN on transport NetBT_Tcpip_{356179F8-4CA4-48CA-92C2-AFEA87D1F884}. The data is the datagram. No more events will be generated until the reset frequency has expired.”
 
Here’s how to get rid of them:
Open Navigator for i
Browse Network, Servers, TCP/IP Servers
Select IBM i NetServer, clic on the Actions tab and select Properties
 

 
In the new window, go into the Advanced tab, click on “Next Start”
and untick “Send browse announcements”
 

 

No responses yet

Next »