V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
wangxiaoaer
V2EX  ›  问与答

Docker 镜像的缓存

  •  
  •   wangxiaoaer · 2018-08-18 17:42:47 +08:00 · 4659 次点击
    这是一个创建于 2280 天前的主题,其中的信息可能已经有所发展或是发生改变。

    docker 镜像每个 RUN 命令都会创建一层缓存:

    比如

    1 RUN apt-get xx

    2 mkdir xx && cd xx

    3 RUN make xx

    上面的第一行命令装了一大堆依赖,大概 500 多 M,每次 build 都比较耗时。

    这个命令会不会创建一个缓存?如果可以,并且如果这行命令一直不变的话,修改后面命令,重新构建的时候会不会利用这个缓存?还是重新 apt 安装?因为根据我自己的测试,好像并没有用到缓存,具体是什么情况?

    换句话说 docker 的缓存有么有直接跟 dockerfile 中的命令关联起来?能不能跨 dockefile 利用呢?

    第 1 条附言  ·  2018-08-18 20:06:14 +08:00

    抱歉没说清楚,我的测试是这样子:

    第一次构建:

    dockerfile:

    1 RUN apt-get xx
    2 mkdir xx && cd xx
    

    构建:docker build -t m:v1 .

    耗费很久但是构建成功,中间通过控制台日志输出可以看到apt-get的日志信息。

    然后修改dockerfile文件,添加第三行:

    3 RUN make xx
    

    第二次构建

    docker build -t m:v1 .

    和第一次构建一样,仍然有apt-get的信息。按我自己的理解,第一行的RUN没有改动,应该是产生了一个缓存层,第二次构建应该使用缓存,但是结果并没有,这才是我疑惑的地方。

    30 条回复    2018-08-19 18:06:08 +08:00
    qsnow6
        1
    qsnow6  
       2018-08-18 18:37:21 +08:00 via iPhone
    可以
    KeatingSmith
        2
    KeatingSmith  
       2018-08-18 19:08:59 +08:00 via iPhone
    每层镜像会有唯一的 Hash 码,每一层镜像,都会在本地存在缓存。

    假如我现在构建 C 容器,C 依赖 B。第一次在本地构建的时候,会先构建 B,再构建 C。假如还需要构建 D,D 也依赖于 B,因为之前已经构建 B 了,所以只系要直接构建 D 就行了。

    如果你换台机器,那么需要全部重新构建。
    KeatingSmith
        3
    KeatingSmith  
       2018-08-18 19:13:40 +08:00 via iPhone
    如果你在 Dockerfile 中使用每一层的命令,因为每一次 apt 不能保证对镜像都有相同的修改,导致 hash 不同。
    presoul
        4
    presoul  
       2018-08-18 19:17:31 +08:00 via Android
    cd xx 没用 不是 shell
    willxiang
        5
    willxiang  
       2018-08-18 19:19:27 +08:00
    会的,2-3L 解释的很清楚了
    只要你生成的镜像不立马删除,重新 run build 时会直接使用之前 run 过的缓存
    momocraft
        6
    momocraft  
       2018-08-18 19:21:23 +08:00
    "缓存"是个结果, 如果某一层可以复用以前的中间层, 才会跳过执行来直接使用上次的. 你不讲具体怎么测试鬼知道什么具体情况.
    shiny
        7
    shiny  
       2018-08-18 19:21:56 +08:00
    我看到很多 Dockerfile 会尽量把构建合并到一条指令里,是不是因为这样生成出来的镜像尺寸比较小?
    whileFalse
        8
    whileFalse  
       2018-08-18 19:24:11 +08:00
    关键是你在哪一步 add 你的代码的。如果在 apt-get 之前,什么缓存也没用。
    yuanfnadi
        9
    yuanfnadi  
       2018-08-18 19:33:31 +08:00 via iPhone   ❤️ 1
    @shiny 对的 是因为一句命令就是一层。
    你上一句话加了一个文件 下一句把文件删了。不会缩小镜像的体积,因为不是一层。


    另外 docker 支持多步构建,可以在镜像 a 把代码 build 好,然后把二进制放到一个超级小的镜像。最终结果会非常小。
    Bardon
        10
    Bardon  
       2018-08-18 19:47:31 +08:00   ❤️ 1
    @yuanfnadi 写到一句,并不会使得最终镜像变小,而是层少了,层的数目会影响到容器执行后的 IO 性能。
    wangxiaoaer
        11
    wangxiaoaer  
    OP
       2018-08-18 19:59:46 +08:00
    @KeatingSmith #2 你说的这个依赖是通过 from 这种方式吗?里面的 A B C D 是指一个单独的镜像还是 dockerfile 里面的一条命令?
    wangxiaoaer
        12
    wangxiaoaer  
    OP
       2018-08-18 20:25:58 +08:00
    @momocraft #6 看 append
    DiamondYuan
        13
    DiamondYuan  
       2018-08-18 21:16:00 +08:00
    @Bardon

    会变小的。例如我的前端项目。最终结果就是一个 nginx 的镜像加一层 html 脚本。

    FROM node:alpine as build

    COPY . /project/

    WORKDIR /project
    RUN echo "Installing dependencies..." && \
    npm install
    RUN echo "Starting dist build..." && \
    npm run-script build

    FROM nginx:stable-alpine

    COPY --from=build /project/dist /usr/share/nginx/html/

    EXPOSE 80
    Havee
        14
    Havee  
       2018-08-18 21:25:22 +08:00
    @DiamondYuan 这是因为没有进行清理操作,或者清理到位

    @wangxiaoaer 因为每一次 apt 结果的 hash 不可能相同,如果你想省去每一次的 apt,那么第一次在 apt 后就打一个 tag,后面的构建都 from 这个 tag 来进行。
    wangxiaoaer
        15
    wangxiaoaer  
    OP
       2018-08-18 21:35:45 +08:00 via Android
    @Havee 我想过这样子做,把耗时操作打成一个镜像,但这样分发的时候就要把那个镜像也拷过去,除非搭建私服。
    Havee
        16
    Havee  
       2018-08-18 21:46:31 +08:00
    譬如如下的两次构建,会直接用缓存,你可以试一下

    FROM alpine
    RUN echo "1" > 1

    FROM alpine
    RUN echo "1" > 1
    RUN echo "2" > 2
    Havee
        17
    Havee  
       2018-08-18 21:53:34 +08:00
    第一次回复说错了,同样的安装环境,应该同样的 hash,譬如

    FROM alpine
    RUN apk add --no-cache curl

    FROM alpine
    RUN apk add --no-cache curl
    RUN apk add --no-cache git

    第二次构建会直接使用前一次 cache
    Havee
        18
    Havee  
       2018-08-18 21:55:47 +08:00
    或许,是因为 apt-get 后产生的日志文件中,时间不同,导致最后的 hash 不一致?
    derek80
        19
    derek80  
       2018-08-18 22:21:19 +08:00
    https://docs.docker.com/develop/develop-images/multistage-build/

    编译一个静态文件放 alpine 这种镜像里就好。
    Kilerd
        20
    Kilerd  
       2018-08-18 22:49:51 +08:00
    如果是编译成二进制的,那么用多端构建,放进 alpine 里面就好了
    如果是 Python 等类型的项目,可以试下 docker-slim 来缩小 image 的大小
    KeatingSmith
        21
    KeatingSmith  
       2018-08-18 23:32:26 +08:00 via iPhone
    @wangxiaoaer

    Dockerfile 的每一条命令就是一层镜像。

    为了精简容器的体积,建议在构建镜像的时候,不要把业务代码放进去,而是在运行容器的时候,利用 volumne 挂载。
    KeatingSmith
        22
    KeatingSmith  
       2018-08-18 23:36:00 +08:00 via iPhone
    apt 在安装会卸载软件的时候,会在 /var/log 下,至少是在这个目录下,生成日志文件,所以,导致每次的 Hash 都不一样。

    如果要实现你说的依赖缓存快速构建,可以先将耗时的一些命令打包成镜像 A,然后在 Dockerfile 中 FROM A
    Reficul
        23
    Reficul  
       2018-08-18 23:36:16 +08:00
    可以手动把老的镜像 pull 下来,然后 build 的时候指定 cache-from 参数
    wangxiaoaer
        24
    wangxiaoaer  
    OP
       2018-08-19 07:56:45 +08:00 via Android
    @KeatingSmith #21 但我看很多项目都是用 cp,用完删掉就行了吧。
    wangxiaoaer
        25
    wangxiaoaer  
    OP
       2018-08-19 07:57:23 +08:00 via Android
    @KeatingSmith 22
    看 15 楼。
    KeatingSmith
        26
    KeatingSmith  
       2018-08-19 08:25:39 +08:00 via iPhone
    @wangxiaoaer

    不好意思啊,手机回复的帖子,没看到已经有前人提供了方法。
    momocraft
        27
    momocraft  
       2018-08-19 14:59:18 +08:00
    选以前的中间结果大致是基于 (build context, 上一个命令得到的层, 下一个 dockerfile 命令)

    不会是什么基于文件的 hash (docker 不可能先验知道 "如果不用 cache 会得到同样 hash" 然后选择用)

    除了 dockerfile 有其他东西变化吗? 比如 build context?
    wangxiaoaer
        28
    wangxiaoaer  
    OP
       2018-08-19 15:37:33 +08:00 via Android
    上午搜了一大堆资料,经过几次测试感觉自己好像理解了:

    1 每一条命令都会对应一个缓存层。

    2 build 的时候按照 dockerfile 里面命令的顺序一条一条执行,如果命令不变,同时涉及到的文件无变动,就可能会使用缓存层(如果有的话)。

    3 如果某一层缓存失效,会导致下面的缓存同样失效。

    ……………………

    不清楚理解的对不对,请大佬指点。
    Havee
        29
    Havee  
       2018-08-19 18:00:00 +08:00
    从来没有用过 ubuntu 镜像,刚才写了两个简单的 dockerfile,也是直接用缓存的呀

    FROM ubuntu
    RUN apt-get -y update && apt-get -y install curl


    FROM ubuntu
    RUN apt-get -y update && apt-get -y install curl
    RUN apt-get -y install git


    在第二次构建的时候
    Sending build context to Docker daemon 2.048kB
    Step 1/3 : FROM ubuntu
    ---> 735f80812f90
    Step 2/3 : RUN apt-get -y update && apt-get -y install curl
    ---> Using cache
    ---> b201fe246f08
    Step 3/3 : RUN apt-get -y install git
    ---> Running in 9ffc294479eb
    。。。。。。
    楼主要不要抛出完整的 Dockerfile 文件看看?
    wangxiaoaer
        30
    wangxiaoaer  
    OP
       2018-08-19 18:06:08 +08:00 via Android
    @Havee 看 28 楼的回复,我之前文件写的有问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5502 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 08:56 · PVG 16:56 · LAX 00:56 · JFK 03:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.