Docker 运行容器前需要本地存在对应的镜像,如果镜像不存在,Docker 会尝试先从默认镜像仓库下载(默认使用 Docker Hub 公共注册服务器中的仓库),用户也可以通过配置,使用自定义的镜像仓库。
使用pull获取镜像
从 Docker Hub 镜像源来下载镜像的命令格式是:
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
- Docker 镜像仓库地址:地址的格式一般是
<域名/IP>[:端口号]
。默认地址是 Docker Hub(docker.io),可以省略; - NAME 是镜像仓库名称(用来区分镜像);
- TAG 是镜像的标签(往往用来表示版本信息)。如果不显式指定TAG,则默认会选择latest标签,这会下载仓库中最新版本的镜像;
通常情况下,描述一个镜像需要包括“名称+标签”信息。
例如,获取一个 Ubuntu 18.04 系统的基础镜像可以使用如下的命令:
> docker pull ubuntu:18.04
> docker pull xxx.com/ubuntu:18.04 # 从xxx.com源下载ubuntu:18.04镜像
上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。而镜像名称是 ubuntu:18.04
,因此将会获取官方镜像 library/ubuntu
仓库中标签为 18.04
的镜像。
从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成,下载也是一层层的去下载,并非单一文件。
下载过程中给出了每一层的 ID 的前 12 位,bf5d46315322 这样的串是层的唯一id(实际上完整的id包括256比特,64个十六进制字符组成)。使用 docker pull 命令下载中会获取并输出镜像的各层信息。当不同的镜像包括相同的层时,本地只会存储层的一份内容,减小了存储空间;并且下载结束后,给出该镜像完整的 sha256
的摘要,以确保下载一致性。
pull 子命令支持的选项主要包括:
- -a, —all-tags=true|false:是否获取仓库中的所有镜像,默认为否;
- —disable-content-trust:取消镜像的内容校验,默认为真。
有时需要使用镜像代理服务来加速 Docker 镜像获取过程,可以在 Docker 服务启动配置中增加 —registry-mirror=proxy_URL 来指定镜像代理服务地址(如https://registry.docker-cn.com)。
下载镜像到本地后,即可随时使用该镜像了,例如利用该镜像创建一个容器,在其中运行 bash 应用,执行打印“Hello World”命令:
> docker run -it --rm ubuntu:18.04 bash
root@e7009c6ce357:/# echo "hello world"
hello world
root@e7009c6ce357:/# exit
docker run
就是运行容器的命令;-it
:这是两个参数,一个是-i
:交互式操作,一个是-t
终端。我们这里打算进入bash
执行一些命令并查看返回结果,因此我们需要交互式终端。--rm
:表示容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动docker rm
。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用--rm
可以避免浪费空间。ubuntu:18.04
:这是指用ubuntu:18.04
镜像为基础来启动容器。bash
:放在镜像名后的是 命令,这里我们希望有个交互式 Shell,因此用的是bash
。
进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里我们执行了 echo “hello world”,终端输出了“hello world”。
最后我们通过 exit
退出了这个容器。
使用ls查看镜像信息
查看已下载的镜像信息
**
使用 docker images
或 docker image ls
命令可以列出本地主机上已有镜像的基本信息。格式如下:
docker images [OPTIONS] [REPOSITORY[:TAG]]
示例如下:
> docker image
# or
> docker image ls
列表包含了 仓库名
、标签
、镜像 ID
、创建时间
以及 所占用的空间
。
镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个标签。因此,在上面的例子中,我们可以看到 hello-world:latest
和 hello-world:linux
拥有相同的 ID,因为它们对应的是同一个镜像。
镜像大小信息只是表示了该镜像的逻辑体积大小,实际上由于相同的镜像层本地只会存储一份,物理上占用的存储空间会小于各镜像逻辑体积之和。
images 子命令主要支持如下选项,用户可以自行进行尝试:
- -a, —all=true|false:列出所有(包括临时文件)镜像文件,默认为否;
- —digests=true|false:列出镜像的数字摘要值,默认为否;
- -f, —filter=[]:过滤列出的镜像,如 dangling=true 只显示没有被使用的镜像;也可指定带有特定标注的镜像等;
- —format “TEMPLATE”:控制输出格式,如 .ID代表ID信息,.Repository代表仓库信息等;
- —no-trunc=true|false:对输出结果中太长的部分是否进行截断,如镜像的ID信息,默认为是;
- -q, —quiet=true|false:仅输出ID信息,默认为否。
更多子命令选项还可以通过 man docker-images 来查看。
示例如下
> docker image ls hello-world # 根据仓库名列出镜像
> docker image ls ubuntu:18.04 # 列出特定的某个镜像(指定仓库名和标签)
docker image ls
还支持强大的过滤器参数 --filter
。比如,我们希望看到在 hello-world 之后建立的镜像,可以用下面的命令:
> docker image ls -f since=hello-world
想查看某个位置之前的镜像也可以,只需要把 since
换成 before
即可。
另外一些时候,我们可能只是对表格的结构不满意,希望自己组织列;或者不希望有标题,这样方便其它程序解析结果等,这就用到了 Go 的模板语法。比如,下面的命令会直接列出镜像结果,并且只包含镜像ID和仓库名:
> docker image ls --format "{{.ID}}: {{.Repository}}"
> docker image ls -q
或者打算以表格等距显示,并且有标题行,和默认一样,不过自己定义列:
> docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
使用tag命令添加标签
为了方便在后续工作中使用特定镜像,还可以使用 docker tag 命令来为本地镜像任意添加新的标签。例如,添加一个新的 my-hello-world:linux 镜像标签:
> docker tag hello-world:linux my-hello-world:linux
> docker image ls
之后,用户就可以直接使用 my-hello-world:linux 来表示这个镜像了。
使用inspect命令查看详细信息
使用 docker[image] inspect
命令可以获取该镜像的详细信息,包括制作者、适应架构、各层的数字摘要等。
> docker image inspect my-hello-world:linux
> docker image inspect -f {{".RepoTags"}}
内容太多,这里就不上截图了。有兴趣可自行测试。
ocker image inspect 返回的是一个JSON格式的消息。如果我们只要其中一项内容时,可以使用 -f 来指定,例如,获取镜像的 RepoTags。
使用history命令查看镜像历史
既然镜像文件由多个层组成,那么怎么知道各个层的内容具体是什么呢?这时候可以使用 history 子命令,该命令将列出各层的创建信息。
例如,查看 hello-world:latest 镜像的创建过程,可以使用如下命令:
> docker history hello-world:latest
使用search搜寻镜像
使用 docker search 命令可以搜索 Docker Hub 官方仓库中的镜像。语法为 :
> docker search [option] keyword
支持的命令选项主要包括:
- -f, —filter filter:过滤输出内容;
- —format string:格式化输出内容;
- —limit int:限制输出结果个数,默认为25个;
- —no-trunc:不截断输出结果。
搜索官方提供的带 nginx 关键字的镜像,如下所示:
> docker search --filter=is-official=true nginx
用rm删除和prune清理镜像
rm删除镜像
如果想要删除本地的镜像,可以使用:
docker image rm [选项] <镜像1> [<镜像2> ...]
or
docker image rm [选项] <镜像ID1> [<镜像ID2> ...]
支持的选项:
- -f, -force:强制删除镜像,即使有容器依赖它;
- -no-prune:不要清理未带标签的父镜像。
:::info 提示:如果一个镜像有多个标签,执行 rm 删除只会删除该镜像指定标签;如果镜像只有一个标签,执行 rm 会删除该镜像。 :::
要删除掉 my-hello-world:linux 镜像,可以使用如下命令:
> docker rmi my-hello-world:linux # 仅删除标签
注意,当有该镜像创建的容器存在时,镜像文件默认是无法被删除的,这时需要先删除依赖该镜像的所有容器,接着再删除镜像。
:::danger 虽然使用 -f 参数可以强制删除有容器依赖的镜像,但是并不推荐这种做法。 :::
如果观察上面这几个命令的运行输出信息的话,你会注意到删除行为分为两类,一类是
Untagged
,另一类是Deleted
。前面说过,镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。而当我们执行命令删除时,首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的
Untagged
的信息,这时可能还有别的标签指向了这个镜像,如果是这种情况,那么Delete
行为就不会发生。当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变得非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。
> 除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。> 如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。
prune清理镜像
使用 Docker 一段时间后,系统中可能会遗留一些临时的镜像文件,以及一些没有被使用的镜像,可以通过 docker image prune
命令来进行清理。支持的选项:
- -a, -all:删除所有无用镜像,不光是临时镜像;
- -filter filter:只清理符合给定过滤器的镜像;
- -f, -force:强制删除镜像,而不进行提示确认。
创建镜像
创建镜像的三种方式:基于已有镜像的容器创建(commit)、基于本地模板导入(import)、基于 Dockerfile 创建(build)。
commit方式创建
镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。**
下面演示通过一个例子来说明如何通过 commit 构建镜像。
首先启动一个镜像,命令为 test,并进入 shell 界面,其中 4faca687ffd9 为容器ID,test 是容器名称。
> docker run --name test -it ubuntu:18.04 /bin/bash # 启动镜像
接着我们往该容器中添加一个新文件,假设为 test.txt,执行 exit 退出。这时我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 docker diff
命令看到具体的改动。
root@4faca687ffd9:/# cd /tmp/
root@4faca687ffd9:/tmp# touch test.txt
root@4faca687ffd9:/tmp# exit
这里可以使用 docker diff {containerName}
查看具体改动。
现在我们定制了变化,希望保存下来形成镜像。而上面我们说过,我们在容器上做的任何操作都是记录在存储层里。Docker 提供了一个 docker commit
命令,可以将容器的存储层保存下来成为镜像。也就是说,在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
docker commit 语法格式为 docker [container] commit [OPTIONS] CONTAINER [REPOSITORY [:TAG]]
,主要选项包括:
- -a, —author=””:作者信息;
- -c, —change=[]:提交的时候执行 Dockerfile 指令,包括CMD|ENTRYPOINT|ENV|EXPOSE|LABEL|ONBUILD|USER|VOLUME|WORKDIR等;
- -m, —message=””:提交消息;
- -p, —pause=true:提交时暂停容器运行。
> docker commit --author "cimi" --message "添加test.txt文件" test test:v2
> docker image ls # 查看新镜像
还可以用 docker history
具体查看镜像内的历史记录,可以看到我们的新增层。之后我们就可以使用这个新的镜像了。
使用
docker commit
命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。首先,如果仔细观察之前的
docker diff test
的结果,你会发现除了真正想要修改的/tmp/test.txt
文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,将会导致镜像极为臃肿。此外,使用
docker commit
意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为 黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用
docker commit
制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。
import方式导入
用户可以从一个操作系统模板文件导入一个镜像。命令格式为:
docker [image] import [OPTIONS] file|URL|-[REPOSITORY [:TAG]]
可以使用 OpenVZ 或其他导出的镜像的模板来导入一个镜像。OPENVZ 模板的下载地址为http://openvz.org/Download/templates/precreated。
例如,下载了 ubuntu-18.04 的模板压缩包,之后使用以下命令导入即可:
> cd /tmp
> wget http://download.openvz.org/template/precreated/ubuntu-14.04-x86_64-minimal.tar.gz
> cat ubuntu-14.04-x86_64-minimal.tar.gz | docker import - ubuntu:14.04
> docker image ls # 查看镜像
Dockerfile构建
在 docker commit
中我们了解镜像的定制就是定制每一层的配置。同样我们也可以将定制内容写入脚本,通过脚本来构建定制镜像,这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
还以 commit 的例子为例,实践如何构建镜像。创建一个目录,并新建一个 Dockerfile。
> cd /tmp
> mkdir docker-test
> docker-test
> touch Dockerfile
Dockerfile 内容如下:
# 指定 Docker 镜像
FROM ubuntu:14.04
# 在test.txt中写入一些内容
RUN echo 'test ubuntu:14.04' > /tmp/test.txt
测试如下:
关于 Dockerfile 的更多配置将在 Dockerfile文件配置 一节说明。
用save存出和load载入镜像
save存出镜像
如果要导出镜像到本地,使用 docker [image] save
。支持的选项是:
- -o,—output:导出镜像到指定位置。
例如,将 test:v2 导出到 /tmp 目录下。
> docker image ls
> docker image save --output /tmp/ubuntu-test.tar test:v2
> ls /tmp
load载入镜像
将导出的 tar 文件再导入到本地镜像库,使用 docker [image] load
。支持选项:
- -i、—input:从指定文件中读入镜像内容。
将上面例子存出的镜像载入。
> docker image ls
> docker image load --input /tmp/ubuntu-test.tar
or
> docker image load < /tmp/ubuntu-test.tar
导入成功后,可以使用 docker images 命令进行查看,与原镜像一致。
用push上传镜像
用户可以使用 docker [image] push 命令上传镜像到仓库,默认上传到 Docker Hub官方仓库(需要登录)。命令格式如下:
docker [image] push NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG]
在 Docker Hub 网站注册后可以上传自制的镜像。例如,先添加新的标签 ${username}/hello-world:v2,然后用 docker [image] push
命令上传镜像,这里以 hello-world:latest 为例:
> docker images
> docker tag hello-world:latest ${username}/hello-world:v2
> docker login
> docker push ${username}/hello-world:v2
最终就可以在网站上查看提交记录。
第一次上传时,会提示输入登录信息或进行注册,之后登录信息会记录到本地 ~/.docker 目录下。
【填坑记录】
1、报错一:denied: requested access to the resource is denied
解决方法思路:
① 必须打上用户信息tag,即 ${username}/hello-world:v2
② docker hub 上需要注册对应 ① 中tag名称的仓库
③ docker push 之前需要先通过 docker login 登录
2、报错二:net/http: TLS handshake timeout
解决方式:修改 /etc/docker/daemon.json 文件并添加上 registry-mirrors 键值:
{
"registry-mirrors": [
"https://registry.docker-cn.com",
...
]
}
to be continue…