容器镜像

镜像是用于创建容器的只读模板。
目前,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卷目录
posted @ 2020-12-24 14:55  扬羽流风  阅读(455)  评论(0编辑  收藏  举报