"After all, the engineers only needed to refuse to fix anything, and modern industry would grind to a halt." -Michael Lewis

Enable Massive Growth

How to Provision a Server with Java using Ansible

Nov 2018

In my post about how to provision any version of Java using a bash script, we saw that:

#!/bin/bash
 
# Get tarball for JDK 10.0.1
wget https://download.java.net/java/GA/jdk10/10.0.1/fb4372174a714e6b8c52526dc134031e/10/openjdk-10.0.1_linux-x64_bin.tar.gz

# make java 10 directory
mkdir -p /usr/lib/java10

# unpack tarball
tar -C /usr/lib/java10/ -xvzf ./openjdk-10.0.1_linux-x64_bin.tar.gz 

# update alternatives
update-alternatives --install /usr/bin/java java /usr/lib/java10/jdk-10.0.1/bin/java 20000
update-alternatives --install /usr/bin/javac javac /usr/lib/java10/jdk-10.0.1/bin/javac 20000

# verify with a version check
java -version

Will get you openJDK version 10.0.1 from the tarball in the link.

While that script works, it's generally a better idea to make this more maintainable. For example, if we re-run this bash script on a server that is already provisioned, it will run all of these steps again. In this case, nothing bad will actually happen, but it can lead to some sticky debug sessions when we start to move to more complicated provisioning steps.

We'll convert this bash script to an ansible playbook. To test this, we'll set up a VagrantFile like so:

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/bionic64"
  config.vm.network "private_network", ip: "192.168.56.115"

  config.vm.provider :virtualbox do |vb|
    vb.memory = 1024
    vb.cpus = 1
  end

  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provision.yml"
  end
end

We'll need an ansible playbook in the same directory called "provision.yml". We are using Ubuntu 18 (bionic64), which does not come with python 2 installed. Since ansible defaults to python 2, one way to deal with that problem is to install it prior to running the playbook. We can't gather facts before installing it, because gathering facts requires, you guessed it, python 2:

---
- hosts: all
  become: yes
  gather_facts: no
  pre_tasks:
    - name: 'install python2 on ubuntu 18'
      raw: test -e /usr/bin/python || (apt-get -y update && apt-get install -y python-minimal)

  tasks:
    - name: Gather facts
      setup:

This bash command checks if we have python first, and only updates and gets python-minimal if it's not already there. The empty setup: command runs the gathering of facts and we can proceed like we never had to do this weird pre-provisioning step in the first place.

We can convert these two steps to:

# Get tarball for JDK 10.0.1
wget https://download.java.net/java/GA/jdk10/10.0.1/fb4372174a714e6b8c52526dc134031e/10/openjdk-10.0.1_linux-x64_bin.tar.gz

# make java 10 directory
mkdir -p /usr/lib/java10

To:

    - name: Get Java tarball
      get_url:
        url: https://download.java.net/java/GA/jdk10/10.0.1/fb4372174a714e6b8c52526dc134031e/10/openjdk-10.0.1_linux-x64_bin.tar.gz
        dest: /etc/open-jdk10.tar.gz

    - name: make java 10 directory
      file:
        path: /usr/lib/java10
        state: directory

Which uses ansible's get_url module to download the tarball and move it to the /etc/open-jdk10.tar.gz location, only downloading it if we haven't already done so. We then create the directory we eventually want to put java10 into

We can then convert:

# unpack tarball
tar -C /usr/lib/java10/ -xvzf ./openjdk-10.0.1_linux-x64_bin.tar.gz

Into:

    - name: unpack tarball
      unarchive:
        dest: /usr/lib/java10
        src: /etc/open-jdk10.tar.gz
        remote_src: yes

This unpacks the tarball and places it into the directory previously created.

Finally, we will update alternatives (and, as a bonus, we'll set the JAVA_HOME environment variable):

    - name: update alternatives for java
      alternatives:
        name: java
        path: /usr/lib/java10/jdk-10.0.1/bin/java
        link: /usr/bin/java
        priority: 20000

    - name: set java home as environment variable
      blockinfile:
        insertafter: EOF
        path: /etc/environment
        block: export JAVA_HOME=/usr/lib/java10/jdk-10.0.1

The final ansible playbook looks like:

---
- hosts: all
  become: yes
  gather_facts: no
  pre_tasks:
    - name: 'install python2 on ubuntu 18'
      raw: test -e /usr/bin/python || (apt-get -y update && apt-get install -y python-minimal)

  tasks:
    - name: Gather facts
      setup:

    - name: Get Java tarball
      get_url:
        url: https://download.java.net/java/GA/jdk10/10.0.1/fb4372174a714e6b8c52526dc134031e/10/openjdk-10.0.1_linux-x64_bin.tar.gz
        dest: /etc/open-jdk10.tar.gz

    - name: make java 10 directory
      file:
        path: /usr/lib/java10
        state: directory

    - name: unpack tarball
      unarchive:
        dest: /usr/lib/java10
        src: /etc/open-jdk10.tar.gz
        remote_src: yes

    - name: update alternatives for java
      alternatives:
        name: java
        path: /usr/lib/java10/jdk-10.0.1/bin/java
        link: /usr/bin/java
        priority: 20000

    - name: set java home as environment variable
      blockinfile:
        insertafter: EOF
        path: /etc/environment
        block: export JAVA_HOME=/usr/lib/java10/jdk-10.0.1

Nick Fisher is a software engineer in the Pacific Northwest. He focuses on building highly scalable and maintainable backend systems.