精簡docker的匯出映象

2022-10-31 18:01:03

Docker 映象是由多個檔案系統(唯讀層)疊加而成,每個層僅包含了前一層的差異部分。當我們啟動一個容器的時候,Docker 會載入映象層並在其上新增一個可寫層。容器上所做的任何更改,譬如新建檔案、更改檔案、刪除檔案,都將記錄與可寫層上。當我們使docker save形式嘗試匯出映象時會匯出該映象的所有檔案層,當然這個行為是必要的,因為你不知道這個映象的被匯入環境是否已包含基礎映象的檔案層。但是如果我們有一批映象且都依賴某一個或兩個基礎映象構建,且不具備批次 save的調價(必須一個一個分開了打包),這種情況下如果一個一個save的話對硬碟資源是極大的浪費,那麼有沒有辦法去掉哪些重複的映象層呢,答案是OK的。(也許有人會吐槽為啥不用docekrfile呢,是的一般情況下是ok的,但是用dockerfile build出來的映象id時不一樣的)

看下docker save 匯出了寫啥

以如下映象為例子

FROM centos:7
COPY main /home/main
RUN chmod +x /home/main

CMD /home/mai

構建

docker build -t ip-server:1.0.0 .

匯出

[root@localhost demo]# docker save -o ip-server.tar ip-server:1.0.0
[root@localhost demo]# du -sh ip-server.tar 
212M	ip-server.tar
[root@localhost demo]# docker save -o centos7.tar centos:7
[root@localhost demo]# du -sh centos7.tar 
202M	centos7.tar

可以看到基於centos7構建的ip-server映象只是大了一丁,也就是我們COPY進來的一個可執行檔案大小,那麼在已知被匯入環境存在centos7映象的檔案層是該如何減小ip-server.tar的體積呢?

拆解匯出的映象

[root@localhost demo]# mkdir ip-server && tar xf ip-server.tar -C ip-server
[root@localhost demo]# tree ip-server
ip-server
├── 3efdc87cec68e28bccf6c0d96894c903e12157ed0797651a2eaa565108de5bd8
│   ├── json
│   ├── layer.tar -> ../fad57ccc4dd192e49d3979f477525a4b4c8fb8532ae31c2c74b5403474a26e4d/layer.tar
│   └── VERSION
├── d8f46057879e2e8614caa5511934d403d7ebea0af9b196bf29b68161fda76766
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── f898e9c3d94a1617bac63c962155327671957f2bcbd35e6411153b7730d6558e.json
├── fad57ccc4dd192e49d3979f477525a4b4c8fb8532ae31c2c74b5403474a26e4d
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── manifest.json
└── repositories

3 directories, 12 files
# manifest.json
[
  {
    "Config": "f898e9c3d94a1617bac63c962155327671957f2bcbd35e6411153b7730d6558e.json",
    "RepoTags": [
      "ip-server:1.0.0"
    ],
    "Layers": [
      "d8f46057879e2e8614caa5511934d403d7ebea0af9b196bf29b68161fda76766/layer.tar",
      "fad57ccc4dd192e49d3979f477525a4b4c8fb8532ae31c2c74b5403474a26e4d/layer.tar",
      "3efdc87cec68e28bccf6c0d96894c903e12157ed0797651a2eaa565108de5bd8/layer.tar"
    ]
  }
]
# f898e9c3d94a1617bac63c962155327671957f2bcbd35e6411153b7730d6558e.json
{
  "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",
      "-c",
      "/home/main"
    ],
    "Image": "sha256:04da435eb95c963effbf9aba060e6f2ddd962b4861ce1bf7bdc5f3cbffceb4b5",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": {
      "org.label-schema.build-date": "20201113",
      "org.label-schema.license": "GPLv2",
      "org.label-schema.name": "CentOS Base Image",
      "org.label-schema.schema-version": "1.0",
      "org.label-schema.vendor": "CentOS",
      "org.opencontainers.image.created": "2020-11-13 00:00:00+00:00",
      "org.opencontainers.image.licenses": "GPL-2.0-only",
      "org.opencontainers.image.title": "CentOS Base Image",
      "org.opencontainers.image.vendor": "CentOS"
    }
  },
  "container": "5d3e8d3176babbe767294d6834bf1dc6e64ce9838bc08993751724a92231d6ac",
  "container_config": {
    "Hostname": "5d3e8d3176ba",
    "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 [\"/bin/sh\" \"-c\" \"/home/main\"]"
    ],
    "Image": "sha256:04da435eb95c963effbf9aba060e6f2ddd962b4861ce1bf7bdc5f3cbffceb4b5",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": {
      "org.label-schema.build-date": "20201113",
      "org.label-schema.license": "GPLv2",
      "org.label-schema.name": "CentOS Base Image",
      "org.label-schema.schema-version": "1.0",
      "org.label-schema.vendor": "CentOS",
      "org.opencontainers.image.created": "2020-11-13 00:00:00+00:00",
      "org.opencontainers.image.licenses": "GPL-2.0-only",
      "org.opencontainers.image.title": "CentOS Base Image",
      "org.opencontainers.image.vendor": "CentOS"
    }
  },
  "created": "2022-10-26T15:53:47.898864929Z",
  "docker_version": "20.10.17",
  "history": [
    {
      "created": "2021-09-15T18:20:23.417639551Z",
      "created_by": "/bin/sh -c #(nop) ADD file:b3ebbe8bd304723d43b7b44a6d990cd657b63d93d6a2a9293983a30bfc1dfa53 in / "
    },
    {
      "created": "2021-09-15T18:20:23.819893035Z",
      "created_by": "/bin/sh -c #(nop)  LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201113 org.opencontainers.image.title=CentOS Base Image org.opencontainers.image.vendor=CentOS org.opencontainers.image.licenses=GPL-2.0-only org.opencontainers.image.created=2020-11-13 00:00:00+00:00",
      "empty_layer": true
    },
    {
      "created": "2021-09-15T18:20:23.99863383Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/bash\"]",
      "empty_layer": true
    },
    {
      "created": "2022-10-26T15:53:46.693528022Z",
      "created_by": "/bin/sh -c #(nop) COPY file:d0c12b416e2bad24636a0f240cc09c4a6b6a0def701b5aaeeca4963507e468c4 in /home/main "
    },
    {
      "created": "2022-10-26T15:53:47.752968676Z",
      "created_by": "/bin/sh -c chmod +x /home/main"
    },
    {
      "created": "2022-10-26T15:53:47.898864929Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/sh\" \"-c\" \"/home/main\"]",
      "empty_layer": true
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02",
      "sha256:185d24f4ae72bebf9b31c3d26486d52163e38e6c09167507a6a4f28d491aeb28",
      "sha256:185d24f4ae72bebf9b31c3d26486d52163e38e6c09167507a6a4f28d491aeb28"
    ]
  }
}

參考docker-ce的相關匯出模組的原始碼(原始碼解讀就不做了,不算複雜)

docekr-ce/components/engin/save
docekr-ce/components/engin/load

可以找出打包出的tar包中放layer資料夾與diff_ids標記的檔案層的對應關係。

1. 解壓壓縮包
2. 讀取 centos7 和 ip-server 的inspect
3. 找出 centos7 和 ip-server 的diff_ids同的層數
4. 按照相同的層數依次找到manifest.json中記錄的layers檔案目錄,並把layer.tar的壓縮包置空
5. 重新打包

測試改處理的匯出包在centos:7映象已存在的環境中可以被正常匯入操作。

程式碼實現

參考 github.com/zn-chen/dockerdiff
懶得琢磨也可以直接使用,在安裝好go環境下git clone 下來 make && make install 後即可食用。