本精神病人又来闹妖啦!
自从有了这种容器化技术之后,想要做测试,构建点什么,或者简单的体验下某个 Linux Distros 的 cli 就变的无比简单:
- -<255:%>- docker run —rm –it ubuntu:20.04 bash
- Unable to find image ‘ubuntu:20.04’ locally
- 20.04: Pulling from library/ubuntu
- 7b1a6ab2e44d: Already exists
- Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
- Status: Downloaded newer image for ubuntu:20.04
- root@aa28b038528e:/# lscpu
- Architecture: x86_64
- CPU op–mode(s): 32–bit, 64–bit
- Byte Order: Little Endian
我们也可以在容器里一顿操作,然后构建出来一个东西拿来用,比如之前在编译 LEDE 固件时,我就是这么玩的。
某一天,我突然接到这样一个需求:使用 pyinstaller 把 Python 脚本编译成不同平台的二进制文件,并且要尽可能跑在容器内。这怎么做?
- 准备 Windows macOS 和 Linux 的机器,编译然后拿文件。太麻烦了
- pyinstaller 交叉编译?不好意思 pyinstaller 不支持交叉编译,更准确的说是 1.5 版本移除了这个功能
- Linux container 跑一个 wine,wine 里跑 Windows 版本的 Python?逆向工程真的靠谱嘛?会出现很多奇奇怪怪的 bug 吧
- 在 docker 里想办法跑个 Windows?哎嘿?
当你兴致勃勃的打开 Docker Hub,搜索 windows 发现竟然有 Windows 的 image,Windows base OS images,Windows Server Core,不管了先跑为敬:
- -<130:%>- docker run mcr.microsoft.com/windows:20H2
- Unable to find image ‘mcr.microsoft.com/windows:20H2’ locally
- 20H2: Pulling from windows
- docker: no matching manifest for linux/amd64 in the manifest list entries.
- See ‘docker run –help’.
docker 其实是使用了 Linux 内核的 cgroup 等技术来实现的,容器还是使用的宿主机内核,容器的进程其实就是跑在宿主机上的,宿主机还能看见只不过有一定隔离。Windows 并不用 Linux Kernel,那跑个什么嘛。上面这些 image 的 OS Arch 其实也是 windows/amd64 的。
可能更需要的是这个, 确实可以跑 Windows container,只不过需要 Windows host 啦。并且 1709 的 Windows 只能跑 1709 的 image
这不就又回到了开头嘛。
突然想起来有一个很神奇的项目 Docker-OSX,其实现,简单说是用 KVM 跑了个 macOS 的虚拟机,就像黑苹果一样。那么理论上 Windows 也一定能找到办法做到。那么就上 qemu!不行我们就软件模拟!
套娃嘛,我最强。
目录
运行环境检查
无论是 VM,还是物理机其实都可以。
KVM 支持
如果能有 KVM 支持的话,虚拟机应该会跑的更流畅。
如果你是 VM,CPU 支持 Intel VT-x/ AMD-v 等,那么在虚拟化软件的设置中就可以看到
ESXi 是这样的
Fusion 是这样的
勾选之后, ls /dev/kvm
就应该有结果啦
容器启用 privileged mode
docker run --privileged xxx
即可,主要是为了能够在容器内访问宿主机的/dev/kvm
。当然也可以用--device
在容器内准备 qemu 一箩筐
以 Ubuntu 20.04 为例
- apt update && apt install qemu–kvm
准备 ISO
以 alpine 为例,方便测试
- wget https://dl-cdn.alpinelinux.org/alpine/v3.15/releases/x86_64/alpine-virt-3.15.0-x86_64.iso
创建 vm 并启动
为了 PoC,我们直接创建 vm 然后启动试试看,-nographic
因为我们在 CLI 下,所以不启用图形界面,-enable-kvm
因为宿主机有 kvm 支持,-cdrom
指定光驱
- qemu–system–x86_64 –nographic –enable–kvm –cdrom /root/alpine–virt–3.15.0–x86_64.iso
意料之中,出现了 Alpine 的启动画面
并且能看到 qemu 默认配置的内存和 CPU。这个 QEMU Virtual CPU 是不是在某些 VPS 中看到过呢?
想要退出 qemu,先按 ctrl a,然后松开,迅速按 x
使用 VNC
既然无图形界面的 alpine 可以,那么安装个 Ubuntu Desktop 也一定可以。
这种情况下就需要我们使用 VNC 啦。
VNC 的默认端口是 5900,我们要在启动容器的时候同时 publish 这个端口,方便我们使用 VNC 客户端
- docker run —rm —privileged –p 5900:5900 –it –v $(pwd):/root/ bennythink/vmid bash
由于 Ubuntu Desktop 需要比较多的内存和 CPU,因此我们这里也多加几个参数
- qemu–system–x86_64 –nographic –enable–kvm –m 4096 –cpu host –smp 2 –cdrom /root/ubuntu–20.04.3–desktop–amd64.iso.iso –vnc 0.0.0.0:0
-m
表示内存大小,单位是 MiB,也可以 -m 2g
表示 2GiB 的内存
-cpu
表示 CPU 类型,之前上面我们看到 CPU 是 QEMU Virtual CPU,在这里我们可以指定 CPU,比如 -cpu EPYC
表示虚拟机看到的 CPU 是 AMD EPYC,当然也要审时度势,你一个 x86-64 的 image,就别想着用 486 了。有几个特殊的值:host
表示使用和宿主机一样的 CPU,base 的话比较鸡肋顾名思义就是基础的什么指令集都没有,max 会把所有的支持的指令集和 kvm 能提供的指令集都加上。
-smp
就是几核心的 CPU 啦,这要看宿主机量力而行。就像 make 时要量力而行一样
-vnc
表示使用 VNC,0.0.0.0 表示监听 0.0.0.0,冒号后的 0 表示 5900,如果用0.0.0.0:1
,那么监听的端口就是 5901 啦,要注意哦。这种情况下开启的 VNC 是没有密码的,如果想要密码那就 -vnc 0.0.0.0:0,password
然后设置密码…… 比较复杂,建议放弃。
然后使用 VNC 客户端连接即可,要注意 macOS 自带的 VNC 客户端需要密码,但是我们并没有设置密码,因此是用不了的。使用 VNC viewer 可破
- brew install —cask vnc–viewer
我们已经能够看到 Ubuntu 的安装界面啦,并且在这台 VM 中,网络也是通的,QEMU 分配了一个 10 段 IP. 如果你发现不能 ping,那么在 host 中 sysctl -w net.ipv4.ping_group_range='0 2147483647'
就可以啦。
分配磁盘
像往常使用虚拟机一样,我们也需要为虚拟机分配一个虚拟磁盘,这样才能够安装操作系统。总不能每次都从光盘启动吧。分配虚拟磁盘也有很多讲究,比如 10G 是一下子都分配了,还是用多少分配多少?
- qemu–img create –f qcow2 /root/test.qcow2 16G
-f 表示虚拟磁盘的格式,支持 raw 和 qcow2,raw 顾名思义就是原始的,对 vm 来说就是一个块设备。如果 vm 文件系统支持空洞,那么 raw 是一点点填满的,raw 性能较好。qcow2 是 qemu 推荐的格式,支持加密快照等等,别想了就这个吧。当然 qemu 还支持 vmdk、vdi 之类的。
需要注意的是,别给磁盘撑爆了。
安装 Ubuntu Desktop
创建好磁盘之后,使用如下命令即可开始开 vm 安装 Ubuntu
- qemu–system–x86_64 –nographic –enable–kvm –m 4096 –cpu host –smp 4 –drive file=/root/test.qcow2 –cdrom /root/ubuntu–20.04.3–desktop–amd64.iso –vnc 0.0.0.0:0
安装过程和普通 VM 没什么差别,就是…… 慢了点。
启动 VM
安装好之后,下次我们可以直接启动 VM 了,可以删掉 – cdrom 参数
- qemu–system–x86_64 –nographic –enable–kvm –m 4096 –cpu host –smp 4 –drive file=/root/test.qcow2 –vnc 0.0.0.0:0
跑 arm……
都知道 qemu 是个模拟神器,想要跑 arm 也不是不可能,比如跑个 liveCD 做个测试什么的:
- apt install qemu–system–arm
- dd if=/dev/zero of=flash0.img bs=1M count=64
- dd if=/usr/share/qemu–efi/QEMU_EFI.fd of=flash0.img conv=notrunc
- dd if=/dev/zero of=flash1.img bs=1M count=64
- qemu–system–aarch64 –m 1024 –cpu cortex–a57 –M virt –nographic –pflash flash0.img –pflash flash1.img –drive if=none,file=alpine–virt–3.15.0–aarch64.iso,id=hd0 –device virtio–blk–device,drive=hd0 –net nic –net user
这次启动的速度就会非常慢了,如果启动成功之后没有网络,那么要开下 dhcp,以 alpine 为例
- vi /etc/network/interfaces
- auto lo
- iface lo inet loopback
- auto eth0
- iface eth0 inet dhcp
然后ifup eth0
即可
如果想要安装到某个磁盘中,拿 Ubuntu 为例
- qemu–img create –f qcow2 ubuntu.qcow2 8G
- mount –o loop ubuntu–20.04.3–live–server–arm64.iso /mnt
- cp /mnt/casper/vmlinuz ./
- cp /mnt/casper/initrd ./
- qemu–system–aarch64 –m 1024 –cpu cortex–a57 –smp cpus=4 –M virt –nographic \
- –kernel ./vmlinuz –initrd ./initrd \
- –drive file=ubuntu.qcow2,if=none,format=qcow2,id=hd0 –device virtio–blk–device,drive=hd0 \
- –drive file=ubuntu–20.04.3–live–server–arm64.iso,if=none,format=raw,id=hd1 –device virtio–blk–device,drive=hd1 \
- –net nic –net user
之后开始正常的安装步骤,会比较慢,而且电脑起飞。
安装好就可以去掉 iso 那行单独启动了。
实际上,Docker 的 Multi Arch build 也是恰巧利用了 QEMU 啦。
binfmt
如果只是简单的想要跑下异构的 image 的话,可以试试 binfmt,Windows 和 Mac 的 Docker Desktop 直接支持,Linux 需要先这样:
- docker run —rm —privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d
然后跑吧
- docker run –it —platform linux/arm64 alpine sh
使用已有的 cloud image
我们当然可以选择把 OS 安装到虚拟磁盘中,但是这样要麻烦一些,安装过程比较繁琐而且很慢。有没有办法用别人已经准备好的 qcow2 呢?
当然有啦。对于 Ubuntu 来说,可以去下载 cloud image,img 结尾的就是 https://cloud-images.ubuntu.com/focal/current/
然后我们需要创建一个 user data 来初始化我们的密码
- cat >user–data <<EOF
- #cloud-config
- password: 123456
- chpasswd: { expire: False }
- ssh_pwauth: True
- EOF
- cloud–localds user–data.img user–data
- qemu–system–x86_64 –nographic –enable–kvm –m 2048 –cpu host –smp 4 –drive file=/root/focal–server–cloudimg–amd64.img –drive file=user–data.img,format=raw
等待启动,用户名 ubuntu 密码 123456
之后你的所有更改实际上都会写入到这个 img 文件中,下次启动的时候就不用 user-data 啦。
那这个 img 只有 2G,不够用怎么办?qemu-img
扩容!
- root@e769eaf9e4ab:~# qemu-img resize focal-server-cloudimg-amd64.img +5G
- Image resized.
- root@e769eaf9e4ab:~# qemu-img info focal-server-cloudimg-amd64.img
- image: focal–server–cloudimg–amd64.img
- file format: qcow2
- virtual size: 7.2 GiB (7730102272 bytes)
- disk size: 601 MiB
- cluster_size: 65536
- Format specific information:
- compat: 0.10
- refcount bits: 16
然后…… 如果有 LVM,那就好办了,如果没 LVM,那就 liveCD 扩容吧。所以建议一开始就qemu-img resize
好。
参考资料
https://github.com/kholia/OSX-KVM
https://blog.ihomura.cn/2020/11/12/%E5%9C%A8qemu-system%E4%B8%8A%E8%B7%91arm-Debian/
https://hub.docker.com/repository/docker/bennythink/vmid