分类目录归档:docker

Proxmox中LXC容器中docker调用宿主机的显卡加速

刚整了个AgentDVR,里面设置支持GPU解码,于是寻思着整一个,因为J4125的CPU带6路视频录像确实有点吃力,即使是使用了摄像头的stream2,用的是低分辨率的视频流来进行分析,对这个轻量级的CPU来说还是有点吃力。

于是进行了一番Google之后,顺利实现,下面记录一下过程。注意的是,我这里使用的是intel的核显,也就是vaapi,如果是AMD或者Nvidia的显卡的话,肯定会有点不一样,具体的差别需要继续搜索。

宿主机安装驱动

首先,安装显卡驱动和相关的查看工具

apt install i965-va-driver intel-media-va-driver vainfo

根据包的说明i965-va-driver是给Intel G45 & HD Graphics的显卡驱动,而intel-media-va-driver是8代之后的显卡驱动,可以根据实际情况酌情选择。至于vainfo,就是查看相关信息的工具。安装之后执行vainfo,可以看到显卡的加速信息:

root@pve4:~# vainfo
error: can't connect to X server!
libva info: VA-API version 1.10.0
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
libva info: Found init function __vaDriverInit_1_10
libva info: va_openDriver() returns 0
vainfo: VA-API version: 1.10 (libva 2.10.0)
vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics - 21.1.1 ()
vainfo: Supported profile and entrypoints
      VAProfileMPEG2Simple            : VAEntrypointVLD
      VAProfileMPEG2Main              : VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointEncSliceLP
      VAProfileH264High               : VAEntrypointVLD
      VAProfileH264High               : VAEntrypointEncSliceLP
      VAProfileJPEGBaseline           : VAEntrypointVLD
      VAProfileJPEGBaseline           : VAEntrypointEncPicture
      VAProfileH264ConstrainedBaseline: VAEntrypointVLD
      VAProfileH264ConstrainedBaseline: VAEntrypointEncSliceLP
      VAProfileVP8Version0_3          : VAEntrypointVLD
      VAProfileHEVCMain               : VAEntrypointVLD
      VAProfileHEVCMain10             : VAEntrypointVLD
      VAProfileVP9Profile0            : VAEntrypointVLD
      VAProfileVP9Profile2            : VAEntrypointVLD

能看到显卡支持的编码,就算是成功了。这时候,可以在母机的/dev/dri/文件夹下面看到以下内容:

root@pve4:/etc/pve/lxc# ls -als /dev/dri
total 0
0 drwxr-xr-x  3 root root        100 Mar 12 16:22 .
0 drwxr-xr-x 23 root root       5140 Mar 17 07:40 ..
0 drwxr-xr-x  2 root root         80 Mar 12 16:22 by-path
0 crw-rw----  1 root video  226,   0 Mar 12 16:22 card0
0 crw-rw----  1 root render 226, 128 Mar 12 16:22 renderD128

将显卡透传给LXC容器

先给上两个参考文档:

简单来说,就是直接用文本编辑器修改lxc容器的配置文件,加上这么一块

lxc.cgroup2.devices.allow: c 226:0 rwm
lxc.cgroup2.devices.allow: c 226:128 rwm
lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file
lxc.mount.entry: /dev/dri/card0 dev/dri/card0 none bind,optional,create=file

根据proxmox论坛里面的说明,如果是proxmox 6或者之前的,就用lxc.cgroup。如果是Promox 7或者之后的,就用lxc.cgrpup2。保存退出。

启动LXC容器,再看看/dev/dri/就能看到已经挂载了:

root@ispy:~# ls -als /dev/dri
total 0
0 drwxr-xr-x 2 root   root          80 Mar 17 09:44 .
0 drwxr-xr-x 8 root   root         520 Mar 17 09:44 ..
0 crw-rw---- 1 nobody nogroup 226,   0 Mar 12 08:22 card0
0 crw-rw---- 1 nobody nogroup 226, 128 Mar 12 08:22 renderD128

然后,就是最后一个问题,权限问题,由于lxc容器有隔离,从上面的结果可以看到card0和renderD128被映射成了nobody:nogroup的ownership。这时候就有这么个几种解决办法:

简单粗暴,母机对两个设备给与666的权限

在母机上,直接运行

chmod 666 /dev/dri/card0 /dev/dri/renderD128

使得显卡所有人可以读写。但是这种方法存在一定的安全隐患,所以用来测试配置是否成功的时候可以用,测试OK的话,应该换个别的方法来赋予权限。

idmap+usermod

思路是这样,lxc容器有个设定,叫做idmap,用来控制容器内部的uid和gid如何映射到母机上对应的部分的。例如这个参考:https://bookstack.swigg.net/books/linux/page/lxc-gpu-access

首先,考虑到两个设备默认的权限是660,所以我们有几个方向:

  • 将LXC容器中需要调用显卡的用户映射为母机的root(明显有严重的安全隐患,而且无法处理多个用户需要调用显卡的情况,pass)
  • 修改/dev/dri/card0 /dev/dri/renderD128的ownership,然后和上面一样将LXC容器中需要调用显卡的用户映射为这个新的owner(也还行,但是同样无法处理多用户同时调用,而且母机系统的变动比较多)
  • 将LXC容器中的video组和render组映射到母机的video和render组,然后将需要调用显卡的用户都加入这两个组(不错,但是对于不同的系统,就需要检查对应的/etc/group,并进行对应的调整)

我选择第3种方法。

首先,要确认一下系统的video组和render组,首先是母机的,因为系统都是proxmox(debian),所以gid理论上都是一样的,video的gid是44,render是103:

root@pve4:/etc/pve/lxc# cat /etc/group
……
video:x:44:root
……
render:x:103:root
……

然后就是检查LXC容器的用户组,我用的是Ubuntu20.04,video是gid是44,render是107:

root@ispy:/# cat /etc/group
……
video:x:44:
……
render:x:107:

准备工作就做好了。接下来关掉容器,直接用文本编辑器修改LXC容器的配置文件,加入这么一块内容:

lxc.idmap: u 0 100000 65535
lxc.idmap: g 0 100000 44
lxc.idmap: g 44 44 1
lxc.idmap: g 45 100045 62
lxc.idmap: g 107 103 1
lxc.idmap: g 108 100108 65427

下面给一点解释:

lxc.idmap: u 0 100000 65535		//映射LXC容器中的uid,将容器中[0-65535)映射为母机的[100000-165535)
lxc.idmap: g 0 100000 44		//映射LXC容器中的gid,将容器中[0-44)映射为母机的[100000-100044)
lxc.idmap: g 44 44 1			//映射LXC容器中的gid,将容器中[44-45)映射为母机的[44-45)
lxc.idmap: g 45 100045 62		//映射LXC容器中的gid,将容器中[45-106)映射为母机的[100045-100106)
lxc.idmap: g 107 103 1			//映射LXC容器中的gid,将容器中[107-108)映射为母机的[103-104)
lxc.idmap: g 108 100108 65427	//映射LXC容器中的gid,将容器中[108-65535)映射为母机的[100108-165535)

然后就是要配置/etc/subgid文件,添加以下内容:

root:44:1
root:103:1

最后,把LXC容器开起来,将需要调用显卡的用户加入两个组:

root@ispy:/# usermod -G video root
root@ispy:/# usermod -G render root

执行测试

测试的方法和母机差不多,都是用vainfo来检查,或者使用ffmpeg来测试,但这两个都需要安装一堆软件包,所以这时候,先给容器打个快照。

vainfo的方法可以参考母机,不同的发行版有不同的包管理器,安装vainfo然后运行就能看到结果了。

另外一个方法是使用ffmpeg,这个更接近实际应用,毕竟很多应用也是通过调用ffmpeg来执行编解码的操作。具体ffmpeg的使用方法可以参考官方的文档

这里给出比较有用的一个参考:

ffmpeg -hwaccel vaapi -hwaccel_output_format vaapi -i input.mp4 -f null -

这样可以让ffmpeg调用vaapi来进行解码,并且不输出任何东西,如果能够顺利解码,那就算是成功了。

LXC透传给Docker

透传给Docker的部分不算非常困难,只需要在docker-compose文件里面加上这么一节就可以透进去了:


version: '2.4'
services:
  agentdvr:
    image: doitandbedone/ispyagentdvr
    restart: unless-stopped
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 8090:8090
      - 3478:3478/udp
      - 50000-50010:50000-50010/udp
    volumes:
      - ./ispyagentdvr/config:/agent/Media/XML
      - ./ispyagentdvr/media:/agent/Media/WebServerRoot/Media
      - ./ispyagentdvr/commands:/agent/commands
    devices:
      - /dev/dri:/dev/dri

但是我们遇到了和LXC透传的时候一样的问题,文件权限的问题。ispy提供的docker镜像是基于Ubuntu18.04的,


root@34c54716eee7:/# ls -las /dev/dri
total 0
0 drwxr-xr-x 2 root   root        80 Mar 17 17:44 .
0 drwxr-xr-x 6 root   root       360 Mar 17 17:44 ..
0 crw-rw---- 1 nobody video 226,   0 Mar 12 16:22 card0
0 crw-rw---- 1 nobody   107 226, 128 Mar 12 16:22 renderD128
root@34c54716eee7:/# cat /etc/group 
root:x:0:
……
video:x:44:
……
messagebus:x:102:

容器里面就没有render组,root用户也不在两个用户组里面。于是还得自己build一下镜像……

root@ispy:~/agentdvr# cat build/Dockerfile 
FROM doitandbedone/ispyagentdvr
RUN     groupadd -g 107 render
RUN     usermod -g 44 root
RUN     usermod -g 107 root

完了之后就可以在docker 容器中调用GPU进行加速了。

建立私有docker镜像源

参考:https://linuxhint.com/setup_own_docker_image_repository/

最近非常着迷docker和kubernetes这玩意,感觉完全就是未来发展方向,于是决定好好研究一番。

有时候服务器处于内网环境,不好上网扒镜像,又或者说,由于一些众所周知的原因,从docker hub扒镜像会非常的缓慢,所以,我觉得有必要建立一个本地的docker镜像源。

建立方法

建立方法很简单,docker官方提供了一个镜像方便人们建立私有的镜像源。

根据参考文档,可以直接用一条命令来将服务跑起来,就像这样:

docker container run -d -p 5000:5000 --name registry -v<br> ~/docker/registry:/var/lib/registry registry

不过我个人推荐使用yaml文件+docker compose,方便日后维护,也习惯一下这种方式,为以后上kubernetes做准备。我就弄了一个,内容如下:

[root@docker-repo ~]# cat repo.yaml 
version: "2.4"
services:
  private_docker_repo:
    image: library/registry:latest
    ports:
      - "5000:5000"
    restart: unless-stopped
    volumes:
      - /storage:/var/lib/registry

然后,只要用

docker-compose -f repo.yaml up -d

就可以将服务跑起来了。-f参数是用来指定yaml文件,如果使用了docker-compose.yml作为文件名,可以省略掉。不过推荐自定义文件名,这样就可以将很多yaml放在同一个文件夹了。

日后如果registry镜像有更新,或者是修改什么设置,只要修改好配置文件,再pull一次和up一次即可,docker-compose会处理好更新的。

例子中,我的给机器挂了个100G的盘,挂在了/storage,到时候,数据会存在这里。

DNS修改

然后就是处理dns或者hosts,根据参考文档来看,不做也行,不过做了的话,可以少打几只字。( =ω=)

推荐做dns强制解析,那就不需要和原文一样去修改hosts。以后镜像源迁移、或者是docker母机增加减少之类的就不需要每次都处理hosts文件了。

举个例子,我修改DNS,将pdr(private docker registry)解析为镜像源服务器地址。

在docker母机上增加源

默认情况下,docker只会从docker hub拉镜像,要指定似有的源,就要修改一下配置文件。配置文件是/etc/docker/daemon.json,如果没有的话,就新建一个,添加以下内容:

{
"insecure-registries": ["pdr:5000", "192.168.11.207:5000"]
}

然后,重启docker。(对,是要重启docker的,所有容器都会重启)然后就可以在客户端使用这个似有源了。

对于在私有源上的镜像,用法和官方源差不多,只不过用户名变成了“服务器ip:端口”,于是,镜像名称格式如下:

IP:PORT/IMAGE_NAME:TAG_NAME

比如说,mariadb,在docker hub上,镜像的名称就是mariadb,或者其他用户的话就是xxx/mariadb(xxx是用户名)。但是在私有源上,就是192.168.11.207:5000/mariadb,或者在处理了dns或者hosts之后,可以用pdr:5000/mariadb。(看,可以少打很多个字!)

至此,似有镜像源搭建完成!

至于镜像源里面有什么镜像和有什么tag这个我暂时还没发现,暂时只发现在/storage/docker/registry/v2/repositories能看到有啥镜像。根据官方文档的说明:可以使用 http://服务器IP:5000/v2/_catalog 查看都有什么image。

列出镜像

做了个小脚本,放在docker-compose文件挂载的目录里面,我的是/storage,然后就可以直接列出镜像和列出指定镜像的tag了

#!/bin/bash
case $1 in
	"")
		ls -l docker/registry/v2/repositories/ | grep "drwx" | awk '{print $9}'
		;;
	*)
		ls -l docker/registry/v2/repositories/$1/_manifests/tags/ | grep "drwx" | awk '{print $9}'
		;;
esac

效果如下图