feat: initial script with readme
This commit is contained in:
parent
46e680dcdc
commit
62c86e1170
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
state/
|
||||
images/
|
||||
ssh.conf
|
52
README.md
52
README.md
@ -1 +1,53 @@
|
||||
# VM Tool
|
||||
|
||||
This script sets up multiple temporary VMs using QEMU. They run in the background, are on the same network as one another, and can be accessed via SSH.
|
||||
|
||||
## Usage
|
||||
|
||||
Ensure QEMU and `mkisofs` are installed. On Debian-like systems:
|
||||
|
||||
```sh
|
||||
sudo apt install qemu-system genisoimage
|
||||
```
|
||||
|
||||
Download a base image:
|
||||
|
||||
```sh
|
||||
mkdir -p images
|
||||
curl -C - -Lo images/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
|
||||
```
|
||||
|
||||
Create the VMs (by default, two VMs are created):
|
||||
|
||||
```sh
|
||||
./vmtool.sh create
|
||||
```
|
||||
|
||||
*Creating VMs will destroy any previous VMs first.*
|
||||
|
||||
Once the VMs are running, access them via SSH:
|
||||
|
||||
```sh
|
||||
ssh -F ssh.conf node0
|
||||
ssh -F ssh.conf node1
|
||||
```
|
||||
|
||||
The VMs are on the same subnet, starting with ip `192.168.30.10` for `node0`, `192.168.30.11` for `node1`, and so on. They can reach one another:
|
||||
|
||||
```console
|
||||
[root@node0 ~]# ping 192.168.30.11
|
||||
PING 192.168.30.11 (192.168.30.11) 56(84) bytes of data.
|
||||
64 bytes from 192.168.30.11: icmp_seq=1 ttl=64 time=0.465 ms
|
||||
64 bytes from 192.168.30.11: icmp_seq=2 ttl=64 time=0.390 ms
|
||||
64 bytes from 192.168.30.11: icmp_seq=3 ttl=64 time=0.684 ms
|
||||
```
|
||||
|
||||
When done, you can stop and/or destroy the VMs:
|
||||
|
||||
```sh
|
||||
./vmtool.sh destroy
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Check the environment variables at the top of the script to set memory, CPU count, max disk size, number of nodes, etc.
|
||||
|
175
vmtool.sh
Executable file
175
vmtool.sh
Executable file
@ -0,0 +1,175 @@
|
||||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=SC2155
|
||||
# shellcheck disable=SC2164
|
||||
readonly script_path="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" # https://stackoverflow.com/a/4774063
|
||||
|
||||
# Location of the SSH config to be generated
|
||||
SSH_CONFIG=${SSH_CONFIG:-"$script_path/ssh.conf"}
|
||||
|
||||
NUM_NODES=${NUM_NODES:-"2"}
|
||||
MEMORY=${MEMORY:-"2048"}
|
||||
CPU=${CPU:-"2"}
|
||||
DISK_MAX_SIZE=${DISK_MAX_SIZE:-"20G"}
|
||||
|
||||
# Location of VM state information, including its persistent disk
|
||||
STATE_DIR=${STATE_DIR:-"$script_path/state"}
|
||||
|
||||
# Location of Linux cloud image
|
||||
BASE_IMAGE_FILENAME=${BASE_IMAGE_FILENAME:-"$script_path/images/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"}
|
||||
|
||||
# Architecture that QEMU will emulate
|
||||
EMU_ARCH=${EMU_ARCH:-"x86_64"}
|
||||
|
||||
# Check for prerequisites
|
||||
if ! command -v mkisofs > /dev/null 2>&1; then
|
||||
echo "mkisofs not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v qemu-system-"$EMU_ARCH" > /dev/null 2>&1; then
|
||||
echo "qemu-system-$EMU_ARCH not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
start() {
|
||||
for ((i=0;i<NUM_NODES;i++))
|
||||
do
|
||||
echo "Starting node $i"
|
||||
qemu-system-x86_64 \
|
||||
-enable-kvm \
|
||||
-cpu host \
|
||||
-m "$MEMORY" \
|
||||
-smp "$CPU" \
|
||||
-device virtio-net-pci,netdev=net$i,mac=52:54:00:00:00:"$(printf '%02x' $i)" \
|
||||
-netdev socket,id=net$i,mcast=230.0.0.1:1234 \
|
||||
-device virtio-net-pci,netdev=host$i \
|
||||
-netdev user,id=host$i,hostfwd=tcp::$((3100 + i))-:22 \
|
||||
-drive file="$STATE_DIR"/node$i.qcow2,media=disk,if=virtio \
|
||||
-drive file="$STATE_DIR"/node$i-cloudinit.iso,media=cdrom \
|
||||
-display none \
|
||||
-daemonize \
|
||||
-pidfile "$STATE_DIR"/node$i.pid
|
||||
done
|
||||
|
||||
for ((i=0;i<NUM_NODES;i++))
|
||||
do
|
||||
local attempts_remaining=10
|
||||
while ! ssh -o ConnectTimeout=2 -o BatchMode=true -F "$SSH_CONFIG" node$i exit >/dev/null 2>&1 && ((attempts_remaining > 0));
|
||||
do
|
||||
((attempts_remaining--))
|
||||
if [ $attempts_remaining == 0 ]; then
|
||||
echo "SSH service on node$i took too long to be ready"
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting for SSH service on node$i to be ready; $attempts_remaining attempts remaining"
|
||||
sleep 5
|
||||
done
|
||||
echo "SSH service on node$i is up"
|
||||
done
|
||||
|
||||
echo "To access a VM, run:"
|
||||
echo
|
||||
echo " ssh -F $SSH_CONFIG node#"
|
||||
echo
|
||||
echo "Where # is the node number you want to reach."
|
||||
}
|
||||
|
||||
stop() {
|
||||
for ((i=0;i<NUM_NODES;i++))
|
||||
do
|
||||
pkill -F "$STATE_DIR"/node$i.pid >/dev/null 2>&1
|
||||
rm -f "$STATE_DIR"/node$i.pid
|
||||
done
|
||||
}
|
||||
|
||||
destroy() {
|
||||
stop
|
||||
|
||||
rm -f "$STATE_DIR"/id_ed25519
|
||||
rm -f "$STATE_DIR"/id_ed25519.pub
|
||||
rm -f "$SSH_CONFIG"
|
||||
|
||||
for ((i=0;i<NUM_NODES;i++))
|
||||
do
|
||||
rm -f "$STATE_DIR"/node$i-cloudinit.iso
|
||||
rm -f "$STATE_DIR"/node$i.qcow2
|
||||
done
|
||||
}
|
||||
|
||||
create() {
|
||||
destroy
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
ssh-keygen -f qemu_key -N "" -t ed25519 -C "" -f "$STATE_DIR"/id_ed25519 >/dev/null 2>&1
|
||||
ssh_pubkey=$(tr -d '\n' < "$STATE_DIR"/id_ed25519.pub)
|
||||
|
||||
for ((i=0;i<NUM_NODES;i++))
|
||||
do
|
||||
# Create cloud-init volume
|
||||
cat > "$STATE_DIR"/user-data <<- EOF
|
||||
#cloud-config
|
||||
|
||||
users:
|
||||
- name: root
|
||||
ssh_authorized_keys:
|
||||
- $ssh_pubkey
|
||||
EOF
|
||||
|
||||
cat > "$STATE_DIR"/meta-data <<- EOF
|
||||
instance-id: node$i
|
||||
local-hostname: node$i
|
||||
EOF
|
||||
|
||||
cat > "$STATE_DIR"/network-config <<- EOF
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
match:
|
||||
macaddress: "52:54:00:00:00:$(printf '%02x' $i)"
|
||||
dhcp4: false
|
||||
addresses:
|
||||
- 192.168.30.$((10 + i))/24
|
||||
EOF
|
||||
|
||||
mkisofs -o "$STATE_DIR"/node$i-cloudinit.iso -V cidata -r -J "$STATE_DIR"/user-data "$STATE_DIR"/meta-data "$STATE_DIR"/network-config >/dev/null 2>&1
|
||||
|
||||
rm "$STATE_DIR"/user-data
|
||||
rm "$STATE_DIR"/meta-data
|
||||
rm "$STATE_DIR"/network-config
|
||||
|
||||
# Create main disk, backed by read-only cloud image
|
||||
qemu-img create -f qcow2 -F qcow2 -b "$BASE_IMAGE_FILENAME" "$STATE_DIR"/node$i.qcow2 20G >/dev/null 2>&1
|
||||
|
||||
# Create SSH config to be used for accessing the VM
|
||||
cat >> "$SSH_CONFIG" <<- EOF
|
||||
Host node$i
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
IdentityFile $STATE_DIR/id_ed25519
|
||||
IdentitiesOnly yes
|
||||
User root
|
||||
Hostname localhost
|
||||
Port $((3100 + i))
|
||||
EOF
|
||||
done
|
||||
|
||||
start
|
||||
}
|
||||
|
||||
case $1 in
|
||||
stop)
|
||||
shift
|
||||
stop "$@"
|
||||
;;
|
||||
destroy)
|
||||
shift
|
||||
destroy "$@"
|
||||
;;
|
||||
create)
|
||||
shift
|
||||
create "$@"
|
||||
;;
|
||||
esac
|
Loading…
x
Reference in New Issue
Block a user