Ansible, JBoss EAP, and Wildfly, Part 1

This three-part series guides you through using Ansible to fine-tune a WildFly or Red Hat JBoss Enterprise Application Platform (JBoss EAP) server configuration. We will use the most recently released version of the Ansible collection for JCliff to extend Ansible's capabilities. The JCliff collection supports configuring several of the application server subsystems directly from Ansible.

In Part 1, we will mostly focus on the groundwork and discuss all the steps required to be able to use JCliff within Ansible. Once properly installed, we'll use JCliff to configure WildFly's system_props subsystem, which lets us declare system variables in WildFly's server configuration. Once we have that foundation in place, we'll begin exploring more interesting configurations in Part 2 and Part 3.

Note: See the Ansible documentation for more about Ansible collections.

Using Ansible to fine-tune a WildFly server configuration

Before we start setting up the Ansible playbook, let’s discuss what we want to achieve. Our use case is finetuning a WildFly server configuration, which means going beyond what Ansible's primitives can do for this software.

It is easy to automate a WildFly installation using Ansible's built-in modules. You can use a package manager to install any necessary dependencies, create the required directory structure, set up the configuration files, and so on. However, you can't as easily fine-tune the WildFly server's configuration. The usual strategy of providing an Ansible template file for the server’s configuration is not suitable. Many server configurations live in the main configuration file (standalone.xml or standalone-full.xml). The server frequently accesses this file at runtime, altering the file contents at the same time. As a result, the main configuration file is not a good target for templating.

Our goal is to provide a fine-tuned configuration to the WildFly server. Within the Ansible playbook, we want a way to define the server's required state. We also want to ensure that Ansible can monitor this state and spot any alterations to it. This is where the JCliff Ansible collection comes in.

What is Ansible collection for JCliff?

JCliff is a command-line interface (CLI) that lets us dynamically change a WildFly server's state. JCliff uses WildFly's JBoss CLI to update the server configuration at runtime. This tool is provided with the application server. Once the server has processed requests sent using the JBoss CLI tool, it ensures that the changes are recorded and updates the main configuration file, standalone.xml, accordingly.

The new Ansible collection for JCliff (wildfly.jcliff) provides integration in the form of an Ansible module. Ansible uses the JCliff integration to verify that the WildFly server configuration is in the proper state. It also uses JCliff to update the server configuration if there are any discrepancies. JCliff builds the JBoss CLI queries that are required for these purposes. JCliff also helps Ansible remain idempotent, meaning that changes will only be applied if the configuration is not in the proper state.

JCliff lets us express our requirements directly inside of the Ansible playbook. For this, we will use the jcliff: module, which is provided with the wildfly.jcliff collection.

Note: If you want to know more about JCliff, see the article Managing JBoss EAP/WildFly using JCliff. This series builds on that article by showing you how to use JCliff's new Ansible collection format for packaging extensions within Ansible. Many thanks to my co-author for the original article, Andrew Block, for reviewing this series.

Prerequisites for the demonstration

Before diving into the demonstration, let's make sure that we have all of the prerequisites in place:

  • A Red Hat Enterprise Linux (RHEL), CentOS, or Fedora operating system.
  • A Java virtual machine for JDK 8 or higher.
  • Ansible (we recommend Ansible 2.9, but an older or more recent version should work).
  • WildFly 19 or higher, which should be installed and running on the system.

Note: Ansible collection for JCliff includes a role to automate installing JCliff. Full support is currently available only for Linux and macOS, but work is emerging to support Windows.

The Ansible playbook

For our demonstration, we will start with a minimal, functional Ansible playbook. To keep the demonstration simple and easy to reproduce, we are running Ansible on a local system.

---

- hosts: localhost

  gather_facts: true

  vars:

  tasks:

Let's verify that Ansible is properly installed on the control node and that the playbook completes successfully:

$ ansible-playbook playbook.yml

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ***********************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************

ok: [localhost]

PLAY RECAP *****************************************************************************************************************************

localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

So far, everything works. Note that we set the gather_facts property to true. This setting allows Ansible to gather information about the system. Soon, Ansible will use this information to install JCliff.

Note: While gathering facts is not a hard requirement for the JCliff Ansible collection, you might have to provide the additional variables yourself if you choose to disable this setting.

Installing Ansible collection for JCliff

We are ready to install the Ansible collection for JCliff. First, we'll use the ansible-galaxy tool (which is included with Ansible by default) to install the wildfly.jcliff module:

# ansible-galaxy collection install wildfly.jcliff

Process install dependency map

Starting collection install process

Installing 'wildfly.jcliff:0.0.2' to '/root/.ansible/collections/ansible_collections/wildfly/jcliff'

Locate the JBOSS_HOME variable

As I previously mentioned, the JCliff Ansible collection comes with a role that automatically installs JCliff. All we have to do is add the required instructions to our Ansible playbook. In particular, JCliff needs the JBOSS_HOME environment variable to locate the WildFly server on a target system. Let's retrieve the server location from this environment variable before going further:

---

- hosts: localhost

  gather_facts: true

  vars:

    jboss_home: "{{ lookup('env','JBOSS_HOME') }}"

  collections:

    - wildfly.jcliff

  roles:

    - jcliff

  tasks:

Note the following:

  • The variable jboss_home is defined with the value of the environment variable called JBOSS_HOME which ensure that there is no discrepancy between them.
  • This playbook requires the wildfly.jcliff collection, which we've just installed locally.
  • The Ansible playbook imports the jcliff role from the wildfly.jcliff collection. This role takes care of installing JCliff itself on the system.

Next, we'll run the Ansible playbook with the instructions and information required to install JCliff.

Run the playbook to install the JCliff CLI

Running the Ansible playbook imports the jcliff role and performs the following tasks to install the JCliff CLI:

# ansible-playbook playbook.yml

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ***********************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Collect Supported Operating Systems] *********************************************************************

ok: [localhost] => (item={u'key': u'homebrew', u'value': [u'MacOSX']})

ok: [localhost] => (item={u'key': u'rpm', u'value': [u'Fedora', u'CentOS', u'RedHat']})

TASK [wildfly.jcliff.jcliff : Verify supported Operating Systems] **********************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Install JCliff using HomeBrew] ***************************************************************************

skipping: [localhost]

TASK [wildfly.jcliff.jcliff : Install JCliff using RPM] ********************************************************************************

included: /root/.ansible/collections/ansible_collections/wildfly/jcliff/roles/jcliff/tasks/install_rpm.yml for localhost

TASK [wildfly.jcliff.jcliff : Add JCliff Yum Repository (RedHat)] **********************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Test if package jcliff is already installed] *************************************************************

fatal: [localhost]: FAILED! => {"changed": false, "cmd": ["rpm", "-q", "jcliff"], "delta": "0:00:00.489702", "end": "2020-08-17 08:54:34.190992", "msg": "non-zero return code", "rc": 1, "start": "2020-08-17 08:54:33.701290", "stderr": "", "stderr_lines": [], "stdout": "package jcliff is not installed", "stdout_lines": ["package jcliff is not installed"]}

TASK [wildfly.jcliff.jcliff : Ensure JCliff is installed] ******************************************************************************

changed: [localhost]

TASK [wildfly.jcliff.jcliff : Install Jcliff using standalone binary] ******************************************************************

skipping: [localhost]

PLAY RECAP *****************************************************************************************************************************

localhost                  : ok=6    changed=1    unreachable=0    failed=0    skipped=2    rescued=1    ignored=0

The playbook ran the jcliff role. The new role noticed that JCliff was not installed on the system and installed the necessary software.

Verify the JCliff installation

Now, let's verify that JCliff is installed:

# jcliff --version

No JBOSS_HOME provided, aborting...

JCliff is installed, but (as I previously noted), it needs us to define the JBOSS_HOME variable to be functional:

$ export JBOSS_HOME=/path/to/wildfly/home

After we've exported the environment variable, try re-running the command:

# jcliff

Jcliff version 2.12.5

Usage:

    jcliff [options] file(s)

where the options are:

  --cli=Path : jboss-cli.sh. Defaults to

               /usr/share/jbossas/bin/jboss-cli.sh

  --controller=host       : EAP6 host. Defaults to localhost.

  --user=username         : EAP6 admin user name

  --password=pwd          : EAP6 admin password

  --ruledir=Path          : Location of jcliff rules.

  --noop                  : Read-only mode

  --json                  : Use json to parse input files

  -v                      : Verbose output

  --timeout=timeout       : Command timeout in milliseconds

  --output=Path           : Log output file

  --reload                : Reload after each subsystem configuration if required

  --waitport=waitport     : Wait this many seconds for the port to be opened

  --nobatch               : Don't use batch mode of jboss-cli

  --redeploy              : Redeploy all apps

  --reconnect-delay=delay : Wait this many milliseconds after a :reload for the server to restart

  --leavetmp              : Don't erase temp files

  --pre=str               : Prepend str to all commands (can be used for domain mode support)

JCliff is now installed and fully operational.

Verify idempotency

There is one last thing to do before the setup is complete. We must ensure that our Ansible playbooks are idempotent. In this context, that means the tasks described in a playbook won’t be performed again if the system is already in the appropriate state. To check this, run the playbook again and verify that no more changes are being applied:

# ansible-playbook  playbook.yml

PLAY [localhost] *****************************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Collect Supported Operating Systems] ***************************************************************************************************************************

ok: [localhost] => (item={u'key': u'homebrew', u'value': [u'MacOSX']})

ok: [localhost] => (item={u'key': u'rpm', u'value': [u'Fedora', u'CentOS', u'RedHat']})

TASK [wildfly.jcliff.jcliff : Verify supported Operating Systems] ****************************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Install JCliff using HomeBrew] *********************************************************************************************************************************

skipping: [localhost]

TASK [wildfly.jcliff.jcliff : Install JCliff using RPM] **************************************************************************************************************************************

included: /root/.ansible/collections/ansible_collections/wildfly/jcliff/roles/jcliff/tasks/install_rpm.yml for localhost

TASK [wildfly.jcliff.jcliff : Add JCliff Yum Repository (RedHat)] ****************************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Test if package jcliff is already installed] *******************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Install Jcliff using standalone binary] ************************************************************************************************************************

skipping: [localhost]

PLAY RECAP ***********************************************************************************************************************************************************************************

localhost                  : ok=6    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

We have completed the setup to integrate our Ansible playbook with JCliff. Now, we are ready to start using the jcliff: module to fine-tune our WildFly server configuration.

Define system variables in the WildFly server configuration

We'll start by using JCliff to add system variables to WildFly's server configuration. It’s not the most complex functionality, but it's a good way to confirm that everything is working:

---

- hosts: localhost

  gather_facts: true

  vars:

    jboss_home: "{{ lookup('env','JBOSS_HOME') }}"

  collections:

    - wildfly.jcliff

  roles:

    - jcliff

  tasks:

The configuration so far is straightforward. The JCliff CLI only requires the path to the WildFly server's home directory, which we defined in the jboss_home variable. After locating the home variable, JCliff uses the script provided with WildFly (${JBOSS_HOME}/bin/jboss-cli.sh) to communicate with the WildFly server.

Now, we want to begin our custom configuration. Most WildFly configurations are defined as subsystems, and JCliff has a list of these subsystems. We will start with something simple: The system_props subsystem, which we'll use to declare system variables in the WildFly server configuration:

    - jcliff:

        wfly_home: "{{ jboss_home }}"

        subsystems:

          - system_props:

              - name: jcliff.enabled

                value: 'enabled.plus'

Re-run the Ansible playbook and see what happens:

# ansible-playbook playbook.yml

PLAY [localhost] *****************************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Collect Supported Operating Systems] ***************************************************************************************************************************

ok: [localhost] => (item={u'key': u'homebrew', u'value': [u'MacOSX']})

ok: [localhost] => (item={u'key': u'rpm', u'value': [u'Fedora', u'CentOS', u'RedHat']})

TASK [wildfly.jcliff.jcliff : Verify supported Operating Systems] ****************************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Install JCliff using HomeBrew] *********************************************************************************************************************************

skipping: [localhost]

TASK [wildfly.jcliff.jcliff : Install JCliff using RPM] **************************************************************************************************************************************

included: /root/.ansible/collections/ansible_collections/wildfly/jcliff/roles/jcliff/tasks/install_rpm.yml for localhost

TASK [wildfly.jcliff.jcliff : Add JCliff Yum Repository (RedHat)] ****************************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Test if package jcliff is already installed] *******************************************************************************************************************

ok: [localhost]

TASK [wildfly.jcliff.jcliff : Install Jcliff using standalone binary] ************************************************************************************************************************

skipping: [localhost]

TASK [jcliff] ********************************************************************************************************************************************************************************

changed: [localhost]

PLAY RECAP ***********************************************************************************************************************************************************************************

localhost                  : ok=7    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

Everything seems to be running smoothly. Ansible noticed that the variables we defined were missing from the server’s initial configuration and added them. All of this happened thanks to JCliff communicating with the server (via the JBoss CLI).

To confirm that the variables are defined in the WildFly configuration, run the following JBoss CLI query:

# ${JBOSS_HOME}/bin/jboss-cli.sh --connect --command='/system-property=jcliff.enabled:read-resource'

{

    "outcome" => "success",

    "result" => {"value" => "enabled.plus"}

}

Let's be thorough and verify one of the promises of JCliff. We said earlier that the WildFly server's XML configuration would be automatically updated. Here is the confirmation that it has been:

...

 </extensions>

    <system-properties>

        <property name="jcliff.enabled" value="enabled.plus"/>

    </system-properties>

    <management>

...

Everything is working as expected. JCliff can communicate with the WildFly server, and it can update the server configuration.

Conclusion

We've completed the basic setup. In Part 2, we will go deeper into the features offered by the Ansible collection for JCliff. We'll cover how to deploy new JDBC drivers along with defining new data sources. We will also deploy applications inside the application server.

Last updated: February 5, 2024