容器镜像
镜像是用于创建容器的只读模板。
目前,OCI镜像规范定义了容器镜像的开放规范、容器运行软件的构建规范。大多数容器运行时均支持此统一的开放镜像标准。
Linux下的镜像存储基于UnionFS,利用union mount(UnionFS的一种挂载机制)将不同的目录挂载到同一个虚拟文件系统下,以实现Layer的概念。Layer可以被其它镜像所复用。
挂载目录的时候会严格按照各目录之间的增量关系,将被增量操作的目录优先于在它基础上增量操作的目录挂载。挂载到同一虚拟文件系统下的所有目录全部设置成read-only权限。
当镜像被运行成一个容器时,会在所有read-only的目录挂载结束后,继续挂载一个空的read-write层,写操作是在read-only上的一种增量操作,不影响read-only目录。这个容器的read-write层也可以通过commit命令把它变成一个镜像顶层最新的read-only层。
示例:使用Docker创建镜像
rootfs的存储位置可以在daemon.json中指定:
{ "data-root": "/data/aikube/docker" }
底层可能基于不同的文件系统,比如AUFS、btrfs、devicemapper、overlay。
docker对不同文件系统分别做了对应定制的存储驱动,通过驱动把镜像存在磁盘上面。
创建方法包括:
(1)基于已有容器创建
docker commit <container name | id> repo:tag
-a --author="" 作者信息
-m --message="" 提交信息
-p --pause=true 提交时暂停容器运行
(2)导出容器为本地文件
将容器的文件系统作为tar文件导出到stdout:
docker export <container name | id> -o xxx.tar docker export <container name | id> > xxx.tar
导出容器为文件,将丢弃所有历史记录和元数据信息。
基于本地tgz文件导入:
cat ubuntu-14.04-x86_64-minimal.tar.gz | docker import - ubuntu:14.04 docker import /path/to/exampleimage.tgz
基于本地目录导入:
tar -c . | docker import - exampleimagedir
(3)导出镜像为本地文件
将镜像的文件系统作为tar文件导出到stdout:
docker save repo:tag > xxx.tar docker save repo:tag -o xxx.tar
基于本地tar文件导入:
docker load --input xxx.tar docker load < xxx.tar
使用脚本批量加载要用到的所有镜像:
imageTgzNames=(`ls /data/k8s-package/images`) for nameTgz in ${imageTgzNames[@]}; do docker load < /data/k8s-package/images/${nameTgz} done
(4)基于Dockerfile创建(正规方法)
Dockerfile是一个文本格式的配置文件,可快速创建自定义镜像
编写完成后通过docker build命令创建镜像,该命令会读取指定目录下的所有dockerfile
docker build -t repo:tag /tmp/docker_builder/
-f 指定Dockerfile,不指定的话默认构建当前目录下所有Dockerfile
-t 指定镜像信息
--no-cache=true/false 指定是否使用缓存。若使用,会遍历本地所有镜像,发现镜像与即将构建出的镜像一致时,将找到的镜像作为cache镜像,复用cache镜像作为构建结果。使用ADD/COPY或RUN命令存在外部依赖(如yum install)时一般需要不使用缓存。
指定目录下不能有多余的文件,如果是当前目录则为.
每个构建步骤都会对已有的文件系统进行操作,这样就会带来文件系统内容的变化,这些变化称之为changeset。
把构建步骤所产生的变化依次作用到一个空文件夹上,就能够得到一个完整的镜像。
指令详解:
①第一行必须指定来自的镜像
FROM repo:tag
有时候会使用多个镜像,可以为镜像指定别名:
FROM golang:1.15 as builder
②维护者信息
MAINTAINER docker_user docker_user@email.com
③从上下文目录中复制文件或者目录到容器里指定路径
ADD <源路径1> <目标路径> COPY <源路径1> <目标路径>
区别:ADD的<源文件>为压缩格式为gzip、bzip2、xz的tar压缩文件时,会自动复制并解压到<目标路径>。
在多个基础镜像间拷贝:
COPY --from=builder /workspace/manager .
④运行命令
RUN <command>
此命令将于构建时在shell中运行
⑤设置容器启动命令(只能设置一次)
CMD ["/usr/sbin/sshd","-D"] ENTRYPOINT ["java","-jar","/xxx.jar"]
CMD命令设置容器启动后默认执行的命令及其参数,但CMD设置的命令能够被docker run命令后面的命令行参数替换
ENTRYPOINT配置容器启动时的执行命令,即使运行docker run时指定了其他命令也不会覆盖,除非docker run使用了--entrypoint 参数
两个命令同时使用时,CMD指定变参,相当于给ENTRYPOINT传参,例如:
FROM nginx ENTRYPOINT ["nginx", "-c"] # 定参 CMD ["/etc/nginx/nginx.conf"] # 变参
直接运行容器时,会执行以下命令:
nginx -c /etc/nginx/nginx.conf
传参运行:
docker run nginx:test -c /etc/nginx/new.conf
此时会覆盖参数
⑥参数
环境变量设置:
ENV <key> <value> ENV <key1>=<value1> <key2>=<value2>...
构建时的参数设置
ARG <参数名>[=<默认值>]
⑦用户设置
USER <用户名>[:<用户组>]
overlay文件系统下docker镜像与容器存储结构
以overlay这个文件系统为例,overlay文件系统的工作原理如下图 。
lower层是镜像层,它是一个只读层。容器刚创建出来的时候,upper其实是空的。这个时候如果去读的话,所有数据都是从lower层读来的。
upper层是容器的读写层,采用了写实复制的机制,只有对某些文件需要进行修改的时候才会从lower层把这个文件拷贝上来,之后所有的修改操作都会对upper层的副本进行修改。
workdir充当中间层,当对upper层里面的副本进行修改时,会先放到workdir,然后再从workdir移到 upper 里面去。
PS:这个是overlay 的工作机制。
mergedir是统一视图层。从mergedir里面可以看到upper和lower中所有数据的整合。docker exec到容器里面,看到的文件系统其实就是mergedir统一视图层。
overlay里面其实是没有真正的删除操作的。删除其实是通过对文件进行标记,然后从最上层的统一视图层去看,看到这个文件标记就认为这个文件是被删掉的。这个标记有两种方式:
一种是 whiteout 的方式。
第二个就是通过设置目录的一个扩展权限,通过设置扩展参数"trusted.overlay.opaque"为y来做到目录的删除。、
devicemapper文件系统下docker镜像与容器存储结构
devicemapper文件系统下实际有两种模式:
(1)loop-lvm模式
用一个稀疏文件来当成一个块设备,给devicemapper用,作为Docker镜像容器文件系统
初始化时创建了两个比较大的稀疏文件:20G的Metadata loop file,默认在/var/lib/docker/devicemapper/metadata;100G的Data loop file,默认在/var/lib/docker/devicemapper/data
把文件映射为两个伪设备,虽然是伪设备,但是可以像普通块设备一样,对其进行格式化为想要的文件系统,比如xfs
基于这两个loop伪设备创建100GB大小的thin-pool(基于linux内核提供的dm机制),每启动一个容器就从 thin-pool里分出来10GB空间给该容器
(2)direct-lvm模式
直接使用块设备创建thin-pool,且块设备可以根据需要增长
启动docker后/var/lib/docker目录的结构如下:
[root@docker-100 docker]# tree ./ ./ ├── containers ├── devicemapper │ ├── devicemapper │ │ ├── data │ │ └── metadata │ └── metadata │ ├── base │ ├── deviceset-metadata │ └── transaction-metadata ├── graph ├── linkgraph.db ├── repositories-devicemapper ├── tmp ├── trust └── volumes
/var/lib/docker/devicemapper/devicemapper/目录下有两个文件:data和metadata,用来存储对应的存储池和相关的元数据。
/var/lib/docker/devicemapper/metadata/目录下有三个文件:base、transaction-metadata和deviceset-metadata,用来存放前面元数据的id、大小、以及UUID等信息。
现在存在几个空目录:
/var/lib/docker/containers
/var/lib/docker/graph
/var/lib/docker/tmp
/var/lib/docker/trust
/var/lib/docker/volumes
还有一个文件/var/lib/docker/repositories-devicemapper
下载centos镜像:
[root@docker-100 metadata]# docker images -a REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos latest ce20c473cd8a 8 weeks ago 172.3 MB <none> <none> 4234bfdd88f8 8 weeks ago 172.3 MB <none> <none> 812e9d9d677f 8 weeks ago 172.3 MB <none> <none> 168a69b62202 8 weeks ago 172.3 MB <none> <none> 47d44cb6f252 3 months ago 0 B
/var/lib/docker目录的结构变为:
[root@docker-100 docker]# tree ./ ./ ├── containers ├── devicemapper │ ├── devicemapper │ │ ├── data │ │ └── metadata │ ├── metadata │ │ ├── 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d │ │ ├── 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 │ │ ├── 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a │ │ ├── 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 │ │ ├── base │ │ ├── ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e │ │ ├── deviceset-metadata │ │ └── transaction-metadata │ └── mnt │ ├── 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d │ ├── 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 │ ├── 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a │ ├── 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 │ └── ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e ├── graph │ ├── 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ ├── 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ ├── 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ ├── 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ ├── ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ └── _tmp ├── linkgraph.db ├── repositories-devicemapper ├── tmp ├── trust └── volumes 20 directories, 27 files
查看/var/lib/docker/devicemapper/metadata目录下的内容(按device_id排序)
[root@docker-100 metadata]# cat base {"device_id":1,"size":107374182400,"transaction_id":3,"initialized":true} [root@docker-100 metadata]# cat 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a {"device_id":2,"size":107374182400,"transaction_id":4,"initialized":false} [root@docker-100 metadata]# cat 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d {"device_id":3,"size":107374182400,"transaction_id":5,"initialized":false} [root@docker-100 metadata]# cat 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 {"device_id":4,"size":107374182400,"transaction_id":6,"initialized":false} [root@docker-100 metadata]# cat 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 {"device_id":5,"size":107374182400,"transaction_id":7,"initialized":false} [root@docker-100 metadata]# cat ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e {"device_id":6,"size":107374182400,"transaction_id":8,"initialized":false}
除了base这个文件以外,其它都对应于刚才添加的中间件
那么可以得出:
/var/lib/devicemapper/metadata 目录下的文件(除了base、 deviceset- metadata、transaction-medatata)都是images本身和images的中间件信息,用来描述它们的id、size、transaction_id、是否initialized,并且它们大小都是一样的。
/var/lib/docker/devicemapper下新出现了目录/var/lib/docker/devicemapper/mnt
它主要是用来挂载 images 和Container的目录,因为 devicemapper 本身就是通过在存储池中挂载的方式进行运行的。
/var/lib/docker/graph目录下是在每个images本身及中间件,每个里面有三个文件,分别为:
json文件是用来描述 images本身或者中间件的详细信息
layersize是用来表示中间件的大小
tar-data.json.gz
另一个文件/var/lib/docker/repositories-devicemapper ,也其实就是记录 images本身(不是中间件)信息的文件;换句话说,它记录了镜像名称、镜像 tag(默认为 latest)、镜像ID等信息。
内容为:
{"Repositories":{"centos":{"latest":"ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e"}},"ConfirmDefPush":true}
运行centos镜像,容器名设为centos
[root@docker-100 docker]# tree /var/lib/docker/ /var/lib/docker/ ├── containers │ └── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052 │ ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052-json.log │ ├── config.json │ ├── hostconfig.json │ ├── hostname │ ├── hosts │ ├── resolv.conf │ ├── resolv.conf.hash │ └── secrets ├── devicemapper │ ├── devicemapper │ │ ├── data │ │ └── metadata │ ├── metadata │ │ ├── 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d │ │ ├── 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 │ │ ├── 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a │ │ ├── 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 │ │ ├── base │ │ ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052 │ │ ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052-init │ │ ├── ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e │ │ ├── deviceset-metadata │ │ └── transaction-metadata │ └── mnt │ ├── 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d │ ├── 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 │ ├── 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a │ ├── 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 │ ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052 │ ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052-init │ └── ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e ├── graph │ ├── 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d │ │ ├── json │ │ ├── layersize │ │ ├── tar-data.json │ │ └── tar-data.json.gz.bak │ ├── 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ ├── 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ ├── 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ ├── ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e │ │ ├── json │ │ ├── layersize │ │ └── tar-data.json.gz │ └── _tmp ├── linkgraph.db ├── repositories-devicemapper ├── tmp ├── trust └── volumes 24 directories, 37 files
/var/lib/docker/devicemapper/metadata/目录下多了两个文件:
[root@docker-100 containers]# tree /var/lib/docker/devicemapper/metadata/ /var/lib/docker/devicemapper/metadata/ ├── 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d ├── 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 ├── 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a ├── 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 ├── base ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052 ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052-init ├── ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e ├── deviceset-metadata └── transaction-metadata 0 directories, 10 files
这两个文件对应于运行容器时在镜像顶部添加的可读写的层
查看base+后面的5个中间件(包括 images本身)对应的文件+新增的两个文件,发现5个中间件对应的文件没变,新增的两个文件(c2adc… 和 c2adc…-init)描述了容器本身的一些信息。
[root@docker-100 metadata]# cat base {"device_id":1,"size":107374182400,"transaction_id":3,"initialized":true} [root@docker-100 metadata]# cat 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a {"device_id":2,"size":107374182400,"transaction_id":4,"initialized":false} [root@docker-100 metadata]# cat 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d {"device_id":3,"size":107374182400,"transaction_id":5,"initialized":false} [root@docker-100 metadata]# cat 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 {"device_id":5,"size":107374182400,"transaction_id":7,"initialized":false} [root@docker-100 metadata]# cat 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 {"device_id":4,"size":107374182400,"transaction_id":6,"initialized":false} [root@docker-100 metadata]# cat ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e {"device_id":6,"size":107374182400,"transaction_id":8,"initialized":false} [root@docker-100 metadata]# cat c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052-init {"device_id":7,"size":107374182400,"transaction_id":9,"initialized":false} [root@docker-100 metadata]# cat c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052 {"device_id":8,"size":107374182400,"transaction_id":10,"initialized":false}
发生变化的第二个目录 /var/lib/docker/devicemapper/mnt,和metadata目录一样,只是在目录下新增了两个目录
[root@docker-100 mnt]# tree /var/lib/docker/devicemapper/mnt/ /var/lib/docker/devicemapper/mnt/ ├── 168a69b6220279e6d5bd8dafd2edf71434a08e32b60a7060f7a705f64857169d ├── 4234bfdd88f8ed2bc4607bd2ebba2d41d61e2693ad0d184e7b05e1b57f8b8b33 ├── 47d44cb6f252ea4f6aecf8a447972de5d9f9f2e2bec549a2f1d8f92557f4d05a ├── 812e9d9d677f15c39277b2edc8f9bc07354c899483409bb07d1c13c2b9c33ec8 ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052 ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052-init └── ce20c473cd8ac1fab6601529ce6a075743f2cf7a8f4cfed2216f8cfcb53bfc4e 7 directories, 0 files
发生变化的第三个目录/var/lib/docker/container:
[root@docker-100 containers]# tree /var/lib/docker/containers/ /var/lib/docker/containers/ └── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052 ├── c2adc691ad19428e44d655d9daa8573cd71b162e05faaaa824010929089bc052-json.log ├── config.json ├── hostconfig.json ├── hostname ├── hosts ├── resolv.conf ├── resolv.conf.hash └── secrets 2 directories, 7 files
下面有一个 c2adc…(容器本身)的目录,并且该目录下有许多子文件,如container log信息、配置文件(需要 cat config.json | Python -mjson.tools 查看)、还有host及resolv等配置
如果在这时候删除该容器,就会发现容器的信息全都没了,所涉及的文件包括:
/var/lib/docker/devicemapper/metadata/c2adc… 及 c2adc…-init 文件
/var/lib/docker/devicemapper/mnt/c2adc… 及 c2adc…-init文件
/var/lib/docker/container/c2adc文件夹
总结:每个文件及作用
1、/var/lib/docker/devicemapper/devicemapper/data #用来存储相关的存储池数据
2、/var/lib/docker/devicemapper/devicemapper/metadata #用来存储相关的元数据。3、/var/lib/docker/devicemapper/metadata/ #用来存储 device_id、size、传输_id、初始化信息
4、/var/lib/docker/devicemapper/mnt #用来存储挂载信息
5、/var/lib/docker/container/ #用来存储容器信息
6、/var/lib/docker/graph/ #用来存储镜像中间件及本身详细信息和大小 、以及依赖信息7、/var/lib/docker/repositores-devicemapper #用来存储镜像基本信息
8、/var/lib/docker/tmp #docker临时目录
9、/var/lib/docker/trust #docker信任目录
10、/var/lib/docker/volumes #docker卷目录