Featured image for automating JBoss Web Server deployments with Ansible.

Infinispan, an in-memory data store, is popular among Java programmers as a fast and scalable key-value store that can be deployed in a variety of settings. This article describes how to use Ansible to automate the installation and setup of an Infinispan instance. Along the way, you'll learn about many aspects of Ansible's capabilities.

The most straightforward use for Infinispan is as an in-memory cache, embedded into an application. Infinispan can also be a separate, standalone cache offering transient memory to separate applications that access the cache using different protocols. A third option is to treat Infinispan as a kind of NoSQL database. And these are only the most common use cases; additional options are available.

Infinispan can be resilient, even across data centers or availability zones, thanks to its efficient cross-site replication and split-brain mitigation options.

With so many use cases at one's disposal, it is nice to have a dedicated Ansible collection to help automate Infinispan deployment and configuration. This article provides a walkthrough of Ansible's usage and shows you how to integrate Infinispan easily into an Ansible Playbook to manage the software like any other piece of the application's infrastructure.

Prerequisites

To follow this tutorial, you need a Red Hat Enterprise Linux or Fedora system, along with Ansible (preferably, the latest version of the 2.9 branch).

Install the collection

The code for this tutorial is stored in a package—a collection, in Ansible terminology—provided by a repository called the Ansible Galaxy. This collection is named middleware_automation.infinispan. When you install it, Ansible Galaxy also automatically pulls in a dependency named middleware_automation.redhat_csp:

$ ansible-galaxy collection install middleware_automation.infinispan
Process install dependency map
Starting collection install process
Installing 'middleware_automation.infinispan:0.1.7' to '/home/rpelisse/.ansible/collections/ansible_collections/middleware_automation/infinispan'
Installing 'middleware_automation.redhat_csp_download:1.2.1' to '/home/rpelisse/.ansible/collections/ansible_collections/middleware_automation/redhat_csp_download'

Depending on the configuration of the machine used as the Ansible controller, you may need to add some Python dependencies so that Ansible has the required libraries to make use of the collection. This is easily achieved by running the following command:

$ pip3 install lxml jmespath
Collecting lxml
  Downloading lxml-4.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (6.9 MB)
     |████████████████████████████████| 6.9 MB 1.9 MB/s
Collecting jmespath
  Downloading jmespath-0.10.0-py2.py3-none-any.whl (24 kB)
Installing collected packages: lxml, jmespath
Successfully installed jmespath-0.10.0 lxml-4.7.1

Now that the collection is installed, you can insert it into a playbook, which you should name playbook.yml:

---
- name: Playbook for infinispan Hosts
  hosts: infinispan
  become: yes
  collections:
    - middleware_automation.infinispan
  tasks:

Before going further, let's check that installation of the collection was successful by running this empty playbook:

# ansible-playbook -i inventory/localhost playbook.yml
PLAY [Playbook for infinispan Hosts] ***************************************************************************************************

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

ok: [localhost]
PLAY RECAP *****************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Install Infinispan with Ansible

Thanks to the collection you just installed, the task in this section is very easy. However, before you look at how to implement the task inside your playbook, you need to understand what I mean here by "installing Infinispan." Indeed, the task encompasses quite a few actions that Ansible performs on the target system:

  • Creating appropriate user and group accounts
  • Downloading the archive from the Infinispan website
  • Unarchiving the content while ensuring that all the files are associated with the appropriate user and groups
  • Ensuring that the required version of the Java Virtual Machine (JVM) is installed
  • Integrating the software into the host service management system (in this case, systemd)
  • And much more

To carry out all the tasks on this list, all you need to do is add to your playbook to provide a secure password for administrative access to the Infinispan server and specify the appropriate role from the collection:

---
- name: Playbook for infinispan Hosts
  hosts: infinispan
  become: yes
  collections:
    - middleware_automation.infinispan
  vars:
    jdg_supervisor_password: "thisshouldbeaverysecurepassword"
  tasks:
    - name: "infinispan"
       include_role:
          name: infinispan
        vars:
            supervisor_password: "{{ jdg_supervisor_password }}"
            infinispan_users: []

What's going on here? First, the middleware_automation.infinispan collection is in the list of collections used by this playbook. Then you have a variable to hold the supervisor password of the Infinispan instance. Note that because this variable is a password, it should really be secured using Ansible Vault. However, that task is beyond the scope of this article.

The last and most important aspect of this playbook is the use of the infinispan collection. You've placed this name in the list of collections used by the playbook, and then used its content in a role called infinispan. This role is provided by the collection that was previously installed and requires you to provide two pieces of information as variables: the supervisor password and the list of Infinispan users.

At this stage, you've kept things simple and specified no extra users for Infinispan. Instead, you've retained the administrator account called supervisor, as explained in the Infinispan security documentation. This account must be secured with a password, so you defined the appropriate value in the tasks property, which is executed before the roles.

Now run this playbook:

$ ansible-playbook -i inventory/localhost playbook.yml
PLAY [Playbook for infinispan Hosts] *********************************************************************************************************************************************************

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

TASK [infinispan] ****************************************************************************************************************************************************************************

TASK [middleware_automation.infinispan.infinispan : Validate parameters] *********************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Include prerequisite tasks] **************************************************************************************************************
included: /root/.ansible/collections/ansible_collections/middleware_automation/infinispan/roles/infinispan/tasks/prereqs.yml for localhost

TASK [middleware_automation.infinispan.infinispan : Validate credentials] ********************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Set required packages facts] *************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Ensures required packages are installed] *************************************************************************************************
included: /root/.ansible/collections/ansible_collections/middleware_automation/infinispan/roles/infinispan/tasks/fastpackages/install.yml for localhost

TASK [middleware_automation.infinispan.infinispan : Set required packages facts] *************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Check packages to be installed] **********************************************************************************************************
included: /root/.ansible/collections/ansible_collections/middleware_automation/infinispan/roles/infinispan/tasks/fastpackages/check.yml for localhost => (item=unzip)
included: /root/.ansible/collections/ansible_collections/middleware_automation/infinispan/roles/infinispan/tasks/fastpackages/check.yml for localhost => (item=procps-ng)
included: /root/.ansible/collections/ansible_collections/middleware_automation/infinispan/roles/infinispan/tasks/fastpackages/check.yml for localhost => (item=initscripts)
included: /root/.ansible/collections/ansible_collections/middleware_automation/infinispan/roles/infinispan/tasks/fastpackages/check.yml for localhost => (item=java-1.8.0-openjdk-devel)

TASK [middleware_automation.infinispan.infinispan : Check if package unzip is already installed] *********************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["rpm", "-q", "unzip"], "delta": "0:00:00.046008", "end": "2022-02-10 14:42:22.708736", "msg": "non-zero return code", "rc": 1, "start": "2022-02-10 14:42:22.662728", "stderr": "", "stderr_lines": [], "stdout": "package unzip is not installed", "stdout_lines": ["package unzip is not installed"]}

TASK [middleware_automation.infinispan.infinispan : If package unzip is missing, add it to the yum install list.] ****************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Check if package procps-ng is already installed] *****************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Check if package initscripts is already installed] ***************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["rpm", "-q", "initscripts"], "delta": "0:00:00.010029", "end": "2022-02-10 14:42:23.259519", "msg": "non-zero return code", "rc": 1, "start": "2022-02-10 14:42:23.249490", "stderr": "", "stderr_lines": [], "stdout": "package initscripts is not installed", "stdout_lines": ["package initscripts is not installed"]}

TASK [middleware_automation.infinispan.infinispan : If package initscripts is missing, add it to the yum install list.] **********************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Check if package java-1.8.0-openjdk-devel is already installed] **************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["rpm", "-q", "java-1.8.0-openjdk-devel"], "delta": "0:00:00.010355", "end": "2022-02-10 14:42:23.536096", "msg": "non-zero return code", "rc": 1, "start": "2022-02-10 14:42:23.525741", "stderr": "", "stderr_lines": [], "stdout": "package java-1.8.0-openjdk-devel is not installed", "stdout_lines": ["package java-1.8.0-openjdk-devel is not installed"]}

TASK [middleware_automation.infinispan.infinispan : If package java-1.8.0-openjdk-devel is missing, add it to the yum install list.] *********************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Install packages: ['unzip', 'initscripts', 'java-1.8.0-openjdk-devel']] ******************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Create group jdg] ************************************************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Create user jdg] *************************************************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Create download directory] ***************************************************************************************************************
changed: [localhost]

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

TASK [middleware_automation.infinispan.infinispan : Set download archive path] ***************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Check download archive path] *************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Check local download archive path] *******************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Download infinispan archive] *************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Performing download from RHN] ************************************************************************************************************
skipping: [localhost]

TASK [middleware_automation.infinispan.infinispan : Download DataGrid archive from alternate location] ***************************************************************************************
skipping: [localhost]

TASK [middleware_automation.infinispan.infinispan : Check downloaded archive] ****************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Copy archive to target nodes] ************************************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Check target directory: /opt/infinispan/infinispan-server-12.1.7.Final/] *****************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Extract Infinispan archive on target] ****************************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Inform decompression was not executed] ***************************************************************************************************
skipping: [localhost]

TASK [middleware_automation.infinispan.infinispan : Reown installation directory to jdg] *****************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Installation directory] ******************************************************************************************************************
ok: [localhost] => {
    "msg": "Infinispan installed at path /opt/infinispan/infinispan-server-12.1.7.Final/"
}

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

TASK [middleware_automation.infinispan.infinispan : Configure systemd unit file for infinispan service] **************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Perform daemon-reload to ensure the changes on infinispan service are picked up] *********************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Parse declarative cache config to cache xml] *********************************************************************************************

TASK [middleware_automation.infinispan.infinispan : Ensures infinispan configuration is deployed: jdg.xml] ***********************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Ensures infinispan log4j2 configuration is deployed] *************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Download database driver jar to target] **************************************************************************************************
skipping: [localhost]

TASK [middleware_automation.infinispan.infinispan : Include users tasks] *********************************************************************************************************************
included: /root/.ansible/collections/ansible_collections/middleware_automation/infinispan/roles/infinispan/tasks/jdg_user.yml for localhost

TASK [middleware_automation.infinispan.infinispan : Validate parameters] *********************************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Ensures users.properties exists.] ********************************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Ensures groups.properties exists] ********************************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Ensures infinispan service is running and enabled] ***************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Link default logs directory] *************************************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Flush handlers] **************************************************************************************************************************

RUNNING HANDLER [middleware_automation.infinispan.infinispan : restart infinispan] ***********************************************************************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Wait for used port to be open] ***********************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan : Include tasks to validate keycloak remote caches] ****************************************************************************************
skipping: [localhost]

PLAY RECAP ***********************************************************************************************************************************************************************************
localhost                  : ok=43   changed=14   unreachable=0    failed=0    skipped=6    rescued=3    ignored=0   

Quite a lot has happened here. More than 40 tasks have been performed that take care of the configuration tasks mentioned in the earlier list (creating users and a group, downloading the software, and installing the required JVM, among others). Once the playbook finishes its execution, you can confirm that Infinispan is now running as a service by verifying its status:

# systemctl status infinispan
● infinispan.service - infinispan service
   Loaded: loaded (/etc/systemd/system/infinispan.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2022-02-10 14:43:04 UTC; 8min ago
  Process: 924 ExecStart=/bin/sh -c /opt/infinispan/infinispan-server-12.1.7.Final//bin/server.sh -c jdg.xml -b localhost & (code=exited, status=0/SUCCESS)
 Main PID: 1008 (java)
    Tasks: 52 (limit: 1638)
   Memory: 329.5M
   CGroup: /system.slice/infinispan.service
           ├─ 925 /bin/sh /opt/infinispan/infinispan-server-12.1.7.Final//bin/server.sh -c jdg.xml -b localhost
           └─1008 java -server -verbose:gc -Xloggc:/opt/infinispan/infinispan-server-12.1.7.Final/server/log/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:>

Feb 10 14:43:09 310305941c20 sh[924]: 2022-02-10 14:43:09,526 INFO  (main) [org.infinispan.CLUSTER] ISPN000079: Channel datagrid local address is localhost, physical addresses are [10.0.2.1>
Feb 10 14:43:09 310305941c20 sh[924]: 2022-02-10 14:43:09,551 INFO  (main) [org.infinispan.CONTAINER] ISPN000390: Persisted state, version=12.1.7.Final timestamp=2022-02-10T14:43:09.549Z
Feb 10 14:43:09 310305941c20 sh[924]: 2022-02-10 14:43:09,861 INFO  (main) [org.jboss.threads] JBoss Threads version 2.3.3.Final
Feb 10 14:43:09 310305941c20 sh[924]: 2022-02-10 14:43:09,944 INFO  (main) [org.infinispan.CONTAINER] ISPN000104: Using EmbeddedTransactionManager
Feb 10 14:43:10 310305941c20 sh[924]: 2022-02-10 14:43:10,343 INFO  (main) [org.infinispan.server.core.RequestTracer] OpenTracing integration is disabled
Feb 10 14:43:10 310305941c20 sh[924]: 2022-02-10 14:43:10,407 INFO  (ForkJoinPool.commonPool-worker-1) [org.infinispan.SERVER] ISPN080018: Started connector HotRod (internal)
Feb 10 14:43:10 310305941c20 sh[924]: 2022-02-10 14:43:10,507 INFO  (main) [org.infinispan.SERVER] ISPN080018: Started connector REST (internal)
Feb 10 14:43:10 310305941c20 sh[924]: 2022-02-10 14:43:10,808 INFO  (main) [org.infinispan.SERVER] ISPN080004: Connector SINGLE_PORT (default) listening on 0.0.0.0:11222
Feb 10 14:43:10 310305941c20 sh[924]: 2022-02-10 14:43:10,809 INFO  (main) [org.infinispan.SERVER] ISPN080034: Server 'localhost' listening on http://0.0.0.0:11222
Feb 10 14:43:10 310305941c20 sh[924]: 2022-02-10 14:43:10,843 INFO  (main) [org.infinispan.SERVER] ISPN080001: Infinispan Server 12.1.7.Final started in 5178ms

Note that the execution has verified that the service is not only running, but is also available:

…
RUNNING HANDLER [middleware_automation.infinispan.infinispan : restart infinispan] *****************************************************
changed: [localhost]

TASK [middleware_automation.infinispan.infinispan : Wait for used port to be open] *****************************************************
ok: [localhost]

However, for the sake of being thorough, you can double-check that the Infinispan port is indeed accessible:

$ curl -I http://localhost:11222
HTTP/1.1 405 Method Not Allowed
content-length: 11

To summarize, at the end of this playbook execution, you have a service running through systemd, managing an instance of Infinispan. However, this instance is bare; there are no caches configured for an application to use. You'll add one in the next section.

Deploy caches in Infinispan using Ansible

To deploy a cache for Infinispan, you need to modify the main configuration file, which is difficult to achieve using templates because it is written in XML. However, here again, the infinispan collection provides a handy role that greatly simplifies the task. Add the following to playbook.yml:

…
- name: "infinispan cache"
  include_role:
    name: infinispan_cache
  vars:
     deployer_user: "supervisor"
     deployer_password: "{{ jdg_supervisor_password }}"
     cache_config:
       name: myCache
       template: replicated

You can find more information about how to configure caches (dynamically, as shown here, or statically) in the Ansible collection for Infinispan documentation.

The configuration is once again pretty self-explanatory. You have provided the infinispan_cache variable giving the user connection information to the role. Because there is no user other than supervisor in your Infinispan installation, you've simply used that user as the deployer_user variable, and provided the password using the jdg_supervisor_password variable you defined in the previous section:

…
TASK [middleware_automation.infinispan.infinispan_cache : Validate parameters - rest api url] ************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan_cache : Validate parameters - cache config] ************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan_cache : Parse cache xml] *******************************************************************************************************************
skipping: [localhost]

TASK [middleware_automation.infinispan.infinispan_cache : Set cache name from xml] ***********************************************************************************************************
skipping: [localhost]

TASK [middleware_automation.infinispan.infinispan_cache : Set cache name from config] ********************************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan_cache : Assemble yaml template into xmlstring] *********************************************************************************************
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan_cache : Check cache myCache state] *********************************************************************************************************
[WARNING]: Module did not set no_log for password
ok: [localhost]

TASK [middleware_automation.infinispan.infinispan_cache : Create cache myCache] **************************************************************************************************************
ok: [localhost]
…

To be cautious here, you should double-check that the cache was indeed created by adding the following instructions to your playbook:

…
- name: "Ensures cache was created"
  ansible.builtin.uri:
    url: http://localhost:11222/rest/v2/caches/myCache
    method: GET
    url_username: "supervisor"
    url_password: "{{ jdg_supervisor_password }}"
    status_code:
      - 200
…

Add content to the cache

Infinispan is now running as a service under the control of systemd, and you have set up a cache called myCache, but there is still no data in it. Although the application using this Infinispan service would normally add data, you can test Infinispan by adding some content to the cache manually via the REST API. To do so, add the following to playbook.yml:

…
- name: "Check service operational: add entry to test cache via rest"
  ansible.builtin.uri:
    url: http://localhost:11222/rest/v2/caches/myCache/firstEntry
    method: POST
    url_username: "supervisor"
    url_password: "{{ jdg_supervisor_password }}"
    status_code:
      - 204

Execute your playbook one final time with this extra task:

…
TASK [Check service operational: add entry to test cache via rest] ***************************************************************************************************************************
ok: [localhost]
…

Conclusion

Ansible and the Ansible collection for Infinispan have fully automated the deployment of Infinispan instances. Ansible performed all the work: downloading software, preparing the environment (user, group, firewall), deploying the binary files and the configuration, setting up the service in systemd, etc. Perhaps this article has encouraged you to try Infinispan in your own projects.

You've also gotten some insights into the versatility of Ansible, which can automate many aspects of program builds and deployments. If you're interested in learning more about Ansible and middleware, you might want to read an article I wrote about automating Red Hat JBoss Web Server. And look for a future article here at Red Hat Developer where you'll learn how Ansible can tackle more complex Infinispan configurations.

Comments