1.5 Docker镜像的基本操作
1.4节讲解了如何简单地运行Nginx、MySQL、Redis容器服务。运行的过程很顺利,因为只用了一条命令。实际上,Docker就是这么简单!Docker这样方便快捷的原因很大程度是因为它丰富的镜像,镜像相当于一个模板,能快速“衍生、克隆”出我们想要的服务。
1.5.1 获取镜像
想使用镜像,首先要有镜像。就像做饭,光有厨艺但是没有食材就是“巧妇难为无米之炊”了。在Docker里获取镜像的方式只有两种:从网上下载或自己动手做。
1.从网上下载镜像
这种方式适合初学者使用,这也是最方便的获取镜像的方式。推荐大家从Docker官方的镜像仓库Docker Hub下载镜像,地址:https://hub.docker.com/explore/。
Docker官方维护了一个公共镜像仓库Docker Hub,已经有超过1.5万个镜像。大部分容器服务需求都可以从Docker Hub下载实现,Docker Hub官方仓库如图1-32所示。
在Docker Hub除了能获取镜像,还能做自动构建和搭建私有仓库,所以建议各位注册一个账号便于日后使用。
既然知道了镜像的获取地址,那么怎么下载这些镜像呢?安装好Docker后,默认会指向这个官方仓库地址。运行docker run命令,如果本地没有要运行的镜像,Docker就会自动从Docker Hub下载镜像。
如果只是下载某些镜像,我们可以使用如下命令。
docker pull [选项] [docker Registry 地址[:端口号]/]仓库名[:标签]
图1-32 Docker Hub官方仓库
比如下载ubuntu 16.04和httpd 2.2的镜像,如果命令中不写冒号+版本号,默认会下载最新版镜像,如图1-33所示。
$ docker pull ubuntu:16.04 $ docker pull httpd:2.2
图1-33 下载最新版镜像
我们可以设置一个国内加速器,让Docker Hub下载快些。方法是在/etc/docker/daemon.json中写入如下内容(如果文件不存在请新建该文件)。
{ "registry-mirrors": [ "https://registry.docker-cn.com" ] }
注意,一定要保证该文件符合json规范,否则Docker无法启动。
然后运行systemctl daemon-reload && systemctl restart docker命令,重启Docker服务。
最后运行docker pull命令,看看速度是否变快了,如图1-34所示。
图1-34 再次运行docker pull命令
2.自己做镜像
如果不想去网上下载官方的镜像或者别人的镜像,我们可以自己做镜像。关于制作镜像的具体方法会在1.6节中进行详细讲解,这里先不做介绍。
1.5.2 列出镜像
下载了镜像,我们肯定想知道镜像都放在哪儿了,另外也想知道有什么方法可以列出下载好的镜像。
镜像下载后,默认是存在/var/lib/docker路径下的。需要注意的是,这个目录有很多结构,大家不要想着镜像就只放在一个目录下,Docker对于镜像的存储是分层管理的,最主要的目录在/var/lib/docker/overlay2下。
1.Docker镜像的目录结构
这里,我们详细说下/var/lib/docker的目录结构。在CentOS 7系统中,Docker版本17.12.0-ce下/var/lib/docker的目录结构如图1-35所示。
图1-35 /var/lib/docker目录结构
(1)builder目录
builder就是用来构建的目录,其中的fscache.db是用来在构建镜像时缓存数据的,也就是缓存了相关镜像的数据,从而大大提高了创建镜像的速度,可以在底层的基础之上构建更高层的镜像。
(2)containerd目录
containerd的主要职责是镜像管理(镜像、元信息等)、容器运行(调用最终运行时组件运行),该目录存放着containerd相关信息。
(3)containers目录
主要用来存储创建容器的内容。我们新建一个容器之后,这里就会多出一个目录,我们创建了3个容器,这里就有3个目录,如图1-36所示。
图1-36 查看创建的容器目录
每个目录都以相应容器特有的ID命名,目录里面存着容器的状态日志、hostname、配置等信息。
(4)image目录
image目录主要用来存储镜像的相关分配、分布、imagedb、layerdb、repositories等信息。信息大部分都是sha256加密形式的。当我们使用docker images命令显示镜像的时候,其实读取的就是这个目录的信息。如果这个目录里没有信息,运行docker images命令就会显示空。
(5)network目录
network目录主要用来存储网络相关的信息,创建容器的时候,如果用到了网络,就会更新network目录的信息。
(6)overlay2目录
overlay2是一种存储驱动,从Docker 1.12开始推出,相较overlay的实现有重大提升。此目录主要用来存储镜像,所有的镜像都会存储在这个位置,包括base image或者是在其上的image。所以我们下载的镜像是存储在overlay2目录,这个目录是镜像存储非常重要的目录,如果你的镜像很多,这个文件夹也是会很大的。
(7)plugins目录
Docker 1.13开始提供插件化管理,这样能快速利用插件的可扩展性。Docker的任务是让所有插件都像容器一样被管理和运行,把Docker Hub作为集中资源,让插件更好用,从而推动过程标准化。这个目录就是存取相关插件信息的目录。
(8)runtimes目录
这是Docker运行时信息存放目录。
(9)swarm目录
swarm是Docker的集群管理工具,存放的是swarm相关文件的信息,如果没装swarm,那么swarm目录里面是空的。
(10)tmp目录
Docker的临时目录。
(11)trust目录
Docker的信任目录。
(12)volumes目录
volumes目录是Docker中数据持久化的最佳方式,默认在主机上会有一个特定的区域(/var/lib/docker/volumes/),该区域用来存放volume。volume在生成的时候如果不指定名称,便会随机生成。所以,这个目录下的名字都是随机的ID。
2.如何修改Docker镜像的存储路径
在CentOS 7下/var/lib/docker目录是很重要的目录,所以这个目录需要保证充足的空间,否则下载镜像的时候空间不够就会下载失败。如果想更改存储镜像的路径,可以采用软连接的方法。首先,停止Docker服务。
$ systemctl stop docker
然后,把/var/lib/docker全部移动到空间足够大的目录里,比如放到/data/docker下。接下来再用ln-s命令创建一个软连接指向/var/lib/docker。最后再启动docker服务,命令如下。
$ mv /var/lib/docker /data/docker $ ln -s /data/docker /var/lib/docker $ systemctl start docker
这个方法比较简单,如果使用其他的方法,还要修改Docker的配置文件,对于原来的配置文件,我们能不修改尽量就不修改。
3.列出Docker镜像的命令
介绍完目录结构,我们再来看如何列出镜像。方法其实很简单,我们只要运行docker images命令就可以把下载好的镜像列出来了,如图1-37所示。
图1-37 查看本地下载好的docker镜像
通过这个命令我们能看到镜像的名称、TAG版本信息、Image ID、创建时间和镜像大小。
1.5.3 导出/导入镜像
假如我们想要把下载好的镜像导出并保存到其他地方或者导入其他容器环境,就要用到save、load和export命令。
1.docker save命令
运行如下命令,可以保存nginx镜像到本地/home路径下。
$ docker save 2d04e7c52fc3 > /home/nginx-save-v3.tar
2d04e7c52fc3就是这个nginx:v3镜像的ID,nginx-save-v3.tar是我们自己命名的tar包名,注意,这里只能是tar包的格式,不要写成其他的格式。
然后通过save命令保存镜像的所有信息,如图1-38所示。
图1-38 运行docker save命令保存镜像信息
2.docker load命令
如果想把保存的镜像导入其他容器环境,只要把导出的tar包文件复制到目标容器环境里,比如放到目录容器服务器里的/root路径下,然后运行docker load命令就可以了。
$ docker load < /root/nginx-save-v3.tar
加载成功后,运行docker images命令发现镜像名称、标签均为none,所以我们要运行docker tag 1e70071f4af4 nginx:v3命令,打上镜像名称和标签名称,方便识别镜像。
3.docker export命令
介绍完docker save和docker load命令,我们来学习docker export命令。
注意,docker export命令用于持久化容器,而不是镜像。所以,这个命令是作用于容器的。它的作用就是把当前运行的容器打包成tar,但是这个tar只保存容器当前的状态信息,没有之前的历史信息或者其他镜像分层信息。
运行docker export<CONTAINER ID> >/home/export.tar命令即可完成镜像导出操作,如图1-39所示。
$ docker export 5eff30763079 > /home/redis-save-latest.tar
图1-39 运行docker export导出镜像信息
注意这里的ID是容器的ID,不是镜像的ID。
我们还可以通过docker import命令把tar导入镜像列表里,如图1-40所示。
$ docker import /home/redis-save-latest.tar redis/redis:v5
图1-40 运行docker import命令导入镜像信息
1.5.4 删除本地镜像
我们需要获取镜像,当然也有可能需要删除镜像。删除镜像的方法很简单,首先要保证当前运行的容器没用到想要删除的镜像。
比如想要删除ubuntu镜像,但是Docker提示有容器正在使用这个镜像,如图1-41所示。
图1-41 删除ubuntu镜像
因此,首先要把这个正在运行的容器停止,然后运行docker rm命令删除这个容器,最终才能删除这个镜像。
$ docker stop b37f8d63c82d #停止容器服务 $ docker rm b37f8d63c82d #删除容器 $ docker image rmi ubuntu #删除镜像
这里要注意的是,假如没有停止容器服务,直接运行docker image rm-f ubuntu命令,则ubuntu那列会显示none,镜像其实没有真正删除,如图1-42所示。
图1-42 查看ubuntu镜像信息显示为none
若删除成功,应该是有如图1-43所示这样的提示。
图1-43 删除镜像成功返回结果
1.5.5 使用commit构建镜像
commit英文里有提交的意思。在Docker中,使用这个命令提交容器的当前状态,然后形成一个镜像文件。其实它是一种制作镜像的方法,但是这种方法有缺陷,我们不推荐使用。Dockerfile是做镜像的专业方法,我们会在下一节中讲解。
从Docker Hub下载的通用官方镜像如果不能满足个性化需求,可以在其基础上做一些修改,安装或者配置我们需要的设置。前面说过,镜像是一层一层叠加的,像集装箱一样,后面一层都是以前面一层为基础的。
比如上文我们运行的Nginx容器,从官网下载运行后,界面是Nginx默认的欢迎界面。我们可以进到这个容器里,运行下面命令修改index.html文件的内容,修改欢迎界面。
$ docker exec -it webserver bash # 进入nginx容器 $ echo '<h1>Welcome, docker</h1>' > /usr/share/nginx/html/index.html
刷新一下浏览器,首页就会改变成Welcome,docker。
我们还可以运行docker diff命令查看都做了哪些操作,如图1-44所示。
简简单单地修改一个界面,就多了这么多变更。
图1-44 运行docker diff命令返回结果
想把当前的容器做成镜像(好比虚拟机做快照打包),可以运行docker commit命令,语法格式为:
docker commit命令,语法格式为: docker commit [选项] <容器ID或容器名> [<仓库名> [:<标签>]]
比如下面这个commit命令。
$ docker commit --author "bowenqiu" --message “修改了默认网页” webserver nginx:v3
●--author “bowenqiu”:注明镜像作者。
●--message“修改了默认网页”:注明这个镜像做了什么修改。
●webserver:指定对哪个运行的容器做commit操作。
●nginx:v3:生成后的镜像名和版本号。
运行完这个命令后,运行docker images命令,可以看到新生成的镜像,如图1-45所示。
图1-45 查看新生成的镜像
新生成的镜像运行成功,界面显示Welcome。
使用docker commit命令构建镜像,虽然可以比较直观地帮助我们理解镜像分层存储的概念,但是还是提醒大家,真正构建镜像应该用Dockerfile方式,因为commit方式有很多缺陷。
首先,如果仔细观察刚才运行的docker diff webserver的结果,会发现除了真正想要修改的/usr/share/nginx/html/index.html文件外,由于命令的运行,还有很多文件被改动或添加。这仅仅是最简单的操作,如果是安装软件包、编译构建镜像,会有大量的无关内容被添加进来,如果不小心清理,会导致镜像极为臃肿。
此外,使用docker commit命令意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人知道运行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是制作这个镜像的人,过一段时间后也可能无法记清具体操作。虽然通过docker diff命令或许可以得到一些线索,但是远远不到确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。
回顾之前提及的镜像所使用的分层存储概念,除当前层外,之前的每一层都不会发生改变,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。
本节我们使用docker commit命令制作镜像,缺点是后期修改的话,每一次修改都会让镜像更加臃肿,所删除的上一层的信息并不会丢失,会一直跟着这个镜像,即使根本无法访问到。所以,构建镜像还是用下一节讲到的Dockerfile方式吧。