二编前言
以下内容仅供参考,并不推荐复现
之前由于ubuntu 20.04上内核过老,对于一些应用的GUI不支持,一直想要升级。而正好服务器上在使用docker来运行实验(采集数据),于是想到了在本地也使用docker,同时也可以给电脑升级。但是在绕了一圈后发现了许多问题。
首先是在服务器上使用docker,大部分情况其实没有必要,因为服务器上由于计算资源不固定,对于测试和数据采集来说会影响呈现结果,因而仅推荐进行模型训练,而模型训练大部分情况下则只需要使用python环境;同时如果要在服务器上测试与采集数据,虚拟屏幕的配置会比较复杂,而测试结果(例如视频)的回传也会比较复杂。于是,推荐在本地PC上进行数据的采集与测试。而在本地使用docker,一是镜像会占用大量空间;二是配置复杂,多出了许多操作。综上所述,虽然采用docker可以标准化训练与测试环境,但是带来的额外操作也非常多,个人在此处选择放弃docker的使用。
前言
在运行vitfly无人机仿真数据采集时,发现在headless服务器运行虚拟显示器,采集视觉数据总会遇到问题,于是改为在本地PC上采集数据。而为了标准化,想到本地环境也可以使用docker。
镜像构建
首先给出如下的Dockerfile
1 | FROM nvidia/cuda:11.8.0-devel-ubuntu20.04 |
其中的基础镜像部分,ros官方其实提供了自己的docker镜像,可以用以下方式拉取
1 | sudo docker pull osrf/ros:noetic-desktop-full |
但是由于本实验需要使用nvidia显卡即会用到cuda,可以预见的从配置好cuda的基础镜像去下载ros,会比从配置好ros的基础镜像去下载cuda更方便。在dockerfile所在文件夹运行docker build -t vitlfy .构建镜像
本地运行
使用以下脚本来运行容器
1 | Allow Docker containers to connect to the X server |
这里有许多参数的作用是为了让容器中运行的程序能够在宿主机上显示出来
服务器运行
在服务器上运行时,由于unity刚需显示器的特性,需要在服务器上运行虚拟屏幕,于是在dockerfile中加入以下内容
1 | COPY docker-entrypoint.sh /docker-entrypoint.sh |
其中docker-entrypoint.sh内容如下
1 |
|
然后运行脚本如下
1 | docker run -d \ |
Dockerfile
首先需要简单说明dockerfile的SHELL,ENTRYPOINT,CMD命令的作用与区别SHELL:指定命令的执行环境
- 作用: 改变 Dockerfile 中后续指令(如 RUN、CMD、ENTRYPOINT)在 shell 格式下所使用的默认 Shell 解析器。
- 默认值: Linux 系统默认是 [“/bin/sh”, “-c”];Windows 系统默认是 [“cmd”, “/S”, “/C”]。
- 应用场景: 想在 Linux 中改用 bash 或 zsh,或者在 Windows 中改用 powershell 时使用。
ENTRYPOINT:指定容器的“主程序”
- 作用: 设置容器启动时必然会执行的命令。它让容器看起来像一个独立的“可执行文件”。
- 特点: 除非在
docker run时显式加上 –entrypoint 参数,否则它不会被覆盖。
CMD:指定默认参数或默认命令
- 作用: 为容器提供默认的执行命令或参数。
- 特点: 它是极易被覆盖的。如果在
docker run后面跟了任何附加命令,CMD 就会被完全忽略。
具体而言,SHELL的作用还算好理解,后两者的使用需要举例来说明
场景 A:单独使用 CMD
1 | FROM ubuntu |
docker run my-image➡️ 输出Hello Worlddocker run my-image echo "Hi"➡️ 输出Hi(CMD 被覆盖了)
场景 B:单独使用 ENTRYPOINT
1 | FROM ubuntu |
docker run my-image➡️ 输出Hellodocker run my-image World➡️ 输出Hello World(传给容器的参数直接追加到了主程序后面!)
场景 C:ENTRYPOINT 与 CMD 组合使用
这是最工业级的用法。ENTRYPOINT 定死主程序,CMD 提供默认参数:
1 | FROM ubuntu |
docker run my-image➡️ 实际执行ping -c 3 localhost(默认拼本地)docker run my-image 8.8.8.8➡️ 实际执行ping -c 3 8.8.8.8(CMD 的默认参数被覆盖,换成了拼外网)
docker-entrypoint.sh
在了解了上述内容后,可以知道ENTRYPOINT ["/docker-entrypoint.sh"]的作用就是运行该脚本
1 | Xvfb :99 -screen 0 1024x768x24 -ac +extension GLX +render & |
其中Xvfb为linux的虚拟屏幕指令,设置端口为99并在后台运行。对应的,需要在dockerfile中加入以下两部分:
- 首先是设置环境变量
ENV DISPLAY=:99告诉系统里的程序,后续画面都渲染到刚才的虚拟屏幕中 - 提供脚本。该脚本是在构建镜像时传入到系统中的,当镜像构建完成之后便写死在了系统内。
1
2COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
运行脚本
上一步中使用脚本创建了虚拟屏幕,但是一旦该脚本结束,那么虚拟屏幕也就结束了,所以需要一些操作来维持虚拟屏幕。一是在.sh末尾有这样的内容:exec "$@",二是在运行脚本的末尾,加入了这样的内容tail -f /dev/null,接下来分别进行解释。
exec "$@"
简单来说该命令就是执行传给容器的后续参数(之前提到的会覆盖CMD的部分)。
- **
"$@"**:代表传给容器的所有后续参数。该场景下,"$@"对应的就是tail -f /dev/null exec:Linux 的一个内置命令。它的特点是启动一个新进程,并用这个新进程完全替换掉当前的 Shell 进程,同时保持原有的进程 ID(PID)不变。这样在应用结束时,系统可以找到该脚本并关闭。
tail -f /dev/null
相当于在脚本内执行该指令。其中/dev/null是linux默认的空文件,无法读取到任何内容,而tail -f则是代表跟踪某文件的最新内容。两者加在一起则会让脚本无限阻塞,从而让脚本不会退出。
后续改进
尝试是否能在服务器上的容器中用虚拟显示器来采集数据,现在采集了数据还要上传到服务器还是太复杂了,希望本地仅用来运行测试。