Deploying Apache Cassandra on IBM PowerVS with Terraform and RHEL 10

IBM Power Systems Virtual Server (PowerVS) is a great fit for data-intensive workloads. In this post, we’ll walk through deploying a single-node Apache Cassandra 5.0 instance on PowerVS using a RHEL 10 Bring-Your-Own-License (BYOL) image, fully automated with Terraform and a cloud-init shell script.

By the end you’ll have:

  • A running PowerVS LPAR (s1022) on RHEL 10
  • IBM Semeru Runtime (OpenJ9) for Java on ppc64le
  • Apache Cassandra 5.0 installed and ready to start
  • A public network interface for SSH access

Architecture Overview

The deployment is split into two layers:

Infrastructure (Terraform)

ResourcePurpose
ibm_pi_networkCreates a public pub-vlan network for external access
ibm_pi_instanceProvisions the LPAR using the RHEL 10 BYOL stock image
null_resourceSSHes into the instance after boot and runs the init script

Configuration (init.sh)

Once the LPAR is up, Terraform copies a templated init.sh script to the instance and executes it over SSH. The script:

  1. Registers the OS with Red Hat Subscription Manager (RHSM)
  2. Enables the RHEL 10 ppc64le base and appstream repos
  3. Updates the OS and installs tooling (wgetcurljq, etc.)
  4. Downloads and installs IBM Semeru Java 17 LTS for ppc64le from GitHub Releases
  5. Registers the cassandra YUM repo and installs Apache Cassandra 5.0
  6. Drops a cassandra-manager.service systemd unit (installed but not auto-started — you control when it runs)

Prerequisites

Before you begin, make sure you have:

  • An IBM Cloud account with PowerVS permissions
  • pre-provisioned PowerVS workspace (get the workspace GUID from the UI)
  • PowerVS SSH key registered in the workspace
  • Red Hat credentials (RHSM username and password) for a RHEL subscription
  • Terraform ≥ 1.3 installed locally
  • The IBM Cloud CLI with the PowerVS plugin (ibmcloud plugin install power-iaas)

RHEL 10 BYOL image: This example uses the stock catalog image ID 585ca713-f303-45f0-a836-e9dd4f4c8f3b (RHEL10-BYOL) which is already available in PowerVS — no COS bucket or manual image upload required.


Repository Layout

.
├── main.tf                  # Provider, network, instance, and provisioner
├── variables.tf             # Input variable declarations
├── outputs.tf               # Instance ID and IP outputs
├── init.sh                  # cloud-init / SSH-executed bootstrap script
└── terraform.tfvars.example # Template — copy to terraform.tfvars

Step 1 — Configure Your Variables

Copy the example variables file and fill in your values:

cp terraform.tfvars.example terraform.tfvars

Then edit terraform.tfvars:

ibmcloud_api_key      = "YOUR_IBM_CLOUD_API_KEY"
workspace_id          = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
region                = "us-south"
zone                  = "dal12"
ssh_key_name          = "my-powervs-ssh-key"
ssh_private_key_path  = "~/.ssh/id_rsa"
memory                = 16
processors            = 2
rhsm_username         = "your-redhat-username"
rhsm_password         = "your-redhat-password"

Never commit terraform.tfvars to source control — it contains your IBM Cloud API key and RHSM credentials. The .gitignore in this repo already excludes it, and all three sensitive variables are declared with sensitive = true in variables.tf so Terraform redacts them from plan/apply output.

Variable Reference

VariableDescriptionDefault
ibmcloud_api_keyIBM Cloud API key
workspace_idPowerVS workspace GUID
regionIBM Cloud region (e.g. us-south)
zoneIBM Cloud zone (e.g. dal12)
ssh_key_nameSSH key registered in the PowerVS workspace
ssh_private_key_pathLocal path to the matching private key~/.ssh/id_rsa
memoryInstance memory in GiB16
processorsNumber of virtual processors2
rhsm_usernameRed Hat Subscription Manager username
rhsm_passwordRed Hat Subscription Manager password

Step 2 — Understand the Terraform Configuration

Provider

main.tf pins the IBM Cloud provider at ~> 2.4.0 and the HashiCorp null provider for the remote-exec provisioner:

terraform {
  required_providers {
    ibm = {
      source  = "IBM-Cloud/ibm"
      version = "~> 2.4.0"
    }
    null = {
      source  = "hashicorp/null"
      version = "~> 3.0"
    }
  }
}

provider "ibm" {
  ibmcloud_api_key = var.ibmcloud_api_key
  region           = var.region
  zone             = var.zone
}

Public Network

pub-vlan network is created to give the instance an external IP for SSH access:

resource "ibm_pi_network" "public_net" {
  pi_cloud_instance_id = var.workspace_id
  pi_network_name      = "cassandra-public-net"
  pi_network_type      = "pub-vlan"
}

PowerVS Instance

The LPAR is an s1022 system (Power10) using a shared processor, the stock RHEL 10 BYOL image, and attached to the public network:

resource "ibm_pi_instance" "cassandra_node" {
  pi_cloud_instance_id = var.workspace_id
  pi_memory            = var.memory
  pi_processors        = var.processors
  pi_instance_name     = "cassandra-rhel10"
  pi_proc_type         = "shared"
  pi_sys_type          = "s1022"
  pi_image_id          = "585ca713-f303-45f0-a836-e9dd4f4c8f3b"
  pi_key_pair_name     = var.ssh_key_name
  pi_health_status     = "WARNING"

  pi_network {
    network_id = ibm_pi_network.public_net.network_id
  }

  depends_on = [ibm_pi_network.public_net]
}

pi_health_status = "WARNING" lets Terraform proceed even while the instance is still booting, which is normal — the null_resource provisioner handles the wait via SSH.

Remote Provisioner

After the instance is up, Terraform templates init.sh with your RHSM credentials and copies it over SSH, then executes it:

resource "null_resource" "cassandra_init" {
  triggers = {
    instance_id = ibm_pi_instance.cassandra_node.instance_id
    script_hash = filemd5("${path.module}/init.sh")
  }

  connection {
    type    = "ssh"
    user    = "root"
    host    = ibm_pi_instance.cassandra_node.pi_network[0].external_ip
    agent   = true
    timeout = "10m"
  }

  provisioner "file" {
    content = templatefile("${path.module}/init.sh", {
      rhsm_username = var.rhsm_username
      rhsm_password = var.rhsm_password
    })
    destination = "/root/init.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /root/init.sh",
      "bash /root/init.sh",
    ]
  }
}

The script_hash trigger means Terraform will re-run the provisioner if you change init.sh, which is handy during development.


Step 3 — The Bootstrap Script (init.sh)

init.sh is a Bash script executed on the instance as root. Here’s what it does, step by step.

RHSM Registration

subscription-manager register \
  --username="${rhsm_username}" \
  --password="${rhsm_password}" \
  --force

subscription-manager repos \
  --enable=rhel-10-for-ppc64le-baseos-rpms \
  --enable=rhel-10-for-ppc64le-appstream-rpms

The --force flag handles re-registration gracefully if the instance was previously registered. The correct repo slugs for RHEL 10 on Power LE (ppc64le) are rhel-10-for-ppc64le-*.

OS Update and Tooling

dnf update -y
dnf install -y wget tar gzip jq curl

IBM Semeru Java 17 (ppc64le)

Cassandra requires Java, and IBM Semeru Runtime (OpenJ9) is the recommended JVM for Power. The script fetches the latest release URL dynamically from the GitHub API:

SEMERU_LATEST_URL=$(curl -s https://api.github.com/repos/ibmruntimes/semeru17-binaries/releases/latest \
  | jq -r '.assets[] | select(.name | contains("jdk_ppc64le_linux")) | select(.name | endswith(".tar.gz")) | .browser_download_url')

wget -qO /tmp/semeru.tar.gz "$SEMERU_LATEST_URL"
mkdir -p /opt/ibm/semeru
tar -xzf /tmp/semeru.tar.gz -C /opt/ibm/semeru --strip-components=1
rm -f /tmp/semeru.tar.gz

alternatives --install /usr/bin/java java /opt/ibm/semeru/bin/java 1
echo "export JAVA_HOME=/opt/ibm/semeru" > /etc/profile.d/semeru.sh
export JAVA_HOME=/opt/ibm/semeru
export PATH="$JAVA_HOME/bin:$PATH"

Installing under /opt/ibm/semeru keeps it clean and separate from the system Java. The alternatives registration makes java available system-wide.

Apache Cassandra 5.0

The official Apache Cassandra RPM repo is added and the package installed. --skip-broken is used because Cassandra’s bundled JVM dependency is skipped in favour of Semeru:

rpm --import https://downloads.apache.org/cassandra/KEYS

cat <<'EOF' > /etc/yum.repos.d/cassandra.repo
[cassandra]
name=Apache Cassandra 
baseurl=https://redhat.cassandra.apache.org/50x/ 
gpgcheck=0 
repo_gpgcheck=0 
gpgkey=https://downloads.apache.org/cassandra/KEYS 
EOF

 dnf clean all && dnf makecache dnf install -y cassandra --skip-broken

Systemd Service Unit

Rather than auto-starting Cassandra, the script drops a custom cassandra-manager.service unit that explicitly sets JAVA_HOME to the Semeru installation. This prevents Cassandra from picking up an incorrect JVM:

cat <<'EOF' > /etc/systemd/system/cassandra-manager.service
[Unit]
Description=Apache Cassandra Lifecycle Manager
After=network-online.target
Wants=network-online.target

[Service]
Type=forking
User=cassandra
Group=cassandra
Environment="JAVA_HOME=/opt/ibm/semeru"
Environment="PATH=/opt/ibm/semeru/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
ExecStart=/usr/sbin/cassandra -p /var/run/cassandra/cassandra.pid
PIDFile=/var/run/cassandra/cassandra.pid
RuntimeDirectory=cassandra
TimeoutStartSec=120
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload

The service is not enabled or auto-started — you decide when Cassandra runs.


Step 4 — Deploy

Initialize

terraform init

This downloads the IBM Cloud and null providers.

Plan

terraform plan -var-file="terraform.tfvars"

Review the output — you should see three resources being created: ibm_pi_network.public_netibm_pi_instance.cassandra_node, and null_resource.cassandra_init.

Apply

terraform apply -var-file="terraform.tfvars"

Total deployment time is approximately 10–15 minutes:

  • Network creation: ~1 minute
  • Instance boot: ~5 minutes
  • init.sh execution (OS update + Java + Cassandra): ~5–10 minutes

Step 5 — Verify

Once terraform apply completes, grab the IP from the outputs:

terraform output instance_external_ip_address

SSH in as root:

ssh root@<external_ip>

Confirm IBM Semeru is the active JVM:

java -version
# Expected output:
# openjdk version "17.x.x" ...
# IBM Semeru Runtime Open Edition ...
# Eclipse OpenJ9 ...

Start Cassandra:

systemctl start cassandra-manager
systemctl status cassandra-manager

Once Cassandra is up (give it 30–60 seconds), verify the node is healthy:

nodetool status

You should see a single node listed as UN (Up, Normal):

Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address    Load       Tokens  Owns   Host ID   Rack
UN  127.0.0.1  ...        16      100.0% <uuid>    rack1

To persist Cassandra across reboots:

systemctl enable cassandra-manager

Outputs Reference

OutputDescription
instance_idPowerVS LPAR instance ID
instance_ip_addressPrivate IP on the attached network
instance_external_ip_addressPublic/external IP (used for SSH)

Security Considerations

  • Credentials in terraform.tfvars — never commit this file. It is already listed in .gitignore.
  • Sensitive variables — ibmcloud_api_keyrhsm_username, and rhsm_password are marked sensitive = true in variables.tf, so Terraform will not print them in plan or apply output.
  • SSH agent forwarding — the provisioner connection uses agent = true, which relies on your local SSH agent. Make sure the private key matching ssh_key_name is loaded (ssh-add ~/.ssh/id_rsa).
  • Firewall — the pub-vlan network exposes the instance publicly. Consider restricting inbound access to port 22 (SSH) and Cassandra’s native transport port 9042 to trusted CIDRs via IBM Cloud security groups.

Cleaning Up

To destroy all resources created by this deployment:

terraform destroy -var-file="terraform.tfvars"

This will remove the PowerVS instance and the public network. The RHEL 10 stock image is not deleted (it is a shared catalog image, not a user-imported resource).


Summary

This walkthrough showed how to:

  1. Use Terraform with the IBM-Cloud/ibm provider to stand up a PowerVS LPAR on RHEL 10
  2. Bootstrap the instance via SSH using a templated shell script
  3. Install IBM Semeru Runtime (OpenJ9) for ppc64le — the right JVM for Power
  4. Add the official Apache Cassandra 5.0 RPM repo and install Cassandra
  5. Register a custom systemd service that pins JAVA_HOME to the Semeru installation

The full source is available in this repository — clone it, fill in your terraform.tfvars, and you’ll have Cassandra running on PowerVS in under 15 minutes.

Note the source is attached

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *