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
|
# 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