DOCKER 五月 25, 2023

docker镜像

文章字数 14k 阅读约需 13 mins. 阅读次数 0

Docker 容器镜像

概念

  • Docker 镜像是只读的容器模板,是Docker容器基础
  • 为Docker容器提供了静态文件系统运行环境(rootfs)
  • 区分: 容器是镜像的运行状态,镜像是容器的静止状态.

联合文件系统(union filesystem)

概念

是实现联合挂载技术的文件系统.可以实现在一个挂载点同时挂在多个文件系统,将挂载点的原目录与被挂载内容进行整合,使得最终可见的文件系统包含整合后的各层文件与目录.

图示

image-20220125080435098

Docker Overlay2

容器文件系统中有多种存储驱动实现方式:aufs,devicemapper,overlay,overlay2等.docker目前默认使用的是overlay2.

概念

  • registry: repository集合.
  • repository: repository 是镜像的集合.
  • image: image 是存储镜像相关的元数据,包括镜像的架构,镜像默认配置信息,镜像的容器配置信息等等。它是“逻辑”上的概念,并无物理上的镜像文件与之对应。
  • layer: layer(镜像层) 组成了镜像,单个 layer 可以被多个镜像共享。

overlay2是由merged,lowerdir,upperdir,workdir组成.

  • lowerdir: 镜像只读层,镜像层.
  • upperdir: 容器读写层, 反应容器的读写操作.
  • workdir: overlayfs的内部层, 用于实现从只读到读写层的copy_up操作.
  • merge: 容器内作为同一视图联合挂载点的目录.

image-20220125082202308

docker 查看存储驱动

docker info | grep overlay
Storage Driver: overlay2
 Network: bridge host macvlan null overlay
  WARNING: You're not using the default seccomp profile
WARNING: bridge-nf-call-iptables is disabled
WARNING: bridge-nf-call-ip6tables is disabled

docker 镜像存储位置

入口: /var/lib/docker/image/overlay2/

ls /var/lib/docker/image/overlay2/
distribution  imagedb  layerdb  repositories.json
  • repositories.json 记录了repo与镜像id的映射关系
  • imagedb 记录了镜像架构,操作系统,构建镜像的容器 ID 和配置以及 rootfs 等信息
  • layerdb 记录了每层镜像层的元数据。

docker 镜像分层文件查找方法

以redis为例.

  1. 通过下载镜像可以得知该镜像有六层.

    # docker pull redis
    Using default tag: latest
    Trying to pull repository docker.io/library/redis ... 
    latest: Pulling from docker.io/library/redis
    3f4ca61aafcd: Already exists 
    c3775af77098: Pull complete 
    fa7c5c7e501c: Pull complete 
    4059b4d2a3ce: Pull complete 
    6bc523bfb16a: Pull complete 
    20bf15ad3c24: Pull complete 
    Digest: sha256:8184cfe57f205ab34c62bd0e9552dffeb885d2a7f82ce4295c0df344cb6f0007
    Status: Downloaded newer image for docker.io/redis:latest
    
  2. 通过docker images 来查看redis的短的image id

    docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    docker.io/redis     latest              0256c63af7db        2 weeks ago         117 MB
    
  3. 进入docker 镜像存储路径,通过短id查找完整的长id.再通过长id可以在imagedb中找到镜像的元数据.

    cd /var/lib/docker/image/overlay2
    more repositories.json 
    
    {
        "Repositories": {
            "docker.io/redis": {
                "docker.io/redis:latest": "sha256:0256c63af7dbecdb8783029521d1bcaf02d194d1451ad8cddc193352b6caedd0",
                "docker.io/redis@sha256:8184cfe57f205ab34c62bd0e9552dffeb885d2a7f82ce4295c0df344cb6f0007": "sha256:0256c63af7dbecdb8783029521d1bcaf02d194d1451ad8cddc193352b6caedd0"
            }
    }
    

    通过结构可以看到redis:latest的长id是0256c63af7dbecdb8783029521d1bcaf02d194d1451ad8cddc193352b6caedd0.在imagedb/content/sha256/文件夹下找到同名文件.查看信息.

    # more /var/lib/docker/image/overlay2/imagedb/content/sha256/0256c63af7dbecdb8783029521d1bcaf02d194d1451ad8cddc193352b6caedd0
    
    ...
    "rootfs": {
        "type": "layers",
        "diff_ids": [
            "sha256:8a70d251b65364698f195f5a0b424e0d67de81307b79afbe662abd797068a069",
            "sha256:f425a4ae4b4e5325d4462e5d617a58784c445405e7435c5dc9eef7f3fc177075",
            "sha256:25573b19d1564d20fb6eafe2cc68eaebea262b2add86bafe0aace6a8d9273ccb",
            "sha256:a2e52a99a99fef7d419c5ce7a3e23a8e60b5b51a03dc3f1417347e12bd21c051",
            "sha256:807cec72b26c8db57733dd867031b00d113d7b834f4b0601ee41ec4fa63b894b",
            "sha256:2ffda52105d36ec4cdbd24bd17e698e26d19bcba072238ec519a0381795108f5"
        ]
    }
    ...
    

    在json文件最下方有一块rootfs目录.其中的diff_ids就记录着分层的数据的id号.这六层分别对应镜像的六层.自上而下,引射着容器的底层到顶层.该id的作用是找到镜像层的镜像.

  4. 根据chainId(底层diff_id即是chainId)可以在layer/sha256/(/var/lib/docker/image/overlay2/layerdb/sha256)目录下同名文件夹.里面存放了文件路径指向.

    # pwd
    /var/lib/docker/image/overlay2/layerdb/sha256/8a70d251b65364698f195f5a0b424e0d67de81307b79afbe662abd797068a069
    # ls
    cache-id  diff  size  tar-split.json.gz
    # more cache-id 
    7a335dd840b2cd775af90da49047a068eae5613484aec5e50b0c0c0018882a30
    # more diff 
    sha256:8a70d251b65364698f195f5a0b424e0d67de81307b79afbe662abd797068a069
    

    文件夹里有个cache-id的文件,里面存放了cache-id,我们可以通过cache-id:7a335dd840b2cd775af90da49047a068eae5613484aec5e50b0c0c0018882a30去/var/lib/docker/overlay2/路径下查找具体文件内容.

  5. 根据查到的cache-id去/var/lib/docker/overlay2/查看文件夹信息

    # cd /var/lib/docker/overlay2/7a335dd840b2cd775af90da49047a068eae5613484aec5e50b0c0c0018882a30/
    # ls 
    committed  diff  link
    # more link 
    KUNIQVGBWFZHEASENNFRPT46MN
    # ls diff
    bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    
    • link: 存放镜像层对应的短id
    • diff: 存放镜像层的原始文件
    • lower: 上一层镜像层的短id.
  6. 找到镜像底层后,需要通过公式来计算出下一层的chainId,然后循环执行4.5.6步骤.依次逐层查找文件信息.

    # echo -n "sha256:8a70d251b65364698f195f5a0b424e0d67de81307b79afbe662abd797068a069 sha256:f425a4ae4b4e5325d4462e5d617a58784c445405e7435c5dc9eef7f3fc177075" | sha256sum -
    
    555a4acbdb957b13ce0e5320a106dba38014f72d84dcab6ca989ce7ab505308f  -
    # ls /var/lib/docker/image/overlay2/layerdb/sha256/555a4acbdb957b13ce0e5320a106dba38014f72d84dcab6ca989ce7ab505308f
    cache-id  diff  parent  size  tar-split.json.gz
    # more parent 
    sha256:8a70d251b65364698f195f5a0b424e0d67de81307b79afbe662abd797068a069
    
    • cache-id: 当前容器层的缓存id.
    • diff: 当前容器层的diffid.
    • parent: 上一层的cache-id.

    docker 引入了内容寻址机制,该机制会根据文件内容来索引镜像和镜像层。docker 利用 rootfs 中的 diff_id 计算出内容寻址的 chainID,通过 chainID 获取 layer 相关信息,最终索引到镜像层文件内容。

    对于最底层镜像层其 diff_id 即是 chainID。因此我们可以查找到它的文件内容。除最底层外,chainID 需通过公式 chainID(n) = SHA256(chain(n-1) diffID(n)) 计算得到.

    eg:"sha256:8a70d251b65364698f195f5a0b424e0d67de81307b79afbe662abd797068a069 sha256:f425a4ae4b4e5325d4462e5d617a58784c445405e7435c5dc9eef7f3fc177075" | sha256sum -

镜像与容器

启动一个docker 容器后,docker会mount一个overlay 的联合文件系统到容器内。这个文件系统由三层组成:

lowerdir,upperdir,workdir. 对外统一用merge目录展示.

# docker run -d --privileged=true -p 6379:6379 --name redis  redis redis-server --appendonly yes
f0689a0be678fa161374cad4da978369cf2eabb30761964cb9419349c882e62f
# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
f0689a0be678        redis               "docker-entrypoint..."   3 seconds ago       Up 2 seconds        0.0.0.0:6379->6379/tcp   redis
# mount |grep overlay
/dev/vda1 on /var/lib/docker/overlay2 type ext4 (rw,relatime,data=ordered)
overlay on /var/lib/docker/overlay2/41142d0283bc4c18abf7229830e7bd6ee37aa438321d0ca910341d2c4951ee42/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/WYS5YBW3HPSLD3LOZ7ENZAQH5K:
/var/lib/docker/overlay2/l/AUC35C4AV2R55HFCPAII4ULPJB:
/var/lib/docker/overlay2/l/3FWL7ZCAH2K47UI6SUERCDFXUO:
/var/lib/docker/overlay2/l/W3LTS35MU3HRPPO6D22VKQ4MMR:
/var/lib/docker/overlay2/l/YMGPW6T3736D2VPYWA5LCM2X3E:
/var/lib/docker/overlay2/l/GUIPEFGV4TLNAML2MR42VREWZD:
/var/lib/docker/overlay2/l/KUNIQVGBWFZHEASENNFRPT46MN,upperdir=/var/lib/docker/overlay2/41142d0283bc4c18abf7229830e7bd6ee37aa438321d0ca910341d2c4951ee42/diff,workdir=/var/lib/docker/overlay2/41142d0283bc4c18abf7229830e7bd6ee37aa438321d0ca910341d2c4951ee42/work)

细心点可以发现容器的lowerdir镜像层有七层目录.而我们的镜像缺只有六层目录.

/var/lib/docker/overlay2/l/AUC35C4AV2R55HFCPAII4ULPJB: /var/lib/docker/overlay2/l/3FWL7ZCAH2K47UI6SUERCDFXUO: /var/lib/docker/overlay2/l/W3LTS35MU3HRPPO6D22VKQ4MMR: /var/lib/docker/overlay2/l/YMGPW6T3736D2VPYWA5LCM2X3E: /var/lib/docker/overlay2/l/GUIPEFGV4TLNAML2MR42VREWZD: /var/lib/docker/overlay2/l/KUNIQVGBWFZHEASENNFRPT46MN:

这六层目录从下到上分别对应镜像的6层镜像层文件内容,它们分别映射到镜像层的 diff 目录。

/var/lib/docker/overlay2/l/WYS5YBW3HPSLD3LOZ7ENZAQH5K 映射的是容器的初始化层 init,该层内容是和容器配置相关的文件内容,它是只读的,从系统的软连接来看比较明显,其软连接的文件路径上的chainId带有init字眼.

# ll -l /var/lib/docker/overlay2/l/
total 32
lrwxrwxrwx 1 root root 72 Jan  8 10:40 3FWL7ZCAH2K47UI6SUERCDFXUO -> ../2784b462764e1047c9812f8179d8dc716484d3fc29fc56254c2c4c28464dd210/diff
lrwxrwxrwx 1 root root 72 Jan  8 13:16 6NZOOWLAX6OVSSWWC2XVPPWNZM -> ../41142d0283bc4c18abf7229830e7bd6ee37aa438321d0ca910341d2c4951ee42/diff
lrwxrwxrwx 1 root root 72 Jan  8 10:40 AUC35C4AV2R55HFCPAII4ULPJB -> ../23f995bf67090d39d2244fba02a92c09238b397f6ed03e922b851c524ec2415d/diff
lrwxrwxrwx 1 root root 72 Jan  8 10:40 GUIPEFGV4TLNAML2MR42VREWZD -> ../1db6ab3ea318bc2ded84f7b0ebee89be34f56b08973e48cfef0e2a0004fb99a3/diff
lrwxrwxrwx 1 root root 72 Dec 30 10:11 KUNIQVGBWFZHEASENNFRPT46MN -> ../7a335dd840b2cd775af90da49047a068eae5613484aec5e50b0c0c0018882a30/diff
lrwxrwxrwx 1 root root 72 Jan  8 10:40 W3LTS35MU3HRPPO6D22VKQ4MMR -> ../36834a7e9756d599a9389d683d2a08912598905b50ad62ac9b5eb8cdfde8a0c0/diff
lrwxrwxrwx 1 root root 77 Jan  8 13:16 WYS5YBW3HPSLD3LOZ7ENZAQH5K -> ../41142d0283bc4c18abf7229830e7bd6ee37aa438321d0ca910341d2c4951ee42-init/diff
lrwxrwxrwx 1 root root 72 Jan  8 10:40 YMGPW6T3736D2VPYWA5LCM2X3E -> ../dfa70e9006936534510da1d5380f9828a5fad83614d3f8d1ba2fded3d5b482a4/diff

容器镜像的操作命令

docker commit

docker 通过commit与build操作实现镜像的构建.

两者的区别:

  • docker commit: 将容器提交为一个镜像 . eg: docker commit {容器id}
  • docker build: 在一个镜像的基础上构建镜像.多与dockerfile配合使用. eg :docker build Dockerfile

docker save

导出容器或镜像,方便分享,可以将多个容器或镜像同时保存在一个tar包里.

当然,虽然可以导出容器,但本质是导出容器所依赖的镜像.

使用场景: 使用了docker-compose 编排的多个镜像组合,且部署的客户服务器并不能连外网.这时需要通过将镜像save成文件,拷贝到目标机器上后使用load载入.

格式: docker save -o {输出文件路径} {name:tag ...}

​ -o 输出到文件.

eg: docker save -o redis.tar redis:v1

补充: 压缩导出

将导出的镜像再通过gzip压缩.使之更小更容易传输.压缩后的文件大小为原来的三分之一左右.

格式:docker save {name:tag} | gzip > {输出文件路径}

eg: docker save redis:v1 | gzip > redis2.tar

docker load

把他人分享的容器镜像导入到本地镜像中,这通常是容器镜像分发方式之一, 与save配套使用.

格式: docker load -i {镜像文件路径}

–input, -i : 指定文件输入

-quiet, -q : 精简输出信息.

docker export

把正在运行的容器导出.应用场景主要是用来制作基础镜像.

使用场景: 制作符合自生环境的基础镜像.比如从一个centos镜像启动一个容器后,安装了一些软件与设置后,使用export保存成一个基础镜像,供以后使用.

格式: docker export -o {输出文件路径} {容器名称/容器id}

eg: docker export -o redis.tar redis

docker import

导入使用docker export导入的容器做为本地容器镜像。

格式: docker import {镜像文件路径} {生成的镜像名称/生成的镜像tag}

eg: docker import redis.tar redis:v1

区别

  • docker save 保存的是镜像, docker export 可以保存容器.
  • docker load 载入镜像包, docker import 载入容器包,但都将载入成镜像.
  • docker load 不能对载入的镜像重命名, docker import 可以为镜像重命名.
  • docker load 不能载入export出来的容器包.docker import 可以载入load出来的镜像包,但启动失败.

容器镜像仓库的使用命令

docker tag

用于给镜像打标签.生成不通版本的镜像,为上传做支撑.

格式: docker tag {目标镜像}:[tag] {生成镜像}:[tag]

eg: docker tag redis:latest redis:v1

docker push

上传容器镜像至远程仓库.

格式: docker push {镜像名称}:[tag]

eg: docker push redis:v1

docker pull

下载容器镜像.

使用场景: 在专门的打包服务器/或本地将镜像更新上传后,在其生产环境上下载并启动,多实例的场景就无需多次上传.且方便回滚.

格式: docker pull {镜像名称}:[tag]

eg: docker pull redis:v1

docker login

登入

docker logout

登出

0%