理解Docker镜像
Docker image概念介绍
简单来说,Docker image是用来启动容器的只读模板,是容器启动所需要的rootfs,类似于虚拟机所使用的镜像。首先需要通过一定的规则和方法Docker image。
Remote-dockerhub.com/namespace/bar:latest
Docker镜像的表示方式,可以看到其被’/'分成三个部分,其中每部分都可以类比Github中的概念。
- Remote docker hub:集中存储镜像的Web服务器地址。该部分的存在使得可以区分从不同镜像库中拉取的镜像。若Docker的镜像表示中缺少该部分,说明使用的是默认镜像库,即Docker官方镜像库。
- Namespace:类似于Github中的命名空间,是一个用户或组织中所有镜像的集合。
- Repository:类似于Git仓库,一个仓库可以有多个镜像,不同镜像通过tag来区分。
- Tag:类似Git仓库中的tag,一般用来区分同一类镜像的不同版本。
- Layer:镜像由一系列层组成,每层都用64位的十六进制数表示,非常类似于Git仓库中的commit。
- Image ID:镜像最上层的layer ID就是该镜像的ID,Repo:tag提供了易于人类识别的名字,而ID便于脚本处理、操作镜像。
镜像库是Docker公司最先提出的概念,非常类似应用市场的概念。用户可以发布自己的镜像,也可以使用别人的镜像。Docker开源了镜像存储部分的源代码(Docker Registry以及Distribution),但是这些开源组件并不适合独立地发挥功能,需要使用Nginx等代理工具添加基本的鉴权功能,才能搭建出私有镜像仓库。本地镜像则是已经下载到本地的镜像,可以使用docker images等命令进行管理。这些镜像默认存储在/var/lib/docker路径下,该路径也可以使用docker daemon -g参数在Daemon时指定。
使用Docker image
Docker内嵌了一系列命令制作、管理、上传和下载镜像。可以调用REST API给Docker daemon发送相关命令,也可以使用client端提供的CLI命令完成操作。
列出本机的镜像
docker images
其中,--filter
用于过滤docker images的结果,过滤器采用key=value的这种形式。目前支持的过滤器为dangling和label。--filter "dangling=true"
会显示所有’悬挂’镜像。‘悬挂’镜像没有对应的名称和tag,并且其最上层不会被任何镜像所依赖。docker commit在一些情况下会产生这种’悬挂’镜像。下面第一条命令产生了一个悬挂镜像,第二条命令根据其特点过滤出该镜像了。
在上面的命令中,–no-trunc参数可以列出完整长度的image ID。若添加参数-q则会只输出Image ID,该参数在管道命令中很有用处。一般来说悬挂镜像并不总是我们所需要的,并且会浪费磁盘空间。可以使用如下命令删除所有悬挂镜像。
docker images --filter "dangling=true" -q | xargs docker rmi
Docker只会保留最核心的image相关命令和功能,因此哪些非核心功能就会被删除。比如–tree和–dot已经从Docker1.7中删除。官方推荐使用dockerviz工具分析Docker image。
安装dockerviz
wget https://github.com/justone/dockviz/releases/download/v0.6.0/dockviz_linux_amd64 -O /usr/bin/dockviz
chmod +x /usr/bin/dock
树状图
dockviz images -t
Build:创建一个镜像
创建镜像是一个很常用的功能,即可以从无到有地创建镜像,也可以以现有的镜像为基础进行增量开发,还可以把容器保存为镜像。
1、直接下载镜像
我们可以从镜像仓库下载一个镜像,比如,以下为下载busybox镜像的实例。
docker pull busybox
2、导入镜像
还可以导入一个镜像,对此,Docker提供了两个可用的命令docker import和docker load。docker load一般只用于导入由docker save导出的镜像,导入后的镜像跟原镜像完全一样,包括拥有相同的镜像ID和分层等内容。下面的第一行命令可以导出busybox为busybox.tar,第二条命令则是导入该镜像。
docker save -o busybox.tar busybox
docker load -i busybox.tar
不同于docker load,docker import不能用于导入标准的Docker 镜像,而是用于导入包含跟文件系统的归档,并将之变成Docker镜像。
3、制作新的镜像
前面说过,docker import用于导入包括跟文件系统的归档,并将之变成Docker镜像。因此,docker import常用来制作Docker基础镜像。与此相对,docker export则是把一个镜像导出为根文件系统的归档。
Docker提供的docker commit命令可以增量地生成一个镜像,该命令可把容器保存为一个镜像,还能注明作者信息和镜像名称,这与git commit类似。当镜像命令为空时,就会形成"悬挂"镜像。当然,使用这种方式每新增加一层都需要数个步骤(比如,启动容器、修改、保存修改等),所以效率是比较低的,因此这种方式适合正式制作镜像前的舱室,当最终确定制作步骤后,可以使用docker build,通过Dockerfile文件生成镜像。
Ship:传输一个镜像
镜像传输是连接开发和部署的桥梁。可以使用Docker镜像仓库做中转传输,还可以使用docker export/docker save生成的tar包来实现,或者使用Docker镜像的模板文件Dockerfile做间接传输。
Run:以image为模板启动一个容器
启动容器时,可以使用docker run命令。
Docker image的组织结构
数据的内容
Docker image包含着数据及必要的元数据。数据由一层层的image layer组成,元数据则是一些JSON文件,用来描述数据之间的关系以及容器的一些配置信息。下面使用overlay存储驱动对Docker image的组织结构进行分析,首先需要启动Docker daemon
docker daemon -D -s overlay -g /var/lib/docker
下载busybox用作分析,由于前面已经下载过了,所以这里并没有重新下载,而只是做了简单的校验。可以看到Docker对镜像进行了完整性检验,这种完整性凭证是由镜像仓库提供的。
docker pull busybox
docker history busybox
数据的组织
docker inspect busybox
[
{
"Id": "sha256:7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14",
"RepoTags": [
"busybox:latest"
],
"RepoDigests": [
"busybox@sha256:e7157b6d7ebbe2cce5eaa8cfe8aa4fa82d173999b9f90a9ec42e57323546c353"
],
"Parent": "",
"Comment": "",
"Created": "2021-11-11T19:19:37.862545075Z",
"Container": "8afe392526b6fa99a3498001c95812b187123968e5a14802c9e837e1cd06d02b",
"ContainerConfig": {
"Hostname": "8afe392526b6",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"sh\"]"
],
"Image": "sha256:d39a5c18a94ca076b3f9fad5b104d1b5555697280b61cbabd1eec6d89908b1b6",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "20.10.7",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"sh"
],
"Image": "sha256:d39a5c18a94ca076b3f9fad5b104d1b5555697280b61cbabd1eec6d89908b1b6",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 1239828,
"VirtualSize": 1239828,
"GraphDriver": {
"Data": {
"MergedDir": "/var/lib/docker/overlay2/0dc6a08c999348a3846199f15431a21624b766eb60377fd5aa41cf166e61abd9/merged",
"UpperDir": "/var/lib/docker/overlay2/0dc6a08c999348a3846199f15431a21624b766eb60377fd5aa41cf166e61abd9/diff",
"WorkDir": "/var/lib/docker/overlay2/0dc6a08c999348a3846199f15431a21624b766eb60377fd5aa41cf166e61abd9/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:d94c78be13527d00673093f9677f9b43d7e3a02ae6fa0ec74d3d98243b5b40e4"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
- id:Image的ID。image ID实际上只是最上层的layer ID,所以docker inspect也适用于任意一层layer
- Parent:该layer的父层,可以递归地获得某个image的所有layer信息。
- Comment:非常类似于Git的commit message,可以为该层做一些历史记录,方便理解。
- Container:这个条目比较有意思,其中包括哲学的味道。比如前面提到容器的启动需要以image为模板。但又可以把该容器保存为镜像,所以一般来说image的每个layer都保存自一个容器,所以该容器可以说是image layer的“模板”
- Config:包含了该image的一些配置信息,其中比较重要的是:"env"容器启动时会作为容器的环境变量,“Cmd”作为容器启动时的默认命令,“Labels”参数可以用于docker images命令过滤。
- Architecture:该image对应的CPU体系结构。
拓展
联合挂载
联合文件系统这种思想由来已久,这些文件系统会把多个目录挂载到同一个目录,对外呈现这些目录的联合。
写时复制
当父进程fork子进程时,内核并没有为子进程分配内存,而是让父子进程共享内存。当两者之一修改共享内存时,会触发一次缺页异常导致真正的内存分配。这样做既加速了子进程的创建速度,又减少了内存的消耗。