简介

  • 镜像相当于一个程序,容器相当于一个进程。
  • 容器就是运行着的镜像。

在这里插入图片描述

  • 镜像由多个层组成,每层叠加之后,从外部看来就是一个独立的镜像。镜像内部是一个精简的操作系统,同时内部还包含应用运行所必须的文件和依赖。
  • 这些层都是只读的

在这里插入图片描述

镜像仓库服务

  • 刚开始是本地docker是没有镜像的,一般需要先从镜像仓库中拉取镜像本地,然后就可以使用这个镜像启动一个或者多个容器
  • 一旦容器从镜像中启动之后,在容器全部停止之前,这个镜像是无法被删除的

远程镜像仓库

  • docker镜像存储在镜像仓库服务中。docker客户端的镜像仓库服务是可配置的,默认使用Docker Hub
  • 一个镜像仓库服务包含多个仓库(就是镜像名),一个仓库中包含多个版本
    在这里插入图片描述

Docker Hub分为官方仓库非官方仓库

  • 官方仓库是由docker公司审查的,安全
  • 非官方仓库没有人审查,可能会不安全

本地镜像仓库

在这里插入图片描述

命令

搜索(远程)镜像

在这里插入图片描述

// 搜索带 nginx 关键字的镜像
$ docker search nginx
NAME                                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
nginx                                             Official build of Nginx.                        16924     [OK]       
linuxserver/nginx                                 An Nginx container, 

// 搜索官方提供的带 nginx 关键字的镜像
$ docker search --filter=is-official=true  nginx
NAME      DESCRIPTION                STARS     OFFICIAL   AUTOMATED
nginx     Official build of Nginx.   16924     [OK]    

// 搜索所有收藏数超过4的带 nginx 关键字的镜像
$ docker search --filter=stars=4  nginx

// 搜索自动创建的仓库(镜像)
$ docker search nginx --filter "is-automated=true"  

可以看到返回了很多包含关键字的镜像,其中

  • NAME :仓库名称,包括docker ID,或者非官方仓库的组织名称
  • DESCRIPTION :描述
  • STARS :收藏数(表示该镜像的受欢迎程度)
  • OFFICIAL: 是否官方创建
  • AUTOMATED: 是否自动创建

默认的输出结果将按照星级评价进行排序。

默认值只返回前25行,可以通过--limit参数来增加内容行数,最多100行

查看(本地)镜像

使用 images 命令列出镜像

使用 docker images 或 docker image ls 命令可以列出本地主机上已有镜像的基
本信息。

$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
gb28181      latest    e2bb49fceade   6 hours ago   880MB

在这里插入图片描述

  • 同一个仓库源可以有多个TAG,代表这个仓库源的不同版本,我们使用镜像仓库源:镜像标签来定义不同的镜像。
  • 这些镜像都是存储在Docker宿主机的/var/lib/docker目录下

images 子命令主要支持如下选项:

在这里插入图片描述

在这里插入图片描述

–format 格式化输出内容

可以通过–format参数来过滤docker image ls的输出内容。

(1)仅显示镜像的大小属性:

$ docker image ls --format "{{.Size}}"
880MB
880MB

(2)仅显示镜像的仓库和标签属性:

$ docker image ls --format "{{.Repository}}:{{.Tag}}"
gb28181:latest

–filter过滤镜像

可以通过–filter参数来过滤docker image ls的输出内容。其支持的过滤器如下

在这里插入图片描述

(1)仅显示标签为latest的镜像

docker image ls --filter=reference="*:latest"
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
gb28181      latest    e2bb49fceade   6 hours ago   880MB

(2)仅会显示悬虚镜像

$  docker image ls --filter dangling=true
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
<none>       <none>    5d1686b1730f   6 months ago   880MB

那什么叫做悬虚镜像呢?

  • 没有标签的镜像叫做悬虚镜像,在列表中展示为<none:none>
  • 为什么出现这种情况,
    • 当构建新镜像时,为该镜像打了一个已经存在的标签
    • 此时,docker在构建新镜像时,会移除旧镜像上的标签,然后将标签移到新镜像上。旧镜像就变成了悬虚镜像

移除全部的悬虚镜像(这个命令没有起作用):

$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

使用 inspect 命令查看详细信息

使用docker image inspect命令可以获取该镜像的详细信息,包括制作者、适应
架构、各层的数字摘要等:

$ docker image inspect ubuntu
[
    {
        "Id": "sha256:27941809078cc9b2802deb2b0bb6feed6c236cde01e487f200e24653533701ee",
        "RepoTags": [
            "myubuntu:lastest",
            "ubuntu:latest"
        ],
        "RepoDigests": [
            "ubuntu@sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac"

上面代码返回的是一个 JSON 格式的消息,如果我们只要其中一项内容时,可以使用f 来指定,例如:

docker image inspect -f {{".Metadata"}} ubuntu

使用history命令查看镜像历史

既然镜像文件由多个层组成,那么怎么知道各个层的内容具体是什么呢?这时候可以使
用 history 子命令,该命令将列出各层的创建信息。

$ docker image history ubuntu
IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
27941809078c   34 hours ago   /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      34 hours ago   /bin/sh -c #(nop) ADD file:11157b07dde10107f…   77.8MB    

注意,过长的命令被自动截断了,可以使用–no-trunc选项来输出完整命令

docker image history ubuntu --no-trunc

拉取镜像

  • Docker 运行容器前需要本地存在对应的镜像,如果镜像不存在, Docker 会尝试先从镜像仓库服务下载。
  • 那怎么知道要下载那个镜像呢?只要给出镜像的名字和标签,就能在官方仓库中定位一个镜像(采用:分隔)

根据标签拉取镜像

从拉取镜像的命令格式如下:

docker image pull <repository>:<tag>
  • repository 是镜像仓库名称(用来区分镜像)
  • tag 是镜像的标签(往往用来表示版本信息)

(1)从官方仓库拉取镜像实例:

$ docker image pull ubuntu:18.04  // 从官方ubuntu库中拉取标签为18.04的镜像
$ docker image pull ubuntu:latest  // 从官方ubuntu库中拉取标签为latest的镜像
$ docker image pull ubuntu  // 从官方ubuntu库中拉取标签为latest的镜像

注意:

  • 如果没有指定标签,则默认拉取latest镜像
  • 标有latest标签的镜像不保证这是仓库中最新的镜像

在这里插入图片描述

下载过程中可以看出,镜像文件一般由若干层 (layer) 组成, 6c953ac5d795 这样的
串是层的唯一 id (实际上完整的 id 包括 256 比特, 64 个十六进制字符组成)。使用 docker pull 命令下载中会获取并输出镜像的各层信息。当不同的镜像包括相同的层时,本地仅存储了层的一份内容,减小了存储空间。

(2) 从非官方仓库拉取镜像

docker pull docker hub账户名/仓库名:镜像名

(3) 从第三方镜像仓库服务中拉取镜像

docker pull 第三方镜像仓库服务地址/docker hub账户名/仓库名:镜像名

pull 子命令支持的选项主要包括:

在这里插入图片描述

关于-a选项:一个镜像可能有多个标签,我们可以用docker image pull -a参数来拉取仓库中的全部镜像

docker image pull -a <repository>

另外,有时需要使用镜像代理服务来加速 Docker镜像获取过程,可以在 Docker 服务
启动配置中增加 --registry-mirror==proxy_URL 来指定镜像代理服务地址

下载镜像到本地后,即可随时使用该镜像了,例如利用该镜像创建一个容器,在其中运
行 bash 应用,执行打印 “Hello World" 命令:

在这里插入图片描述

根据摘要拉取镜像

问题:如果有两个镜像,它们的标签和名字都是一样的,但是内容不一样,怎么区分它们呢?

答案

通过摘要区别,每一个镜像都有一个摘要,只要镜像内容变更了散列值一定会改变。

每次拉取镜像时会有有一个摘要作为返回值。

也可以通过如下命令在本地查看摘要:

$ docker image ls --digests ubuntu
$  docker image pull ubuntu@sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac

添加镜像标签

为了方便在后续工作中使用特定镜像,还可以使用 docker tag 命令来为本地镜像任
意添加新的标签。例如,添加一个新的 myubun七u:la七 es 七镜像标签:

docker tag ubuntu:latest myubuntu:latest

再次使用 docker images 列出本地主机上镜像信息,可以看到多了一个 myubuntu:latest的镜像:
在这里插入图片描述
可以看到,这些镜像的 ID是一样的,它们实际上指向了同一个镜像文件,只是别名不同而已。 docker tag命令添加的标签实际上起到了类似链接的作用。

删除和清理镜像

使用标签删除镜像

删除操作会在当前主机上删除该镜像以及相关的镜像层,这意味着无法通过docker image ls命令查看删除后的镜像(它只能看本机仓库的),并且对应的包含镜像层数据的目录会被删除

在这里插入图片描述

  • 当同一个镜像拥有多个标签的时候, docker rmi 命令只是删除了该镜像多个标签中的指定标签而已,并不影响镜像文件
$ docker rmi myubuntu:latest
Untagged: myubuntu:latest
  • 当镜像只剩下一个标签的时候就要小心了,此时再使用 docker rmi 命令会彻底删除镜像(如下可以看出,删除了这个镜像的所有文件层)

在这里插入图片描述

使用镜像 ID 来删除镜像

当使用 docker rmi 命令,并且后面跟上镜像的 ID (也可以是能进行区分的部分 ID 串前缀)时,会先尝试删除所有指向该镜像的标签,然后删除该镜像文件本身

注意,当有该镜像创建的容器存在时,镜像文件默认是无法被删除的,看个例子:

(1) 先启动一个容器

$ docker run ubuntu:latest echo 'hello'
hello

(2)使用 docker ps -a 命令可以看到本机上存在的所有容器:

$ docker ps -a
CONTAINER ID   IMAGE           COMMAND                CREATED          STATUS                      PORTS                                                                                                                                                                                                                                                                                                                                                                       NAMES
1cc612923b2a   ubuntu:latest   "echo hello"           6 seconds ago    Exited (0) 5 seconds ago    

可以看到,后台存在一个退出状态的容器,是刚基于ubuntu:latest镜像创建的。

(3)试图删除该镜像, Docker 会提示有容器正在运行,无法删除:

$ docker rmi ubuntu
Error response from daemon: conflict: unable to remove repository reference "ubuntu" (must force) - container 1cc612923b2a is using its referenced image 27941809078c

如果要想强行删除镜像,可以使用-f 参数

$ docker rmi -f ubuntu

注意,通常并不推荐使用-f参数来强制删除一个存在容器依赖的镜像。正确的做法是,先删除依赖该镜像的所有容器,再来删除镜像。

(1)先删除容器

$ docker rm 1cc612923b2a
1cc612923b2a

(2)再删除镜像

$ docker rm 27941809078c

批量删除镜像

删除全部镜像

可以在docker image rm命令中传入当前系统的全部镜像ID,可以通过docker image ls -q获取全部镜像ID

# 停止所有容器

docker ps -a | grep "Exited" | awk '{print $1 }'|xargs docker stop

# 删除所有容器
docker ps -a | grep "Exited" | awk '{print $1 }'|xargs docker rm


#  删除异常停止的docker容器
docker rm `docker ps -a | grep Exited | awk '{print $1}'`

# 可以删除所有名字中带 “none” 关键字的镜像
docker rmi $(docker images | grep "none" | awk '{print $3}') 


# 删除所有镜像
docker image rm $(docker image ls -q) -f
# 删除所有镜像
docker rmi `docker images -q`

遇到过的错误:

image has dependent child images

在 Docker 中删除 image 时有时会遇到类似

Error response from daemon: conflict: unable to delete 6ec9a5a0fc9f (cannot be forced) - image has dependent child images

这样的错误,是因为镜像有从属关系,因为当前镜像是其他镜像的创建底层。而且这个镜像在容器里面还在运行。

解决:

  • 先查询依赖
docker image inspect --format='{{.RepoTags}} {{.Id}} {{.Parent}}' $(docker image ls -q --filter since=xxxxxx)

其中 xxxxxx 是报错 image 的 id,在文章开头的例子中就是 6ec9a5a0fc9f。从列表中查找到之后就可以核对并删除这些 image。

  • 然后根据根据TAG删除容器
docker rm REPOSITORY:TAG
image is referenced in multiple repositories

原因:IMAGE ID为7042885a156a的nginx指向两个repository,所以删除失败了
解决:指定repository和tag删除

docker rmi xxx:rag
Error response from daemon: No command specified.

一般来说产生这个报错是因为启动的容器是被docker export出来的镜像

[root@dockertest container]# docker run --name nginx-import-test -d -p 4000:80 nginx-import-test:1.0 
docker: Error response from daemon: No command specified.
See 'docker run --help'.

被docker export出来的镜像在启动的时候需要指定command,例如我启动上述nginx容器,需要指定Command

Command可以通过下面命令查询到:

$ docker ps -a --no-trunc                                                                   

在这里插入图片描述
查询到Command之后,运行容器:

docker run --name nginx-import-test -d -p 4000:80 nginx-import-test:1.0  /docker-entrypoint.sh nginx -g 'daemon off;

容器就正常启动了

清理镜像

在这里插入图片描述

比如,如下命令会自动删除临时的遗留镜像文件层,最高提示是否的存储空间:

$ docker image prune -f
....
Total reclaimed space: ...

创建镜像

镜像是容器的基础,每次执行 docker run 的时候都会指定哪个镜像作为容器运行的基础。当我们使用Docker Hub的镜像无法满足我们的需求时,我们就需要自己定制镜像来满足我们的需求。

创建镜像的方法主要有三种:

  • 基于已有镜像的容器创建,对应commit命令
  • 基于本地模板导入,对应import命令
  • 基于dockerfile创建,对应build命令

基于已有容器创建

docker commit 语法格式:

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

在这里插入图片描述

实例

(1)启动一个镜像,并在其中做一些修改:

$ docker run -it ubuntu /bin/bash
root@944848df736f:/$ touch test
root@944848df736f:/$ exit 

注意,容器的ID为944848df736f

(2)此时容器已经和原来的镜像由一些改变,可以使用docker commit将之提交为一个新的镜像:

$ docker commit --author "oceanstar" --message "add a new file" 944848df736f test:0.1
sha256:b33d05e3bc50856da633dda7982067b5934e8eb96298129daeb50bb88b4cc5c7

顺利的话,会返回新创建镜像的 ID 信息

(3)此时查看本地镜像列表,会发现新创建的镜像已经存在了:

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED              SIZE
test         0.1       b33d05e3bc50   About a minute ago   77.8MB

实例二

如下命令会用 nginx:1.11 镜像启动一个容器,命名为 webServer,并且映射了80 端口,这样便可以去访问这个 nginx 服务器。然后我们直接访问宿主机的IP可以看到Nginx的欢迎页面

[root@server ~]# docker run --name webServer -d -p 80:80 nginx:1.11  #启动一个容器,基于docker hub上面的nginx:1.11镜像

现在,假设我们不喜欢这个欢迎页面,我们喜欢改成别的文字,我们可以使用docker exec 命令进入到容器,修改其内容给

root@714830c04e5e:/# echo '<h1>Hello Docker Nginx Server</h1>' >/usr/share/nginx/html/index.html   #修改默认首页的内容
root@714830c04e5e:/# exit
exit

已交互式终端方式进入 webServer 容器,并执行了 bash命令, 也就是获得了一个可操作的shell。然后覆盖了index.html内容,再次刷新浏览器,会发现内容被改变了。

修改了容器的文件,也就是改动了容器的存储器,可以通过 docker diff 命令查看具体的改动

[root@server ~]# docker diff webServer  #查看webServer容器改动的内容

现在我们定制好了变化,我们希望能将其保存下来形成镜像。要知道,当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储器里。而Docker提供了一个 docker commit 命令,可以将容器的存储层保存下来称为镜像。也就是说在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们在运行这个新镜像的时候,就会拥有原有容器最后的文件变化。

示例将上面更改首页的webServer 容器保存为镜像:

[root@server ~]# docker commit \
--author "Bu Ji <381347268@qq.com>" \
--message "修复了默认首页" \
webServer \
nginx:v1

........

[root@server ~]# docker images nginx   #查看制作完成的nginx镜像
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v1                  b639fbcc5ec4        2 minutes ago       183MB
nginx               1.11                5766334bdaa0        21 months ago       183MB

其中 --author 是指定修改的作者,而 --message 则是记录本次修改的内容。这点和 git 版本控制器相似。还可以使用 docker history 具体查看镜像内的历史记录

[root@server ~]# docker history nginx:v1  #查看nginx:v1镜像的历史记录

新的镜像定制好后,我们来运行这个镜像

[root@server ~]# docker run --name web1 -d -p 81:80 nginx:v1  #基于上面新建的nginx:v1启动一个名字为web1的容器

当我们访问宿主机IP:81时候,其内容和之前修改后的 webServer一样

至此,完成了一个定制镜像,使用的是 docker commit 命令,手动给旧的镜像添加了新的一层,形成了新的镜像,对镜像多层存储应该有了很直观的感受。

慎用 docker commit

使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中很少这样使用。

  • 首先, 从上面的 docker diff webServer 的结果中,可以发现除了真正想要修改的 /usr/share/nginx/html/index.html 文件外,由于命令的执行,还有很多文件被改动或添加了。这还只是最简单的操作,如果安装软件包、编译构建,那么有大量的无关内容被添加进来,如果不小心清理,将会导致镜像为臃肿。
  • 此外,使用docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无法从知。虽热docker diff 或许可以告诉得到一些线索,但是远远不到可以确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。
  • 而且,除当前层外,之前的每一层都是不会发生改变的,也就是说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每次一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。

基于本地模板导入

要直接导入一个镜像,可以使用 OpenVZ 提供的模板来创建,或者用其他已导出的镜像模板来创建。 OPENVZ 模板的下载地址为http://openvz.org/Download/templates/pr,ecr~ated。
例如,下载了 ubuntu-18.04 的模板压缩包,之后使用以下命令导入即可:

在这里插入图片描述

基于 Dockerfile 创建

基于 Dockerfile 创建是最常见的方式。 Dockerfile 是一个文本文件,利用给定的指令描述基于某个父镜像创建新镜像的过程。

步骤:

  • 先写一个dockerfile脚本
  • 使用docker build命令构建

导出和载入镜像

docker save

如果要导出镜像到本地文件,可以使用 docker [image] save 命令。该命令支持-o 、 -output string 参数,导出镜像到指定的文件中。

在这里插入图片描述

docker load

可以使用 docker [image] load 将导出的七 ar 文件再导入到本地镜像库。支持-i、一input string选项,从指定文件中读入镜像内容。

在这里插入图片描述

这将导入镜像以及相关的元数据信息(包括标签)。导入成功后,可以使用docker images命令进行查看,和原镜像一致。

上传镜像

可以使用 docker [image] push 命令上
传镜像到仓库,默认上传到 Docker Hub 官方仓库(需要登录)
在这里插入图片描述

理论

镜像是分层的

镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储是在以镜像为基础层,在基础层上加一层作为容器运行时的存储层。

怎么验证

docker镜像由一些只读镜像层组成,如下图:
在这里插入图片描述

怎么查看某个镜像的分层呢?可以通过docker image pull
在这里插入图片描述

可以看出这个镜像包括5个镜像层,如下图:
在这里插入图片描述

也可以可以通过使用docker image inspect来查看分层信息

在这里插入图片描述

这些"24efXXXXXX"是镜像摘要

什么叫做镜像摘要:

  • 镜像是由一层层独立的层组合起来的
  • 镜像本身是一个配置对象,其中包含了镜像层的列表以及一些元数据信息
  • 镜像层才是实际数据存储的地方,各个精选层之间是完全独立的,没有从属于某个镜像集合的概念
  • 镜像的唯一标识是一个加密ID,即配置对象的摘要(散列值)。每个镜像层也由一个加密ID区分,其值为镜像层本身内容的摘要
  • 只要镜像内容有一点点变化,都会改变这个摘要就会改变。

问题:

  • 在推送和拉取镜像的时候,都会对镜像层进行压缩以节省网络带宽和仓库存储空间
  • 但是压缩会改变镜像内容,这就意味着镜像的摘要在推送或者拉取之后,会与镜像内容不相符!

怎么构建

  • 所有docker镜像都起始于一个基础镜像层,当进行修改或者增加新的内容时,就会在当前镜像层之上,创建新的镜像层。

在这里插入图片描述

  • 在额外添加精选层的同时,镜像时钟是当前所有镜像的组合
    在这里插入图片描述
    在这里插入图片描述

  • 所有镜像堆叠合并,对外提供统一的视图
    在这里插入图片描述

  • docker通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保障多镜像层对外展示为统一的文件系统。linux上可用的存储引擎用AUFS、Device Mapper等。每种存储引擎都基于linux中对应的文件系统或者块设备技术。

共享镜像层

  • 多个镜像之间会共享镜像层,以便节省空间提升性能。
  • docker在linux上支持很多存储引擎,每个存储引擎都有自己的镜像分层、镜像层共享以及写时复制技术的具体实现

怎么映射

在这里插入图片描述
docker在拉取镜像时,会先看看这一层是否在本地已经存在,如果存在,就不去拉取了。

Docker 镜像构建之 docker commit
docker save和docker export的区别

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐