docker镜像深入理解

大家好,本篇文章和大家聊下docker相关的话题~~

工作中经常有关于docker镜像的问题,让人百思不解

  1. docker镜像加载到系统中到哪里去了?docker load 加载镜像的流程是怎样的?
  2. 为什么容器修改内容后,删除容器后再次开启容器内容消失了?
  3. docker images查看的镜像大小与docker save后的大小不一致?
  4. 通过docker build或docker pull后的镜像层的层级关系怎么查看?
  5. docker save导出的镜像后如何查看到镜像内容?
  6. docker镜像层内容是否可以修改?

根据上述问题,本篇将docker镜像深入解析(实践+理论)。内容较长,大家先关注收藏呀~~

本次试验环境版本信息:

  • CPU:Intel
  • 系统:Centos 8
  • Docker Server: 23.0.1
  • Docker Client: 20.10.17
  • Storage Driver: overlay2

本节内容

  • 镜像组成
  • 镜像层内容
  • 镜像文件结构
  • 容器文件系统
  • 案例:修改文件系统镜像层
  • 案例:替换镜像文件层内容

镜像组成

镜像结构

(该图引用自网络)

Docker镜像是由文件系统叠加而成。最低层是一个引导文件系统,即bootfs,这很像典型的Linux/Unix的引导文件系统。Docker用户几乎永远不会和引导文件系统有什么交互。实际上,当一个容器启动后,它将会被移动到内存中,而引导文件系统则会被卸载(umount),以留出更多的内存供initrd磁盘镜像使用。
Docker镜像的第二层(由下而上数)是root文件系统rootfs也就是我们称为的base image基础镜像,它位于引导文件系统上。rootfs可以是一种或多种操作系统(如Debian或者Ubuntu文件系统)。

在传统的Linux引导过程中,root文件系统会最先以只读的方式加载,当引导结束并完成了完整检查后,它才会被切换为读写模式。 但是在Docker里,root文件系统永远只能是只读状态,并且Docker利用联合加载(overlay mount)技术又会在root文件系统层上加载更多的只读文件系统。联合加载会将各层文件系统叠加到一起,这样最终的文件系统会包含所有底层的文件和目录。

**Docker将这样的文件系统称为镜像。一个镜像可以放到另一个镜像的顶部。位于下面的镜像称为父镜像(parent image),可以依次类推直到镜像栈的最底部,最底部的镜像称为基础镜像 (base image)。**最后,当从一个镜像启动容器时,Docker会在该镜像的最顶层加载一个读写文件系统。Docker中运行的程序就是在这个读写层中执行的。

镜像层说明

Docker镜像是由镜像层文件和镜像 json 文件组成,不论静态内容还是动态信息,Docker 均为将其在 json 文件中更新。
镜像层文件,可以查看Dockerfile为例每一行命令则代表一层镜像内容。



(该图引用自https://docs.docker.com/build/guide/images/layers.png)

Docker 每一层镜像的 json 文件,都扮演着一个非常重要的角色。

主要的作用如下:

  1. 记录 Docker 镜像中与容器动态信息相关的内容。
  2. 记录父子 Docker 镜像之间真实的差异关系。
  3. 弥补 Docker 镜像内容的完整性与动态内容的缺失Docker。

Docker 镜像的 json 文件可以认为是镜像的元数据信息,其重要性不言而喻。

镜像层内容

docker默认存储目录/var/lib/docker

[root@k8s-host docker]# tree /var/lib/docker -L 1
/var/lib/docker
├── buildkit
├── containers
├── engine-id
├── image          # 镜像层级关系
├── network
├── overlay2       # 镜像实际数据
├── plugins
├── runtimes
├── swarm
├── tmp
├── trust
└── volumes

其中 image目录主要记录镜像层级关系,overlay2目录存储镜像实际数据。

内容寻址机制

首先认识下镜像层ID

每一层镜像数据对应着三项ID

  • DiffID 是制作镜像时针对每层产生的hash值,可以通过命令docker image inspect 查看字段中的 RootFS.Layers 拿到 DiffID 哈希值 (默认排序:第一行则是最底层,由底层往上排序)。
  • ChainID 是通过计算公式得出的ID,作用是与CacheID对应的层做内容寻址的索引,进而关联到每一个镜像层的镜像文件。对应的目录是/var/lib/docker/image/overlay2/layerdb/sha256/$(ChainID计算公式后的命名目录)
  • CacheID 作用是镜像层存储位置,对应的目录/var/lib/docker/overlay2/$(CacheID命名目录) ,可以通过内容寻址拿到对应的层值。 该ID是根据镜像层中数据使用加密哈希算法生成UUID。

计算公式

公式1:第一层镜像层
ChainID = 本层DiffID
公式2:除第一层外,其他层按照公式2来计算
ChainID = sha256sum(上一层ChainID + 空格 + 本层DiffID) (值采用sha256加密)
命令如下

$ echo -n "sha256:4693057ce2364720d39e57e85a5b8e0bd9ac3573716237736d6470ec5b7b7230 sha256:f6807e1a58ab4d83200064e3653c3cfd446c2a31dc3a0cbf4c9657aeb844cccd" | sha256sum -

内容寻址流程

  1. 通过DiffID值利用计算公式得到ChainID,在ChainID目录文件找中得到CacheID目录则是最终镜像层的目录。
  2. 查看镜像的镜像层顺序依据,来源于docker image inspect 镜像ID 字段中的 RootFS.Layers DiffID列表。(也就是我们制作镜像时对每层数据生成的hash值)
  3. 寻址关系为:DiffID > ChainID > CacheID

下面举例来演示,寻找镜像层内容寻址流程:
alpine:test2 镜像为例
首先,查找该镜像的 DiffID 层

该镜像一共分为三层镜像层数据。

第一层镜像层,根据公式1中定义,本层 DiffID 则为 ChainID。
下面开始拼接ChainID目录,在ChainID目录中可以拿到CacheID。
ChainID目录拼接:/var/lib/docker/image/overlay2/layerdb/sha256/ + ChainID值

[root@k8s-host docker]# ls /var/lib/docker/image/overlay2/layerdb/sha256/4693057ce2364720d39e57e85a5b8e0bd9ac3573716237736d6470ec5b7b7230
cache-id  diff  size  tar-split.json.gz

可以看到ChainID目录中,已经查到了 cache-id 的文件

[root@k8s-host docker]# cat /var/lib/docker/image/overlay2/layerdb/sha256/4693057ce2364720d39e57e85a5b8e0bd9ac3573716237736d6470ec5b7b7230/cache-id && echo
5c203c0adfa1c33600242df609195ceb17f8b8918a783920efe0fffbcb54b0af

拿到CacheID后,进行拼接CacheID目录就可以查看到第一层的镜像层数据。
CacheID目录拼接:/var/lib/docker/overlay2/ + CacheID值 + /diff/

[root@k8s-host docker]# ls /var/lib/docker/overlay2/5c203c0adfa1c33600242df609195ceb17f8b8918a783920efe0fffbcb54b0af/diff/
bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

到此为止,第一层镜像层内容寻址结束。

第二层镜像层,根据公式2中定义,ChainID 等于 sha256sum(上一层ChainID + 空格 + 本层DiffID)
通过内容寻址第一层镜像层,我们已经知道上一层的ChainID值,下面通过计算得出第二层的ChainID值。
第二层的DiffID为:sha256:7d02cdab9bc74fbcfca8c9be9872527557431cfe6ee05dd242050a9baea6e6b9

[root@k8s-host docker]# echo -n "sha256:4693057ce2364720d39e57e85a5b8e0bd9ac3573716237736d6470ec5b7b7230 sha256:7d02cdab9bc74fbcfca8c9be9872527557431cfe6ee05dd242050a9baea6e6b9" | sha256sum
4b76dffd2e327a97a54138646d95a29cb9f364fc8d87d323e68279831a9249ab  -

第二层ChainID值:4b76dffd2e327a97a54138646d95a29cb9f364fc8d87d323e68279831a9249ab
拿到ChainID值后,进行拼接ChainID目录:/var/lib/docker/image/overlay2/layerdb/sha256/ + ChainID值**,**最后找到cache-id文件,进行拼接CacheID目录:/var/lib/docker/overlay2/ + CacheID值 + /diff/

[root@k8s-host docker]# cat /var/lib/docker/image/overlay2/layerdb/sha256/4b76dffd2e327a97a54138646d95a29cb9f364fc8d87d323e68279831a9249ab/cache-id && echo
icen45ia1w23bcq3deye0n8bf
[root@k8s-host docker]# tree /var/lib/docker/overlay2/icen45ia1w23bcq3deye0n8bf/diff/
/var/lib/docker/overlay2/icen45ia1w23bcq3deye0n8bf/diff/
└── root
    └── test.sh

1 directory, 1 file

到此为止,第二层镜像层内容寻址结束。

第三层镜像层,根据公式2中定义,ChainID 等于 sha256sum(上一层ChainID + 空格 + 本层DiffID)
寻址方式还是和第二层寻址相同,我们继续操作~
寻址参考上述步骤,下面我直接将具体操作罗列

[root@k8s-host docker]# echo -n "sha256:4b76dffd2e327a97a54138646d95a29cb9f364fc8d87d323e68279831a9249ab sha256:535c535e0e2bf467f64c9f42210982a0f0a69eca171aeaaa2297beac7a449a95" | sha256sum 
eaeaa2e5b3a2c635d6f120b56c11bac690cf877846f0731b7892ad332e3c0ab6  -
[root@k8s-host docker]# cat /var/lib/docker/image/overlay2/layerdb/sha256/eaeaa2e5b3a2c635d6f120b56c11bac690cf877846f0731b7892ad332e3c0ab6/cache-id && echo
a493w6d8xuz1sucb2l1ahega6
[root@k8s-host docker]# tree /var/lib/docker/overlay2/a493w6d8xuz1sucb2l1ahega6/diff/
/var/lib/docker/overlay2/a493w6d8xuz1sucb2l1ahega6/diff/
└── root
    └── test2.sh

1 directory, 1 file

到此为止,第三层镜像层内容寻址结束。

镜像层级关系

镜像层级关系目录:/var/lib/docker/image/overlay2

[root@k8s-host overlay2]# tree /var/lib/docker/image/overlay2 -L 1
.
├── distribution
├── imagedb
├── layerdb
└── repositories.json

distribution目录记录包含了Layer层DiffID和digest之间的对应关系,digest的产生是本地构建完之后推送至远程仓库所生成。
imagedb目录记录元数据信息。
layerdb目录记录层级关系。
repositories.json文件记录镜像、digest信息。

distribution目录

该目录主要记录,DiffID和digest之间的对应关系**,**Digest是镜像内容的哈希值,可以保证在推送和拉取镜像时,内容不被篡改。
/var/lib/docker/image/overlay2/distribution/目录结构如下

[root@k8s-host distribution]# tree /var/lib/docker/image/overlay2/distribution/ -L 3
/var/lib/docker/image/overlay2/distribution/
├── diffid-by-digest
│   └── sha256
│       └── 7264a8db6415046d36d16ba98b79778e18accee6ffa71850405994cffa9be7de
└── v2metadata-by-diffid
    └── sha256
        └── 4693057ce2364720d39e57e85a5b8e0bd9ac3573716237736d6470ec5b7b7230

举例,以alpine:latest镜像为例,获取DiffID

[root@k8s-host ~]# docker inspect alpine:latest | grep RootFS -A 5
        "RootFS": {
   
            "Type": "layers",
            "Layers": [
                "sha256:4693057ce2364720d39e57e85a5b8e0bd9ac3573716237736d6470ec5b7b7230"
            ]
        },

distribution目录下,使用DiffID可以查看到对应的digest

[root@k8s-host distribution]# cat v2metadata-by-diffid/sha256/4693057ce2364720d39e57e85a5b8e0bd9ac3573716237736d6470ec5b7b7230 | jq
[
  {
   
    "Digest": "sha256:7264a8db6415046d36d16ba98b79778e18accee6ffa71850405994cffa9be7de",
    "SourceRepository": "docker.io/library/alpine",
    "HMAC": ""
  }
]

下面通过拿到的 digest 信息,在diffid-by-digest目录可以查看到DiffID信息

[root@k8s-host distribution]# cat diffid-by-digest/sha256/7264a8db6415046d36d16ba98b79778e18accee6ffa71850405994cffa9be7de && echo
sha256:4693057ce2364720d39e57e85a5b8e0bd9ac3573716237736d6470ec5b7b7230

主要注意的是:查看镜像本身是否有digests信息,可以如下命令,看下DIGEST字段是否有信息。 若是<none>则代表没有经过公共仓库发布的镜像,则不适用这种DiffID查找digests信息方法。

$ docker images --digests | grep alpine

imagedb目录

该目录主要记录,镜像的元数据。
我们通过 docker pull 下载了镜像后,docker会在宿主机上基于现有镜像层文件包和 docker pull image 数据构建本地的 layer 元数据,包括diff、parent、size等。
当docker将在宿主机上产生的新镜像层上传registry时,layer 元数据不会与镜像层一块打包上传。
元数据目录位置 /var/lib/docker/image/overlay2/imagedb ,元数据内容为JSON格式。

[root@k8s-host imagedb]# tree . -L 2
.
├── content
│   └── sha256
└── metadata
    └── sha256

content 目录记录镜像元数据:/var/lib/docker/image/overlay2/imagedb/content/sha256/
metadata 目录记录元数据最后更新时间: /var/lib/docker/image/overlay2/imagedb/metadata/sha256

列如:要查找 alpine:latest 镜像的元数据,可通过下面方式

[root@k8s-host ~]# docker image inspect alpine:latest -f '{
   { .Id }}' | cut -d : -f2
7e01a0d0a1dcd9e539f8e9bbd80106d59efbdf97293b3d38f5d7a34501526cdb
[root@k8s-host ~]# cat /var/lib/docker/image/overlay2/imagedb/content/sha256/7e01a0d0a1dcd9e539f8e9bbd80106d59efbdf97293b3d38f5d7a34501526cdb
{
   "architecture":"amd64","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":["/bin/sh"],"Image":"sha256:39dfd593e04b939e16d3a426af525cad29b8fc7410b06f4dbad8528b45e1e5a9","Volumes":null,"WorkingDir":"","En
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值