服务器上docker创建及qemu环境搭建
1. 需要准备的软件
1.1 VS Code

这个软件无需多言,值得一提的是,VS Code只是一个高级的编辑器,而非一个编译器,下载好之后其自身是不带任何编译和调试功能的,如果想在本机上开发c/c++项目,还需要安装Mingw-w64,理解成gcc的windows版本即可,这样就可以愉快的在本地上进行c/c++的开发了。但下载VS Code的主要目的是连接远程的服务器,直接在服务器上开发,而服务器本身是有gcc的,所以如果后续没有需要本地开发的项目,下载好VS Code之后就不需要再安装Mingw-w64了
1.2 Filezilla

这个软件使用起来很简单,主要作用是可以远程连接主机,实现本地和远程主机之间的文件上传和下载,虽然在命令行中也可以实现,但我还是喜欢这种有UI界面的实现方式


连接后就可以看到服务器和你操作的电脑的目录,可以在主机和服务器之间上传下载文件

1.3 ATrust
因为学校服务器需要连接北邮校园网才可以使用,所以如果电脑不在学校,就需要使用vpn来连接
1.4 Clash Verge
这个无需多言,自己捣鼓
1.5 MobaXterm

这是一款有很多功能的软件,主要用vnc连接后续的容器,所以也可以使用其他的vnc软件,比如RealVnc Viewer
2 . 连接服务器
2.1 申请服务器账号
因为此项目需要套三层虚拟机,所以建议在服务器上运行,如果电脑配置足够高,也可以在本机电脑上运行。
2.2 连接服务器
2.2.1 安装SSH服务插件
在vscode的扩展里,搜索remote-ssh扩展然后安装

2.2.2 连接服务器
在搜索栏里输入命令
>ssh juze.han@10.112.105.34

其中,juze.han是申请的服务器的账号名,10.112.105.34是服务器ip,值得注意的是,如果在校外,需要使用atrust连接北邮vpn才能连上,并且vscode最好使用管理员权限启动,如果不用管理员权限,我的vscode会提示ssh.exe被拒绝访问。
在进入服务器后,ls查看目录

要注意自己在服务器上的权限并非root,所以无法执行sudo系列命令
3 配置docker环境
3.1 docker理解
关于docker,理解为一个轻量化的虚拟机就可以,具有很好的移植性。配置一个Docker的流程就是三步,编写Dockerfile,生成doker镜像文件,根据镜像文件生成具体的容器。Dockerfile,Dockerimg(docker镜像),DockerContainer(容器),三者关系是递进的,镜像文件是根据Dockerfile生成的,容器是根据镜像文件生成的。一个dockerfile可以生成一个镜像文件,一个镜像文件在启动时输入不同的命令可以生成多个容器
3.2 Dockerfile
连接服务器后,创建一个Dockerfile文件夹,进入此文件夹,在vscode终端里输入
code dockerfile
此时会生成Dockerfile文件,在文件里填写如下内容
FROM ubuntu:24.04
# 定义构建时变量,可以在docker build构建通过--build-arg来修改
ARG GID=1005
ARG UID=1004
ARG username=hjz
# 设置 DEBIAN_FRONTEND 环境变量以避免交互式对话框,否则可能会卡在一些交互式输入中
ENV DEBIAN_FRONTEND=noninteractive
# 修改ubuntu的镜像源为阿里云
# ubuntu24.04版本修改软件源的位置:/etc/apt/sources.list 替换为 /etc/apt/sources.list.d/ubuntu.sources
RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list.d/ubuntu.sources \
&& sed -i s@/security.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list.d/ubuntu.sources \
&& apt-get update -y && apt-get install -y sudo locales \
&& apt-get clean \
&& echo "%sudo ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# 修改容器或系统中的 sudoers 文件,允许属于 sudo 组的用户执行 sudo 命令时无需输入密码。
# 修改语言环境(locale)设置
RUN locale-gen en_US.UTF-8 && update-locale
# 注:用户id和组id尽可能的和当前用户id一致,使得读写共享文件时的权限一致,否则可能出现docker无法写入共享文件的问题。
# 命令 id 可以查看用户的各种id
RUN getent group ${GID} || groupadd -g ${GID} dev \
    && getent passwd ${username} || useradd -ms /bin/bash -u ${UID} -g ${GID} -G sudo ${username} \
    && echo "${username}:012515" | chpasswd \
    && echo 'root:012515' | chpasswd
# 添加用户:赋予sudo权限,指定密码012515,建议docker的密码不要太复杂,太多了很容易忘记。
# 安装各种以依赖软件,可以根据需要定制,也可以后续手动安装。
# QEMU编译需要的依赖
RUN apt-get update && apt-get install -y build-essential meson ninja-build pkg-config \
                diffutils python3 python3-venv  \
                libglib2.0-dev libusb-1.0-0-dev libncursesw5-dev \
                libpixman-1-dev libepoxy-dev libv4l-dev libpng-dev \
                libsdl2-dev libsdl2-image-dev libgtk-3-dev libgdk-pixbuf2.0-dev \
                libasound2-dev libpulse-dev \
                libx11-dev libfdt-dev libiscsi-dev
# # riscv-gnu-toolchain 需要的依赖
# RUN apt-get install -y autoconf automake autotools-dev curl \
# python3 python3-pip libmpc-dev libmpfr-dev libgmp-dev gawk build-essential \
# bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev \
# ninja-build git cmake libglib2.0-dev libslirp-dev
# RUN apt-get install -y git gdb clang cmake vim gdb
# 还原 DEBIAN_FRONTEND 环境变量(可选)
ENV DEBIAN_FRONTEND=dialog
# 指定容器启动后的工作目录
WORKDIR /home/${username}
# 指定容器启动后的登录用户
USER ${username}
具体内容可以自行搜索并更改,输入后保存即可。
其中linux的发行版本和版本号可以更换,GID和UID需要改为自己在服务器中的id,可以使用id命令查看

username是容器中的用户,可以随意指定
注意:存放 Dockerfile 的文件夹中最好不要存放任何其他无关的文件,在创建镜像时,docker 会将该文件夹中的所有内容都复制到容器中,这会大大增加镜像创建的时间
3.3 docker镜像
在dockerfile所在文件夹下运行如下命令
docker build -t hjz-qemu .
	 其中 -t 后跟镜像名称,可任意更改。docker build -t hjz-qemu . 这条命令会从当前目录(.)中寻找 Dockerfile,然后根据其中的指令构建一个名为 hjz-qemu 的 Docker 镜像。
3.4 创建容器
使用如下命令根据上边创建的镜像来生成一个容器
docker run -it --network host --name hjz-qemu-con --mount type=bind,source=/home/juze.han/work/,target=/home/hjz/work --device /dev/kvm hjz-qemu
# --network host  使创建的容器处于host模式(容器与宿主机共享网络,容器的所有端口都会直接暴露在宿主机的端口上,可以设置为其他的网络模式,自行搜索)
# --name hjz-qemu-con  创建一个名为 hjz-qemu-con 的容器
# --mount type=bind,source=/home/juze.han/work/,target=/home/hjz/work 将主机的文件夹 /home/juze.han/work共享(挂载)到容器中的 /home/hjz/work 文件夹
# type=bind 表示挂载类型是绑定挂载容器对挂载的目录拥有完全的读写权限,容器内删除了文件,宿主机的文件也会删除,因此要小心操作,如果不想同步,可以使用copy指令,自行搜索
# --device /dev/kvm 表示容器可以直接访问主机/dev/kvm
# hjz-qemu 表示是使用hjz-qemu这个镜像创建的容器
执行完后就会生成一个docker容器了,容器内运行的是dockerfile里指定的系统,对我来说就是24.04版本的Ubuntu。在创建容器后,就无法再对容器的权限做更改了,想要修改容器的权限或者其他属性,就得用docker镜像重新生成一个容器,在生成容器的命令中对容器的属性进行增删改。如果不想重新在新容器里配置环境,也有可以保存当前容器状态并继承到新容器的方法,自行搜索。
3.5 连接容器
在vscode里下载dev containers扩展插件,就可以使用vscode连接自己创建的容器了,可以直接在vscode操作容器



4. Qemu源码编译
首先明确一下环境配置的顺序,qemu本身是可以模拟cpu,网卡等硬件设备的,而linux本身就是一个操作系统,linux与qemu可以模拟一个完整的计算机。目标是在qemu内运行linux,而qemu本身要运行在linux下,所以就是linux下运行qemu,qemu内再运行linux。虽然服务器本身就是linux环境,但由于是与其他同学共用,且没有root权限,所以对linux内核方面是没有操作权限的,为了获得root权限,就在服务器上生成一个属于自己的docker容器,容器内也是linux环境,并且自己在容器内是有root权限的。因此,在成功生成docker容器后,我们就可以在容器内运行qemu,然后再用qemu运行linux。本节所有操作都是在容器内进行的
4.1 目录结构
为了方便理解,贴上我服务器与容器内的目录结构


4.2 下载qemu源码
采用的是从源码编译qemu的方法,这样就可以自己进行需要的功能的配置。
首先要安装编译qemu源码需要的依赖
sudo apt -y install clang ninja-build build-essential zlib1g-dev pkg-config libglib2.0-dev binutils-dev libpixman-1-dev libfdt-dev
这一步其实有点多余,因为在Dockerfile文件里,已经写了qemu编译所需要的依赖了,但我在运行的时候不知道为什么,容器内确实没有安装,所以我又重新安装了一下,如果确定已经安装,这一步可以跳过
然后从官网下载qemu源码,最好将源码单独放一个文件夹内,如果网络不通畅,要么用vpn,要么用百度网盘从别人那下载好后,使用filezilla上传到服务器的work文件夹下存放qemu源码的文件夹(对我来说就是~/work/qemu-9.0.0),然后就会自动同步到容器内的work文件夹了
wget https://download.qemu.org/qemu-9.0.0.tar.xz
4.3 解压
下载后的源码是压缩包,需要解压
xz -d qemu-9.0.0.tar.xz
tar xvf qemu-9.0.0.tar
4.4 编译
进去qemu源码目录(~/work/qemu-9.0.9)后,新建build文件夹,用来存放编译后的文件

这里仅编译系统模式下的x86_64模拟器和aarch模拟器,一个用来跑Ubuntu,另一个在未来用来跑openwrt;启用kvm用于在模拟时加速,启用debug模式用于调试qemu:
../configure --enable-kvm --enable-debug --target-list=x86_64-softmmu,aarch64-softmmu
生成配置文件这一步如果提示python报错,可以重新安装一下python-is-python3,一般不会报错
sudo apt install python-is-python3 
然后就可以直接开始编译
ninja
之后会在build目录下生成所需的三个可执行文件:
- 
qemu-system-x86_64x86_64的模拟器
- 
qemu-img用于生成虚拟磁盘文件,对guest来说就是磁盘
- 
qemu-system-aarch64aarch64的模拟器 
5. Qemu中Ubuntu环境搭建
5.1 下载ubuntu镜像
操作路径为~/work/download

wget https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/22.04/ubuntu-22.04.4-desktop-amd64.iso
这里安装Ubuntu的desktop版本的系统,虽然server版本的也能用,但配置网络环境的过程过于繁琐,而且server版本启动时需要连接网络读取配置文件,在没有配置好qemu网络时启动非常的慢。
如果下载很慢,可以更换为其他镜像源,但我在下载时不管阿里还是清华都只有几十kb的下载速度,最后我去ubuntu官网下载到我主机上,然后通过filezilla上传到了服务器对应文件夹内

5.2 制作虚拟磁盘
不需要手动制作文件系统,Ubuntu的安装程序会自动的格式化磁盘并创建文件系统的目录,因此仅需要创建一个虚拟磁盘文件即可;
使用qemu自带的工具创建磁盘文件,这里创建一个名为ubuntu.qcow2的文件,注意文件路径即可
../qemu-9.0.0/build/qemu-img create -f qcow2 ubuntu.qcow2 40G
5.3 安装系统
/home/hjz/work/qemu-9.0.0/build/qemu-system-x86_64 \
-m 2G \
-drive format=qcow2,file=ubuntu.qcow2 \
-cdrom /home/hjz/work/download/ubuntu-22.04.2-desktop-amd64.iso \
-nographic \
-vnc 10.112.105.34:6 \
-enable-kvm
各个参数的作用自行搜索即可,比较简单
安装好后再次启动ubuntu时,可以不再加-cdrom /home/hjz/work/download/ubuntu-22.04.2-desktop-amd64.iso 这条命令,推荐把命令保存到.sh脚本文件,直接运行.sh文件即可启动,不需要再敲一大串命令
注意:脚本.sh文件不能有空行,不能通过注释的方法取消掉命令,需要直接删除一整行,并且脚本文件默认没有执行权限,需要使用chmod修改文件的执行权限
我在启动ubuntu这一步时遇到了报错,如下

报错内容是访问不到服务器上的kvm设备,kvm作用是加速虚拟化
原因是我在构建容器时输入的命令
docker run -it --network host --name hjz-qemu-con --mount type=bind,source=/home/juze.han/work/,target=/home/hjz/work --device /dev/kvm hjz-qemu
没有添加--device /dev/kvm hjz-qemu这一项,而且我在服务器上也不是kvm组,导致我的容器无法访问kvm设备
有两种方法解决报错,第一是将上述启动ubuntu命令的最后一行-enable-kvm去掉,这样就可以启动ubuntu了,但后果是启动过程极其漫长,我等了十分钟都没进去。
我采用的第二种办法,就是联系学校服务器管理员将我加入kvm组,然后在容器里sudo su进入root,再运行上述命令就可以正常启动了。
5.4 vnc连接容器
可以发现,在上一步里有个-nographic的参数,为了可视化ubuntu,就可以使用vnc软件连接容器,在上一步有个命令-vnc 10.112.105.34:6,设置好了vnc监听的端口。qemu启动的虚拟机默认会在5900端口上启动一个vnc server,这里的:6并不是指的端口号为6,而是指vnc server的编号,0代表的端口号为5900,1代表5901,以此类推。
然后再VNC软件内输入ip和端口号,即可连接qemu启动的ubuntu



6 Qemu网络配置
安装Ubuntu目的就是利用其丰富的线上安装包仓库来安装各种工具,方便我们对qemu进行开发;qemu提供了多种方式让guest连接Internet,其原生支持启动时自动执行/etc/qemu-ifup脚本来创建tap虚拟网卡并连接Internet,关闭时自动执行/etc/qemu-ifdown脚本删除tap虚拟网卡;
由于从源码编译的qemu-system-x86_64并没有安装进host系统中,因此执行的脚本路径变为/build/qemu-bundle/usr/local/etc/
编译完成后/build/qemu-bundle/usr/local/路径下并没有/etc目录,需要手动新建目录和文件,并写入qemu wiki上指定的脚本
执行该脚本需要先在host系统中安装如下工具:
sudo apt install bridge-utils iptables dnsmasq
qemu-ifup
#!/bin/sh
#
# Copyright IBM, Corp. 2010  
#
# Authors:
#  Anthony Liguori <aliguori@us.ibm.com>
#
# This work is licensed under the terms of the GNU GPL, version 2.  See
# the COPYING file in the top-level directory.
# Set to the name of your bridge
BRIDGE=br0
# Network information
NETWORK=192.168.53.0
NETMASK=255.255.255.0
GATEWAY=192.168.53.1
DHCPRANGE=192.168.53.2,192.168.53.254
# Optionally parameters to enable PXE support
TFTPROOT=
BOOTP=
do_brctl() {
    brctl "$@"
}
do_ifconfig() {
    ifconfig "$@"
}
do_dd() {
    dd "$@"
}
do_iptables_restore() {
    iptables-restore "$@"
}
do_dnsmasq() {
    dnsmasq "$@"
}
check_bridge() {
    if do_brctl show | grep "^$1" > /dev/null 2> /dev/null; then
    return 1
    else
    return 0
    fi
}
create_bridge() {
    do_brctl addbr "$1"
    do_brctl stp "$1" off
    do_brctl setfd "$1" 0
    do_ifconfig "$1" "$GATEWAY" netmask "$NETMASK" up
}
enable_ip_forward() {
    echo 1 | do_dd of=/proc/sys/net/ipv4/ip_forward > /dev/null
}
add_filter_rules() {
do_iptables_restore <<EOF
# Generated by iptables-save v1.3.6 on Fri Aug 24 15:20:25 2007
*nat
:PREROUTING ACCEPT [61:9671]
:POSTROUTING ACCEPT [121:7499]
:OUTPUT ACCEPT [132:8691]
-A POSTROUTING -s $NETWORK/$NETMASK -j MASQUERADE 
COMMIT
# Completed on Fri Aug 24 15:20:25 2007
# Generated by iptables-save v1.3.6 on Fri Aug 24 15:20:25 2007
*filter
:INPUT ACCEPT [1453:976046]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [1605:194911]
-A INPUT -i $BRIDGE -p tcp -m tcp --dport 67 -j ACCEPT 
-A INPUT -i $BRIDGE -p udp -m udp --dport 67 -j ACCEPT 
-A INPUT -i $BRIDGE -p tcp -m tcp --dport 53 -j ACCEPT 
-A INPUT -i $BRIDGE -p udp -m udp --dport 53 -j ACCEPT 
-A FORWARD -i $1 -o $1 -j ACCEPT 
-A FORWARD -s $NETWORK/$NETMASK -i $BRIDGE -j ACCEPT 
-A FORWARD -d $NETWORK/$NETMASK -o $BRIDGE -m state --state RELATED,ESTABLISHED -j ACCEPT 
-A FORWARD -o $BRIDGE -j REJECT --reject-with icmp-port-unreachable 
-A FORWARD -i $BRIDGE -j REJECT --reject-with icmp-port-unreachable 
COMMIT
# Completed on Fri Aug 24 15:20:25 2007
EOF
}
start_dnsmasq() {
    do_dnsmasq \
    --strict-order \
    --except-interface=lo \
    --interface=$BRIDGE \
    --listen-address=$GATEWAY \
    --bind-interfaces \
    --dhcp-range=$DHCPRANGE \
    --conf-file="" \
    --pid-file=/var/run/qemu-dnsmasq-$BRIDGE.pid \
    --dhcp-leasefile=/var/run/qemu-dnsmasq-$BRIDGE.leases \
    --dhcp-no-override \
    ${TFTPROOT:+"--enable-tftp"} \
    ${TFTPROOT:+"--tftp-root=$TFTPROOT"} \
    ${BOOTP:+"--dhcp-boot=$BOOTP"}
}
setup_bridge_nat() {
    if check_bridge "$1" ; then
    create_bridge "$1"
    enable_ip_forward
    add_filter_rules "$1"
    start_dnsmasq "$1"
    fi
}
setup_bridge_vlan() {
    if check_bridge "$1" ; then
    create_bridge "$1"
    start_dnsmasq "$1"
    fi
}
setup_bridge_nat "$BRIDGE"
if test "$1" ; then
    do_ifconfig "$1" 0.0.0.0 up
    do_brctl addif "$BRIDGE" "$1"
fi
qemu-ifdown
#! /bin/sh
# Script to shut down a network (tap) device for qemu.
# Initially this script is empty, but you can configure,
# for example, accounting info here.
#! /bin/sh
switch=br0
brctl delif ${switch} $1
ifconfig $1 down 
#ip link set $1 down
#tunctl -d $1
对应的需要在qemu启动脚本start.sh里修改启动命令
/home/hjz/work/qemu-9.0.0/build/qemu-system-x86_64 \
-m 2G \
-drive format=qcow2,file=ubuntu.qcow2 \
-cdrom /home/hjz/work/download/ubuntu-22.04.2-desktop-amd64.iso \
-nographic \
-vnc 10.112.105.34:6 \
-net tap -net nic \
-enable-kvm
之后执行qemu启动脚本即可

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号