docker 简单介绍
docker 容器技术使得开发测试非常方便,自 2013 年发布至今,一直广受瞩目。软件开发最大的麻烦事之一,就是环境配置。虚拟机虽然能够解决上面问题,但是有如下缺点:1.资源占用多,2.冗余步骤多,3. 启动慢;由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。 或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。由于容器是进程级别的,相比虚拟机有很多优势。1. 启动快, 2, 资源占用少,3. 体积小. 总之,容器有点像轻量级的虚拟机,能够提供虚拟化的环境,但是成本开销小得多。Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。 它是目前最流行的 Linux 容器解决方案。下面介绍2020年08月以后,如何安装使用 docker,本篇以 Windows 10 wsl 2 Ubuntu-20.04 为例。
Docker 的主要用途,目前有三大类:
(1)提供一次性的环境。 比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境;
(2)提供弹性的云服务。 因为 Docker 容器可以随开随关,很适合动态扩容和缩容;
(3)组建微服务架构。 通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构。
docker 安装
在线安装
1 | sudo apt-get update |
离线安装
除了在线安装外,有时候,离线安装也是很常见的。如内网服务器(不方便连接互联网)等。下面介绍在 Debian/Ubuntu 系统中安装的方法。
Debian 系统请访问 Docker 官网,并在 Install from a package 一节中,打开网址:https://download.docker.com/linux/debian/dists/ ,然后选择自己服务器版本,进入 pool/stable
,选择 amd64, armhf, arm64, s390x
下载 .deb
,注意需要下载 4 个 .deb
,分别是 docker-ce, docker-ce-cli, containerd.io, docker-compose-plugin
.
1 | # 下载必要的包 |
Ubuntu 系统请访问 Docker 官网,并在 Install from a package 一节中,打开网址:https://download.docker.com/linux/ubuntu/dists/ ,然后选择自己服务器版本,进入 pool/stable
,选择 amd64, armhf, arm64, s390x
下载 .deb
,注意需要下载 4 个 .deb
,分别是 docker-ce, docker-ce-cli, containerd.io, docker-compose-plugin
.
1 | # 下载必要的包 |
- CentOS 等其他系统请访问 Docker 官网 获取安装方法。
docker-compose
docker compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。下载地址: github docker compose
Compose 使用的三个步骤:
- 使用 Dockerfile 定义应用程序的环境。
- 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
- 最后,执行 docker-compose up 命令来启动并运行整个应用程序。
docker-compose 安装
1 | wget https://github.com/docker/compose/releases/download/v2.10.2/docker-compose-linux-x86_64 |
命令行容器自动生成 docker-compose.yml
使用 ghcr.io/red5d/docker-autocompose 来对命令行运行的容器生成 docker-compose.yml 文件,方便下次使用 docker-compose 运行。生成的配置文件包含非常丰富的参数,可以根据个人情况删除、更改。
1 | # 对于单个容器 |
中文显示
在 docker 镜像中运行:
1 | export LC_ALL="C.UTF-8" |
此时,命令行可正常查看中文。在命令行设置,可对当前用户所有终端生效:
1 | echo 'export LC_ALL="C.UTF-8"' >> ~/.bashrc |
如下设置,可对全局生效,默认 root 用户
1 | echo 'export LC_ALL="C.UTF-8"' >> /etc/profile |
建议在创建容器时直接设置语言:
1 | docker run -itd --name python -e LANG="C.UTF-8" ubuntu:python /bin/bash |
时区
默认运行的容器时区都是 UTC,想要改为 CST,方法如下:
- 在构建镜像时,设置时区:
1
2ENV TZ=Asia/Shanghai
RUN ... - docker-compose 配置文件中,设置时区:
1
2
3
4
5
6
7
8
9# 方式一:
environment:
- SET_CONTAINER_TIMEZONE=true
- CONTAINER_TIMEZONE=Asia/Shanghai
# 方式二:
environment:
- TZ=Asia/Shanghai - 在启动容器时,更改时区:
1
docker run -e TZ=Asia/Shanghai ...
- 对运行的容器更改时区:
1
2
3
4
5
6# 把宿主机的时区配置拷贝到容器中,假设我的容器名为 colab
# /usr/share/zoneinfo/Asia/Shanghai -> ../PRC
docker cp /usr/share/zoneinfo/PRC colab:/etc/localtime
# 重启容器
docker restart colab
查看时区:
1 | date |
如果上面方法不管用,那么请直接在容器中安装 tzdata:
1 | # 安装时选择地区和时区 |
docker 中使用 GPU
想要在 docker 容器中使用宿主机的 GPU,除了宿主机配置 CUDA 环境和安装 docker 外,还需要安装如下:
1 | curl -s -L https://nvidia.github.io/nvidia-container-runtime/gpgkey | sudo apt-key add - |
另一种方法:
1 | # Nvidia Docker |
当安装完 nvidia-container-toolkit
后,运行时有时会出现:nvidia-container-cli: ldcache error: open failed: /sbin/ldconfig.real: no such file or directory: unknown.
,解决方法:
1 | # 以 root 身份执行 |
创建容器
1 | # 以镜像 ubuntu-xjz:v1 或 {IMAGE ID} 创建容器 ubuntu-xjz-python |
测试在 docker 容器中是否可以使用 GPU
1 | # nvidia/cuda:10.2-base 根据驱动版本进行选择,如 nvidia/cuda:11.0-base |
打印出 GPU 信息说明验证成功,--gpus
和 -e NVIDIA_VISIBLE_DEVICES
都能实现指定 GPU,但后者 api 接口更容易。更多请访问 NVIDIA 官网教程.
docker 使用
因 docker hub 在国外,为了加速访问需要给 docker 添加国内镜像,方法如下,打开如下文件,没有则自动创建:
1 | sudo vim /etc/docker/daemon.json |
添加如下内容
1 | { |
注意:
- insecure-registries 为你本地网络私有仓库地址,根据个人情况设置;
- runtimes 为容器调用宿主机服务器 GPU 运行时,根据个人情况设置。
测试
1 | docker pull tensorflow/tensorflow:latest |
image
Docker 把应用程序及其依赖,打包在 image 文件里面。
1 | # 从 docker 官方拉取 image 文件 hello-world |
container
image 文件生成的容器实例,本身也是一个文件,称为容器 (Container) 文件。 也就是说,一旦容器生成,就会同时存在两个文件: image 文件和容器文件。而且关闭容器并不会删除容器文件,只是容器停止运行而已。
1 | # 查看运行的容器 |
注意:
- CONTAINER ID 和 IMAGE ID 只需要输入前几个字符,只要能够唯一识别它们;
- docker image 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。image 文件可以看作是容器的模板;
- image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image 文件,加上一些个性化设置而生成。举例来说,你可以在 Ubuntu 的 image 基础上,往里面加入 Apache 服务器,形成你的 image;
- 一般来说,为了节省时间,我们应该尽量使用别人制作好的 image 文件,而不是自己制作。即使要定制,也应该基于别人的 image 文件进行加工,而不是从零开始制作;
docker container 设置自启动一般推荐使用 always 参数:–restart=always,更多参数取值如下:
1 | --restart |
具体的开启自启和取消自启的命令如下:
1 | # 开启自启 |
制作 docker image
需要用到 Dockerfile 文件。它是一个文本文件,用来配置 image. Docker 根据 该文件生成二进制的 image 文件。
首先,克隆项目
1 | git clone https://github.com/xxx/demos.git |
在项目的根目录下,新建一个文本文件 .dockerignore
, 把要排除的文件或文件夹路径按行写到文件里,参考如下,即说明那些不需要打包进入 image 文件。如果你没有路径要排除,这个文件可以不新建。
1 | .git |
在项目的根目录下,新建一个文本文件 Dockerfile,
1 | FROM node:11 |
说明如下:
1 | FROM node:11:该 image 文件继承官方的 node image,冒号表示标签,这里标签是 11,即 11 版本的 node。 |
创建 image 文件. 有了 Dockerfile 文件以后,就可以使用 docker image build
命令创建 image 文件了。
1 | docker image build -t demo . |
上面代码中,-t
参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是 latest
. 最后的那个点表示 Dockerfile 文件所在的路径,上例是当前路径,所以是一个点。
如果运行成功,就可以看到新生成的 image 文件 demo
了。
1 | docker image ls |
发布 image 的命令参考上面。
常见保留字
- FROM:
FROME ubuntu:22.04
构建新镜像基于的基础镜像。Dockerfile中第一条必须是 FROM - MAINTAINER:
MAINTAINER xjz xjz@gmail.com
镜像维护者姓名和邮箱 - RUN:
RUN apt install vim -y
容器构建时(docker build)执行的命令,支持两种格式,一种是shell(等同于终端中执行的SHELL命令RUN apt install vim -y
),一种是exec命令RUN ["./test.php", "dev", "offline"]
,后两个为参数,等同于RUN ./test.php dev offline
- ENV:
ENV MYPATH /usr/local
配置环境变量,指定export MYPATH=/usr/local
- WORKDIR:
WORKDIR /usr/local
指定在创建容器后,终端默认登录的工作目录 - USER:
USER root
指定该镜像以什么用户执行,不指定,默认为 root - VOLUME:
VOLUME ["/tmp", "/tmp"]
作用同-v /tmp:/tmp
,指定挂载目录 - ADD:
ADD java.tar.gz /usr/local/java
把本地压缩包拷贝到镜像,并会自动解压缩tar压缩包。添加时并重命名ADD docker_boot-0.0.1-SNAPSHOT.jar hello_docker.jar
- COPY:
COPY java /usr/local/java
把本地文件夹拷贝到镜像,不解压 - EXPOSE:
EXPOSE 80
启动容器是暴露的端口 - CMD:
CMD ./start-jupyter.sh
orCMD ["./start-jupyter.sh", "run"]
容器启动命令,也支持shell命令和exec命令。指定容器启动后要做的事情。Dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run之后的参数替换 - ENTRYPOINT:
ENTRYPOINT ["/bin/ping", "-c", "3"]
也是用来指定一个容器启动时要运行的命令,类似于CMD,但是ENTRYPOINT不会被docker run后面的命令覆盖,如果docker run后面的命令行参数会被当作参数送给ENTRYPOINT指令指定的程序。其实ENTRYPOINT也可以被覆盖:docker run --entrypoint hostname ubuntu:22.04
。有些命令运行后就退出了,如/etc/init.d/ssh start
,有些命令会一直运行,如/bin/bash
。如果想要容器启动后一直运行应该选择常驻(一直运行)的命令作为ENTRYPOINT或CMD,如上面的/bin/bash
,或者最后增加上常驻命令。
ENTRYPOINT 和 CMD 搭配使用,ENTRYPOINT 用作执行命令,CMD用作传参数例子:
1 | # 只有ENTRYPOINT和CMD都用Exec表示法, 才能得到预期的效果 |
执行时,可以在docker run后指定新参数覆盖CMD参数:
1 | docker run -it myubuntu:v1.0 www.baidu.com |
docker network
docker 服务默认会创建一个docker0网桥(其上有一个docker0内部接口),该网桥网络的名称为docker0,它在内核层联通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。docker默认指定docker0接口的IP地址和子网掩码,让主机和容器之间可以通过网桥相互图像。
docker network 分为 bridge, host, none, container 和自建网络。
- bridge: 为每一个容器分配、设置IP等,并将容器连接到一个docker0。即虚拟网桥模式,默认为该模式:
docker run ubuntu
。网桥docker0创建一对对等虚拟设备接口,一个叫veth(宿主机docker0),一个叫eth0(容器内),两者一一匹配。整个宿主机的网桥模式都是docker0,类似一个交换机有一堆接口,每个接口都叫做veth,在本地主机和容器内创建一个虚拟接口,并让他们彼此联通,这样一对接口叫做veth pari. - host: 容器将不会虚拟出自己的网卡,配置自己的IP等,而是直接使用宿主机的IP和端口与外界通信,不在需要额外进行NAT转换。与宿主机公用一个network namespace,容器不会虚拟出自己的网卡:
docker run --network host ubuntu
- none: 容器有独立的 Network namespace,但并没有对其进行任何网络设置,如分配veth pair和网桥连接、IP等,不常用:
docker run --network none ubuntu
- container: 新创建的容器不会创建自己的网卡和配置自己的IP,而是和一个指定的容器共享IP、端口范围等:
docker run --network container:容器名或者容器ID
。当指定的共享容器关闭时,当前的容器网络也会断开,只会存在本地回环127.0.0.1。 - 自建网络:
docker network create mybridge
,使用自建网络:docker run --network mybridge ubuntu
,使用自建网络的好处是除了使用可变的IP互相PING通或连接通外,还可以使用容器名进行PING通或连接上。当容器IP发生改变不会影响使用容器名PING通。建议使用这种方式。
使用bridge模式当容器关闭后重启可能会导致容器IP发生改变,原IP会分配给关闭期间新创建的容器。需要注意。
容器导出和导入镜像
本节演示从拉取基镜像,然后增加个人内容,从容器导出镜像文件(Export a container’s filesystem as a tar archive,不建议这样导出镜像)。把导出的镜像文件拷贝到新的电脑上,然后再导入使用。
1 | # 拉取最新 Ubuntu 镜像 |
上面的方法其实是将容器的文件系统导出为 tar 存档,也可以先把容器打标签成本地镜像,然后用下面的本地镜像导出和导入镜像到其他主机使用。容器打标签成镜像方法如下:
1 | # 把容器转存镜像 |
镜像导出和导入镜像
同容器导出和导入镜像,主要区别是,使用的命令从 export
/import
分别变为 save
/load
.
1 | # 查看镜像 |
镜像和容器导入导出的区别
- 镜像导入是一个复制的过程,容器导入是将当前容器变成一个新的镜像;
- docker save 命令保存的是镜像(image),docker export 命令保存的是容器(container);
- export 命令导出的 tar 文件略小于 save 命令导出的;
- 因为 export 导出的是容器,export 导出的文件在 import 导入时,无法保留镜像所有的历史(即每一层 layer 信息),不能进行回滚操作。而 save 是根据镜像来的,所以导入时可以完整保留下每一层 layer 信息;
- docker load 不能对导入的镜像重命名,而 docker import 导入可以为镜像指定新名称。
注意:<font color=red>
测试发现 docker 20.10.9 运行 ubuntu22.04 出现异常。</font>
问题描述:
外网 docker 20.10.18 基于 ubuntu22.04 安装 基于 miniconda 的 python 环境,并安装支持 GPU 的 torch 和 torchvision,测试能够在 python 中调用服务器 GPU。导出该镜像并刻录到内网,内网部署 docker 20.10.9,加载镜像后能够启动,但 python 无法正常使用,出现 ipython (Original error was: PyCapsule_Import could not import module “datetime” 和 could not start threads 错误)和 numpy (import numpy 出现 OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 999: Operation not permitted OpenBLAS blas_thread_init: RLIMIT_NPROC -1 current, -1 max)。但把镜像拷贝到其他一台能够连接外网的服务器(安装有 docker 20.10.11)上时,能够正常运行。
问题排查:
经查发现是 ubuntu21.10 和 fedora35 开始使用glibc2.34甚至更高的版本。在glibc2.34 版本里面,开始使用一个名为 clone3 的系统调用。通常情况下,容器里面所有的系统调用都会被 docker 捕获,然后 docker 决定如何处理它们。如果 docker 中没有为特定系统调用指定策略,则默认的策略会通知容器这边”Permission Denied”。但是,如果 Glibc 收到此错误,它不会回退。它仅在收到响应“此系统调用不可用”时才执行此操作。
解决方法:
运行容器的时候,加上这个参数来绕过docker系统调用限制
1
2
3
4--security-opt seccomp=unconfined
# 例子
docker run -itd --name gputest --gpus all -p 9090:80 -v /workspace:/data --security-opt seccomp=unconfined ubuntu22-gpu:v1 /bin/bash这能够使得上面的 python 运行但会有很大的问题,一个是你的容器将变得不安全,另一个是这些参数在构建镜像的时候是不可用的。
将 docker 升级到 20.10.11 以上的版本
不要使用基础镜像 ubuntu22.04,而是使用 ubuntu18.04
容器端口映射
有时候在我们运行的容器中有一些服务需要外部网络(docker 容器外)访问,此时就需要在我们从镜像运行容器时指定好本机端口和容器端口的映射,方便在本机直接访问容器中的服务。
1 | # 把容器的80端口映射到主机的9000端口,默认是“0.0.0.0”,即所有能访问本机的设备都可以访问 |
给运行的容器新添加端口映射等其他涉及到非 CPU、内存的操作
对于运行的容器,更改CPU和内存、设置随docker自动开启等都可以使用 docker update
来完成,但是其他涉及到修改容器的端口、挂载点、GPU等需要停止docker服务,然后修改对应容易的配置文件才能完成。关于更新运行容器的CPU和内存等方法可以查看帮助:
1 | docker update --help |
目前 docker 没有直接为运行的容器动态增加端口映射的方法。建议从镜像运行容器时考虑清楚哪些端口需要进行映射。如果真的需要为运行的容器再增加新端口映射,可以考虑如下方法:
重新运行容器
最简单的方法就是直接重新再运行容器。如果已经运行的容器个人增加了一些不方便拷贝出来的内容,那么可以先将该容器 commit 成镜像,然后再从新镜像运行容器,增加新端口映射。
容器 commit 镜像的方法:
1 | # 把容器转存镜像 |
修改容器配置文件
如果运行的容器体量太多(如几十G),上面的方法就显得有些不太合适,这里提供一种方法就是修改运行容器的配置文件。
值得一提的是,当对容器的 CPU 和内存修改,可以直接使用命令 docker update --help
。除此之外,如容器修改挂载点、修改端口映射等需要修改配置文件。查看配置文件前需要先查看 docker 容器存放的位置:docker info | grep "Root"
和需要修改的容器 ID,使用 docker ps
查看。
修改容器配置文件前,需要先暂时关闭 docker 服务:
1 | sudo systemctl stop docker |
然后修改配置文件 hostcongfig.json
、config.v2.json
(如果该文件中有记录)。
查看容器配置信息
1 | docker inspect {CONTAINER ID} |
进入配置文件目录,修改配置文件
1 | # 以 Ubuntu 为例 |
修改成功后,然后重启 docker 服务。
挂载宿主机目录到容器
有时候需要容器能够访问宿主机的某些文件或文件夹,此时,我们可以从镜像创建容器时通过设置挂载点的方式,将宿主机的目录挂载到容器的某个目录,使得容器内部就可以访问宿主机的文件夹。注意,与上面的 cp
命令的区别。
1 | # 把宿主机的目录 /home/jinzhongxu/Documents 挂载到容器的 /data 目录 |
有时候,我们需要创建一个新容器,挂载信息同已经创建的一个容器,那么我们可以使用如下方法:
1 | # 使用同上面的容器 'ubuntu-test' 相同的挂载信息 |
此时,创建的新容器 ‘ubuntu-test2’ 同旧容器 ‘ubuntu-test’ 具有相同的挂载信息。
容器 SSH 访问宿主机
从容器内部通过SSH访问宿主机能够为容器和宿主机提供更好的交互方式。此时,我们只需要知道宿主机的 docker0 IP,以及宿主机用户名和密码即可。
查看宿主机 docker0 IP 方法是,在宿主机上执行 ifconfig
命令,查看 docker0 对应的 IP,一般为 172.17.0.1;
通过如下方式访问宿主机:
1 | # 我这里用户名是 jinzhongxu |
本地私有仓库 docker hub
有时候使用 Docker Hub 这样的公共仓库可能不方便,如内网环境下。此时,用户可以创建一个本地仓库供私人使用。
docker-registry 是官方提供的工具,可以用于构建私有的镜像仓库。本文内容基于 docker-registry v2.x 版本。
安装 docker-registry
如果在能联网的服务器上:
1 | # 自动从官网下载 registry 镜像并允许 |
如果想在内网服务器使用,需要现在能够连接互联网的机器上下载 registry 镜像,然后把该镜像导出,刻录到内网,然后在内网服务器上加载镜像并打标签为 registry:latest(以个人喜好,我这里以此名和标签做演示)。然后使用下面的方式运行。
1 | docker run -d --restart=always --name registry -p 5000:5000 -v /opt/data/registry:/var/lib/registry registry:latest |
此时,registry 容器在运行,并监控 5000 端口。
在私有仓库上传、搜索、下载镜像
创建好私有仓库之后,就可以使用 docker tag 来标记一个镜像,然后推送它到仓库。例如私有仓库地址为 127.0.0.1:5000,把镜像打标签,以 127.0.0.1:5000 开头:
1 | docker tag ubuntu:latest 127.0.0.1:5000/ubuntu18-gpu:v1 |
提交镜像
1 | docker push 127.0.0.1:5000/ubuntu18-gpu:v1 |
搜索私有仓库中的镜像
1 | curl 127.0.0.1:5000/v2/_catalog |
结果大概这样
1 | {"repositories":["ubuntu18-gpu"]} |
从仓库中获取指定镜像的标签
1 | curl 127.0.0.1:5000/v2/ubuntu18-gpu/tags/list |
结果大概这样
1 | {"name":"ubuntu18-gpu","tags":["v1"]} |
从私有仓库下载镜像
1 | docker pull 127.0.0.1:5000/ubuntu18-gpu:v1 |
配置非 https 仓库地址
如果你不想使用 127.0.0.1:5000 作为仓库地址,比如想让本网段的其他主机也能把镜像推送到私有仓库。你就得把例如 192.168.199.100:5000 这样的内网地址作为私有仓库地址,这时你会发现无法成功推送镜像。
这是因为 Docker 默认不允许非 HTTPS 方式推送镜像。我们可以通过 Docker 的配置选项来取消这个限制,或者查看下一节配置能够通过 HTTPS 访问的私有仓库。
Ubuntu 16.04+, Debian 8+, centos 7
对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存在请新建该文件):
1 | { |
注意:
- 该文件必须符合 json 规范,否则 Docker 将不能启动
<font color=red>
需要重启 docker 才能生效</font>
- 上面我增加了容器调用宿主机服务器 GPU 的运行时,根据个人情况添加
其他
指定容器 IP 与宿主机同网段
1 | # 查看网卡是否支持 macvlan |
容器中访问宿主机服务
要求: Docker 版本高于 v20.10 (2020年12月4日更新)
命令行模式下:
1 | --add-host=host.docker.internal:host-gateway |
Docker Compose 模式下:
1 | extra_hosts: |
而在 container 内,可请求 host.docker.internal:PORT
,来获取宿主机上提供的服务。
容器中安装 opencv-python
Ubuntu18.04 容器中安装 opencv-python 需要安装一些依赖包:
1 | apt update |
容器中运行 GUI 界面在宿主机显示
DISPLAY 环境变量格式如下 host:NumA.NumB,其中 host 指 Xserver 所在的主机主机名或者 ip 地址,图形将显示在这一机器上,可以是启动了图形界面的 Linux/Unix 机器,也可以是安装了 Exceed、X-Deep/32 等 Windows 平台运行的 Xserver 的 Windows 机器。如果 Host 为空,则表示 Xserver 运行于本机,并且图形程序(Xclient)使用 unix socket 方式连接到 Xserver,而不是 TCP 方式。使用 TCP方式连接时,NumA 为连接的端口减去 6000 后的值,如果 NumA 为 0,则表示连接到 6000 端口;使用 unix socket 方式连接时则表示连接的 unix socket 的路径,如果为 0,则表示连接到 /tmp/.X11-unix/X0,NumB 则几乎总是0。
以 deeplabcut[gui] 镜像为例,先拉取镜像:
1 | docker pull deeplabcut/deeplabcut:2.2.1.1-gui-cuda11.0.3-runtime-ubuntu18.04 |
然后设置宿主机运行远程连接 X server
1 | xhost + |
启动镜像,注意把本地 DISPLAY 设置上
1 | docker run -itd --name deeplabcut --gpus=all -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$DISPLAY deeplabcut/deeplabcut:2.2.1.1-gui-cuda11.0.3-runtime-ubuntu18.04 /bin/bash |
进入容器启动 GUI 服务
1 | python -m deeplabcut |
镜像包依赖问题可能需要把 python 包 wxpython 更改为 4.0.7:
1 | pip install wxPython==4.0.7.post2 |
或者直接在容器中安装桌面环境,然后开启远程访问,如 VNC, XRDP 等,远程访问 docker 容器桌面环境,类似于一台正常的远程桌面服务器一样。但是安装桌面环境对于不同的基镜像方法不同,安装的包会有些差别。但是使用起来会更全面。
另外,有时候除了显示容器里GUI程序界面在宿主机,还需要与宿主机程序通信等,可以采用如下方式:
1 | DIR=$(pwd) |
–cap-add 参数解决权限问题(无法使用gdb调试、无法date -s修改时间)
–security-opt seccomp=unconfined 选项,它可以取消 Seccomp (Seccomp,即安全计算模式,是 Linux 内核的一部分,用于限制进程可以使用的系统调用。通过禁止或限制对某些系统调用的访问,Seccomp 可以有效减少攻击者能够利用的攻击面。Docker 在默认情况下启用了 Seccomp,并提供了一个默认的配置文件,该文件白名单了大约300个系统调用,其他的则被禁止。这意味着在 Docker 容器中运行的进程只能访问这些已经过滤的系统调用。)的限制,使容器内的进程可以访问所有系统调用。虽然提供了灵活性,但也带来了一些系统风险。禁用 Seccomp 意味着容器内的进程可以访问系统调用,包括那些可能被利用来执行恶意操作的系统调用。
–env NVIDIA_DISABLE_REQUIRE=1 当容器内的 cuda 版本比较宿主机上的驱动版本高时,可能容器里的程序会出现报错。设置改环境变量,让容器内的 cuda 切换为宿主机上的。
–ipc=host 容器共享宿主机的 IPC(inter-process communication) 命名空间,让容器内进程与宿主机的进程进行通信。
容器中运行 pytorch 多 GPU 模型
在容器中运行 pytorch 多 GPU 模型可能会出现如下错误:
ERROR: Unexpected bus error encountered in worker. This might be caused by insufficient shared memory (shm). Dataloader中的num_workers设置与docker的shared memory相关问题
这是因为 docker 容器内 shm_size 默认大小 64M 太小导致的问题,有两个解决思路:
- 在 Dataloader 中将 num_worker 设置为 0,只需在代码中修改比较简单,缺点是训练过程变慢,特别是对较大数据例如视频图像
- 改变容器中 shared_memory 大小,如下:
1
docker run -itd --name mmdet-dlc-multi-gpus --gpus '"device=1,2"' -p 33336:80 --shm-size 8G mmdet-dlc:v1.2 /bin/bash
容器内存和 CPU 限制
有一次在容器中运行一个程序直接爆满宿主机内存,导致服务器无法使用。此时,可以尝试更新容器的内存限制,然后再运行程序,方法如下:
1 | # mmdet-dlc-tencent 为容器名 |
上面命令设置后,容器就只能使用宿主机内存 40000 MB,但可以使用所有的交换分区。同时,当程序遇到内存不够时会出现 OOM(Out Of Memory)异常退出,解决方法是,在创建容器时,设置如下:
1 | # 设置 oom-kill-disable 能够避免内存不够时异常退出 |
用于测试 docker 中 CPU 分配情况的程序 Python 程序(cpus-limit-test.py):
1 | #!/usr/bin/env python |
对容器进行内存现在时有时候会出现错误提示:No swap limit support,此时内存限制是无效的。可以尝试通过如下方法解决:
1 | sudo vim /etc/default/grub |
mac docker 无法启动
mac 中 docker 无法启动,提示:
com.docker.backend cannot start Exit code 101
可尝试如下方法解决:
1 | pkill Docker |
然后在重启 docker
mac docker GUI
以 ubuntu:22.04 为例,在容器中安装VNC桌面,并在mac宿主机上访问。
1 | docker pull ubuntu:22.04 |
在Mac上安装tigervnc或其他VNC桌面程序,访问:localhost::8311,输入vncpasswd设置的密码,即可访问。进入后,可以设置默认程序。
安装firefox,建议从firefox官网下载firefox完整程序,手动解压到指定文件夹,设置默认程序时找到解压的firefox文件夹里的firefox可执行文件。
nvidia-smi 在 docker 中看不到进程号
当我们使用 --gpus all
等启动一个想要在 docker 容器中使用 GPU 时,使用命令 nvidia-smi
无法看到当前使用 GPU 的进程 ID,it is related to PID namespaces, the driver is not aware of the PID namespace and thus nvidia-smi in the container doesn’t see any process running. github-nvidia-docker issue. 一种解决方法是启动容器时,增加上一个参数:
1 | --pid=host |
或者,使用 python 包 py3nvml
,方法如下:
1 | # install |
同时,py3nvml 作为 python 的一个包,可以在 python 代码中使用来获取 gpu 的实时信息:
1 | import py3nvml |
迁移 docker 镜像
默认情况下安装的 docker 会自动把镜像文件存储到 /var/lib/docker 目录下,当我们创建的镜像较多较大时,会导致磁盘空间不足等问题,此时,我们可以尝试将镜像存储路径迁移到其他挂载盘:
1 | # 停止 docker 进程 |
docker 磁盘清理
在长时间使用后,docker 镜像会累积,占用大量的磁盘空间。
docker 磁盘分析
使用如下命令可查看 docker 使用空间情况:
1 | docker system df |
docker 磁盘清理
1 | # 清理所有停止的容器、不使用的网络、所有悬空的镜像 |
容器中使用 systemctl
当在容器中使用 systemctl 时有时会出现如下错误:
1 | Failed to connect to bus: No such file or directory |
此时,可以尝试如下方法:
1 | # 拉取 centos8 |
进入容器后,使用命令查看是否可使用 systemctl
1 | systemctl status |
centos8 yum update
如果出现 centos8 中无法 yum update
,可使用如下方法:
1 | sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* |
centos8 中安装 ssh
1 | yum install openssh-clients openssh-server -y |
centos8 中安装 nginx
1 | yum install gcc wget epel-release -y |
no gsettings
当在容器中运行图像界面打开模型交互式界面时出现:
No such file or directory: ‘gsettings’
解决方法:
1 | apt update |
docker pull proxy
1 | # 配置 |
参考链接
- docker国内镜像源
- Docker 入门教程
- Docker 微服务教程
- Docker:docker镜像与容器的导入和导出
- runoob.com docker-tutorial
- 外部访问容器
- 添加和修改docker容器端口映射的方法
- Docker和宿主机之间共享文件
- docker 挂载宿主机文件目录
- docker容器挂载host宿主机的本地目录,docker容器与宿主机之间互相拷贝文件
- Docker Compose
- docker使用GPU总结
- nathzi1505/install-docker.sh
- Docker运行ubuntu22.04出现异常
- 私有仓库
- 解决docker警告WARNING: No swap limit support
- 关于export DISPLAY=:0.0
- Docker 网络模型之 macvlan 详解,图解,实验完整
- docker容器内访问宿主机host服务