Use hostname-safe VM names
This commit is contained in:
parent
306412c995
commit
bbd57d8dd2
3 changed files with 110 additions and 11 deletions
12
README.md
12
README.md
|
|
@ -5,6 +5,8 @@ Minimal Firecracker launcher.
|
||||||
## Requirements
|
## Requirements
|
||||||
- Linux host with KVM (`/dev/kvm` access)
|
- Linux host with KVM (`/dev/kvm` access)
|
||||||
- `sudo`, `ip`, `curl`, `ssh`
|
- `sudo`, `ip`, `curl`, `ssh`
|
||||||
|
- `dmsetup`, `losetup`, `blockdev` (device-mapper snapshot for rootfs)
|
||||||
|
- `e2cp`, `e2rm` (writes hostname and resolv.conf into rootfs snapshot)
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
- `firecracker`: Firecracker binary
|
- `firecracker`: Firecracker binary
|
||||||
|
|
@ -21,17 +23,19 @@ Minimal Firecracker launcher.
|
||||||
```
|
```
|
||||||
./run.sh --name calm_otter --vcpu 4 --ram 2048 --home-size 6G
|
./run.sh --name calm_otter --vcpu 4 --ram 2048 --home-size 6G
|
||||||
```
|
```
|
||||||
- `--name`: must be unique and match `[a-z0-9][a-z0-9_-]{0,63}`.
|
- `--name`: must be unique and match `[a-z0-9][a-z0-9-]{0,63}`.
|
||||||
- `--vcpu`: defaults to 2, max 16.
|
- `--vcpu`: defaults to 2, max 16.
|
||||||
- `--ram`: MiB, defaults to 1024, max 32768.
|
- `--ram`: MiB, defaults to 1024, max 32768.
|
||||||
- `--rootfs`: path to the base rootfs image (default: `./rootfs.ext4`).
|
- `--rootfs`: path to the base rootfs image (default: `./rootfs.ext4`).
|
||||||
- `--kernel`: path to the kernel image (default: `./vmlinux`).
|
- `--kernel`: path to the kernel image (default: `./vmlinux`).
|
||||||
- `--home-size`: M/G suffixes supported (default: 2G).
|
- `--home-size`: M/G suffixes supported (default: 2G).
|
||||||
|
- `--var-size`: M/G suffixes supported (default: 2G).
|
||||||
|
|
||||||
## Storage Layout
|
## Storage Layout
|
||||||
- `rootfs.ext4` is mounted read-only as `/` and shared across VMs.
|
- `rootfs.ext4` is used as the read-only origin for a per-VM device-mapper snapshot mounted as `/`.
|
||||||
- Each VM gets a writable ext4 disk mounted at `/home`.
|
- Each VM gets writable ext4 disks mounted at `/home` and `/var`.
|
||||||
- The base image must include an `/etc/fstab` entry for `/dev/vdb` → `/home`.
|
- The base image must include `/etc/fstab` entries for `/dev/vdb` → `/home` and `/dev/vdc` → `/var`.
|
||||||
|
- `/run` and `/tmp` should be tmpfs via `/etc/fstab`.
|
||||||
|
|
||||||
## SSH
|
## SSH
|
||||||
```
|
```
|
||||||
|
|
|
||||||
2
namegen
2
namegen
|
|
@ -412,4 +412,4 @@ rand() {
|
||||||
|
|
||||||
adjective="${ADJECTIVES[$(rand ${#ADJECTIVES[@]})]}"
|
adjective="${ADJECTIVES[$(rand ${#ADJECTIVES[@]})]}"
|
||||||
substantive="${SUBSTANTIVES[$(rand ${#SUBSTANTIVES[@]})]}"
|
substantive="${SUBSTANTIVES[$(rand ${#SUBSTANTIVES[@]})]}"
|
||||||
printf '%s_%s' "$adjective" "$substantive"
|
printf '%s-%s' "$adjective" "$substantive"
|
||||||
|
|
|
||||||
107
run.sh
107
run.sh
|
|
@ -10,12 +10,13 @@ usage() {
|
||||||
Usage: ./run.sh [options]
|
Usage: ./run.sh [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--name <name> VM name (lowercase letters, digits, -, _)
|
--name <name> VM name (lowercase letters, digits, -)
|
||||||
--vcpu <count> vCPU count (default: 2)
|
--vcpu <count> vCPU count (default: 2)
|
||||||
--ram <mib> RAM in MiB (default: 1024)
|
--ram <mib> RAM in MiB (default: 1024)
|
||||||
--rootfs <path> Root filesystem image (default: ./rootfs.ext4)
|
--rootfs <path> Root filesystem image (default: ./rootfs.ext4)
|
||||||
--kernel <path> Kernel image (default: ./vmlinux)
|
--kernel <path> Kernel image (default: ./vmlinux)
|
||||||
--home-size <size> Home disk size (e.g. 4G, 10240M)
|
--home-size <size> Home disk size (e.g. 4G, 10240M)
|
||||||
|
--var-size <size> Var disk size (e.g. 4G, 10240M)
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
@ -40,16 +41,19 @@ CIDR="24"
|
||||||
DEFAULT_VCPU=2
|
DEFAULT_VCPU=2
|
||||||
DEFAULT_RAM=1024
|
DEFAULT_RAM=1024
|
||||||
DEFAULT_HOME_SIZE="2G"
|
DEFAULT_HOME_SIZE="2G"
|
||||||
|
DEFAULT_VAR_SIZE="2G"
|
||||||
MIN_VCPU=1
|
MIN_VCPU=1
|
||||||
MAX_VCPU=16
|
MAX_VCPU=16
|
||||||
MIN_RAM=256
|
MIN_RAM=256
|
||||||
MAX_RAM=32768
|
MAX_RAM=32768
|
||||||
MAX_DISK_BYTES=$((128 * 1024 * 1024 * 1024))
|
MAX_DISK_BYTES=$((128 * 1024 * 1024 * 1024))
|
||||||
DNS_SERVER="1.1.1.1"
|
DNS_SERVER="1.1.1.1"
|
||||||
|
COW_SIZE="2G"
|
||||||
|
|
||||||
VCPU_COUNT="$DEFAULT_VCPU"
|
VCPU_COUNT="$DEFAULT_VCPU"
|
||||||
RAM_MIB="$DEFAULT_RAM"
|
RAM_MIB="$DEFAULT_RAM"
|
||||||
HOME_SIZE="$DEFAULT_HOME_SIZE"
|
HOME_SIZE="$DEFAULT_HOME_SIZE"
|
||||||
|
VAR_SIZE="$DEFAULT_VAR_SIZE"
|
||||||
KERNEL="$DEFAULT_KERNEL"
|
KERNEL="$DEFAULT_KERNEL"
|
||||||
ROOTFS="$DEFAULT_ROOTFS"
|
ROOTFS="$DEFAULT_ROOTFS"
|
||||||
VM_NAME=""
|
VM_NAME=""
|
||||||
|
|
@ -109,6 +113,10 @@ while [[ $# -gt 0 ]]; do
|
||||||
HOME_SIZE="${2:-}"
|
HOME_SIZE="${2:-}"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--var-size)
|
||||||
|
VAR_SIZE="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
|
|
@ -149,6 +157,16 @@ if (( HOME_BYTES > MAX_DISK_BYTES )); then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
VAR_BYTES=""
|
||||||
|
if ! VAR_BYTES="$(parse_disk_size "$VAR_SIZE")"; then
|
||||||
|
log "invalid --var-size value: $VAR_SIZE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if (( VAR_BYTES > MAX_DISK_BYTES )); then
|
||||||
|
log "var-size exceeds max of $((MAX_DISK_BYTES / 1024 / 1024 / 1024))G"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -z "$VM_NAME" ]]; then
|
if [[ -z "$VM_NAME" ]]; then
|
||||||
if [[ ! -x "$NAMEGEN" ]]; then
|
if [[ ! -x "$NAMEGEN" ]]; then
|
||||||
log "name generator not found: $NAMEGEN"
|
log "name generator not found: $NAMEGEN"
|
||||||
|
|
@ -167,7 +185,7 @@ if [[ -z "$VM_NAME" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! [[ "$VM_NAME" =~ ^[a-z0-9][a-z0-9_-]{0,63}$ ]]; then
|
if ! [[ "$VM_NAME" =~ ^[a-z0-9][a-z0-9-]{0,63}$ ]]; then
|
||||||
log "invalid --name value: $VM_NAME"
|
log "invalid --name value: $VM_NAME"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
@ -213,6 +231,12 @@ VM_STARTED=0
|
||||||
CLEANUP_ON_EXIT=0
|
CLEANUP_ON_EXIT=0
|
||||||
KEEP_VM_DIR_ON_FAIL=1
|
KEEP_VM_DIR_ON_FAIL=1
|
||||||
HOME_PATH="$VM_DIR/home.ext4"
|
HOME_PATH="$VM_DIR/home.ext4"
|
||||||
|
VAR_PATH="$VM_DIR/var.ext4"
|
||||||
|
COW_FILE="$VM_DIR/cow.ext4"
|
||||||
|
BASE_LOOP=""
|
||||||
|
COW_LOOP=""
|
||||||
|
DM_NAME="fc-rootfs-$VM_TAG"
|
||||||
|
DM_DEV=""
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
|
|
@ -227,6 +251,15 @@ cleanup() {
|
||||||
sudo ip link del "$TAP_DEV" 2>/dev/null || true
|
sudo ip link del "$TAP_DEV" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
rm -f "${API_SOCK:-}"
|
rm -f "${API_SOCK:-}"
|
||||||
|
if [[ -n "${DM_NAME:-}" ]]; then
|
||||||
|
sudo dmsetup remove "$DM_NAME" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [[ -n "${COW_LOOP:-}" ]]; then
|
||||||
|
sudo losetup -d "$COW_LOOP" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [[ -n "${BASE_LOOP:-}" ]]; then
|
||||||
|
sudo losetup -d "$BASE_LOOP" 2>/dev/null || true
|
||||||
|
fi
|
||||||
if [[ -n "${VM_DIR:-}" && -d "$VM_DIR" ]]; then
|
if [[ -n "${VM_DIR:-}" && -d "$VM_DIR" ]]; then
|
||||||
if [[ "$KEEP_VM_DIR_ON_FAIL" -eq 0 || "$VM_STARTED" -eq 1 || "$CLEANUP_ON_EXIT" -eq 1 ]]; then
|
if [[ "$KEEP_VM_DIR_ON_FAIL" -eq 0 || "$VM_STARTED" -eq 1 || "$CLEANUP_ON_EXIT" -eq 1 ]]; then
|
||||||
rm -rf "$VM_DIR"
|
rm -rf "$VM_DIR"
|
||||||
|
|
@ -262,11 +295,57 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! command -v mkfs.ext4 >/dev/null 2>&1; then
|
if ! command -v mkfs.ext4 >/dev/null 2>&1; then
|
||||||
log "mkfs.ext4 required to create home disk"
|
log "mkfs.ext4 required to create home/var disks"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
truncate -s "$HOME_BYTES" "$HOME_PATH"
|
truncate -s "$HOME_BYTES" "$HOME_PATH"
|
||||||
mkfs.ext4 -F "$HOME_PATH" >/dev/null
|
mkfs.ext4 -F "$HOME_PATH" >/dev/null
|
||||||
|
truncate -s "$VAR_BYTES" "$VAR_PATH"
|
||||||
|
mkfs.ext4 -F "$VAR_PATH" >/dev/null
|
||||||
|
|
||||||
|
if ! command -v dmsetup >/dev/null 2>&1 || ! command -v losetup >/dev/null 2>&1 || ! command -v blockdev >/dev/null 2>&1; then
|
||||||
|
log "dmsetup, losetup, and blockdev are required for rootfs snapshots"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v e2cp >/dev/null 2>&1 || ! command -v e2rm >/dev/null 2>&1; then
|
||||||
|
log "e2cp and e2rm are required to set hostname and resolv.conf"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
COW_BYTES="$(parse_disk_size "$COW_SIZE")"
|
||||||
|
if [[ -z "$COW_BYTES" ]]; then
|
||||||
|
log "invalid COW size: $COW_SIZE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BASE_LOOP="$(sudo losetup -f --show --read-only "$ROOTFS")"
|
||||||
|
truncate -s "$COW_BYTES" "$COW_FILE"
|
||||||
|
COW_LOOP="$(sudo losetup -f --show "$COW_FILE")"
|
||||||
|
SECTORS="$(sudo blockdev --getsz "$BASE_LOOP")"
|
||||||
|
sudo dmsetup create "$DM_NAME" --table "0 $SECTORS snapshot $BASE_LOOP $COW_LOOP P 8"
|
||||||
|
DM_DEV="/dev/mapper/$DM_NAME"
|
||||||
|
|
||||||
|
RESOLV_TMP="$VM_DIR/resolv.conf"
|
||||||
|
HOSTNAME_TMP="$VM_DIR/hostname"
|
||||||
|
HOSTS_TMP="$VM_DIR/hosts"
|
||||||
|
printf 'nameserver %s\n' "$DNS_SERVER" >"$RESOLV_TMP"
|
||||||
|
printf '%s\n' "$VM_NAME" >"$HOSTNAME_TMP"
|
||||||
|
printf '127.0.0.1 localhost\n127.0.1.1 %s\n' "$VM_NAME" >"$HOSTS_TMP"
|
||||||
|
sudo e2rm "$DM_DEV:/etc/resolv.conf" >/dev/null 2>&1 || true
|
||||||
|
sudo e2rm "$DM_DEV:/etc/hostname" >/dev/null 2>&1 || true
|
||||||
|
sudo e2rm "$DM_DEV:/etc/hosts" >/dev/null 2>&1 || true
|
||||||
|
sudo e2cp "$RESOLV_TMP" "$DM_DEV:/etc/resolv.conf" >/dev/null 2>&1 || {
|
||||||
|
log "failed to write /etc/resolv.conf into rootfs snapshot"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
sudo e2cp "$HOSTNAME_TMP" "$DM_DEV:/etc/hostname" >/dev/null 2>&1 || {
|
||||||
|
log "failed to write /etc/hostname into rootfs snapshot"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
sudo e2cp "$HOSTS_TMP" "$DM_DEV:/etc/hosts" >/dev/null 2>&1 || {
|
||||||
|
log "failed to write /etc/hosts into rootfs snapshot"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# Host bridge
|
# Host bridge
|
||||||
if ! ip link show "$BR_DEV" >/dev/null 2>&1; then
|
if ! ip link show "$BR_DEV" >/dev/null 2>&1; then
|
||||||
|
|
@ -333,7 +412,7 @@ log "configuring machine"
|
||||||
|
|
||||||
# Boot source
|
# Boot source
|
||||||
log "configuring boot source"
|
log "configuring boot source"
|
||||||
KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=${GUEST_IP}::${BR_IP}:${DNS_SERVER}:255.255.255.0::eth0:off hostname=${VM_NAME}"
|
KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=${GUEST_IP}::${BR_IP}:255.255.255.0::eth0:off:${DNS_SERVER} hostname=${VM_NAME}"
|
||||||
|
|
||||||
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/boot-source \
|
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/boot-source \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
|
@ -348,9 +427,9 @@ log "attaching root filesystem"
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{
|
-d "{
|
||||||
\"drive_id\": \"rootfs\",
|
\"drive_id\": \"rootfs\",
|
||||||
\"path_on_host\": \"$ROOTFS\",
|
\"path_on_host\": \"$DM_DEV\",
|
||||||
\"is_root_device\": true,
|
\"is_root_device\": true,
|
||||||
\"is_read_only\": true
|
\"is_read_only\": false
|
||||||
}" >/dev/null
|
}" >/dev/null
|
||||||
|
|
||||||
# Home filesystem
|
# Home filesystem
|
||||||
|
|
@ -364,6 +443,17 @@ log "attaching home filesystem"
|
||||||
\"is_read_only\": false
|
\"is_read_only\": false
|
||||||
}" >/dev/null
|
}" >/dev/null
|
||||||
|
|
||||||
|
# Var filesystem
|
||||||
|
log "attaching var filesystem"
|
||||||
|
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/drives/var \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"drive_id\": \"var\",
|
||||||
|
\"path_on_host\": \"$VAR_PATH\",
|
||||||
|
\"is_root_device\": false,
|
||||||
|
\"is_read_only\": false
|
||||||
|
}" >/dev/null
|
||||||
|
|
||||||
# Network interface
|
# Network interface
|
||||||
log "configuring network interface"
|
log "configuring network interface"
|
||||||
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/network-interfaces/eth0 \
|
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/network-interfaces/eth0 \
|
||||||
|
|
@ -391,6 +481,11 @@ guest_ip=$GUEST_IP
|
||||||
tap=$TAP_DEV
|
tap=$TAP_DEV
|
||||||
api_sock=$API_SOCK
|
api_sock=$API_SOCK
|
||||||
log=$LOG_FILE
|
log=$LOG_FILE
|
||||||
|
base_loop=$BASE_LOOP
|
||||||
|
cow_file=$COW_FILE
|
||||||
|
cow_loop=$COW_LOOP
|
||||||
|
dm_name=$DM_NAME
|
||||||
|
dm_dev=$DM_DEV
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
log "vm started successfully"
|
log "vm started successfully"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue