Featured image for Red Hat JBoss Enterprise Application Platform.

When it comes to Java web servers, Apache Tomcat remains a strong favorite. Some of these instances have been containerized over the years, but many still run in the traditional setup of a Linux-based virtual machine or even on bare metal.

Red Hat JBoss Web Server (JWS) combines the servlet engine (Apache Tomcat) with the web server (Apache HTTPD), and modules for load balancing (mod_jk and mod_cluster). Ansible is an automation tool that provides a suite of tools for managing an enterprise at scale.

In this article, we will illustrate how Ansible can be used to completely automate the deployment of a JBoss Web Server 6 instance on a Red Hat Enterprise Linux 9 server. This automation encompasses the following tasks:

  • Retrieve the archive containing the JBoss Web Server from the Red Hat Customer Portal and install the files on the system.
  • Configure the Red Hat Enterprise Linux (RHEL) operating system including the users, groups, and the required setup files to enable JBoss Web Server as a systemd service.
  • Ensure the required Java Virtual Machine is installed
  • Fine-tune the configuration of the JBoss Web Server server, such as binding it to the appropriate interface and port.
  • Deploy web applications along with enabling and starting the JBoss Web Server as a systemd service.
  • Perform a health check to ensure that the deployed application is accessible.

Our Ansible playbook will fully automates all of those operations, so no manual steps will be required.

Preparing the target environment

Prerequisites

Before we start with the automation work, we need to specify the target environment. In this case, you'll be using Red Hat Enterprise Linux 9 with Python 3.9. We'll use this configuration on both the Ansible control node (where Ansible is executed), which will be referred to from now on as controller, and the Ansible target (the system being configured).

The controller for this demonstration has the following requirements:

  •  Red Hat Enterprise Linux 9.3
  •  Python 3.x
  •  Ansible 2.14

To check with version of RHEL the controller is running, the following command can be used:

$ cat /etc/redhat-release
Red Hat Enterprise Linux release 9.3 (Plow)

Verifying the version of Ansible is pretty straightforward, and it also provides the needed information on the Python version used to run it:

$  ansible --version
ansible [core 2.14.9]
  config file = /work/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.18 (main, Jan  4 2024, 00:00:00) [GCC 11.4.1 20230605 (Red Hat 11.4.1-2)] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True

Note: The procedure in this article may not execute successfully if you use a different Python version or target operating system.

Installing the Red Hat Ansible Certified Content Collection

Once you have Red Hat Enterprise Linux 9 set up and Ansible 2.14 ready to go, you need to install the Red Hat Ansible Certified Content Collection 2.0 for Red Hat JBoss Web Server. 

To install the Red Hat Certified Collection for JBoss Web Server, you will need to configure Ansible to use Red Hat Automation Hub as the preferred Galaxy server. Follow the instructions on Automation Hub to retrieve your token and update the ansible.cfg configuration on your Ansible controller. Update the <your-token> field with the token obtained from Automation Hub:

[galaxy]
server_list = automation_hub, galaxy

[galaxy_server.galaxy]
url=https://galaxy.ansible.com/

[galaxy_server.automation_hub]
url=https://cloud.redhat.com/api/automation-hub/api/galaxy/
auth_url=https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token

token=<your-token>

If you are not familiar with Ansible, note that this configuration file lives in the same directory as the Ansible playbook we are going to design for our JWS deployment. 

Once you have configured Ansible to use Automation Hub, install the certified collection:

$ ansible-galaxy collection install redhat.jws
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://console.redhat.com/api/automation-hub/v3/plugin/ansible/content/published/collections/artifacts/redhat-jws-2.0.0.tar.gz to /root/.ansible/tmp/ansible-local-88isxfxlvv/tmpvujtdugq/redhat-jws-2.0.0-zf_lh9ed
Installing 'redhat.jws:2.0.0' to '/root/.ansible/collections/ansible_collections/redhat/jws'
Downloading https://console.redhat.com/api/automation-hub/v3/plugin/ansible/content/published/collections/artifacts/redhat-runtimes_common-1.1.3.tar.gz to /root/.ansible/tmp/ansible-local-88isxfxlvv/tmpvujtdugq/redhat-runtimes_common-1.1.3-pf34k4r_
redhat.jws:2.0.0 was installed successfully
Installing 'redhat.runtimes_common:1.1.3' to '/root/.ansible/collections/ansible_collections/redhat/runtimes_common'
Downloading https://console.redhat.com/api/automation-hub/v3/plugin/ansible/content/published/collections/artifacts/ansible-posix-1.5.4.tar.gz to /root/.ansible/tmp/ansible-local-88isxfxlvv/tmpvujtdugq/ansible-posix-1.5.4-yie4utve
redhat.runtimes_common:1.1.3 was installed successfully
Installing 'ansible.posix:1.5.4' to '/root/.ansible/collections/ansible_collections/ansible/posix'
ansible.posix:1.5.4 was installed successfully

Ansible Galaxy fetches and downloads the collection's dependencies. These dependencies include the redhat.runtimes_common collection, which helps facilitate the retrieval of the archive containing the JBoss Web Server server from the Red Hat customer portal.

Red Hat customer portal credentials

For the collection to be able to download the JWS archive from the Red Hat Customer Portal, we need to supply the credentials associated with a Red Hat service account. One way to provide those values parameters is to create a service_account.yml which can be passed to Ansible as an extra source of variables:

---
rhn_username: <service_account_id>
rhn_password: <service_account_password>

Installing the Red Hat JBoss Web server

The configuration steps in this section include downloading JBoss Web Server, installing Java, and enabling JBoss Web Server as a system service (systemd).

Configuring the JVM

JBoss Web Server is a Java-based server, so the target system must have a Java Virtual Machine (JVM) installed. Although Ansible primitives can perform such tasks natively, the redhat.jws collection can also take care of this task as well provided that the jws_java_version variable is defined. By default, the value is the latest Red Hat supported version of OpenJDK (17).

While we will keep the latest version for this demonstration, note that a different version of the OpenJDK can be set using thejws_java_version variable:

jws_java_version: 11

Note This feature works only if the target system's distribution belongs to the Red Hat family.

Enabling JBoss Web Server as a system service (systemd)

The JBoss Web Server server on the target system should run as a service system. The collection can also take care of this task if the jws_systemd_enabled variable is defined as True (which is the default value as the target systems are expected to be RHEL machines).

Note: This configuration works only when systemd is installed and the system belongs to the Red Hat family.

Running the playbook

The Red Hat Ansible Certified Content Collection comes with a playbook that can be used directly to ensure that JWS is properly installed on target instances.

Execute the following command to execute playbook included within the collection along with the extra variables file created previously:

$ ansible-playbook -i inventory -e @service_account.yml redhat.jws.playbook

PLAY [Red Hat JBoss Web Server installation and configuration] *****************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [redhat.jws.jws : Validating arguments against arg spec 'main'] ***********
ok: [localhost]

TASK [redhat.jws.jws : Check for conflicting Java variables] *******************
skipping: [localhost]

TASK [redhat.jws.jws : Set default values] *************************************
skipping: [localhost]

TASK [redhat.jws.jws : Check that jws_home has been defined.] ******************
ok: [localhost] => {
	"changed": false,
	"msg": "All assertions passed"
}

TASK [redhat.jws.jws : Add firewalld to dependencies list (if enabled)] ********
skipping: [localhost]

TASK [redhat.jws.jws : Add 'openssl' and 'apr' to dependencies list required for natives (if enabled)] ***
skipping: [localhost]

TASK [redhat.jws.jws : Include tasks for Java installation (if Java version is provided)] ***
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/java_install.yml for localhost

TASK [redhat.jws.jws : Add 'java-17-openjdk-headless' to dependencies list] ****
ok: [localhost]

TASK [redhat.jws.jws : Determine JAVA_HOME for selected JVM RPM] ***************
ok: [localhost]

TASK [redhat.jws.jws : Install required dependencies] **************************
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/fastpackage.yml for localhost

TASK [redhat.jws.jws : Check if "zip, unzip, tzdata, sudo, java-17-openjdk-headless" packages are already installed] ***
ok: [localhost]

TASK [redhat.jws.jws : Add missing packages to the yum install list] ***********
ok: [localhost]

TASK [redhat.jws.jws : Install packages: ['java-17-openjdk-headless']] *********
changed: [localhost]

TASK [redhat.jws.jws : Ensure tomcatjss rpm is not installed] ******************
ok: [localhost]

TASK [redhat.jws.jws : Create group: tomcat] ***********************************
changed: [localhost]

TASK [redhat.jws.jws : Create user: tomcat] ************************************
changed: [localhost]

TASK [redhat.jws.jws : Check state of install_dir: /opt] ***********************
ok: [localhost]

TASK [redhat.jws.jws : Ensure install dir is created: /opt] ********************
skipping: [localhost]

TASK [redhat.jws.jws : Set defaults values based on facts (if values not provided)] ***
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/defaults.yml for localhost

TASK [redhat.jws.jws : Set filename for JWS zipfile] ***************************
ok: [localhost]

TASK [redhat.jws.jws : Set native zipfile architecture (if not provided)] ******
ok: [localhost]

TASK [redhat.jws.jws : Set RHEL major version based on facts (if not provided).] ***
ok: [localhost]

TASK [redhat.jws.jws : Set filename for JWS native zipfile] ********************
ok: [localhost]

TASK [redhat.jws.jws : Ensure patch version is specified when installing offline.] ***
skipping: [localhost]

TASK [redhat.jws.jws : Ensure credentials are defined when installing from JBossNetwork API.] ***
ok: [localhost]

TASK [redhat.jws.jws : Check main zipfile] *************************************
skipping: [localhost]

TASK [redhat.jws.jws : Check native zipfile exists] ****************************
skipping: [localhost]

TASK [redhat.jws.jws : Check patch zipfile exists] *****************************
skipping: [localhost]

TASK [redhat.jws.jws : Check native patch zipfile exists] **********************
skipping: [localhost]

TASK [redhat.jws.jws : Include install tasks] **********************************
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/install.yml for localhost

TASK [redhat.jws.jws : Check arguments] ****************************************
ok: [localhost]

TASK [redhat.jws.jws : Check working directory /work for local repository] *****
ok: [localhost]

TASK [redhat.jws.jws : Display install method] *********************************
ok: [localhost] => {
	"msg": "Install method: zipfiles"
}

TASK [redhat.jws.jws : Include installation tasks using zipfiles method] *******
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/install/local.yml for localhost

TASK [redhat.jws.jws : Deploy jws-6.0.0-application-server.zip to target.] *****
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/install/deploy_archive.yml for localhost

TASK [redhat.jws.jws : Check that required parameters have been provided.] *****
ok: [localhost]

TASK [redhat.jws.jws : Check download archive path on target: /opt/jws-6.0.0-application-server.zip] ***
ok: [localhost]

TASK [redhat.jws.jws : Retrieve zipfiles, if missing, from RHN (if credentials provided)] ***
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/install/download_from_rhn.yml for localhost

TASK [redhat.jws.jws : Search for product to download using JBoss Network API] ***
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/rhn/search.yml for localhost

TASK [redhat.jws.jws : Ensure required parameters are provided] ****************
ok: [localhost]

TASK [redhat.jws.jws : Retrieve product download using JBossNetwork API] *******
ok: [localhost]

TASK [redhat.jws.jws : Ensure search results are valid.] ***********************
ok: [localhost]

TASK [redhat.jws.jws : Determine install zipfile from search results] **********
ok: [localhost]

TASK [redhat.jws.jws : Download Red Hat JWS] ***********************************
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/rhn/download.yml for localhost

TASK [redhat.jws.jws : Ensure required parameters are provided] ****************
ok: [localhost]

TASK [redhat.jws.jws : Load metadata on target location for download (/work/jws-6.0.0-application-server.zip)] ***
ok: [localhost]

TASK [redhat.jws.jws : Ensure /work/jws-6.0.0-application-server.zip is accessible] ***
ok: [localhost]

TASK [redhat.jws.jws : Download Red Hat product into {{ rhn_product_path }} (rhn_download_become: {{ rhn_download_become }})] ***
changed: [localhost]

TASK [redhat.jws.jws : Retrieve zipfiles from URL (if provided).] **************
skipping: [localhost]

TASK [redhat.jws.jws : Copy archives /work/jws-6.0.0-application-server.zip to target nodes: /opt/jws-6.0.0-application-server.zip] ***
changed: [localhost]

TASK [redhat.jws.jws : Deploy jws-6.0.0-optional-native-components-RHEL9-x86_64.zip to target.] ***
skipping: [localhost]

TASK [redhat.jws.jws : Include installation tasks for zip operations] **********
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/install/zipfiles.yml for localhost

TASK [redhat.jws.jws : Check arguments] ****************************************
ok: [localhost]

TASK [redhat.jws.jws : Add zipfile to unarchive list] **************************
ok: [localhost]

TASK [redhat.jws.jws : Add native zipfile to unarchive list] *******************
skipping: [localhost]

TASK [redhat.jws.jws : Install Jboss Web Server and required binaries from local zipfiles (install method: zipfiles)] ***
changed: [localhost] => (item={'src': 'jws-6.0.0-application-server.zip', 'creates': '/opt/jws-6.0/tomcat/bin'})

TASK [redhat.jws.jws : Move the zipfile extracted directory to custom jws_home] ***
skipping: [localhost]

TASK [redhat.jws.jws : Move the version.txt to custom jws_home] ****************
skipping: [localhost]

TASK [redhat.jws.jws : Include installation tasks for rpm method] **************
skipping: [localhost]

TASK [redhat.jws.jws : Include systemd tasks] **********************************
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/systemd/systemd.yml for localhost

TASK [redhat.jws.jws : Check arguments] ****************************************
ok: [localhost]

TASK [redhat.jws.jws : Ensure requirements for systemd] ************************
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/fastpackage.yml for localhost

TASK [redhat.jws.jws : Check if "systemd, procps-ng" packages are already installed] ***
ok: [localhost]

TASK [redhat.jws.jws : Add missing packages to the yum install list] ***********
ok: [localhost]

TASK [redhat.jws.jws : Install packages: ['java-17-openjdk-headless']] *********
ok: [localhost]

TASK [redhat.jws.jws : Set required default for jws_service_conf if not provided.] ***
ok: [localhost]

TASK [redhat.jws.jws : Set required default for jws_service_conf if not provided.] ***
ok: [localhost]

TASK [redhat.jws.jws : Set required default for jws_service_conf if not provided.] ***
ok: [localhost]

TASK [redhat.jws.jws : Ensure service script is deployed] **********************
changed: [localhost]

TASK [redhat.jws.jws : Ensure service configurations files is deployed: /opt/jws-6.0/tomcat/conf/jws6-tomcat.conf] ***
changed: [localhost]

TASK [redhat.jws.jws : Ensure systemd service is configured] *******************
changed: [localhost]

TASK [redhat.jws.jws : Include patch install tasks] ****************************
skipping: [localhost]

TASK [redhat.jws.jws : Ensure /opt/jws-6.0/tomcat/ directories have appropriate privileges] ***
ok: [localhost] => (item=conf)
ok: [localhost] => (item=temp)
ok: [localhost] => (item=logs)
ok: [localhost] => (item=webapps)
ok: [localhost] => (item=bin)

TASK [redhat.jws.jws : Ensure /opt/jws-6.0/tomcat/ files have the recommended priviliges, owner and group] ***
changed: [localhost] => (item=./conf/catalina.properties)
changed: [localhost] => (item=./conf/catalina.policy)
changed: [localhost] => (item=./conf/logging.properties)
changed: [localhost] => (item=./conf/jaspic-providers.xml)
changed: [localhost] => (item=conf/tomcat-users.xml)

TASK [redhat.jws.jws : Include ajp sanity check tasks] *************************
skipping: [localhost]

TASK [redhat.jws.jws : Include https sanity check tasks] ***********************
skipping: [localhost]

TASK [redhat.jws.jws : Deploy custom configuration files] **********************
changed: [localhost] => (item={'template': 'templates/6/server.xml.j2', 'dest': '/opt/jws-6.0/tomcat/./conf/server.xml'})
changed: [localhost] => (item={'template': 'templates/6/web.xml.j2', 'dest': '/opt/jws-6.0/tomcat/./conf/web.xml'})
changed: [localhost] => (item={'template': 'templates/6/context.xml.j2', 'dest': '/opt/jws-6.0/tomcat/./conf/context.xml'})
changed: [localhost] => (item={'template': 'templates/6/catalina.properties.j2', 'dest': '/opt/jws-6.0/tomcat/./conf/catalina.properties'})

TASK [redhat.jws.jws : Include selinux configuration tasks] ********************
skipping: [localhost]

TASK [redhat.jws.jws : Remove apps] ********************************************
ok: [localhost] => (item=examples)

TASK [redhat.jws.jws : Create vault configuration (if enabled)] ****************
skipping: [localhost]

TASK [redhat.jws.jws : Ensure firewalld, if enabled, allows communication over 8080.] ***
skipping: [localhost]

RUNNING HANDLER [redhat.jws.jws : Reload Systemd] ******************************
ok: [localhost]

RUNNING HANDLER [redhat.jws.jws : Ensure Jboss Web Server runs under systemd] ***
included: /root/.ansible/collections/ansible_collections/redhat/jws/roles/jws/tasks/systemd/service.yml for localhost

RUNNING HANDLER [redhat.jws.jws : Check arguments] *****************************
ok: [localhost]

RUNNING HANDLER [redhat.jws.jws : Enable jws service] **************************
changed: [localhost]

RUNNING HANDLER [redhat.jws.jws : Start jws service] ***************************
changed: [localhost]

RUNNING HANDLER [redhat.jws.jws : Restart Jboss Web Server service] ************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost              	: ok=66   changed=14   unreachable=0	failed=0	skipped=22   rescued=0	ignored=0

As you can see, quite a lot happened during this execution. Indeed, the redhat.jws role took care of the entire setup of JWS on the target system.

Deploying a web application

Now that JBoss Web Server is running, we will go a bit further by deploying a web application and ensuring it is running. As we’ll need to write our own playbook, the first step will be to copy the one included playbook within the collection and use it as a base:

$ cp ~/.ansible/collections/ansible_collections/redhat/jws/playbooks/playbook.yml .
$ cat playbook.yml
---
- name: "Red Hat JBoss Web Server installation and configuration"
  hosts: all
  become: True
  vars_files:
	- vars.yml
  roles:
	- redhat.jws.jws
$ cp ~/.ansible/collections/ansible_collections/redhat/jws/playbooks/vars.yml .
$ cat vars.yml
---
jws_setup: true
jws_java_version: 17
jws_listen_http_bind_address: 127.0.0.1
jws_systemd_enabled: True
jws_service_systemd_type: forking
jws_selinux_enabled: False

Now, we’ll add a tasks: section to the playbook. This section of the playbook will be run after the roles have executed successfully So, we know that JWS will be operational on the targets at this point.

We will include the following tasks to perform the deployment of a web application:

---
- name: "Red Hat JBoss Web Server installation and configuration"
  hosts: all
  become: True
  vars_files:
    - vars.yml
  roles:
    - redhat.jws.jws
  tasks:
    - name: "Deploy webapp"
      ansible.builtin.get_url:
       url: "https://drive.google.com/uc?export=download&id=1w9ss5okctnjUvRAxhPEPyC7DmbUwmbhb"
       dest: "{{ jws_home }}/webapps/info.war"

Let’s run again the playbook:

$ ansible-playbook -i inventory -e @service_account.yml playbook.yml 

PLAY [Red Hat JBoss Web Server installation and configuration] *************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************
ok: [localhost]

…

TASK [Deploy webapp] *******************************************************************************************************************
changed: [localhost]

RUNNING HANDLER [redhat.jws.jws : Restart Jboss Web Server service] ********************************************************************
changed: [localhost]

PLAY RECAP *****************************************************************************************************************************
localhost              	: ok=3	changed=2	unreachable=0	failed=0	skipped=0	rescued=0	ignored=0

To be thorough, we will add a post_tasks section to verify that the web application has been successfully and that the associated service is now available:

post_tasks:
  - name: " Checks that /info is accessible"
	ansible.builtin.uri:
  	url: "http://localhost:8080/info"
  	status_code: 200
  	return_content: no

The benefits of such automation

In short, automation saves time and reduces the risk of error inherent to any human manipulation.

The Red Hat Ansible Certified Content Collection encapsulates (as much as possible) the complexities and the inner workings of Red Hat JBoss Web Server deployment. With the help of the certified Ansible collection, you can focus on your business use case, such as deploying applications, instead of establishing the underlying application server. The result is reduced complexity and faster time to value. The automated process is also repeatable and can be used to set up as many systems as needed.