This guide covers deploying OpenShift two-node clusters using the kcli virtualization management tool. This approach provides a way of testing UPI deployments which is not available through the dev-scripts method.
The kcli deployment method automates OpenShift two-node cluster creation supporting both fencing and arbiter topologies. Fencing topology is the default.
This section is identical to the main README. Please refer to section 1 of the main README for complete machine requirements including:
- Client machine requirements (Ansible)
- Remote machine requirements (RHEL 9, 64GB RAM, etc.)
- Optional AWS hypervisor setup
The same prerequisites apply whether using dev-scripts or kcli deployment methods. If you're using a baremetal server not provisioned through the aws-hypervisor directory, please see the appropiate README to know how to run the init-host.yml playbook.
Tip: To skip the -i inventory.ini argument in all ansible commands, copy the inventory file to Ansible's default location (/etc/ansible/hosts on Linux, may vary on other operating systems).
Install required Ansible collections on the client machine (where you run ansible-playbook):
ansible-galaxy collection install -r collections/requirements.ymlThis installs:
community.libvirt: For libvirt virtualization managementkubernetes.core: For Kubernetes resource managementcontainers.podman: For container operationsansible.posix: Some systems might need this for certain system-level operations
The kcli-install role automatically handles target host setup including:
- Complete libvirt virtualization stack installation
- kcli package installation from COPR repository
- Default kcli configuration for local KVM hypervisor
- User permissions for libvirt group access
- Pull Secret: Download from https://cloud.redhat.com/openshift/install/pull-secret
- For CI builds: Ensure pull secret includes
registry.ci.openshift.orgaccess - Standard pull secrets from console.redhat.com may not include CI registry access
- For CI builds: Ensure pull secret includes
- SSH Key: For cluster access (default:
~/.ssh/id_ed25519.pub)
Place your pull secret in the role files directory:
# Navigate to the kcli-install files directory
cd roles/kcli/kcli-install/files/
# Create pull secret file (paste your pull secret content)
cat > pull-secret.json << EOF
{"auths":{"your-pull-secret-content-here"}}
EOFThe deployment will automatically copy the pull secret from the files directory to the remote host during deployment.
The deployment automatically reads your SSH public key from ~/.ssh/id_ed25519.pub on your local machine (ansible controller) and:
- Copies it to the remote host for kcli cluster deployment
- Installs it as an authorized key for SSH access
If you don't have an SSH key on your local machine, generate one:
# Generate SSH key pair on your local machine
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519Note: The SSH key must exist on the machine where you run ansible, not on the remote host.
The kcli deployment supports multiple configuration approaches with clear variable precedence. It is recommended that you create
You can configure the deployment using any combination of these methods (in precedence order).
- Command line variables (highest precedence)
- Playbook vars section
- vars/kcli.yml (user configuration file)
- Role defaults (lowest precedence) (
vars/kcli.yml.template)
For simple overrides, the command line is recommended. For setting your preferred permanent config, copy kcli.yml.template to kcli.yml and update the values to your preference. This file is not tracked by Git and will persist between TNT updates.
You can find more information on the official ansible documentation https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html:
Override any variable at runtime:
ansible-playbook kcli-install.yml \
-e "test_cluster_name=emergency-cluster" \
-e "vm_memory=49152"| Variable | Description | Example |
|---|---|---|
test_cluster_name |
Cluster identifier | "edge-cluster-01" |
topology |
Cluster type | "fencing" (default) |
domain |
Base domain | "edge.company.com" |
| Variable | Default | Description |
|---|---|---|
vm_memory |
32768 |
Memory per node (MB) |
vm_numcpus |
16 |
CPU cores per node |
vm_disk_size |
120 |
Disk size per node (GB) |
ocp_version |
"candidate" |
OpenShift version channel |
ocp_tag |
"4.20" |
Specific version tag |
network_name |
"default" |
kcli network name |
bmc_user |
"admin" |
BMC username (fencing) |
bmc_password |
"admin123" |
BMC password (fencing) |
force_cleanup |
false |
Auto-remove existing cluster before deploy |
Fencing Topology:
topology: "fencing"
platform: "baremetal"
bmc_user: "admin"
bmc_password: "admin123"
bmc_driver: "redfish"
ksushy_port: 9000Arbiter Topology (TNA):
topology: "arbiter"
platform: "none"
arbiter_memory: 8192
arbiter_numcpus: 2
arbiter_disk_size: 30The deployment uses a fencing topology by default and runs non-interactively for consistent automation:
# Install required Ansible collections
ansible-galaxy collection install -r collections/requirements.yml
# Update inventory with your target host (if not using the automatic inventory management)
cp inventory.ini.sample inventory.ini
# Edit inventory.ini with your host details
# Deploy fencing cluster (default)
ansible-playbook kcli-install.yml -i inventory.ini
# Deploy with custom cluster name
ansible-playbook kcli-install.yml -i inventory.ini \
-e "test_cluster_name=prod-edge-cluster"
# Deploy arbiter cluster
ansible-playbook kcli-install.yml -i inventory.ini \
-e "topology=arbiter" \
-e "interactive_mode=false"
# Deploy arbiter cluster with custom configuration
ansible-playbook kcli-install.yml -i inventory.ini \
-e "topology=arbiter" \
-e "platform=none" \
-e "arbiter_memory=16384" \
-e "interactive_mode=false"To redeploy a cluster, check the redeployment section
Since the cluster runs on a remote host, you might need proxy configuration to access it from your local machine. After cluster installation, proxy setup will run to provide the same access as the dev-scripts (IPI) installation method.
For direct cluster access from within the hypervisor host (not from your local machine), authentication files are automatically copied to a standard location:
~/auth/kubeconfig # Cluster admin kubeconfig
~/auth/kubeadmin-password # Default admin password
From the hypervisor host only, you can access the cluster directly:
# SSH into the hypervisor first
ssh your-hypervisor-host
# Option 1: Use default kubeconfig location (recommended)
oc get nodes
# Option 2: Explicitly set KUBECONFIG environment variable
export KUBECONFIG=~/auth/kubeconfig
oc get nodes
# Get admin password for web console
cat ~/auth/kubeadmin-passwordThe deployment automatically creates a symlink from ~/.kube/config to ~/auth/kubeconfig, so oc commands work without setting the KUBECONFIG environment variable.
Note: This direct access only works from within the hypervisor. For access from your local machine, use the proxy setup described above.
After successful cluster deployment, the inventory file is automatically updated to include the cluster VMs. This allows you to run Ansible playbooks directly on the cluster nodes from your local machine.
The deployment automatically discovers running cluster VMs and adds them to the inventory with ProxyJump configuration through the hypervisor. Your inventory.ini will be updated to include:
[metal_machine]
ec2-user@44.196.182.72
[cluster_vms]
tnt-cluster-ctlplane-0 ansible_host=192.168.122.10
tnt-cluster-ctlplane-1 ansible_host=192.168.122.11
[cluster_vms:vars]
ansible_ssh_common_args="-o ProxyJump=ec2-user@44.196.182.72 ..."
ansible_user=coreYou can now run Ansible commands on cluster VMs from your local machine:
# Ping all cluster VMs
ansible cluster_vms -m ping -i inventory.ini
# Run ad-hoc commands
ansible cluster_vms -m shell -a "uptime" -i inventory.ini
# Run playbooks targeting cluster VMs
ansible-playbook my-cluster-playbook.yml -i inventory.iniThe VMs are automatically accessible via SSH ProxyJump through the hypervisor, so you don't need direct network access to the cluster VMs.
For deploying unreleased or development OpenShift builds, kcli supports CI builds from registry.ci.openshift.org:
# CI build configuration
ocp_version: "ci"
ocp_tag: "4.20" # Target versionRequirements for CI builds:
- Enhanced Pull Secret: Your pull secret must include
registry.ci.openshift.orgaccess - No CI Token Required: Unlike dev-scripts, kcli does not use
CI_TOKENenvironment variables
Getting CI Registry Access:
- Visit https://console-openshift-console.apps.ci.l2s4.p1.openshiftapps.com
- Log in with your Red Hat account
- Click your name → "Copy login command" → "Display Token"
- Use the provided registry credentials to update your pull secret
Example CI deployment:
# Deploy latest CI build
ansible-playbook kcli-install.yml -i inventory.ini \
-e "ocp_version=ci" \
-e "ocp_tag=4.20" \
-e "interactive_mode=false"Verify CI registry access:
# Check if your pull secret includes CI registry
jq '.auths | has("registry.ci.openshift.org")' < roles/kcli/kcli-install/files/pull-secret.jsonAfter a successful 4.19 kcli deployment with fencing topology, STONITH fencing needs to be configured to enable automatic node recovery. If you are using the kcli-install playbook, this will be done for you automatically via kcli-redfish.yml*. If you're doing it some other way, you can use the kcli-redfish,yml playbook manually.
The existing redfish.yml playbook will not work with kcli deployments because it expects BMH resources that don't exist in virtualized environments.
The specialized kcli-redfish.yml playbook is designed for kcli deployments. All configuration is automatically detected - no manual variables required:
# Configure fencing for kcli-deployed cluster (fully automatic)
ansible-playbook kcli-redfish.yml -i inventory.iniThe kcli-redfish playbook automatically:
- Detects cluster name from running kcli clusters or kcli-install defaults
- Uses hypervisor IP from ansible inventory host
- Pulls BMC credentials from kcli-install role defaults
- Discovers cluster nodes from the OpenShift API
- Calculates BMC endpoints using the ksushy simulator configuration
- Configures PCS stonith resources on each node
- Enables stonith globally in the cluster
The playbook uses reasonable defaults that work for typical kcli deployments:
| Variable | Default Value | Description |
|---|---|---|
test_cluster_name |
tnt-cluster |
From kcli-install defaults |
ksushy_ip |
192.168.122.1 |
Standard libvirt network gateway |
bmc_user |
admin |
From kcli-install defaults |
bmc_password |
admin123 |
From kcli-install defaults |
ksushy_port |
9000 |
From kcli-install defaults |
These defaults work for standard kcli deployments where VMs use the default libvirt network (192.168.122.x/24).
Do not use the redfish.yml playbook with kcli deployments. It will fail because:
# This will fail for kcli deployments
ansible-playbook redfish.yml # Expects BMH resources that don't exist
# Use this instead for kcli deployments
ansible-playbook kcli-redfish.yml # Uses defaults optimized for kclikcli installation issues:
# The role automatically installs kcli, but you can verify:
ssh your-host "which kcli && kcli version"
# Check libvirt connectivity
ssh your-host "virsh list --all"Pull secret issues:
# Verify pull secret format
jq . < roles/kcli/kcli-install/files/pull-secret.json
# For CI builds, check registry access
jq '.auths | has("registry.ci.openshift.org")' < roles/kcli/kcli-install/files/pull-secret.jsonResource constraints:
# Check available resources on target host
ssh your-host "free -h && df -h"Deployment failures:
# Check kcli logs
ssh {your-host} "kcli list vm"
ssh {your-host} "journalctl -u libvirtd"Note: Remember to source proxy.env before any oc commands if you're using the integrated proxy pod.
Network connectivity:
# Check that VMs can reach ksushy on the default IP (192.168.122.1)
oc debug node/$(oc get nodes --no-headers -o custom-columns=NAME:.metadata.name | head -1) -- chroot /host curl -s http://192.168.122.1:8000/redfish/v1/
# Check what gateway IP the VMs actually use
oc debug node/$(oc get nodes --no-headers -o custom-columns=NAME:.metadata.name | head -1) -- chroot /host ip route show defaultCluster fencing diagnostics:
# Check stonith resources in cluster
oc debug node/$(oc get nodes --no-headers -o custom-columns=NAME:.metadata.name | head -1) -- chroot /host pcs stonith status
# Test fencing manually (replace node name and cluster details)
oc debug node/your-node -- chroot /host pcs stonith fence your-node_redfishNote: If your kcli deployment uses a non-standard network, override the ksushy_ip parameter to match your libvirt gateway IP.
Check the status of an ongoing kcli installation using kcli's internal tracking mechanisms. Run this from inside the host where it is being deployed:
# List all clusters managed by kcli
kcli list cluster
# List VMs associated with your cluster
kcli list vm | grep {cluster-name}
# Check cluster directory exists and contents
ls -la ~/.kcli/clusters/{cluster-name}/
# Monitor the OpenShift installation log in real-time
tail -f ~/.kcli/clusters/{cluster-name}/.openshift_install.logKey status indicators:
- Deployment started:
~/.kcli/clusters/{cluster}/directory exists - Parameters configured:
kcli_parameters.ymlfile present - VMs running: VMs appear in
kcli list vmoutput - Installation progress: Activity in
.openshift_install.log - Deployment complete:
auth/kubeconfigfile created
If you need to redeploy a cluster (either due to failure or configuration changes), use the force_cleanup=true parameter to automatically remove the existing cluster before deploying:
# Automatic cleanup and redeploy
ansible-playbook kcli-install.yml -i inventory.ini \
-e "force_cleanup=true"The force_cleanup=true parameter performs comprehensive cleanup before deployment:
- Cluster cleanup: Attempts
kcli delete cluster openshift <cluster-name>if the cluster exists - VM cleanup: Removes individual VMs (
{cluster-name}-ctlplane-0,{cluster-name}-ctlplane-1,{cluster-name}-arbiter) if they exist - Handles edge cases: Works even if VMs exist but aren't tracked as a kcli cluster
This eliminates the need for manual cleanup steps in most scenarios.
Note: If you change the test_cluster_name between deployments, the automatic cleanup won't find the old cluster. In this case, you may need to manually remove the old cluster: kcli delete cluster openshift {old-cluster-name}
# Advanced network configuration
network_name: "production-network"
api_ip: "192.168.100.10"
ingress_ip: "192.168.100.11"
# kcli will create/configure the network as neededFor additional advanced scenarios and troubleshooting, refer to the kcli-install role documentation.