Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PVE 记录 -- 高权限 LXC 容器 及 虚拟机显卡直通 beta #10

Open
lisaac opened this issue May 8, 2024 · 0 comments
Open

PVE 记录 -- 高权限 LXC 容器 及 虚拟机显卡直通 beta #10

lisaac opened this issue May 8, 2024 · 0 comments

Comments

@lisaac
Copy link
Owner

lisaac commented May 8, 2024

高权限 LXC 容器配置 --- 只运行 docker 的容器

创建一个基于CT 模板为alpine 的 LXC 容器,启动之后需要修改源和安装 docker

https://mirrors.ustc.edu.cn/alpine/latest-stable/main
https://mirrors.ustc.edu.cn/alpine/latest-stable/community
#https://mirrors.ustc.edu.cn/alpine/edge/main
#https://mirrors.ustc.edu.cn/alpine/edge/community
apk update
apk add docker docker-compose

块设备

将设备挂载入容器的前提,必须对 cgroup2 进行设置,lxc.cgroup2.devices.allow: b *:* rwm,b 表示块设备,设备号可以使用 ls -l /dev 或者 lsblk 查看,: 表示所有设备号

lxc.cgroup2.devices.allow: b *:* rwm
lxc.cgroup2.devices.allow: c *:* rwm
lxc.mount.entry: /dev/sda dev/sda none bind,create=file # 对单个设备挂载入lxc容器

创建设备文件也可以使用 autodev hook 实现,这个脚本实现了所有块设备的创建。

lxc.autodev: 1
lxc.hook.autodev: sh -c 'cat /proc/partitions | awk '"'"'{if ($1~/[0-9]+/) system("mknod -m 777 ${LXC_ROOTFS_MOUNT}/dev/"$4" b "$1" "$2)}'"'"''

后续,容器使用过程中,例如硬盘重新分区,usb 等热插拔设备,想让容器能够获取到设备最新状态,可以运行下列命令:

cat /proc/partitions | awk '{if ($1~/[0-9]+/) system("rm ${LXC_ROOTFS_MOUNT}/dev/"$4" ;; mknod -m 777 ${LXC_ROOTFS_MOUNT}/dev/"$4" b "$1" "$2)}'

字符设备

同样的,与块设备一样,只需对 cgroup2 进行设置 lxc.cgroup2.devices.allow: c *:* rwm,c 表示字符设备,设备号可以使用 ls -l /dev查看,: 表示所有设备号

lxc.cgroup2.devices.allow: c *:* rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file # 将tun设备挂入 lxc 容器, 可以实现 tailscale/wireguard 等
lxc.mount.entry: /dev/dri/card0 dev/dri/card0 none bind,optional,create=file  
lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file #intel 核显设备

*所有设备

** 可能会出现意想不到的问题 **

再进一步,如果要对所有设备进行直通,可以使用,lxc.cgroup2.devices.alllow: a 表示允许所有设备,但是如何对所有文件挂载进入 lxc 容器的,同时,如果设备文件发生变化,例如硬盘重新分区,usb 等热插拔设备,需要让容器能够获取到,就需要一些小小的魔法,这里将 /dev 目录挂载为 overlayfs 并挂入容器内部,这样,lxc 容器就能直接使用 /dev 内的内容了。

lxc.cgroup2.devices.alllow: a
lxc.hook.pre-mount: sh -c "umount /tmp/.ct0_dev/merge 2&> /dev/null; rm -fr /tmp/.ct0_dev; mkdir -p /tmp/.ct0_dev/upper /tmp/.ct0_dev/work /tmp/.ct0_dev/merge; mount -t overlay overlay -o lowerdir=/dev,upperdir=/tmp/.ct0_dev/upper,workdir=/tmp/.ct0_dev/work /tmp/.ct0_dev/merge"  # 在/tmp/.ct0_dev/merge 创建 /dev 对 overlayfs
lxc.mount.entry: /tmp/.ct0_dev/merge dev none bind,create=dir # 挂载入容器
lxc.hook.post-stop: sh -c "umount /tmp/.ct0_dev/merge 2&> /dev/null; rm -fr /tmp/.ct0_dev" # 在关闭容器的时候销毁 overlayfs

网卡直通

默认情况下,pve 会给容器通过 veth 网桥来分配虚拟网卡,如果 pve 主机有多个网卡,可以将网卡直通入 lxc:

lxc.net.0.type: phys
lxc.net.0.link: enp1s0
lxc.net.0.name: eth0
lxc.net.1.ipv4.address: 10.1.1.222
lxc.net.1.ipv4.gateway: 10.1.1.1
lxc.net.1.flags: up

macvlan网卡

只有一张网卡的情况,使用 macvlan 会比 veth 带来更好的性能表现:

xc.net.1.type: macvlan
lxc.net.1.link: vmbr0
lxc.net.1.name: eth0
lxc.net.1.ipv4.address: 10.1.1.222
lxc.net.1.ipv4.gateway: 10.1.1.1
lxc.net.1.flags: up

将 /volumes 及 fstab 挂入容器

挂载 /volumes 以及 fstab 的目的是,计划通过编辑容器内的 /tmp/fstab 来实现PVE宿主开机自动挂载到 /volumes 目录中,使用 rbind参数使其,具有挂载传播性,即:宿主机挂载硬盘,在容器中也能看到。

lxc.mount.entry: /volumes volumes none rbind,create=dir
lxc.mount.entry: /etc/fstab tmp/fstab none bind,create=file

创建挂载的目录,同时为了保护 /meida 目录下的 disk* 目录,可以使用chattr修改权限

mkdir -p /volumes/disk{0..9}
chattr +i /volumes/disk{0..9}

挂载硬盘

使用fstab

创建完目录之后,可以挂载硬盘,当然如果是新硬盘,要分区格式化

mount /dev/sdXn /volumes/disk* # 根据自己情况修改

挂载完成所有硬盘之后,可以将相关挂载信息写入fstab,以便下次重启,自动挂载,这里写了一个小脚本来帮助填写

#!/bin/bash
MEDIA=/volumes
# 获取所有分区设备
partitions=$(lsblk -o UUID,MOUNTPOINT -n -l | grep -E "\s+${MEDIA}/\w+$")
# 遍历每个分区
while IFS= read -r partition; do
    # 提取分区名称、UUID 和挂载点
    uuid=$(echo $partition | awk '{print $1}')
    mountpoint=$(echo $partition | awk '{print $2}')
    # 添加分区挂载信息到 fstab
    echo "UUID=$uuid $mountpoint auto defaults 0 0"
done <<< "$partitions"

执行之后,会输出相应的配置,只要追加复制到 /etc/fstab 就行了

使用脚本

考虑到写入 fstab后,如果硬盘出现问题或者硬盘拔出后,会在启动时报错,所以考虑在容器启动时执行用脚本挂载操作。
而使用 mp0=/dev/sdaX...则会导致每次重启容器都需要卸载和挂载,带来不必要的操作。
同样创建完目录之后,可以挂载硬盘,当然如果是新硬盘,要分区格式化

mount /dev/sdXn /volumes/disk* # 根据自己情况修改
lsblk -o UUID,MOUNTPOINT -n -l | grep -E "\s+/volumes/\w+$" > /root/mttab # 创建uuid和挂载目录一一对应的文件

cat /root/mount.sh    # mount.sh 内容:
#!/bin/sh

DF=$(mount)
MEDIA="/volumes"
LSBLK=$(lsblk -o UUID,MOUNTPOINT -n -l | grep -E "\s+${MEDIA}/\w+$")
cat /root/mttab | while read line
do
  uuid=$(echo $line | awk '{print $1}')
  mountpoint=$(echo $line | awk '{print $2}')
  [ -n "$(echo ${LSBLK} | grep ${uuid})" ] || mount /dev/disk/by-uuid/$uuid $mountpoint
done

# 修改lxc 配置文件:
lxc.hook.pre-start: /root/mount.sh

挂载 proc sys cgroup tmpfs 等

lxc.mount.auto: proc:rw sys:rw cgroup-full:rw
lxc.mount.entry: tmp tmp tmpfs rw,nodev,relatime,mode=1777 0 0

权限设置

禁用 AppArmor 提升容器权限,lxc.cap.drop 空表示容器支持所有 Linux Capabilities 权限,进一步提升权限,可以使用 capsh --print 查看,可以使用smartctl 查看磁盘 S.M.A.R.T. 信息

lxc.apparmor.profile: unconfined
lxc.cap.drop:

使用部分宿主 namespaces

lxc 可以支持 clone/keep/share 等方式将宿主机的 namespaces 与容器共用。
可以使用 lxc.namespace.keep lxc.namespace.clone lxc.namespace.share.* 这三个参数进行设置,详情可以查看 man page。

但是 pve 的 pct 配置文件并不支持者三个参数,可以使用下面这个补丁来解除限制:

sed -i '/my $valid_lxc_conf_keys = {/a\    '"'"'lxc.namespace.keep'"'"' => 1,\n    '"'"'lxc.namespace.clone'"'"' => 1,\n    '"'"'lxc.namespace.share.*'"'"' => 1,' /usr/share/perl5/PVE/LXC/Config.pm
# 与宿主共享网络,相当于 docker 的 host 网络
lxc.namespace.keep: net

# 与共享宿主的 pid,相当于 docker 的 --pid=host,相当于 chroot,但是共享 pid 会导致容器中的 init 程序会无法启动,从而导致容器失败,解决办法是把容器的启动命令改成其他的,例如tini,存在的问题是,无法使用 console 进入,可以使用 pct enter 进入,我这里只直接配置了启动 dockerd:
lxc.namespace.keep:user pid net
lxc.init.cmd: env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /sbin/tini-static -sg -- dockerd

信号

lxc 默认的 stop 信号是 SIGKILL,这会倒是 tini 下面的子程序无法结束,改成SIGTERM,dockerd 的重载信号是 SIGHUP:

lxc.signal.stop: SIGTERM
lxc.signal.reboot: SIGHUP

STOP HOOK

为了保证容器停止的时候杀死容器相关的进程,例如:pct enter 101 进入容器后一些列操作,在容器关闭时,确保这些进程被杀死,添加一个停止钩子:

lxc.hook.stop: sh -c 'kill $(pgrep -f "lxc-attach -n ${LXC_NAME}") 2> /dev/null'

其他配置

  • 默认配置:可以在/usr/share/lxc/config/ 中找到 pve 自带模板的默认配置,如果要确定容器使用来哪个模板,可以在/var/lib/lxc/<CID>/config中的 include 中找到
  • seccomp默认配置: 同样在 /usr/share/lxc/config/common.seccomp 中配置

日志

lxc.log.level: 0
lxc.log.file: /tmp/lxc-100.log

整体配置文件

下载alpine 模板
第一次进入容器安装必要 docker
apk add docker docker-cli tini

# /etc/pve/lxc/100.conf
#net0: name=eth0,bridge=vmbr0,firewall=1,gw=10.1.1.1,hwaddr=BC:24:11:8B:5F:E4,ip=10.1.1.201/24,type=veth
#lxc.cgroup.cpu: /sys/fs/cgroup/cpu,cpuacct
#lxc.cgroup.memory: /sys/fs/cgroup/memory

cores: 20
memory: 81920
swap: 4096
#lxc.namespace.clone: mnt uts
arch: amd64
hostname: CT0
nameserver: 10.1.1.1
ostype: alpine
rootfs: local:101/vm-101-disk-0.raw,size=24G
timezone: Asia/Shanghai
lxc.signal.reboot: SIGHUP
lxc.signal.stop: SIGTERM
lxc.cgroup2.devices.allow: a
lxc.log.level: 0
lxc.log.file: /tmp/lxc-100.log
lxc.apparmor.profile: unconfined
lxc.cap.drop:
lxc.autodev: 1
lxc.hook.autodev: sh -c 'cat /proc/partitions | awk '"'"'{if ($1~/[0-9]+/) system("mknod -m 777 ${LXC_ROOTFS_MOUNT}/dev/"$4" b "$1" "$2)}'"'"''
lxc.mount.auto: proc:rw sys:rw cgroup-full:rw
lxc.mount.entry: /dev/dri/card0 dev/dri/card0 none rbind,optional,create=file
lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none rbind,optional,create=file
lxc.mount.entry: /dev/net/tun dev/net/tun none rbind,create=file
lxc.mount.entry: tmp tmp tmpfs rw,nodev,relatime,mode=1777 0 0
lxc.mount.entry: /etc/fstab tmp/fstab none rbind,create=file
lxc.mount.entry: /volumes volumes none rbind,create=dir
lxc.mount.entry: /sys/fs/bpf sys/fs/bpf none rbind,create=dir
lxc.hook.stop: sh -c 'kill $(pgrep -f "lxc-attach -n ${LXC_NAME}") 2> /dev/null'
lxc.seccomp.profile: /root/unconfined.seccomp
lxc.namespace.keep: user pid net ipc
lxc.init.cmd: env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /sbin/tini-static -sg -- dockerd

中文终端 locales

pct start 100 # 启动容器
pct enter 100 # 进入容器
dpkg-reconfigure locales #添加 zh.CN_UTF8

PVE 虚拟机

独显直通

bios 相关设置

  • 启用 VT-d
  • 禁用 CSM (启用CSM才能提取显卡bios rom)
  • ACS Enable # 如果存在,设置为已启用(自动不起作用)
  • 启用 4G解码 4G Decoding
  • 禁用 Resizable BAR/Smart Access Memory智能访问内存 #(如果启用,AMD GPUS(Vega 及更高版本)会遇到“代码 43 错误”)
  • 启用 IOMMU # 如果存在,主要用于 AMD 主板
  • 将主显示器设置为 CPU/iGPU # 如果您的 CPU 有 iGPU
  • 预分配内存为 64M
  • 核显设置为Enabled,同时设置主显卡为igfx(有独先情况下,在pve下禁用i915驱动,这样才能使用独显输出pve终端画面。如果不配置igfx,在有些主板上会无法使用核显)

内核启动参数

vi /etc/default/grub
# 修改 cmdline 如果是AMD处理器, 将intel_iommu改为iommu
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt initcall_blacklist=sysfb_init pci=assign-busses pcie_acs_override=downstream,multifunction"

相关参数含义

References

https://www.jinbuguo.com/kernel/boot_parameters.html
  • iommu=pt:启用 Intel VT-d 或 AMD-Vi 的 IOMMU。这是一种硬件功能,用于管理设备对系统内存的访问。在虚拟化环境中,启用 IOMMU 后,可以将物理设备直通到虚拟机中,以便虚拟机可以直接访问硬件设备。“iommu=pt”不是必须的,PT模式只在必要的时候开启设备的IOMMU转换,可以提高未直通设备PCIe的性能,建议添加。
  • initcall_blacklist=sysfb_init:禁用 sysfb_init 内核初始化函数。这个函数通常用于在内核启动过程中初始化系统帧缓冲。在使用 GPU 直通的情况下,这个函数可能会干扰直通操作,因此需要禁用它。
  • initcall_blacklist=sysfb_init:屏蔽掉pve7.2以上的一个bug,方便启动时候就屏蔽核显等设备驱动;
  • pcie_acs_override=downstream,multifunction:便于iommu每个设备单独分组,以免直通导致物理机卡死等问题
  • pci=nommconf:意思是禁用pci配置空间的内存映射,所有的 PCI 设备都有一个描述该设备的区域(您可以看到lspci -vv),访问该区域的最初方法是通过 I/O 端口,而 PCIe 允许将此空间映射到内存以便更简单地访问。
  • i915.enable_gvt=1:启用 Intel GVT-g 虚拟 GPU 技术。这个选项用于创建一个虚拟的 Intel GPU 设备,以便多个虚拟机可以共享物理 GPU 设备。启用 GVT-g 需要在支持虚拟 GPU 的 Intel CPU 和主板上运行,并且需要正确配置内核和虚拟机。想开启GVT-g的就添加这条,需要注意的是,11代之后的已经不支持,转为sr-iov

* 启动 VFIO 模块 (非必需,虚拟机直通时会自动加载相关模块)

打开 /etc/modules ,追加添加:

vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd

修改 vfio 配置文件,启用 audio

lspci -nn | grep AMD
01:00.0 PCI bridge [0604]: Advanced Micro Devices, Inc. [AMD/ATI] Navi 10 XL Upstream Port of PCI Express Switch [1002:1478] (rev c7)
02:00.0 PCI bridge [0604]: Advanced Micro Devices, Inc. [AMD/ATI] Navi 10 XL Downstream Port of PCI Express Switch [1002:1479]
03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Navi 23 [Radeon RX 6600/6600 XT/6600M] [1002:73ff] (rev c7)
03:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Navi 21/23 HDMI/DP Audio Controller [1002:ab28]

根据返回结果,得到 id, [1002:73ff],[1002:ab28]

echo "options vfio-pci ids=1002:73ff,1002:ab28" > /etc/modprobe.d/vfio.conf

更新 grub 和 initramfs

update-grub
update-initramfs -u -k all

核显直通

intel gen11+ 核显 sriov

https://github.com/strongtz/i915-sriov-dkms
更新内核后记得重新安装 dkms 内核模块

使用 displaylink 作为显示器

将 displaylink usb 端口通入虚拟机,win11 会自动安装驱动。这样 sriov 核显就可以输出画面了

References

https://linuxcontainers.org/lxc/manpages/man5/lxc.container.conf.5.html
https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pct_settings
https://github.com/strongtz/i915-sriov-dkms
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant