基于Dockerfile构建容器镜像的最佳实践⽬录
1、背景概述
容器镜像是容器化落地转型的第⼀步,总结⼏点需要做镜像优化的原因
随着应⽤容器化部署的⼤规模迁移以及版本迭代的加快,优化基础设施之docker镜像主要有以下⽬的缩短部署时的镜像下载时间
提升安全性,减少可供攻击的⽬标
减少故障恢复时间
节省存储开销
2、为什么镜像会这么⼤
这⾥简要分析了⼏个典型的Repo,总结了现有Docker镜像较⼤的⼏个原因
2.1 基础镜像过⼤
举例:仓库A,制作出来的镜像⼤⼩9.67GB
⽤到的基础镜像:镜像⼤⼩8.72GB
逆向分析了⼀下,为啥基础镜像还这么⼤?结果就不⽤多说了0.0
2.2 基础镜像过⼤,⽽且不到了
举例:仓库B,制作出来的镜像⼤⼩22.7GB
⽤到的基础镜像: 404 not found,没错,不到了0.0
2.3 .git⽬录(⾮必要⽬录)
这个问题更多内容可以参考我之前的⽂章
举例:仓库C,代码⼤⼩795MB
其中.git⽬录⼤⼩225MB,dockerfile中的指令如下(全部添加到了镜像中)
ADD . /app/startapp/
其中还包含了d⽬录⼤⼩约300MB,是否需要使⽤不得⽽知,但⽬测不需要使⽤,仅为测试数据
d
├── [ 503] test_421.json
├── [ 483] test_havalB9.json
...
├── [ 484] test_144.json
docker打包镜像├── [ 104] .gitmodules
├── [ 122] .idea
├── [ 0] __init__.py
├── [ 11M] 164103.zip
├── [108M] test_180753.csv
├── [ 68M]
.
..
└── [ 335] README.md
以上其实都不需要提交到镜像中制作成镜像
2.4 Dockerfile本⾝有其他问题
这个原因不⾔⽽喻,不是专业的⼈写的Dockerfile可能都有⼀定的优化空间,只是暂时没关注这些细节⽽已例如,放任各路repo研发⾃⾏写Dockerfile,没有⼀定的标准,前期可能⽆所谓,到后期问题就慢慢浮现了正所谓《能⽤就⾏》~
3、Dockerfile如何优化
3.1 从哪⾥⼊⼿
优化docker镜像应该从镜像分层概念⼊⼿
3.1.1 举个栗⼦
⼀个实际的例⼦
nginx:alpine镜像 23.2MB
# docker history nginx:alpine
IMAGE CREATED CREATED BY SIZE COMMENT
b46db85084b8 9 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 9 days ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
<missing> 9 days ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 9 days ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 9 days ago /bin/sh -c #(nop) COPY file:09a214a3e07c919a… 4.61kB
<missing> 9 days ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB
<missing> 9 days ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0… 1.96kB
<missing> 9 days ago /bin/sh -c #(nop) COPY file:65504f71f5855ca0… 1.2kB
<missing> 9 days ago /bin/sh -c set -x && addgroup -g 101 -S … 17.6MB
<missing> 9 days ago /bin/sh -c #(nop) ENV PKG_RELEASE=1 0B
<missing> 9 days ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.0 0B
<missing> 9 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.21.4 0B
<missing> 9 days ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 10 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 10 days ago /bin/sh -c #(nop) ADD file:762c899ec0505d1a3… 5.61MB
python:alpine镜像 45.5MB
# docker history python:alpine
IMAGE CREATED CREATED BY SIZE COMMENT
382a63bb2f25 10 days ago /bin/sh -c #(nop) CMD ["python3"] 0B
<missing> 10 days ago /bin/sh -c set -ex; wget -O get-pip.py "$P… 8.31MB
<missing> 10 days ago /bin/sh -c #(nop) ENV PYTHON_GET_PIP_SHA256… 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV PYTHON_GET_PIP_URL=ht… 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV PYTHON_SETUPTOOLS_VER… 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV PYTHON_PIP_VERSION=21… 0B
<missing> 10 days ago /bin/sh -c cd /usr/local/bin && ln -s idle3… 32B
<missing> 10 days ago /bin/sh -c set -ex && apk add --no-cache --… 29.8MB
<missing> 10 days ago /bin/sh -c set -ex && apk add --no-cache --… 29.8MB
<missing> 10 days ago /bin/sh -c #(nop) ENV PYTHON_VERSION=3.10.0 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV GPG_KEY=A035C8C19219B… 0B
<missing> 10 days ago /bin/sh -c set -eux; apk add --no-cache c… 1.82MB
<missing> 10 days ago /bin/sh -c #(nop) ENV LANG=C.UTF-8 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV PATH=/usr/local/bin:/… 0B
<missing> 10 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 10 days ago /bin/sh -c #(nop) ADD file:762c899ec0505d1a3… 5.61MB
实际存储
# docker inspect nginx:alpine| jq '.[0]|{GraphDriver}'
{
"GraphDriver": {
"Data": {
"LowerDir": "/data/docker-overlay2//diff:/data/docker-overlay2//diff:/data/docker-overlay2//diff:/data/docker-overlay2//diff: "MergedDir": "/data/docker-overlay2//merged",
"UpperDir": "/data/docker-overlay2//diff",
"WorkDir": "/data/docker-overlay2//work"
},
"Name": "overlay2"
}
}
分层概念的描述
镜像解决了应⽤运⾏及环境的打包问题,实际应⽤中应⽤都是基于同⼀个rootfs来打包和迭代的,但并不是每个rootfs都会多份,实际
上docker利⽤了存储驱动AUFS,devicemapper,overlay,overlay2的存储技术实现了分层
例如上⾯查看⼀个docker镜像会发现这些层
LowerDir:镜像层
MergedDir:整合了lower层和upper读写层显⽰出来的视图
UpperDir:读写层
WorkDir:中间层,对Upper层的写⼊,先写⼊WorkDir,再移⼊UpperDir
3.1.2 Copy on write
当Docker第⼀次启动⼀个容器时,初始的读写层是空的,当⽂件系统发⽣变化时,这些变化都会应⽤到这⼀层之上。⽐如,如果想修改⼀个
⽂件,这个⽂件⾸先会从该读写层下⾯的只读层复制到该读写层。由此,该⽂件的只读版本依然存在于只读层,只是被读写层的该⽂件副本
所隐藏,该机制则被称之为写时复制
3.1.3 UnionFS
把多个⽬录(也叫分⽀)内容联合挂载到同⼀个⽬录下,⽽⽬录的物理位置是分开的
⼀个直观的效果,第⼀次拉取⼀个nginx:1.15版本镜像,再次拉取nginx:1.16镜像,速度要快很多
3.2 ⽅案
了解了镜像⼤⼩的主要构成之后,就很容易知道从哪些⽅向⼊⼿减少镜像⼤⼩了
3.2.1 减少镜像层数
镜像层数的增加,对Dockerfile来说主要在于RUN指令出现的次数,因此,合并RUN指令可以⼤⼤减少镜像层数
举个栗⼦:
合并前,三层
RUN apk add tzdata
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
合并后,⼀层
RUN apk add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
3.2.2 减少每层镜像⼤⼩
3.2.2.1 选⽤更⼩的基础镜像
scratch:空镜像,⼜叫镜像之⽗!任何镜像都需要有⼀个基础镜像,那么问题来了,就好⽐是先有鸡还是先有蛋的问题,基础镜像
scratch:空镜像,⼜叫镜像之⽗!任何镜像都需要有⼀个基础镜像,那么问题来了,就好⽐是先有鸡还是先有蛋的问题,基础镜像的“祖宗”是什么呢?能不能在构建时不以任何镜像为基础呢?答案是肯定的,可以选⽤scratch,具体就不展开了,可以参考:,使⽤scratch镜像的例⼦pause
busybox:对⽐scratch,多了常⽤的linux⼯具等
alpine:多了包管理⼯具apk等
3.3.2.2 多阶段构建
多阶段构建⾮常适⽤于编译性语⾔,简单来说就是允许⼀个Dockerfile中出现多条FROM指令,只有最后⼀条FROM指令中指定的基础镜像作为本次构建镜像的基础镜像,其它的阶段都可以认为是只为中间步骤
FROM … AS …和COPY --from组合使⽤
例如java镜像,镜像⼤⼩812MB
FROM centos AS jdk
COPY /usr/local/src
RUN cd /usr/local/src && \
tar -xzvf -C /usr/local
使⽤多阶段构建,镜像⼤⼩618MB
FROM centos AS jdk
COPY /usr/local/src
RUN cd /usr/local/src && \
tar -xzvf -C /usr/local
FROM centos
COPY --from=jdk /usr/local/jdk1.8.0_231 /usr/local
3.3.2.3 忽略⽂件
构建上下⽂build context,“上下⽂” 意为和现在这个⼯作相关的周围环境
docker build时当前的⼯作⽬录,不管构建时有没有⽤到当前⽬录下的某些⽂件及⽬录,默认情况下这个上下⽂中的⽂件及⽬录都会作为构建上下⽂内容发送给Docker Daemon
当docker build开始执⾏时,控制台会输出Sending build context to Docker daemon xxxMB,这就表⽰将当前⼯作⽬录下的⽂件及⽬录都作为了构建上下⽂
前⾯提到可以在RUN指令中添加--no-cache不使⽤缓存,同样也可以在执⾏docker build命令时添加该指令以在镜像构建时不使⽤缓存
构建上下⽂中,使⽤.dockerignore⽂件在构建时就可以避免将本地模块以及调试⽇志被拷贝进⼊到Docker镜像中,这和git版本控制的.gitignore很类似
3.3.2.4 远程下载
使⽤远程下载代替ADD可以减少镜像⼤⼩
RUN curl -s 192.168.1.1/repository/tools/ | tar -xC /opt/
3.3.2.5 拆分COPY
例如⼀个COPY指令的⽬录下A有4个⼦⽬录AA/BB/CC/DD被COPY,但常变化的只有⼀个BB
这个时候拆分COPY会更快
COPY A/AA /app/A/AA
COPY A/BB /app/A/BB
COPY A/CC /app/A/CC
COPY A/DD /app/A/DD
3.3.2.6 构建时挂载
构建时挂载()
配置
修改docker启动参数,添加--experimental
dockerfile头部添加# syntax=docker/dockerfile:1.1.1-experimental
使⽤
挂载本地golang缓存
# syntax = docker/dockerfile:experimental
FROM golang
FROM golang
...
RUN --mount=type=cache,target=/root/.cache/go-build go build ...
挂载cache⽬录
# syntax = docker/dockerfile:experimental
FROM ubuntu
RUN rm -f /etc/f.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/f.d/keep-cache RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \
apt update && apt install -y gcc
挂载某些凭据
# syntax = docker/dockerfile:experimental
FROM python:3
RUN pip install awscli
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials aws s3 cp s3://... ...
等等
3.3.2.7 构建后清理
删除压缩包
清理安装缓存
--no-cache
rm -rf /var/lib/apt/lists/*
rm -rf /var/cache/yum/*
3.3.2.8 镜像压缩
export和import组合进⾏压缩镜像(压缩效果不是很明显)
这种⽅法不好的就是会丢失⼀部分镜像信息
# docker run -d --name nginx nginx:alpine
# docker export nginx |docker import - nginx:alpine2
sha256:dd6a3cf822ac3c3ad3e7f7b31675cd8cd99a6f80e360996e04da6fc2f3b98cb5
# docker history nginx:alpine
IMAGE CREATED CREATED BY SIZE COMMENT
b46db85084b8 10 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 10 days ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
<missing> 10 days ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 10 days ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 10 days ago /bin/sh -c #(nop) COPY file:09a214a3e07c919a… 4.61kB
<missing> 10 days ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB
<missing> 10 days ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0… 1.96kB
<missing> 10 days ago /bin/sh -c #(nop) COPY file:65504f71f5855ca0… 1.2kB
<missing> 10 days ago /bin/sh -c set -x && addgroup -g 101 -S … 17.6MB
<missing> 10 days ago /bin/sh -c #(nop) ENV PKG_RELEASE=1 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.0 0B
<missing> 10 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.21.4 0B
<missing> 10 days ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 10 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 10 days ago /bin/sh -c #(nop) ADD file:762c899ec0505d1a3… 5.61MB
# docker history nginx:alpine2
IMAGE CREATED CREATED BY SIZE COMMENT
dd6a3cf822ac 40 seconds ago 23MB Imported from -
# docker images|grep nginx
nginx alpine2 dd6a3cf822ac 54 seconds ago 23MB nginx alpine b46db85084b8 10 days ago 23.2MB 3.3 样例
3.3.1 go 样例
样例⼀
kubeadm安装的k8s集,kube-apiserver镜像的Dockerfile是利⽤bazel编译⼯具编译的
bazel build ...
LABEL maintainers=Kubernetes Authors
LABEL description=go based runner for distroless scenarios
WORKDIR /
COPY /workspace/go-runner . # buildkit
ENTRYPOINT ["/go-runner"]
COPY file:2e904ea733ba0ded2a99947847de31414a19d83f8495dd8c1fbed3c70bf67a22 in /usr/local/bin/kube-apiserver
代码⽬录28M(包含.git⽬录20.5M)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论