来列举下更多的 overlay diff 遗留案例
有些场景下,xxx.tar.gz
并没有可供下载的直链,例如存在对象存储里,而 ARG 指令传递 aksk 之类的是会在 docker 镜像构建信息里看到,可以本地起一个监听 docker0 web(这样非本机无法访问,杜绝安全问题),类似下面这样:
RANDOM_PORT := $(shell shuf -i 1024-65535 -n 1)
CTR_NAME ?= build-$(RANDOM_PORT)
DOCKER0_IP ?= $(shell ip -4 addr show docker0 | awk -F '[ /]+' '/inet/{print $$3;exit}')
FILE_URL ?= $(DOCKER0_IP):$(RANDOM_PORT)
download:
mc cp xxx/ops/xxx/xxx.tar.gz ./
docker run -d --name $(CTR_NAME) \
-v $$PWD/xxx.tar.gz:/shared/xxx.tar.gz \
-p $(RANDOM_PORT):80 webshare-img
image: download
docker build . -t xxx --build-arg GZ_URL=$(FILE_URL)
docker rm -f $(CTR_NAME)
然后几个相关文件内容为如下:
# .gitignore
xxx.tar.gz
# .dockerignore
xxx.tar.gz
# Dockerfile
FROM xxxx
ARG GZ_URL
RUN set -eux; \
wget http://${GZ_URL}/xxx.tar.gz; \
tar zxf xxx.tar.gz; \
cd xxx; \
./configure; \
make; \
make install; \
rm -rf xxx;
除了下层添加,上层删除以外,实际上在上层修改也会产生这样的问题,镜像会附带 底层文件
+ 上层修改后的文件
,类似下面这种:
FROM xxx
WORKDIR /opt/app
COPY . .
RUN chown -R node:node /opt/app
...
例如上面这个,假设源码有 100M,最后镜像附带就有两份大小了,可以下面这样避免:
FROM xxx
WORKDIR /opt/app
COPY --chown=node:node . .
同样的还有下面这样类似:
FROM xxx
ADD xxx.tar.gz /opt/xxx/font
RUN chmod -R 755 /opt/xxx/font
上面这种最好文件打包 xxx.tar.gz
前就处理好目录权限,或者多阶段构建处理:
FROM xxx as build
ADD xxx.tar.gz /opt/xxx/font
RUN chmod -R 755 /opt/xxx/font
FROM xxx
COPY --from=build /opt/xxx/font /opt/xxx/font
不光是目录,单个文件的属性变更也一样,这个是之前业务 golang 里之前一个优化的案例:
FROM golang as build
WORKDIR /opt/app
COPY . .
RUN make build
FROM base-run
COPY --from=build --chown=app:app /opt/app/bin_file /opt/app
RUN chmod +x /opt/app/bin_file # <---
编译的二进制有 154M,然后让业务去掉第二阶段结尾 RUN 后镜像缩减了 154M,go build 的文件都有 chmod a+x
权限的,不需要额外 chmod。
接下来介绍一个插件案例,同事在 es 镜像里添加一些自研插件:
FROM elasticsearch:7.x
RUN set -eux; \
es_version=$(./bin/elasticsearch -V | awk -F'[: ,]' '{print $3}'); \
./bin/elasticsearch-plugin install https://xxx; \
chown -R elasticsearch:root /usr/share/elasticsearch
镜像里目录大小 516M /usr/share/elasticsearch
,让镜像浪费了挺多空间的,后面修改为 find 配合 chown 只处理 plugins 目录解决:
...; \
find plugins \! -user elasticsearch -exec chown elasticsearch '{}' +; \
同时,不要以为 owner 一样,再次执行 chown 同样属性文件还是一样的,例如下面:
$ cat > Dockerfile << EOF
FROM alpine
RUN dd if=/dev/urandom of=random-file bs=1M count=10
RUN chown root:root random-file
EOF
$ docker build . -t test
$ docker history test
IMAGE CREATED CREATED BY SIZE COMMENT
81f6f4a0e427 About a minute ago /bin/sh -c chown root:root random-file 10.5MB
9d7678242207 About a minute ago /bin/sh -c dd if=/dev/urandom of=random-file… 10.5MB
1d34ffeaf190 8 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 8 days ago /bin/sh -c #(nop) ADD file:e3abcdba177145039… 7.79MB
# 折叠的话记得看最右边的 Size --->
可以看到文件同样的 owner 是 root:root
,因为 overlayfs 是遵循写时复制,对文件会产生修改行为的,都会从下层复制到本层里处理。对于以上 overlay diff 浪费了容量的,可以使用 dive 工具分析:
$ CI=true dive test
Using default CI config
Image Source: docker://test
Fetching image... (this can take a while for large images)
Analyzing image...
efficiency: 63.5422 %
wastedBytes: 20971520 bytes (21 MB)
userWastedPercent: 100.0000 %
Inefficient Files:
Count Wasted Space File Path
2 21 MB /random-file
如果不在 CI 构建里使用,可以去掉前面的 CI=true
交互使用,dive 会分析 layer 里上下层重复以及添加的文件。例如上面左下角,说 /random-file
出现了两次。
COPY/ADD
添加文件后权限会变化的,可以COPY --chown
、多阶段或者 Makefile 之类预处理解决COPY
添加的文件被后面的RUN
使用完删除掉的,换成RUN
里下载- 不要直接 chown 和 chmod 修改下层的文件,而是使用 find 条件 + -exec 组合使用,只修改不符合的文件
- 目录
- 上一节:overlay2 和层 diff
- 下一节:一些最小依赖优化大小经验