一 关于对镜像和容器的理解
Docker镜像好比操作系统的镜像(iso)
Docker容器好比安装完成的操作系统
# docker镜像文件运行起来之后就是我们所说的docker容器
Docker 中的三个概念:镜像,容器,仓库
1、镜像(image):Docker 镜像就是一个只读的模板,镜像可以用来创建 Docker 容器。Docker 提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用。
镜像是一种文件结构。Dockerfile中的每条命令都会在文件系统中创建一个新的层次结构,文件系统在这些层次上构建起来,镜像就构建于这些联合的文件系统之上。Docker官方网站专门有一个页面来存储所有可用的镜像,网址是:index.docker.io。
2、容器( Container):容器是从镜像创建的运行实例。它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。可以把容器看做是一个简易版的 Linux 环境,Docker 利用容器来运行应用。镜像是只读的,容器在启动的时候创建一层可写层作为最上层。
3、仓库:仓库是集中存放镜像文件的场所,仓库注册服务器(Registry)上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。目前,最大的公开仓库是 Docker Hub,存放了数量庞大的镜像供用户下载。
Docker仓库用来保存我们的images,当我们创建了自己的image之后我们就可以使用push命令将它上传到公有或者私有仓库,这样下次要在另外一台机器上使用这个image时候,只需要从仓库上pull下来就可以了。Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务。
那到底什么是Docker镜像呢?
Docker 镜像是由文件系统叠加而成(是一种文件的存储形式)。最底端是一个文件引 导系统,即 bootfs,这很像典型的 Linux/Unix 的引导文件系统。Docker 用户几乎永远不会和 引导系统有什么交互。实际上,当一个容器启动后,它将会被移动到内存中,而引导文件系 统则会被卸载,以留出更多的内存供磁盘镜像使用。Docker 容器启动是需要一些文件的, 而这些文件就可以称为 Docker 镜像。
Docker 把应用程序及其依赖,打包在 image 文件里面。只有通过这个文件,才能生成 Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。
image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image 文件,加上一些个性化设置而生成。举例来说,你可以在 Ubuntu 的 image 基础上,往里面加入 Apache 服务器,形成你的 image。
image 文件是通用的,一台机器的 image 文件拷贝到另一台机器,照样可以使用。一般来说,为了节省时间,我们应该尽量使用别人制作好的 image 文件,而不是自己制作。即使要定制,也应该基于别人的 image 文件进行加工,而不是从零开始制作。
为了方便共享,image 文件制作完成后,可以上传到网上的仓库。Docker 的官方仓库 Docker Hub 是最重要、最常用的 image 仓库。此外,出售自己制作的 image 文件也是可以的。
二 Docker镜像操作
列出镜像
docker image ls -a
- REPOSITORY:镜像所在的仓库名称
- TAG:镜像标签
- IMAGEID:镜像ID
- CREATED:镜像的创建日期(不是获取该镜像的日期)
- SIZE:镜像大小
为了区分同一个仓库下的不同镜像,Docker 提供了一种称为标签(Tag)的功能。每个 镜像在列出来时都带有一个标签,例如latest、 12.10、12.04 等等。每个标签对组成特定镜像的一 些镜像层进行标记(比如,标签 12.04 就是对所有 Ubuntu12.04 镜像层的标记)。这种机制 使得同一个仓库中可以存储多个镜像。— 版本号
在运行同一个仓库中的不同镜像时,可以通过在仓库名后面加上一个冒号和标签名 来指定该仓库中的某一具体的镜像,例如 docker run –name custom_container_name –i –t docker.io/ubunto:12.04 /bin/bash,表明从镜像 Ubuntu:12.04 启动一个容器,而这个镜像的操 作系统就是 Ubuntu:12.04。在构建容器时指定仓库的标签也是一个好习惯。
拉取镜像
Docker维护了镜像仓库,分为共有和私有两种,共有的官方仓库Docker Hub(Docker Hub)是最重要最常用的镜像仓库。私有仓库(Private Registry)是开发者或者企业自建的镜像存储库,通常用来保存企业 内部的 Docker 镜像,用于内部开发流程和产品的发布、版本控制。
要想获取某个镜像,我们可以使用pull命令,从仓库中拉取镜像到本地,如
docker image pull library/hello-world
上面代码中,docker image pull是抓取 image 文件的命令。library/hello-world是 image 文件在仓库里面的位置,其中library是 image 文件所在的组,hello-world是 image 文件的名字。
由于 Docker 官方提供的 image 文件,都放在library组里面,所以它的是默认组,可以省略。因此,上面的命令可以写成下面这样。
docker image pull hello-world
删除镜像
docker image rm 镜像名或镜像id 如:
docker image rm hello-world
正在运行容器的镜像是无法删除的
三 Docker容器操作
1 容器的创建与启动
docker创建容器可以用docker create命令来执行,
docker create -it debian:latest
从上可以看出 debian:latest 是image, 如果没有会pull
但是通过docker create命令创建的容器是停止的,我们可以用 docker ps -a -q 命令来查看哪些是停止状态的容器。这个命令后面会再讲到。docker create 创建的容器需要用docker start命令来启动容器, 命令中 f4086492e895 是新建容器的id, 也可以通过 docker ps -a -q 命令来查看
docker start f4086492e895
2 直接创建启动一个容器
先创建在启动比较麻烦,可以用docker run命令来实现直接创建启动一个容器的,其实docker run命令等价于先执行docker create然后执行docker start命令。
创建容器
语法
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]docker run [option] 镜像名 [向启动容器中传入的命令
常用可选参数说明:
-i 表示以“交互模式”运行容器,表示让容器的标准输入保持打开, 通常与 -t 同时使用
-t 表示让docker分配一个伪输入终端(pseudo-tty)并绑定到容器的标准输入上,同时容器启动后会进入其命令行,通常与 -i 同时使用。
-d 则会创建一个守护式容器在后台运行
--name 为创建的容器命名
--volume,-v 绑定一个卷。表示目录映射关系(前者是宿主机目录,后者是映射到宿主机上的目录,即 宿主机目录:容器中目录),可以使用多个-v 做多个目录或文件映射。注意:最好做目录映射,在宿主机上做修改,然后可以共享到容器上。
-p 指定端口映射,格式为:主机(宿主)端口:容器内映射端口
-P: 随机端口映射,容器内部端口随机映射到主机的高端口
-e 为容器设置环境变量
--network=host 表示将主机的网络环境映射到容器中,容器的网络与主机相同
当使用docker run命令创建启动容器时,Docker在后台运行的标准操作有下面几个步骤
- 检测本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个IP地址给容器
- 执行用户指定的应用程序
- 执行完毕后终止容器
示例:
使用docker镜像 nginx:latest 以后台模式启动一个容器,并将容器命名为my-nginx, 同时将容器80端口映射到主机8081端口,可以通过浏览器访问 http://主机IP:8081
docker run --name my-nginx -p 8081:80 -d nginx:latest
使用镜像 nginx:latest 以交互模式启动一个容器,在容器内执行/bin/bash命令
docker run -it nginx:latest /bin/bash
docker挂载目录
通过-v参数,冒号前为宿主机目录,必须为绝对路径,冒号后为镜像内挂载的路径
docker run -it -v /home/dock/Downloads:/usr/Downloads ubuntu64 /bin/bash
如果要挂载多个宿主机目录,可以写多个 -v
docker run -it -v [宿主机目录1]:[docker目录1] -v [宿主机目录2]:[docker目录2] ubuntu64 /bin/bash
交互式容器
例如,创建一个交互式容器,并命名为mycentos
docker run -it --name=mycentos centos /bin/bash
在容器中可以随意执行linux命令,就是一个ubuntu的环境,当执行exit命令退出时,该容器也随之停止。
守护式容器(让docker 在后台保持运行状态)
在实际应用中我们更多的是需要容器在后台以守护状态(Daemonized)形式运行,我们可以使用-d参数来实现容器的后台守护状态运行
例如,创建一个守护式容器,并命名为mycentos2
docker run -dit --name=mycentos2 centos
创建一个守护式容器:如果对于一个需要长期运行的容器来说,我们可以创建一个守护式容器。在容器内部exit退出时,容器也不会停止。注意:如果想要docker 在后台运行,退出时应该
Ctrl + p + q
否则dockers容器会退出关闭
3 进入容器
进入已运行的容器
docker exec 命令
语法
语法:
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
其中 CONTAINER 可以是 容器名或容器id
OPTIONS说明:
- -d : 分离模式: 在后台运行
- -i : 即使没有附加也保持STDIN 打开
- -t : 分配一个伪终端
docker exec -it mycentos2 /bin/bash
在容器名称 mycentos2 中开启一个交互模式的终端
docker exec -it 721eb23901ce /bin/bash
使用容器ID 721eb23901ce 开启一个交互模式的终端
查看容器
docker ps -a // 查看全部容器
docker ps -l // 查看最新容器docker container ls #列出本机正在运行的容器
docker container ls --all # 列出本机所有容器,包括已经终止运行的
退出容器
方法一:使用exit命令
在容器内部,通过"exit"命令可以安全地退出容器,并返回到主机系统。这个命令会将容器停止并删除它。
这个方法适用于不需要保留容器状态或文件系统更改的情况。如果需要保留容器状态或文件系统更改,可以使用第二种方法。
方法二:使用Ctrl+P+Q组合键
另一种退出Docker容器的方法是使用Ctrl+P, Ctrl+Q组合键。这个方法可以将容器放入后台运行,而不停止它或删除它。通过这种方法,你可以保留容器状态,并在需要时重新进入。
停止与删除容器
docker stop hunter_dev && docker rm hunter_dev
docker stop 容器名或容器id # 停止一个已经在运行的容器
docker start 容器名或容器id # 启动一个已经停止的容器
docker rm 容器名或容器id # 删除一个已经在运行的容器
Docker之容器的创建、启动、终止、删除、迁移等 | Linux–不是那么难
四 Docker 报错问题:
1 、Pytorch下运行Dataloader报错, num_workers设置与docker的shared memory相关问题
如下面的截图错误:
只要是下面这两个错误
错误1: ERROR: Unexpected bus error encountered in worker. This might be caused by insufficient shared memory (shm).
错误2:RuntimeError: DataLoader worker (pid 2212) is killed by signal: Bus error. It is possible that dataloader's workers are out of shared memory. Please try to raise your shared memory limit.
产生错误的原因:
最可能的原因是,Docker的共享内存不足。由于在docker镜像中默认限制了shm(shared memory), 然而数据处理时pythorch则使用了shm。这就导致了在运行多线程时会将超出限制的DataLoader并直接被kill掉。这个原因可以从docker的说明文档中可以看出:
--oom-kill-disable=false:Whether to disable OOM Killer for the container or not.
这个命令表示是否要kill超出内存的程序,以免崩溃。
dataloader从RAM中找本轮迭代要用的batch,如果找到了就使用。如果没找到,就要num_worker个worker继续加载batch到内存,直到dataloader在RAM中找到目标batch。num_worker设置得大,好处是寻batch速度快,因为下一轮迭代的batch很可能在上一轮/上上一轮…迭代时已经加载好了。坏处是内存开销大,也加重了CPU负担(worker加载数据到RAM的进程是CPU复制的嘛)。num_workers的经验设置值是自己电脑/服务器的CPU核心数,如果CPU很强、RAM也很充足,就可以设置得更大些。
解决办法
方法1(测试过可用):在Dataloader中将num_worker设置为0。意味着每一轮迭代时,dataloader不再有自主加载数据到RAM这一步骤(因为没有worker了),而是在RAM中找batch,找不到时再加载相应的batch。
方法2:batch size设置得过大,shared memory不够(因为docker限制了shm)。因此改成更小的batchsize, 这样多线程就不会导致超出docker内存不够了。
方法3(推荐):重新调整docker的运行内存。退出docker后,重新运行docker并指定更大的共享内存。
docker run -m='16g' -memory-swap="20g" # 这个命令没测试过
或者在起Docker容器时,设置 --ipc=host 或者 --shm-size
--shm-size 8G 命令测试过
docker run --runtime=nvidia --shm-size 8G -it --rm dev:v1 /bin/bash
docker中内存相关的命令
关于docker内存相关的命令:(docker中有很多与内存相关的命令,如kernel-memory,oom等,这里列出常用的几个)
命令 | 说明 |
-m, --memory="" | Memory limit (format: []). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M. |
--memory-swap="" | 总内存设置:内存+交换内存;Total memory limit (memory + swap, format: []). Number is a positive integer. Unit can be one of b, k, m, or g. |
--shm-size="" | 共享内存 Size of /dev/shm. The format is . number must be greater than 0. Unit is optional and can be b (bytes), k (kilobytes), m (megabytes), or g (gigabytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses 64m. |