一、运行
docker run -it --rm \
ubuntu:18.04 \
bash
root@e7009c6ce357:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.1 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.1 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
docker run
就是运行容器的命令,具体格式我们会在 容器 一节进行详细讲解, 我们这里简要的说明一下上面用到的参数。
-
-it
:这是两个参数,一个是-i
:交互式操作,一个是-t
终端。我们这里打算进入bash
执行一些命令并查看返回结果,因此我们需要交互式终端。 -
--rm
:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动docker rm
。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用--rm
可以避免 浪费空间。 -
ubuntu:18.04
:这是指用ubuntu:18.04
镜像为基础来启动容器。 -
bash
:放在镜像名后的是 命令,这里我们希望有个交互式Shell
,因此用的是bash
。
进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release
,这是 Linux 常用的查看当前系统版本的命令,从返回的 结果可以看到容器内是 Ubuntu 18.04.1 LTS
系统。
最后我们通过exit
退出了这个容器。
二、列出镜像
要想列出已经下载下来的镜像,可以使用docker image ls
命令
docker image ls
列表包含了仓库名、标签 、镜像 ID、创建时间以及所占用的空间。
其中仓库名、标签在之前的基础概念章节已经介绍过了。镜像 ID则是镜像的唯一 标识,一个镜像可以对应多个 标签。因此,在上面的例子中,我们可以看到 ubuntu:18.04 和 ubuntu:latest 拥有相同的 ID,因为它们对应的是同一个镜像。
查看镜像、容器、数据卷所占用的空间
docker system df
显示中间层镜像
为了加速镜像构建、重复利用资源,Docker 会利用 中间层镜像。所以在使用一段 时间后,可能会看到一些依赖的中间层镜像。默认的 docker image ls
列表中 只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a
参数。
docker image ls -a
这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都 是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导 致上层镜像因为依赖丢失而出错。实际上,这些镜像也没必要删除,因为之前说 过,相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被 列出来而多存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像 后,这些依赖的中间层镜像也会被连带删除。
列出部分镜像
不加任何参数的情况下,docker image ls
会列出所有顶层镜像,但是有时候我们只希望列出部分镜像。docker image ls
有好几个参数可以帮助做到这个事情。
根据仓库名列出镜像
docker image ls ubuntu
列出特定的某个镜像,也就是说指定仓库名和标签
docker image ls ubuntu:18.04
除此以外, docker image ls
还支持强大的过滤器参数 --filter
,或者简写 -f
。之前我们已经看到了使用过滤器来列出虚悬镜像的用法,它还有更多的用 法。比如,我们希望看到在 mongo:3.2
之后建立的镜像,可以用下面的命令:
docker image ls -f since=mongo:3.2
想查看某个位置之前的镜像也可以,只需要把since
换成before
即可。此外,如果镜像构建时,定义了LABEL
,还可以通LABEL
来过滤。
docker image ls -f label=com.example.version=0.1
以特定格式显示
默认情况下,docker image ls
会输出一个完整的表格,但是我们并非所有时候 都会需要这些内容。比如,刚才删除虚悬镜像的时候,我们需要利用 docker image ls
把所有的虚悬镜像的 ID 列出来,然后才可以交给 docker image rm
命令作为参数来删除指定的这些镜像,这个时候就用到了 -q
参数。
docker image ls -q
三、删除本地镜像
如果要删除本地的镜像,可以使用docker image rm
命令,其格式为:
docker image rm [选项] <镜像1> [<镜像2> ...]
用 ID、镜像名、摘要删除镜像
其中,<镜像>可以是镜像短 ID、镜像长 ID、镜像名或者镜像摘要。
我们可以用镜像的完整 ID,也称为 长 ID ,来删除镜像。使用脚本的时候可能会 用长 ID,但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜 像。 docker image ls
默认列出的就已经是短 ID 了,一般取前3个字符以上,
只要足够区分于别的镜像就可以了。
Untagged 和 Deleted
如果观察上面这几个命令的运行输出信息的话,你会注意到删除行为分为两类,一 类是 Untagged
,另一类是Deleted
。我们之前介绍过,镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。
因此当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。 所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged
的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定 的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么Delete
行为就不会发生。所以并非所有的 docker image rm
都会产生删除镜像的行 为,有可能仅仅是取消了某个标签而已。
当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发 删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次 进行判断删除。镜像的多层结构让镜像复用变得非常容易,因此很有可能某个其它 镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到 没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇 怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么 有时候会发现所删除的层数和自己 docker pull
看到的层数不一样的原因。
除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的 容器存在(即使容器没有运行),那么同样不可以删除这个镜像。之前讲过,容器 是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需 要的,应该先将它们删除,然后再来删除镜像。
用 docker image ls 命令来配合
像其它可以承接多个实体的命令一样,可以使用 docker image ls -q
来配合使 用 docker image rm
,这样可以成批的删除希望删除的镜像。我们在“镜像列表”章节介绍过很多过滤镜像列表的方式都可以拿过来使用。
比如,我们需要删除所有仓库名为redis
的镜像:
docker image rm $(docker image ls -q redis)
或者删除所有在mongo:3.2
之前的镜像:
docker image rm $(docker image ls -q -f before=mongo:3.2)
充分利用你的想象力和 Linux 命令行的强大,你可以完成很多非常赞的功能。
三、操作 Docker 容器
容器是 Docker 又一核心概念。
简单的说,容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的, 虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环 境)和跑在上面的应用。
本节将具体介绍如何来管理一个容器,包括创建、启动和停止等。
启动容器
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止 状态(stopped
)的容器重新启动。
因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。
新建并启动
所需要的命令主要为docker run
。 例如,下面的命令输出一个 “Hello World”,之后终止容器。
docker run ubuntu:18.04 /bin/echo 'Hello world'
Hello world
这跟在本地直接执行 /bin/echo 'hello world'
几乎感觉不出任何区别。 下面的命令则启动一个 bash 终端,允许用户进行交互。
docker run -t -i ubuntu:18.04 /bin/bash
root@af8bae53bdd3:/#
其中,-t
选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入 上,-i
则让容器的标准输入保持打开。
在交互模式下,用户可以通过所创建的终端来输入命令,例如
root@af8bae53bdd3:/# pwd
/
root@af8bae53bdd3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin
srv sys tmp usr var
当利用 docker run
来创建容器时,Docker 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个 ip 地址给容器
- 执行用户指定的应用程序 执行完毕后容器被终止
启动已终止容器
可以利用 docker container start
命令,直接将一个已经终止的容器启动运行。
容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此 之外,并没有其它的资源。可以在伪终端中利用 ps
或 top
来查看进程信息。
root@ba267838cc1b:/# ps
可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率 极高,是货真价实的轻量级虚拟化。
后台运行
更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d
参数来实现。 下面举两个例子来说明一下。 如果不使用 -d
参数运行容器。
$ docker run ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结 果可以用 docker logs
查看)。 注: 容器是否会长久运行,是和 docker run
指定的命令有关,和 -d
参数无 关。 使用 -d
参数启动后会返回一个唯一的 id
,也可以通过 docker container ls
命令来查看容器信息。要获取容器的输出信息,可以通过docker container logs
命令。
终止容器
可以使用 docker container stop
来终止一个运行中的容器。 此外,当 Docker 容器中指定的应用终结时,容器也自动终止。 例如对于上一章节中只启动了一个终端的容器,用户通过 exit
来退出终端时,所创建的容器立刻终止。
终止状态的容器可以用 docker container ls -a
命令看到。
处于终止状态的容器,可以通过docker container start
命令来重新启动。
此外,docker container restart
命令会将一个运行态的容器终止,然后再重新启动它。
进入容器
在使用-d
参数时,容器启动后会进入后台。 某些时候需要进入容器进行操作,包括使用 docker attach
命令或 docker exec
命令,推荐大家使用 docker exec
命令,原因会在下面说明。
attach
命令
下面示例如何使用docker attach
命令。
docker run -dit ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550
docker container ls
docker attach 243c
root@243c32535da7:/#
注意: 如果从这个 stdin 中 exit,会导致容器的停止。
exec
命令
-i
-t
参数
docker exec
后边可以跟多个参数,这里主要说明-i
-t
参数。
只用 -i
参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回。
当 -i
-t
参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。
docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6
docker container ls
docker exec -i 69d1 bash
ls
bin
boot
dev
docker exec -it 69d1 bash
root@69d137adef7a:/#
如果从这个 stdin 中 exit,不会导致容器的停止。这就是为什么推荐大家使用 docker exec
的原因。
更多参数说明请使用docker exec --help
查看。
导出和导入容器
导出容器
如果要导出本地某个容器,可以使用docker export
命令。
docker container ls -a
docker export 7691a814370e > ubuntu.tar
这样将导出容器快照到本地文件。
导入容器快照
可以使用docker import
从容器快照文件中再导入为镜像,例如
cat ubuntu.tar | docker import - test/ubuntu:v1.0
docker image ls
此外,也可以通过指定 URL 或者某个目录来导入,例如
docker import http://example.com/exampleimage.tgz example/imagerepo
注:用户既可以使用 docker load
来导入镜像存储文件到本地镜像库,也可以使用docker import
来导入一个容器快照到本地镜像库。这两者的区别在于容 器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
删除容器
可以使用docker container rm
来删除一个处于终止状态的容器。例如
docker container rm trusting_newton
如果要删除一个运行中的容器,可以添加-f
参数。Docker 会发送SIGKILL
信号给容器。
清理所有处于终止状态的容器
用 docker container ls -a
命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用下面的命令可以清理掉所有处于终止状态的容器。
docker container prune
四、Docker 数据管理
这一节介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:
- 数据卷(Volumes)
- 挂载主机目录 (Bind mounts)
数据卷
数据卷
是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:
-
数据卷
可以在容器之间共享和重用 - 对
数据卷
的修改会立马生效 - 对
数据卷
的更新,不会影响镜像 -
数据卷
默认会一直存在,即使容器被删除
注意:
数据卷
的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷
。
创建一个数据卷
docker volume create my-vol
查看所有的数据卷
docker volume ls
在主机里使用以下命令可以查看指定数据卷
的信息
docker volume inspect my-vol
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
启动一个挂载数据卷的容器
在用 docker run
命令的时候,使用--mount
标记来将数据卷
挂载到容器里。在一次 docker run
中可以挂载多个数据卷
。
下面创建一个名为web
的容器,并加载一个数据卷
到容器的/webapp
目录。
docker run -d -P \
--name web \
# -v my-vol:/wepapp \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py
查看数据卷的具体信息
在主机里使用以下命令可以查看web
容器的信息
docker inspect web
数据卷
信息在 "Mounts" Key 下面
"Mounts": [
{
"Type": "volume",
"Name": "my-vol",
"Source": "/var/lib/docker/volumes/my-vol/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
删除数据卷
docker volume rm my-vol
数据卷
是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在 容器被删除后自动删除数据卷
,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷
。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用docker rm -v
这个命令。
无主的数据卷可能会占据很多空间,要清理请使用以下命令
docker volume prune
挂载主机目录
挂载一个主机目录作为数据卷
使用--mount
标记可以指定挂载一个本地主机的目录到容器中去。
docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py
上面的命令加载主机的 /src/webapp
目录到容器的 /opt/webapp
目录。这个 功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,以前使用 -v
参数时如果本地目录不存在 Docker 会自动为你创建一个文件夹,现在使用 --mount
参数时如果本地目录不存在,Docker 会报错。
Docker 挂载主机目录的默认权限是读写
,用户也可以通过增加 readonly
指定为只读
。
docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp:ro \
--mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
training/webapp \
python app.py
加了readonly
之后,就挂载为只读
了,如果你在容器内/opt/webapp
目录新建文件,会显示如下错误
/opt/webapp # touch new.txt
touch: new.txt: Read-only file system
查看数据卷的具体信息
在主机里使用以下命令可以查看web
容器的信息。
docker inspect web
挂载主机目录
的配置信息在 "Mounts" Key 下面
"Mounts": [
{
"Type": "bind",
"Source": "/src/webapp",
"Destination": "/opt/webapp",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
挂载一个本地主机文件作为数据卷
--mount
标记也可以从主机挂载单个文件到容器中
$ docker run --rm -it \
# -v $HOME/.bash_history:/root/.bash_history \
--mount type=bind,source=$HOME/.bash_history,target=/root/.ba
sh_history \
ubuntu:18.04 \
bash
root@2affd44b4667:/# history
1 ls
2 diskutil list
这样就可以记录在容器输入过的命令了。