dockur / windows

Windows inside a Docker container.
MIT License
18k stars 1.57k forks source link

Qemu monitor for interactive VM management #286

Closed avoiceofreason closed 3 weeks ago

avoiceofreason commented 7 months ago

Seems the qemu docker image doesn't have libvirt or virsh for VM management.

However you can do some interactive management via the qemu monitor interface.

First you have to enable monitor and add an endpoint. Unix sockets is probably more secure but telnet is ok for me. Add this qemu argument to your docker startup:

-e ARGUMENTS="-monitor telnet::55555,server,nowait"

and rebuild your container.

I tried to docker port map 55555 to the host, but there is something very weird with telnet to containers. Anyway no matter we just bash straight into the container with:

docker exec -it windows10 bash

We need the telnet client, so do this:

apt update ; apt install -y telnet

then telnet to the qemu monitor from inside the container:

telnet 127.0.0.1 55555

Now you get the (qemu) prompt and type help for all the commands:

(qemu) help

You can add/remove devices, take snapshots, shutdown the VM etc etc Google is your friend

use ctrl+] to quit out of monitor and then "quit" at the telnet prompt and "exit" from the container bash to your host

My use case was to try and hot swap USB devices. But its super flakey and only really works for USB devices that are plugged in at container boot.

Some useful commands are:

see usb devices in VM: (qemu) info usb

see host usb devices: (this only seems to work if you have already hooked up a host device already) (qemu) info usbhost

add a host usb device to VM: (qemu) device_add usb-host,id=myusb,vendorid=0x1234,productid=0x5678

remove a host usb device from VM: (qemu) device_del myusb

Remember to eject usb storage devices in the VM OS first

Caveats: 1.Telnet is not secure. 2.You can easily wreck your VM by changing boot devices and removing devices 3.You need to install telnet into the container every time you rebuild it 4.I couldn't get telnet to connect properly by mapping ports to the host 5.I couldn't get USB devices to get recognised after ejecting them from the host after VM starts

kroese commented 7 months ago

Great! However, you can do this much easier I think. First, the monitor is already enabled, because the default value for the MONITOR environment variable is:

MONITOR="telnet:localhost:7100,server,nowait,nodelay"

So you dont need to add one on port 55555, because one is already running on port 7100.

Secondly, you dont need to go inside the machine to connect. You can add this port (7100) to the list of ports that will not be forwarded to the VM, by adding this to the compose file:

environment:
  HOST_PORTS: "7100,8600"

Now you can connect to the IP of the container on port 7100 with your telnet client and control the monitor.

avoiceofreason commented 7 months ago

Thanks kroese.

I didn't spot that the telnet/monitor had already been enabled. Maybe another one for documentation.

A few things though:

1.Port 7100 appears as "font-service" in "netstat -a" so its not easy to spot in the container 2.The monitor telnet is insecure so might be better to have the default as off with a docker variable to turn on 3.I'm still struggling to map a port or hit the container with telnet from the host. It looks to me like something very funky with telnet in docker containers (or me)

with this mapping:

-p 55555:7100

from container >telnet 127.0.0.1 7100 (works) from host >telnet {container IP} 7100 (just get trying...) from host >telnet {host IP} 55555 (just get trying...) from host >telnet {host IP 55556 (get connection refused)

Is it just me or does this work for others?

kroese commented 7 months ago

The monitor telnet is insecure so might be better to have the default as off with a docker variable to turn on

This monitor is used to send the poweroff signal for graceful shutdown, so it cannot be disabled otherwise there is no way to shutdown Windows cleanly.

Also, it is not really insecure since port 7100 is not mapped in the default Dockerfile, so normally this port is not reachable from outside the container.

Is it just me or does this work for others?

It worked for me last time I tried. Does it work when you dont map 55555, but just use 7100:7100 and add 7100 to HOST_PORTS?

avoiceofreason commented 7 months ago

Nope, if I map 7100:7100 then I get this:

❯ telnet 192.168.1.6 7100 Trying 192.168.0.5... ^C ❯ telnet 192.168.1.6 7101 Trying 192.168.0.5... telnet: Unable to connect to remote host: Connection refused

So no connection refused on 7100, but also no telnet I have no problem port mapping anything else...just telnet...weird

svargh commented 5 months ago

This would greatly improve kvm experience for me. I like to snapshot a running vm multiple times, and revert back to a given running state. It helps debugging running software or starting again from an another running branch of a software.

chuan1127 commented 5 months ago

!/bin/bash

定义Docker容器的名称

DOCKER_CONTAINERS=("qbittorrent" "nas-tools" "transmission" "xiaoyaliu" "MoviePilot")

获取当前小时(24小时制)

CURRENT_HOUR=$(date +"%H")

获取当前分钟

CURRENT_MINUTE=$(date +"%M")

日志文件路径

LOG_FILE="/mnt/user/domains/docker_control.log"

函数:记录日志

log() { echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" >> "$LOG_FILE" echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" }

函数:清理日志

cleanup_logs() { log_size=$(du -m "$LOG_FILE" | cut -f1) max_log_size=50 if [ "$log_size" -gt "$max_log_size" ]; then mv "$LOG_FILE" "/mnt/user/domains/dockercontrol$(date +"%Y%m%d%H%M%S").log" touch "$LOG_FILE" # 清空日志文件 log "日志文件超过50M,已清理." fi }

log "开始脚本执行."

如果当前时间在0点到7点59分59秒之间

if [ "$CURRENT_HOUR" -ge 0 ] && [ "$CURRENT_HOUR" -lt 8 ] && [ "$CURRENT_MINUTE" -lt 60 ]; then log "当前时间在0点到7点59分59秒之间,启动Docker容器..." for CONTAINER in "${DOCKER_CONTAINERS[@]}"; do log "启动容器 $CONTAINER..." echo "启动容器 $CONTAINER..." if docker start "$CONTAINER" >> "$LOG_FILE" 2>&1; then log "容器 $CONTAINER 启动成功." echo "容器 $CONTAINER 启动成功." sleep 5 # 等待5秒 else log "容器 $CONTAINER 启动失败. 详细错误信息请查看日志." echo "容器 $CONTAINER 启动失败. 详细错误信息请查看日志." fi done else log "当前时间不在0点到7点59分59秒之间,停止Docker容器..." for CONTAINER in "${DOCKER_CONTAINERS[@]}"; do log "停止容器 $CONTAINER..." echo "停止容器 $CONTAINER..." if docker stop "$CONTAINER" >> "$LOG_FILE" 2>&1; then log "容器 $CONTAINER 停止成功." echo "容器 $CONTAINER 停止成功." sleep 5 # 等待5秒 else log "容器 $CONTAINER 停止失败. 详细错误信息请查看日志." echo "容器 $CONTAINER 停止失败. 详细错误信息请查看日志." fi done fi

log "脚本执行结束."

清理日志

cleanup_logs

DOCKER_CONTAINERS=("qbittorrent" "nas-tools" "transmission" "xiaoyaliu" "MoviePilot")你可以修改这里的,定时重启容器. 这是一个脚本.

geckolinux commented 3 months ago

I would also find this functionality very useful, but I can't figure out how to make it work.

      HOST_PORTS: "7100,8600"
    devices:
      - /dev/kvm
      - /dev/bus/usb
    cap_add:
      - NET_ADMIN
    ports:
      - 7100:7100
      - 8006:8006
      - 3389:3389/tcp
      - 3389:3389/udp

myself@host ~> docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' Tiny11
172.22.0.2

myself@host ~> telnet 172.22.0.2 7100
Trying 172.22.0.2...
telnet: Unable to connect to remote host: Connection refused

myself@host ~ [1]> telnet localhost 7100
Trying ::1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.

myself@host ~ [1]> telnet localhost 8600
Trying ::1...
Connection failed: Connection refused
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused

myself@host ~ [1]> telnet 172.22.0.2 8600
Trying 172.22.0.2...
telnet: Unable to connect to remote host: Connection refused
nxxxse commented 3 weeks ago

compose.yml:

    environment:
      ARGUMENTS: "-monitor unix:/run/qemu-monitor/socket.unix,server,nowait"
    volumes:
      - ./qemu-monitor:/run/qemu-monitor

And then on the host because the Docker image does not have socat installed:

$ sudo socat -,echo=0,icanon=0 unix-connect:qemu-monitor/socket.unix    
QEMU 9.1.0 monitor - type 'help' for more information
(qemu) info status
VM status: running

Works.