Prerequisites:
Before we start, make sure you have the following:
- RHEL/CentOS 8+ machine with network access to the ESXi host.
- ESXi credentials with permissions to deploy VMs.
- OVA template for the VM.
- sshpass and ovftool installed on your RHEL system.
sshpass allows automated SSH login using a password (necessary for our script).
Enable EPEL repository (if not already installed)
# sudo dnf install -y epel-release
Install sshpass
# sudo dnf install -y sshpass
Verify installation
# sshpass -V
Step 2: Installing ovftool on RHEL
ovftool is VMware's OVF deployment utility.
Download VMware-ovftool for Linux from VMware’s official site "https://developer.broadcom.com/tools/open-virtualization-format-ovf-tool/latest"
Extract and install:
# dnf install libnsl libxcrypt-compat -y
Step 4: Prepare Your CSV Input
Your VM deployment details are stored in a CSV file. Example:
#vm_name,hostname,primary_ip,primary_gateway,primary_dns,secondary_ip,secondary_gateway,secondary_dns
Save this file as vm-deploy.csv.
Step 5: Deploying VMs with the Script
We use a Bash script to read the CSV file and deploy each VM using ovftool. The script also configures network settings and hostnames on the VM using sshpass for password-based SSH.
Key features:
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Step 6: Running the Deployment
[root@inddcpppz01 scripts]# chmod +x deploy-configure-vms.sh
[root@inddcpppz01 scripts]# ./deploy-configure-vms.sh
The script will:
After deployment, logs like deploy_INDRXLTST11.log are generated:
Step 8: Summary
By combining ovftool, sshpass, and a CSV-driven Bash script, you can:
# unzip VMware-ovftool-5.0.0-24781994-lin.x86_64.zip -d /opt/
# chmod +x /opt/ovftool/ovftool /opt/ovftool/ovftool.bin
# ln -s /opt/ovftool/ovftool /usr/local/bin/ovftool
Verify installation:
# ovftool --version
Step 2: Passwordless Authentication RHEL server to ESXI8 Server
# ovftool --version
Step 2: Passwordless Authentication RHEL server to ESXI8 Server
Generate the Key on RHEL
Log in to your RHEL server and generate the 4096-bit RSA key.
# ssh-keygen -t rsa -b 4096
Press Enter to save to the default location (/root/.ssh/id_rsa).
Enter a passphrase for extra security.
Display and copy the key:
# cat ~/.ssh/id_rsa.pub
Highlight and copy the entire string starting with ssh-rsa.
To fix Error :Unable to negotiate with 192.168.10.103 port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss
To fix Error :Unable to negotiate with 192.168.10.103 port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss
# vi ~/.ssh/config
Host *
HostKeyAlgorithms +ssh-rsa
PubkeyAcceptedAlgorithms +ssh-rsa
To Fix Error: ssh_dispatch_run_fatal: Connection to 192.168.10.103 port 22: error in libcrypto
system that uses crypto-policies, you can set it to LEGACY to restore compatibility with older hardware:
# update-crypto-policies --set LEGACY
Prepare the ESXi 8 Server
Enable SSH: Log into the ESXi Host Client (Web UI) -> Manage -> Services -> Start TSM-SSH.
Login via SSH using your root password.
Install the Key on ESXi
In ESXi 8, the default location for the root user's authorized keys is /etc/ssh/keys-root/authorized_keys.
Open the file:
# vi /etc/ssh/keys-root/authorized_keys
Paste the key: Press i for Insert mode, paste your key, then press Esc and type :wq! to save and exit.
Set Strict Permissions: ESXi will ignore the key if the permissions are too open.
# chmod 600 /etc/ssh/keys-root/authorized_keys
Test the Connection from RHEL
Go back to your RHEL terminal and attempt to connect.
# ssh root@<ESXi_IP_Address>
Note: If it still asks for a password, check the ESXi /etc/ssh/sshd_config file to ensure PubkeyAuthentication yes and AuthorizedKeysFile points to /etc/ssh/keys-root/authorized_keys.
Step 4: Prepare Your CSV Input
Your VM deployment details are stored in a CSV file. Example:
#vm_name,hostname,primary_ip,primary_gateway,primary_dns,secondary_ip,secondary_gateway,secondary_dns
#vm_name,hostname,primary_ip,primary_gateway,primary_dns,secondary_ip,secondary_gateway,secondary_dns
INDRXLTST11,indrxltst11.ppc.com,192.168.10.50,192.168.10.1,192.168.10.100,192.168.20.50,192.168.20.1,192.168.20.100
INDRXLTST12,indrxltst12.ppc.com,192.168.10.51,192.168.10.1,192.168.10.100,192.168.20.51,192.168.20.1,192.168.20.100
INDRXLTST13,indrxltst13.ppc.com,192.168.10.52,192.168.10.1,192.168.10.100,192.168.20.52,192.168.20.1,192.168.20.100
Step 5: Deploying VMs with the Script
We use a Bash script to read the CSV file and deploy each VM using ovftool. The script also configures network settings and hostnames on the VM using sshpass for password-based SSH.
Key features:
- Deploy VMs from an OVA template.
- Set hostname, primary, and secondary NIC IPs.
- Reboot the VM and wait for SSH availability.
- Run post-deployment checks (hostname, IP, disk, and network).
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#!/bin/bash
# Author: adminCtrlX
set -e
ESXI_IP="192.168.10.101"
ESXI_USER="root"
ESXI_PASS="Welcome@123"
OVA_PATH="/ppcdata/servers-template/redhat10-template.ova"
DATASTORE="STG-DC01-MNG"
PRIMARY_NETWORK="Private Network"
SECONDARY_NETWORK="Local Network"
VM_ROOT_PASS="root123"
# Max concurrent deployments
MAX_PARALLEL=3
# Check if sshpass is installed
if ! command -v sshpass &>/dev/null; then
echo "sshpass is required but not installed. Install it first."
exit 1
fi
# =========================================
# Read CSV file
# =========================================
read -p "Enter CSV file path: " CSV_FILE
[[ ! -f "$CSV_FILE" ]] && { echo "CSV file not found!"; exit 1; }
# Function to deploy/configure a single VM
deploy_vm() {
local VM_NAME="$1"
local HOSTNAME="$2"
local P_IP="$3"
local P_GW="$4"
local P_DNS="$5"
local S_IP="$6"
local S_GW="$7"
local S_DNS="$8"
local LOGFILE="deploy_${VM_NAME}.log"
echo "==== Deploying $VM_NAME ====" | tee -a "$LOGFILE"
# Deploy OVA if VM doesn't exist
VMID=$(sshpass -p "$ESXI_PASS" ssh -o StrictHostKeyChecking=no ${ESXI_USER}@${ESXI_IP} \
"vim-cmd vmsvc/getallvms" | awk -v vm="$VM_NAME" '$0 ~ vm {print $1}')
if [[ -z "$VMID" ]]; then
echo "Deploying OVA template..." | tee -a "$LOGFILE"
ovftool --acceptAllEulas --skipManifestCheck \
--name="$VM_NAME" \
--datastore="$DATASTORE" \
--network="$PRIMARY_NETWORK" \
"$OVA_PATH" \
"vi://${ESXI_USER}:${ESXI_PASS}@${ESXI_IP}/" &>> "$LOGFILE"
sleep 10
VMID=$(sshpass -p "$ESXI_PASS" ssh -o StrictHostKeyChecking=no ${ESXI_USER}@${ESXI_IP} \
"vim-cmd vmsvc/getallvms" | awk -v vm="$VM_NAME" '$0 ~ vm {print $1}')
fi
[[ -z "$VMID" ]] && { echo "ERROR: VM deployment failed for $VM_NAME" | tee -a "$LOGFILE"; return; }
echo "VMID: $VMID" | tee -a "$LOGFILE"
# Power on VM
STATE=$(sshpass -p "$ESXI_PASS" ssh -o StrictHostKeyChecking=no ${ESXI_USER}@${ESXI_IP} \
"vim-cmd vmsvc/power.getstate $VMID" | tail -n1)
if [[ "$STATE" != "Powered on" ]]; then
echo "Powering on VM..." | tee -a "$LOGFILE"
sshpass -p "$ESXI_PASS" ssh -o StrictHostKeyChecking=no ${ESXI_USER}@${ESXI_IP} \
"vim-cmd vmsvc/power.on $VMID" &>> "$LOGFILE"
else
echo "VM already powered on." | tee -a "$LOGFILE"
fi
# Wait for initial VMware Tools IP (optional logging)
VM_IP=""
for i in {1..30}; do
VM_IP=$(sshpass -p "$ESXI_PASS" ssh -o StrictHostKeyChecking=no ${ESXI_USER}@${ESXI_IP} \
"vim-cmd vmsvc/get.guest $VMID" | awk -F\" '/ipAddress/ {print $2; exit}')
[[ -n "$VM_IP" && "$VM_IP" != "0.0.0.0" ]] && break
sleep 5
done
[[ -n "$VM_IP" ]] && echo "VM initially reports IP: $VM_IP" | tee -a "$LOGFILE"
# Configure hostname & network
sshpass -p "$VM_ROOT_PASS" ssh -o StrictHostKeyChecking=no root@$VM_IP <<EOF &>> "$LOGFILE"
set -e
HN_SHORT=\$(echo "$HOSTNAME" | cut -d. -f1)
hostnamectl set-hostname "\$HN_SHORT"
cat > /etc/hosts <<EOL
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
$P_IP $HOSTNAME \$HN_SHORT
EOL
# Primary NIC (ens192)
CON1=\$(nmcli -t -f NAME,DEVICE con show | grep ens192 | cut -d: -f1)
nmcli con mod "\$CON1" ipv4.addresses $P_IP/24 ipv4.gateway $P_GW ipv4.dns $P_DNS ipv4.method manual
# Secondary NIC (ens224)
CON2=\$(nmcli -t -f NAME,DEVICE con show | grep ens224 | cut -d: -f1)
nmcli con mod "\$CON2" ipv4.addresses $S_IP/24 ipv4.gateway $S_GW ipv4.dns $S_DNS ipv4.method manual
reboot
EOF
echo "Waiting for VM $VM_NAME to come back..." | tee -a "$LOGFILE"
# Wait for SSH on primary IP
for i in {1..60}; do
if nc -z -w5 "$P_IP" 22 &>/dev/null; then
echo "SSH available on $P_IP" | tee -a "$LOGFILE"
break
fi
echo "SSH not ready yet... retry $i/60" | tee -a "$LOGFILE"
sleep 10
done
if ! nc -z -w5 "$P_IP" 22 &>/dev/null; then
echo "ERROR: SSH never became available on $P_IP" | tee -a "$LOGFILE"
return
fi
# Run post-checks
sshpass -p "$VM_ROOT_PASS" ssh -o StrictHostKeyChecking=no root@$P_IP <<'POSTEOF' &>> "$LOGFILE"
set -e
echo "====== Post-deployment checks ======"
echo "Hostname: $(hostname)"
echo "IP Addresses:"; ip addr show
echo "Disk Usage:"; df -h
echo "Network connectivity test:"; ping -c 2 $P_IP || echo "Ping failed"
echo "Services status (example: sshd):"; systemctl status sshd | head -20
echo "Post-deployment checks completed successfully."
POSTEOF
echo "==== VM $VM_NAME deployment complete! ====" | tee -a "$LOGFILE"
}
# =========================================
# Read CSV and deploy VMs in parallel
# =========================================
PIDS=()
while IFS=, read -r VM_NAME HOSTNAME P_IP P_GW P_DNS S_IP S_GW S_DNS; do
[[ -z "$VM_NAME" || "$VM_NAME" =~ ^# ]] && continue
deploy_vm "$VM_NAME" "$HOSTNAME" "$P_IP" "$P_GW" "$P_DNS" "$S_IP" "$S_GW" "$S_DNS" &
PIDS+=($!)
# Limit parallel jobs
while [[ $(jobs -r -p | wc -l) -ge $MAX_PARALLEL ]]; do
sleep 5
done
done < "$CSV_FILE"
# Wait for all background jobs to finish
wait
echo "All VMs processed successfully ..............................................."
Step 6: Running the Deployment
[root@inddcpppz01 scripts]# chmod +x deploy-configure-vms.sh
[root@inddcpppz01 scripts]# ./deploy-configure-vms.sh
[root@inddcpppz01 scripts]# ./esxi-deploy-configure.sh
Enter CSV file path: /root/scripts/deploy-configure-vms.csv
==== Deploying INDRXLTST11 ====
==== Deploying INDRXLTST12 ====
==== Deploying INDRXLTST13 ====
Deploying OVA template...
Deploying OVA template...
Deploying OVA template...
VMID: 4
VMID: 5
VMID: 6
Powering on VM...
Powering on VM...
Powering on VM...
VM initially reports IP: 192.168.10.38
VM initially reports IP: 192.168.10.48
VM initially reports IP: 192.168.10.47
Waiting for VM INDRXLTST13 to come back...
Waiting for VM INDRXLTST12 to come back...
Waiting for VM INDRXLTST11 to come back...
SSH not ready yet... retry 1/60
SSH not ready yet... retry 1/60
SSH not ready yet... retry 1/60
SSH not ready yet... retry 2/60
SSH not ready yet... retry 2/60
SSH not ready yet... retry 2/60
SSH available on 192.168.10.51
SSH available on 192.168.10.50
SSH available on 192.168.10.52
==== VM INDRXLTST12 deployment complete! ====
==== VM INDRXLTST13 deployment complete! ====
==== VM INDRXLTST11 deployment complete! ====
All VMs processed successfully ...............................................
[root@inddcpppz01 scripts]#
- Check if VM exists; if not, deploy using OVA.
- Power on the VM.
- Configure hostnames and network interfaces.
- Wait for SSH to become available.
- Run post-deployment checks.
After deployment, logs like deploy_INDRXLTST11.log are generated:
[root@inddcpppz01 scripts]# cat deploy_INDRXLTST11.log
==== Deploying INDRXLTST11 ====
Deploying OVA template...
Opening OVA source: /ppcdata/servers-template/redhat10-template.ova
Opening VI target: vi://root@192.168.10.101:443/
Deploying to VI: vi://root@192.168.10.101:443/
Transfer Completed
The manifest does not validate
Warning:
- The manifest is present but user flag causing to skip it
Completed successfully
VMID: 53
Powering on VM...
Powering on VM:
VM initially reports IP: 192.168.10.39
Pseudo-terminal will not be allocated because stdin is not a terminal.
*********************************************************
* !!!! WELCOME TO PPC.COM TEST LAB SERVER'S !!!! *
* This server is meant for testing Linux commands and *
* Tools. If you are not associated with ppc.com and *
* Not authorized. Please dis-connect immediately. *
*********************************************************
Waiting for VM INDRXLTST11 to come back...
SSH not ready yet... retry 1/60
SSH not ready yet... retry 2/60
SSH available on 192.168.10.50
Pseudo-terminal will not be allocated because stdin is not a terminal.
*********************************************************
* !!!! WELCOME TO PPC.COM TEST LAB SERVER'S !!!! *
* This server is meant for testing Linux commands and *
* Tools. If you are not associated with ppc.com and *
* Not authorized. Please dis-connect immediately. *
*********************************************************
====== Post-deployment checks ======
Hostname: indrxltst11
IP Addresses:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:ca:5b:29 brd ff:ff:ff:ff:ff:ff
altname enp11s0
altname enx000c29ca5b29
inet 192.168.10.50/24 brd 192.168.10.255 scope global noprefixroute ens192
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:feca:5b29/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:ca:5b:33 brd ff:ff:ff:ff:ff:ff
altname enp19s0
altname enx000c29ca5b33
inet 192.168.20.50/24 brd 192.168.20.255 scope global noprefixroute ens224
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:feca:5b33/64 scope link noprefixroute
valid_lft forever preferred_lft forever
Disk Usage:
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/rootvg-root 17G 2.7G 15G 16% /
devtmpfs 4.0M 0 4.0M 0% /dev
tmpfs 478M 0 478M 0% /dev/shm
tmpfs 192M 3.8M 188M 2% /run
tmpfs 1.0M 0 1.0M 0% /run/credentials/systemd-journald.service
/dev/sda2 960M 279M 682M 29% /boot
tmpfs 1.0M 0 1.0M 0% /run/credentials/getty@tty1.service
tmpfs 96M 4.0K 96M 1% /run/user/0
Network connectivity test:
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=128 time=51.7 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=128 time=51.8 ms
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 51.660/51.710/51.761/0.050 ms
Services status (example: sshd):
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: enabled)
Active: active (running) since Fri 2026-02-06 21:13:27 IST; 5s ago
Invocation: 1c42fed46327449098fb26c3255a7190
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 1003 (sshd)
Tasks: 1 (limit: 5893)
Memory: 7.6M (peak: 24.1M)
CPU: 127ms
CGroup: /system.slice/sshd.service
└─1003 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
Feb 06 21:13:27 indrxltst11 systemd[1]: Starting sshd.service - OpenSSH server daemon...
Feb 06 21:13:27 indrxltst11 (sshd)[1003]: sshd.service: Referenced but unset environment variable evaluates to an empty string: OPTIONS
Feb 06 21:13:27 indrxltst11 sshd[1003]: Server listening on 0.0.0.0 port 22.
Feb 06 21:13:27 indrxltst11 systemd[1]: Started sshd.service - OpenSSH server daemon.
Feb 06 21:13:27 indrxltst11 sshd[1003]: Server listening on :: port 22.
Feb 06 21:13:30 indrxltst11 sshd-session[1330]: Accepted password for root from 192.168.10.104 port 40404 ssh2
Feb 06 21:13:31 indrxltst11 sshd-session[1330]: pam_unix(sshd:session): session opened for user root(uid=0) by root(uid=0)
Post-deployment checks completed successfully.
==== VM INDRXLTST11 deployment complete! ====
[root@inddcpppz01 scripts]#
Step 8: Summary
By combining ovftool, sshpass, and a CSV-driven Bash script, you can:
- Rapidly deploy multiple VMs in parallel.
- Automatically configure hostnames and network settings.
- Perform initial health checks post-deployment.
- Maintain logs for auditing and troubleshooting.
No comments:
Post a Comment