The script previously required a manually supplied image path and only installed the client tools, so a bare host aborted at virt-install with a missing libvirt-sock. It now derives and downloads the Ubuntu cloud image from --version, detects and installs the full libvirt+QEMU stack (daemon and system emulator included) through the host package manager, then enables libvirtd. Adds a --download-only mode, a hypervisor preflight check, clean error messages instead of Python tracebacks, and a bilingual usage guide (README). Generated by Claude Code 2.1.210 claude-opus-4-8 Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
251 lines
8.9 KiB
Markdown
251 lines
8.9 KiB
Markdown
|
|
# QEMU/KVM — Ubuntu VM deployment
|
|
|
|
`deploy_qemu.py` deploys an Ubuntu VM (libvirt/KVM) from an official Ubuntu
|
|
cloud image, using `qemu-img` + `cloud-init` + `virt-install`. It:
|
|
|
|
1. **Downloads the Ubuntu cloud image by itself** (cached, no double download).
|
|
2. Converts it to a dedicated qcow2 working disk and resizes it.
|
|
3. Generates `user-data` / `meta-data` and builds the `seed.iso` (cloud-init).
|
|
4. Runs `virt-install` importing the disk + the seed as a CD-ROM.
|
|
5. Waits for the DHCP lease and prints the SSH command.
|
|
|
|
## Prerequisites
|
|
|
|
- A host with KVM available (bare-metal or nested virtualization enabled).
|
|
- `sudo` rights (the deployment writes to `/var/lib/libvirt/images` and drives
|
|
libvirt).
|
|
|
|
## Installation
|
|
|
|
The script **auto-installs the missing pieces it needs**: on first run it
|
|
detects your package manager (apt / dnf / pacman / zypper / brew), lists the
|
|
missing components (the client tools, **plus the libvirt daemon and the QEMU
|
|
system emulator**), asks for confirmation, installs them with `sudo`, then
|
|
enables and starts `libvirtd`. Use `-y` to accept automatically or
|
|
`--no-install-deps` to disable this behaviour.
|
|
|
|
To install everything manually on Ubuntu/Debian (recommended full KVM stack):
|
|
|
|
```bash
|
|
sudo apt install qemu-utils virtinst libvirt-clients cloud-image-utils \
|
|
libvirt-daemon-system qemu-system-x86
|
|
sudo systemctl enable --now libvirtd
|
|
sudo usermod -aG libvirt,kvm "$USER" # re-login / reconnectez-vous
|
|
```
|
|
|
|
`libvirt-daemon-system` provides the `libvirtd` daemon (and the
|
|
`/var/run/libvirt/libvirt-sock` socket) and `qemu-system-x86` the emulator —
|
|
without them `virt-install` fails with *"Failed to connect socket to
|
|
'/var/run/libvirt/libvirt-sock'"*. The script installs and starts them for
|
|
you; this manual command is only needed if you prefer to prepare the host
|
|
yourself or run with `--no-install-deps`.
|
|
|
|
## Usage
|
|
|
|
Simplest form — the image is downloaded automatically (path derived from
|
|
`--version`, cached in `/var/lib/libvirt/images/iso`):
|
|
|
|
```bash
|
|
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 \
|
|
--ssh-key ~/.ssh/id_ed25519.pub
|
|
```
|
|
|
|
Download (and verify) an image without creating a VM:
|
|
|
|
```bash
|
|
sudo ./script/qemu/deploy_qemu.py --download-only --version 24.04 --verify
|
|
```
|
|
|
|
Deploy with an interactive password instead of an SSH key:
|
|
|
|
```bash
|
|
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 --ask-password
|
|
```
|
|
|
|
Larger VM (8 GB RAM, 8 vCPU, 120 GB disk), overwriting an existing disk:
|
|
|
|
```bash
|
|
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 \
|
|
--memory 8192 --vcpus 8 --disk-size 120G --ask-password --force
|
|
```
|
|
|
|
Preview what would happen, without doing anything (no sudo, no download):
|
|
|
|
```bash
|
|
./script/qemu/deploy_qemu.py --name test-vm --version 24.04 --dry-run
|
|
```
|
|
|
|
Non-interactive deployment (accept dependency install automatically):
|
|
|
|
```bash
|
|
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 \
|
|
--ssh-key ~/.ssh/id_ed25519.pub -y
|
|
```
|
|
|
|
Supported Ubuntu versions: `20.04`, `22.04`, `24.04` (default), `24.10`,
|
|
`25.04`, `25.10`. Provide an explicit image path as a positional argument to
|
|
override the automatic download location.
|
|
|
|
## After deployment
|
|
|
|
```bash
|
|
virsh list --all
|
|
virsh console test-vm # Ctrl+] to quit / pour quitter
|
|
virsh domifaddr test-vm --source lease # find the IP / trouver l'IP
|
|
ssh erplibre@<IP>
|
|
```
|
|
|
|
The default user is `erplibre` (change it with `--user`).
|
|
|
|
## Via the TODO menu
|
|
|
|
The script is integrated into the interactive assistant. Run `make todo` (or
|
|
`./script/todo/todo.py`), then go to **Execute → Deploy → QEMU/KVM - Deploy an
|
|
Ubuntu VM (libvirt)**. From there you can deploy a VM, preview a dry-run,
|
|
download an image, list VMs and show a VM IP address — the menu asks for the
|
|
parameters and builds the command for you.
|
|
|
|
## Main options
|
|
|
|
- `--version` — Ubuntu version (default `24.04`).
|
|
- `--image-dir` — image cache directory (default `/var/lib/libvirt/images/iso`).
|
|
- `--download-only` — download the image then exit (no VM).
|
|
- `--name` — VM name (required for deployment).
|
|
- `--memory`, `--vcpus`, `--disk-size` — VM sizing.
|
|
- `--ssh-key`, `--ask-password`, `--password-hash` — authentication.
|
|
- `-y` / `--assume-yes` — auto-accept dependency installation.
|
|
- `--no-install-deps` — never auto-install dependencies.
|
|
- `--dry-run` — show the commands without executing anything.
|
|
- `--force` — overwrite the existing working qcow2 disk.
|
|
|
|
Run `./script/qemu/deploy_qemu.py --help` for the full list.
|
|
|
|
## Managing VMs
|
|
|
|
List, stop and remove VMs (the qcow2 disk under `/var/lib/libvirt/images`
|
|
is kept unless you delete it):
|
|
|
|
```bash
|
|
sudo virsh list --all # toutes les VM et leur état / all VMs and state
|
|
sudo virsh shutdown <nom-vm> # arrêt propre ACPI / graceful shutdown
|
|
sudo virsh destroy <nom-vm> # arrêt forcé / force off (pull the plug)
|
|
sudo virsh undefine <nom-vm> # supprime la définition / remove definition
|
|
sudo virsh domifaddr <nom-vm> # adresse IP de la VM / VM IP address
|
|
```
|
|
|
|
`destroy` only powers the VM off (disk kept); `undefine` removes its
|
|
definition. To fully recreate a VM with the same name, `destroy` + `undefine`
|
|
it first, or redeploy with `--force`.
|
|
|
|
## SSH access from another machine (ProxyJump)
|
|
|
|
With the default NAT network the VM is reachable **only from the KVM host**.
|
|
To reach it from another machine **without changing the network**, use the
|
|
host as a jump host (it already reaches the VM). Get the VM IP with
|
|
`sudo virsh domifaddr <nom-vm>`, then from the other machine:
|
|
|
|
```bash
|
|
# Rebond SSH vers la VM / jump through the KVM host
|
|
ssh -J user@<ip-hote> erplibre@<ip-vm>
|
|
|
|
# Tunnel d'un service, ex. Odoo 8069 / tunnel a service, then http://localhost:8069
|
|
ssh -L 8069:<ip-vm>:8069 user@<ip-hote>
|
|
```
|
|
|
|
To make it permanent, add this to `~/.ssh/config` on the other machine (then
|
|
just `ssh myvm`):
|
|
|
|
```text
|
|
Host myvm
|
|
HostName <ip-vm> # ex. 192.168.122.50 (reseau NAT)
|
|
User erplibre
|
|
ProxyJump user@<ip-hote> # IP LAN de l'hote KVM
|
|
```
|
|
|
|
This works over Wi-Fi and needs no VM shutdown — the simplest option for
|
|
personal access. Prefer a bridge (below) if the VM must be a full server
|
|
exposed on the LAN.
|
|
|
|
## QEMU inside QEMU (nested) & exposing the VM via a bridge
|
|
|
|
If the KVM host is **itself a VM** (QEMU-in-QEMU), the deployment works only
|
|
when **nested virtualization** is enabled on the outer/physical host and the
|
|
middle VM uses CPU mode `host-passthrough`. Check from inside the KVM host
|
|
(the first command must be non-empty):
|
|
|
|
```bash
|
|
grep -E -o '(vmx|svm)' /proc/cpuinfo | sort -u # extensions visibles / visible
|
|
# Sur l'hote PHYSIQUE / on the PHYSICAL host:
|
|
cat /sys/module/kvm_intel/parameters/nested # Intel -> Y/1
|
|
cat /sys/module/kvm_amd/parameters/nested # AMD -> Y/1
|
|
```
|
|
|
|
To enable nesting on the physical host (Intel shown; use `kvm_amd` on AMD),
|
|
then recreate the middle VM with `host-passthrough`:
|
|
|
|
```bash
|
|
echo "options kvm_intel nested=1" | sudo tee /etc/modprobe.d/kvm-nested.conf
|
|
sudo modprobe -r kvm_intel && sudo modprobe kvm_intel # ou / or reboot
|
|
```
|
|
|
|
If nesting is unavailable, QEMU still runs via software emulation (TCG) — it
|
|
works but is slow.
|
|
|
|
### Bridge for external access
|
|
|
|
A NAT VM is isolated; a **bridged** VM gets an IP directly on the LAN,
|
|
reachable by any machine. On the KVM host, create a bridge `br0` over the
|
|
physical NIC (**wired only** — Wi-Fi cannot be bridged). netplan (Ubuntu
|
|
server) — replace `enp3s0` with your interface:
|
|
|
|
```yaml
|
|
# /etc/netplan/01-br0.yaml
|
|
network:
|
|
version: 2
|
|
renderer: networkd
|
|
ethernets:
|
|
enp3s0: {dhcp4: no, dhcp6: no}
|
|
bridges:
|
|
br0:
|
|
interfaces: [enp3s0]
|
|
dhcp4: yes
|
|
parameters: {stp: false, forward-delay: 0}
|
|
```
|
|
|
|
Apply safely (auto-reverts if you lose the connection) and verify — or use
|
|
NetworkManager (Ubuntu desktop):
|
|
|
|
```bash
|
|
# netplan
|
|
sudo netplan try && sudo netplan apply
|
|
ip addr show br0 # br0 porte l'IP du LAN / br0 holds the LAN IP
|
|
|
|
# NetworkManager (alternative)
|
|
nmcli con add type bridge ifname br0 con-name br0
|
|
nmcli con add type ethernet ifname enp3s0 master br0 con-name br0-port
|
|
nmcli con modify br0 ipv4.method auto
|
|
nmcli con down "Wired connection 1" ; nmcli con up br0
|
|
```
|
|
|
|
Then attach the VM to the bridge — **either at creation**:
|
|
|
|
```bash
|
|
sudo ./script/qemu/deploy_qemu.py --name <nom-vm> --version 24.04 \
|
|
--ssh-key ~/.ssh/id_ed25519.pub --network bridge=br0,model=virtio -y --force
|
|
```
|
|
|
|
**or by editing a VM already created**: stop it, replace its `<interface>`
|
|
block (`type='network'` / `<source network='default'/>` → `type='bridge'` /
|
|
`<source bridge='br0'/>`), then start it again:
|
|
|
|
```bash
|
|
sudo virsh shutdown <nom-vm>
|
|
sudo virsh edit <nom-vm> # mettre l'interface en bridge=br0
|
|
sudo virsh start <nom-vm>
|
|
sudo virsh domifaddr <nom-vm> # nouvelle IP LAN / new LAN IP
|
|
```
|
|
|
|
The VM now gets a LAN IP from your router, reachable by other machines. From
|
|
the Internet you additionally need a port-forward on your router (or a VPN);
|
|
in a nested setup the outer host must also forward/expose the middle VM.
|