2.docker镜像

docker

在使用docker命令时需使用管理员身份运行

Docker镜像介绍

在上一章介绍到镜像是docker的三大组件之一。docker运行容器需要本地存在对应的镜像。在执行时如果镜像不存在本地,docker会从镜像仓库下载(默认是Docker Hub中的仓库)。

base镜像

base镜像的含义

1.不依赖其他镜像
2.其他镜像可以以之为基础进行扩展
能称之为base镜像的通常是各种linux发行版的docker镜像。如:centos、ubuntu等

base镜像原理介绍

内核空间和用户空间
内核空间:bootfs,linux系统启动时会加载bootfs文件系统,之后会将boots卸载
用户空间:rootfs,包含/dev、/proc、/bin等目录
镜像底层直接使用docker宿主机的kernel,只需要运行rootfs即可,所以base镜像都是很小的。

  • base 镜像只是在用户空间与发行版一致,kernel 版本与发型版是不同的。
  • 容器只能使用 Host 的 kernel,并且不能修改。
    所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器

获取镜像

  1. 查找想要的镜像
    搜索指定版本需要到 docker hub中查看
    1
    docker search [选项] image_name
    【选项】:

-s NUM:可以查看NUM星以上的镜像
–automated:默认false,显示automated build镜像
–no-trunc:默认false,镜像描述不截断

  1. 获取想要的镜像
    1
    2
    3
    docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
    #例如下载默认仓库的ubuntu:18.04其实相当于
    docker pull registry.hub.docker.com/ubuntu:18.04
    镜像仓库地址:地址的格式一般是<域名/IP>[:端口号],默认地址是Docker Hub。
    仓库名:正如上一章所述,仓库名为两段式 <用户名>/<软件名>;若仓库地址为Docker Hub,未给出用户名,则默认为library,即官方镜像。
    标签:若忽略tag则下载最新版本的镜像,即tag为latest,镜像的latest标签意味着该镜像的内容会跟踪最新版本的变更而变化,内容是不稳定的。

获取ubuntu镜像

1
2
3
4
5
6
7
8
9
$docker pull ubuntu:18.04  
18.04: Pulling from library/ubuntu
35c102085707: Pull complete
251f5509d51d: Pull complete
8e829fe70a46: Pull complete
6001e1789921: Pull complete
Digest: sha256:d1d454df0f579c6be4d8161d227462d69e163a8ff9d20a847533989cf0c94d90
Status: Downloaded newer image for ubuntu:18.04
docker.io/library/ubuntu:18.04

这样就获取了ubuntu标签为18.04的景象,从上面的下载过程中可以看到分层存储的概念,竟像是由多层存储构成的,并给出了每一层的IMAGE ID的前12位,以及该镜像完整的sha256 摘要,以确保下载的一致性。
镜像下载速度会非常缓慢,推荐使用阿里镜像加速器
阿里云
按照操作文档执行完成后使用docker info查看 Registry Mirrors是否修改。

运行镜像

镜像下载完成后,以该镜像为基础启动并运行一个容器。

1
docker run -it --rm ubuntu:18.04 bash

-it:i为交互式操作,t为终端,这样就可以进入交互式终端
–rm:容器退出后删除容器,一般不会指定这个参数,这里只是进行测试
ubuntu:18.04:指定镜像,需执行tag,若不指定tag则默认tag为latest,且若本地无该标签的镜像,会自动进行下载。
bash:放在镜像后面的是命令,因为需要一个交互式shell。

退出容器,执行exit即可

列出本地镜像

docker images [选项] [镜像仓库[:镜像标记]]
docker image ls [选项] [镜像仓库[:镜像标记]]
【选项】:
-a:显示本地所有的镜像文件(含中间层镜像,默认不展示中间层镜像)
–digests:显示镜像文件的摘要信息
–no-trunc:显示完整的镜像信息
-f /–filter:过滤,例如-f since=mongo:3.2,是查看mongo:3.2之后建立的镜像,查看之前的镜像使用before。
-q:只显示镜像的ID
–format:列出镜像列表指定的列,如:
docker image ls --format "{ {.ID} }: { {.Repository} }" 是只展示镜像ID、和仓库名列;还可以以表格的形式进行展示,如:docker image ls --format "table { {.ID} }\t{ {.Repository} }\t{ {.Tag} }",这样列出的信息可以等距展示,并且有标题行,和默认展示的结构一样。因语法原因,导致在两个花括号之间是加上了空格
man docker-images来查看
镜像列表
REPOSITORY:镜像仓库
TAG:镜像标签,区分版本
IMANE ID:镜像ID(唯一)
CREATED:镜像创建时间
SIZE:镜像大小
可以看到列出的镜像的大小与Docker Hub上显示的镜像带下不同,这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。而 docker images 显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空间的总和,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小。
另外一个需要注意的问题是,docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
可以使用docker system df查看镜像、容器、数据卷所占用的空间。
docker system df

使用tag命令添加镜像标签

1
docker tag ubuntu:18.04 myubuntu:18.04

tag
可以发现两个ubuntu的IMAGE ID是一样的,它们实际上指向了同一个镜像文件,只是别名不同而已。docker tag命令添加的标签实际上起到了类似链接的作用。

使用inspect命令查看详细信息

1
docker inspect ubuntu:18.04

使用docker inspect命令可以获取到镜像的详细信息,返回的是一个JSON格式的消息,可以使用-f 来指定获取的指定信息。

1
docker inspect -f "{{.Id}}" ubuntu:18.04

使用history命令查看镜像历史

镜像文件由多个层组成,可以使用history子命令,该命令将列出各层的创建信息

1
docker history ubuntu:18.04 [--no-trunc]

默认情况下过长的命令自动被阶段,使用--no-trunc可以显示完整命令。

虚悬镜像

当我们可能会在本地镜像列表中看到仓库名、标签均为<none>。这个镜像原本是有镜像名和标签的,随着镜像的维护,发布了新版本,重新docker pull后镜像名被转移到新下载的镜像上,而旧的镜像上的名称被取消,从而形成了<none>。docker build 也可以导致这样的问题,由于新旧镜像名一样,旧镜像名被取消。这类无标签镜像被称为 虚悬镜像
可以使用docker images -f dangling=true查看虚悬镜像
一般虚悬镜像失去了价值,可以使用docker image prune 进行删除

中间层镜像

在使用一段时间后,可能会看到一些依赖的中间层镜像,默认的docker imagesdocker image ls只能会显示顶层的镜像,要显示所有镜像的话需要加上-a。
这样会看到许多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是中中间层镜像,是其他镜像依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。他们没有被多存一份。只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。

创建镜像

创建镜像的方法主要有两种:基于已有镜像的容器创建、基于Dockerfile创建。

修改已有镜像

使用修改已有镜像的方法创建一个新的镜像,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像

  1. 先使用下载的镜像启动容器
    docker run -i -t ubuntu /bin/bash

-i:交互式操作
-t:终端
- -rm:退出容器后随之将该容器删除
- -name= :指定容器名称

启动容器
root@后面的为该容器的ID
2. 在容器中进行修改,如安装软件,
apt-get -y update
apt-get -y install apache2
3. 退出容器
exit
4. 提交更新后的副本
docker commit [选项] 容器ID/容器名 镜像仓库名[:tag]
【选项】
-m=”commit message”:指定提交的说明信息
- -author=”author”:镜像作者信息
-p:提交时暂停容器运行
提交镜像
创建成功后会返回镜像ID信息
容器ID除了在之前容器中的交互式中查看之外,还可以通过docker ps -a查看
docker ps -a
创建成功后就可以在docker的本地镜像列表中查看到了
镜像列表-2
慎用docker commit,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,如果不小心清理,将会导致镜像极为臃肿。
此外,使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体在操作的。虽然 docker diff 或许可以告诉得到一些线索,但是远远不到可以确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。
而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。

通过Dockerfile文件和docker build创建镜像

具体查看 3.使用Dockerfile定制镜像

上传镜像

用户可以通过docker push命令将自己创建的镜像上传到仓库来共享

将自己的docker镜像上传到阿里云的本地仓库

登录阿里云,在容器镜像服务的命名空间中创建一个命令空间,然后在镜像仓库中创建镜像仓库(镜像仓库名为所要存储的镜像的名,镜像仓库中存储不同版本的镜像),具体的操作步骤可以查看仓库后的管理
阿里云仓库管理

1
2
3
$ sudo docker login --username=guang123hb registry.cn-shanghai.aliyuncs.com
$ sudo docker tag [ImageId] registry.cn-shanghai.aliyuncs.com/daguangy/my_images:[镜像版本号]
$ sudo docker push registry.cn-shanghai.aliyuncs.com/daguangy/my_images:[镜像版本号]

导出和导入镜像

导出镜像

导出镜像到本地文件,可以使用docker save命令

1
2
例如:
sudo docker save -o ubuntu_14.04.tar ubuntu:14.04

-o :表示输出到文件

导入镜像

使用docker load从导出的本地文件中再导入到本地镜像

1
2
3
sudo docker load -i ubuntu_14.04.tar

sudo docker load < ubuntu_14.04.tar

这将会导入镜像以及其的元数据信息(包括标签等)

删除本地镜像

如果要移除本地镜像,可以使用docker rmi [选项] 镜像docker image rm [选项] <镜像1> [<镜像2> ...]命令,镜像可以是镜像短ID、镜像长ID、镜像名、或者镜像摘要。完整的镜像ID也成为长ID,docker images ls展示的是短ID,在删除镜像时一般提取前三个字符以上,只要能够区分镜像就可以了。注意docer rm CONTAINER ID是删除容器。
删除镜像
-f:强制删除镜像,即使有容器依赖它。

Untagged和Deleted

镜像的唯一标识就是ID和digests(摘要),而一个镜像可以有多个标签。从上面的图片中可以看出在删除镜像时有Untagged、Deleted行为。
当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker image rm 都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。
当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变动非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的源。
除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。之前讲过,容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。

清理虚悬镜像

虚悬镜像一般是已经失去了存在价值的

1
2
3
4
5
6
docker rmi $(docker images -q -f "dangling=true")

docker rmi $(docker images --quiet --filter "dangling=true")
这种docker rmi $(....)可以嵌套docker images来选择行的批量删除镜像

docker image prune

镜像的缓存特性

  • Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。
    例如使用ubuntu镜像为基础进行构建,安装vim,将构建的镜像命名为test1;再次使用ubuntu镜像为基础进行构建,安装vim,将某个文件拷贝到镜像指定位置,将构建的镜像命名为test2。两次构件都是使用ubuntu镜像为基础并安装vim,在构建test1时安装vim的步骤将会单独创建一个镜像层,在构建test2中安装vim的这一步则会直接使用这一镜像层。
  • 除了构建时使用缓存,Docker 在下载镜像时也会使用。
    例如已经拉取了一个base镜像,再拉取其他镜像时,若使用的是改base镜像,则这一层的镜像层就不在拉取。