Docker镜像构建技巧小记 —— 筑梦之路

132 篇文章 7 订阅
116 篇文章 3 订阅

一、删除缓存

一般的包管理器,比如 aptpip 等,下载包的时候,都会下载缓存,下次安装同一个包的时候不必从网络上下载,直接使用缓存即可。

#正例

RUN dnf install -y --setopt=tsflags=nodocs \
    httpd vim && \
    systemctl enable httpd && \
    dnf clean all

#反例

FROM fedora
RUN dnf install -y mariadb
RUN dnf install -y wordpress
RUN dnf clean all


Dockerfile 里面的每一个 RUN 都会创建一层新的 layer,如上所说,这样其实是创建了 3 层  layer,前 2 层带来了缓存,第三层删除了缓存。如同 git 一样,你在一个新的 commit 里面删除了之前的文件,其实文件还是在 git 历史中的,最终的 docker image 其实没有减少。

但是 Docker 有了一个新的功能,docker build --squash。squash 功能会在 Docker 完成构建之后,将所有的 layers 压缩成一个 layer,也就是说,最终构建出来的 Docker image 只有一层。所以,如上在多个 RUN 中写 clean 命令,其实也可以。我不太喜欢这种方式,因为前文提到的,多个 image 共享 base image 以及加速 pull 的 feature 其实就用不到了。

一些常见的包管理器删除缓存的方法:

yumyum clean all
dnfdnf clean all
rvmrvm cleanup all
gemgem cleanup
cpanrm -rf ~/.cpan/{build,sources}/*
piprm -rf ~/.cache/pip/*
apt-getapt-get clean

另外,上面这个命令其实还有一个缺点。因为我们在同一个 RUN 中写多行,不容易看出这个 dnf 到底安装了什么。而且,第一行和最后一行不一样,如果修改,diff 看到的会是两行内容,很不友好,容易出错。

可以写成这种形式,比较清晰

RUN true \
    && dnf install -y --setopt=tsflags=nodocs \
        httpd vim \
    && systemctl enable httpd \
    && dnf clean all \
    && true

 二、改动不频繁的内容往前放

对于一个 Docker image 有 ABCD 四层,B 修改了,那么 BCD 会改变。

根据这个原理,我们在构建的时候可以将系统依赖往前写,因为像 aptdnf 这些安装的东西,是很少修改的。然后写应用的库依赖,比如 pip install,最后 copy 应用。

比如下面这个 Dockerfile,就会在每次代码改变的时候都重新 Build 大部分 layers,即使只改了一个网页的标题。

FROM python:3.7-buster
 
# copy source
RUN mkdir -p /opt/app
COPY myapp /opt/app/myapp/
WORKDIR /opt/app
 
# install dependencies nginx
RUN apt-get update && apt-get install nginx
RUN pip install -r requirements.txt
RUN chown -R www-data:www-data /opt/app
 
# start server
EXPOSE 8020
STOPSIGNAL SIGTERM
CMD ["/opt/app/start-server.sh"]

可以改成,先安装 Nginx,再单独 copy requirements.txt,然后安装 pip 依赖,最后 copy 应用代码。

FROM python:3.7-buster
 
# install dependencies nginx
RUN apt-get update && apt-get install nginx
COPY myapp/requirements.txt /opt/app/myapp/requirements.txt
RUN pip install -r requirements.txt
 
# copy source
RUN mkdir -p /opt/app
COPY myapp /opt/app/myapp/
WORKDIR /opt/app
 
RUN chown -R www-data:www-data /opt/app
 
# start server
EXPOSE 8020
STOPSIGNAL SIGTERM
CMD ["/opt/app/start-server.sh"]

三、构建和运行Image分离

我们在编译应用的时候需要很多构建工具,比如 gcc, golang 等。但是在运行的时候不需要。在构建完成之后,去删除那些构建工具是很麻烦的。

我们可以这样:使用一个 Docker 作为 builder,安装所有的构建依赖,进行构建,构建完成后,重新选择一个 Base image,然后将构建的产物复制到新的 base image,这样,最终的 image 只含有运行需要的东西。

FROM golang as build
ENV CGO_ENABLED 0
RUN go install github.com/ericchiang/pup@latest
 
FROM alpine:3.15.4 as run
COPY --from=build /go/bin/pup /usr/local/bin/pup

四、构建产物

dive 是一个 TUI,命令行的交互式 App,它可以让你看到 docker 每一层里面都有什么。

GitHub - wagoodman/dive: A tool for exploring each layer in a docker image

dive ubuntu:latest 命令可以看到 ubuntu image 里面都有什么文件。内容会显示为两侧,左边显示每一层的信息,右边显示当前层(会包含之前的所有层)的文件内容,本层新添加的文件会用黄色来显示。通过 tab 键可以切换左右的操作。

---------------------------------------------------

国内软件源

使用默认的软件源安装构建时所需的依赖,对于绝大多数基础镜像来说,可以通过修改软件源的方式更换为国内的软件源镜像站。目前国内稳定可靠的镜像站主要有,华为云、阿里云、腾讯云、163 等。

对于 alpine 基础镜像修改软件源

echo "http://mirrors.huaweicloud.com/alpine/latest-stable/main/" > /etc/apk/repositories ;\
echo "http://mirrors.huaweicloud.com/alpine/latest-stable/community/" >> /etc/apk/repositories ;\
apk update ;

debian 基础镜像修改默认软件源

sed -i 's/deb.debian.org/mirrors.huaweicloud.com/g' /etc/apt/sources.list ;\
sed -i 's|security.debian.org/debian-security|mirrors.huaweicloud.com/debian-security|g' /etc/apt/sources.list ;\
apt update ;\

Ubuntu 基础镜像修改默认软件源

sed -i 's/archive.ubuntu.com/mirrors.huaweicloud.com/g' /etc/apt/sources.list
apt update ;\

建议这些命令就放在 RUN 指令的第一条,update 以下软件源,之后再 install 相应的依赖。

 时区设置:

由于绝大多数基础镜像都是默认采用 UTC 的时区,与北京时间相差 8 个小时,这将会导致容器内的时间与北京时间不一致,因而会对一些应用造成一些影响,还会影响容器内日志和监控的数据。

因此对于东八区的用户,最好在构建镜像的时候设定一下容器内的时区,以免以后因为时区遇到一些 bug😂。
可以通过环境变量设置容器内的时区。在启动的时候可以通过设置环境变量 -e TZ=Asia/Shanghai 来设定容器内的时区

alpine

但对于 alpine 基础镜像无法通过 TZ 环境变量的方式设定时区,需要安装 tzdata 来配置时区。

docker run --rm -it -e TZ=Asia/Shanghai alpine date
Thu Jan  2 03:37:44 UTC 2022

对于 alpine 基础镜像,可以在 RUN 指令后面追加上以下命令

apk add --no-cache tzdata ;\
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\
echo "Asia/Shanghai" > /etc/timezone ;\
apk del tzdata ;\

通过 tzdate 设定时区

docker build -t alpine:tz2 .

docker run --rm -it alpine:tz2 date
Thu Jan  2 11:12:23 CST 2022


debian

通过启动时设定环境变量指定时区


docker run --rm -it -e TZ=Asia/Shanghai debian date
Thu Jan  2 11:38:56 CST 2022

也可以再构建镜像的时候复制时区文件设定容器内时区


cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\
echo "Asia/shanghai" > /etc/timezone ;\


ubuntu

通过启动时设定环境变量指定时区,失败 😂,只能通过时区文件来设定时区了。

docker run --rm -it -e TZ=Asia/Shanghai debian date
Thu Jan  2 11:38:56 CST 2022

root@ubuntu:~/docke/alpine# ^debian^ubuntu
docker run --rm -it -e TZ=Asia/Shanghai ubuntu date
Thu Jan  2 03:44:13 Asia 2022


在这里有个命令执行的小技巧,通过脱字符 ^ 来替换上一条命令中的 debian 为 ubuntu 然后执行相同的命令

通过时区文件来设定时区

apt update ;\
apt install tzdata -y ;\
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\
echo "Asia/shanghai" > /etc/timezone ;\



尽量使用 URL 添加源码

如果不采用分阶段构建,对于一些需要在容器内进行编译的项目,最好通过 git 或者 wegt 的方式将源码打入到镜像内,而非采用 ADD 或者 COPY ,因为源码编译完成之后,源码就不需要可以删掉了,而通过 ADD 或者 COPY 添加进去的源码已经用在下一层镜像中了,是删不掉滴啦。

也就是说 git & wget source 然后 build,最后 rm -rf source/ 这三部放在一条 RUN 指令中,这样就能避免源码添加到镜像中而增大镜像体积啦

下面以 FastDFS 的 Dockerfile 为例

项目官方的 Dockerfile

# centos 7
FROM centos:7
# 添加配置文件
# add profiles
ADD conf/client.conf /etc/fdfs/
ADD conf/http.conf /etc/fdfs/
ADD conf/mime.types /etc/fdfs/
ADD conf/storage.conf /etc/fdfs/
ADD conf/tracker.conf /etc/fdfs/
ADD fastdfs.sh /home
ADD conf/nginx.conf /etc/fdfs/
ADD conf/mod_fastdfs.conf /etc/fdfs

# 添加源文件
# add source code
ADD source/libfastcommon.tar.gz /usr/local/src/
ADD source/fastdfs.tar.gz /usr/local/src/
ADD source/fastdfs-nginx-module.tar.gz /usr/local/src/
ADD source/nginx-1.15.4.tar.gz /usr/local/src/

# Run
RUN yum install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim -y \
  &&  mkdir /home/dfs   \
  &&  cd /usr/local/src/  \
  &&  cd libfastcommon/   \
  &&  ./make.sh && ./make.sh install  \
  &&  cd ../  \
  &&  cd fastdfs/   \
  &&  ./make.sh && ./make.sh install  \
  &&  cd ../  \
  &&  cd nginx-1.15.4/  \
  &&  ./configure --add-module=/usr/local/src/fastdfs-nginx-module/src/   \
  &&  make && make install  \
  &&  chmod +x /home/fastdfs.sh
# export config
VOLUME /etc/fdfs

EXPOSE 22122 23000 8888 80
ENTRYPOINT ["/home/fastdfs.sh"]

优化后的:


FROM alpine:3.10

RUN set -x \
    && echo "http://mirrors.huaweicloud.com/alpine/latest-stable/main/" > /etc/apk/repositories \
    && echo "http://mirrors.huaweicloud.com/alpine/latest-stable/community/" >> /etc/apk/repositories \
    && apk update \
    && apk add --no-cache --virtual .build-deps gcc libc-dev make perl-dev openssl-dev pcre-dev zlib-dev git \
    && mkdir -p /usr/local/src \
    && cd /usr/local/src \
    && git clone https://github.com/happyfish100/libfastcommon.git --depth 1 \
    && git clone https://github.com/happyfish100/fastdfs.git --depth 1    \
    && git clone https://github.com/happyfish100/fastdfs-nginx-module.git --depth 1  \
    && wget http://nginx.org/download/nginx-1.15.4.tar.gz \
    && tar -xf nginx-1.15.4.tar.gz \
    && cd /usr/local/src/libfastcommon \
    && ./make.sh \
    && ./make.sh install \
    && cd /usr/local/src/fastdfs/ \
    && ./make.sh \
    && ./make.sh install \
    && cd /usr/local/src/nginx-1.15.4/ \
    && ./configure --add-module=/usr/local/src/fastdfs-nginx-module/src/ \
    && make && make install \
    && apk del .build-deps \
    && apk add --no-cache pcre-dev bash \
    && mkdir -p /home/dfs  \
    && mv /usr/local/src/fastdfs/docker/dockerfile_network/fastdfs.sh /home \
    && mv /usr/local/src/fastdfs/docker/dockerfile_network/conf/* /etc/fdfs \
    && chmod +x /home/fastdfs.sh \
    && rm -rf /usr/local/src*
VOLUME /home/dfs
EXPOSE 22122 23000 8888 8080
CMD ["/home/fastdfs.sh"]

使用虚拟编译环境

对于只在编译过程中使用到的依赖,我们可以将这些依赖安装在虚拟环境中,编译完成之后可以一并删除这些依赖,比如 alpine 中可以使用 apk add --no-cache --virtual .build-deps,后面加上需要安装的相关依赖。

apk add --no-cache --virtual .build-deps gcc libc-dev make perl-dev openssl-dev pcre-dev zlib-dev git

构建完成之后可以使用 apk del .build-deps 命令,一并将这些编译依赖全部删除


需要注意的是,.build-deps 后面接的是编译时以来的软件包,并不是所有的编译依赖都可以删除,不要把运行时的依赖包接在后面,最好单独 add 一下。

 最小化层数

docker 在 1.10 以后,只有 RUN、COPY 和 ADD 指令会创建层,其他指令会创建临时的中间镜像,但是不会直接增加构建的镜像大小了。

前文提到了建议使用 git 或者 wget 的方式来将文件打入到镜像当中,但如果我们必须要使用 COPY 或者 ADD 指令呢?

FastDFS 为例


# centos 7
FROM centos:7
# 添加配置文件
# add profiles
ADD conf/client.conf /etc/fdfs/
ADD conf/http.conf /etc/fdfs/
ADD conf/mime.types /etc/fdfs/
ADD conf/storage.conf /etc/fdfs/
ADD conf/tracker.conf /etc/fdfs/
ADD fastdfs.sh /home
ADD conf/nginx.conf /etc/fdfs/
ADD conf/mod_fastdfs.conf /etc/fdfs

# 添加源文件
# add source code
ADD source/libfastcommon.tar.gz /usr/local/src/
ADD source/fastdfs.tar.gz /usr/local/src/
ADD source/fastdfs-nginx-module.tar.gz /usr/local/src/
ADD source/nginx-1.15.4.tar.gz /usr/local/src/


多个文件需要添加到容器中不同的路径,每个文件使用一条 ADD 指令的话就会增加一层镜像,这样戏曲就多了 12 层镜像 。

其实大可不必,我们可以将这些文件全部打包为一个文件为 src.tar.gz 然后通过 ADD 的方式把文件添加到当中去,然后在 RUN 指令后使用 mv 命令把文件移动到指定的位置。这样仅仅一条 ADD 和 RUN 指令取代掉了 12 个 ADD 指令


FROM alpine:3.10
COPY src.tar.gz /usr/local/src.tar.gz
RUN set -xe \
    && apk add --no-cache --virtual .build-deps gcc libc-dev make perl-dev openssl-dev pcre-dev zlib-dev tzdata \
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && tar -xvf /usr/local/src.tar.gz -C /usr/local \
    && mv /usr/local/src/conf/fastdfs.sh /home/fastdfs/ \
    && mv /usr/local/src/conf/* /etc/fdfs \
    && chmod +x /home/fastdfs/fastdfs.sh \
    && rm -rf /usr/local/src/* /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache
VOLUME /var/fdfs


其他最小化层数无非就是把构建项目的整个步骤弄成一条 RUN 指令,不过多条命令合并可以使用 && 或者 ; 这两者都可以,不过据我在 docker hub 上的所见所闻,使用 ; 的居多,尤其是官方的 Dockerfile。

使用 Docker Squash 减小镜像大小 

Docker 在构建镜像时创建了很多层。压缩有助于在逻辑层中组织镜像。我们可以控制镜像的结构,而不是让镜像具有多个不必要的层。

您可以使用以下命令安装 docker-squash。

pip install docker-squash
您可以运行以下命令来减小镜像的大小。

docker-squash image:old -t image:new
-----------------------------------

docker build --squash -t  jdk:test .   # 需要开启实验特性

apt 安装中使用 --no-install-recommends 标志

当我们运行 apt install 命令来安装某些包时,它会安装一些不需要的推荐包。使用 --no-install-recommends 标志可以显着减小镜像大小。

FROM ubuntu:latest
RUN apt update -y && \
apt install unzip -y --no-install-recommends && \
apt install curl --no-install-recommends -y && \
apt install python3 -y --no-install-recommends

在 apt install 命令后添加 rm -rf /var/lib/apt/lists/*

FROM ubuntu:latest
RUN apt update -y && \
apt install unzip -y --no-install-recommends && \
apt install curl --no-install-recommends -y && \
apt install python3 -y --no-install-recommends && \
rm -rf /var/lib/apt/lists/*

在 apt install 之后添加这个命令来减少 docker 镜像的大小

使用 .dockerignore 文件

如果您不想将某些文件复制到 docker 镜像,那么使用 .dockerignore 文件可以为您节省一些空间。

在构建上下文中有一些隐藏的文件/文件夹,您可以使用 ADD 或 COPY 命令(如 .git 等)将其传输到镜像。包含一个 .dockerignore 文件以减小 docker 镜像大小是一个很好的做法。

.dockerignore 文件示例。

ignorethisfile.txt
logs/
ignorethisfolder/
.git
.cache
*.md

在 RUN 之后放置 COPY

在某些情况下,您对代码进行了细微的更改,并且需要反复从 dockerfile 构建镜像。
在这种情况下,将 COPY 命令放在 RUN 命令之后将有助于减小镜像大小,因为在这种情况下 docker 将能够更好地使用缓存功能。

它将为安装了依赖项的镜像创建缓存,每次更改代码时,docker 都会使用该缓存并创建镜像。它还将减少 docker 构建时间。
 

# Dockerfile

FROM ubuntu:latest
RUN apt update -y && \
apt install unzip -y --no-install-recommends && \
apt install curl --no-install-recommends -y && \
apt install python3 -y --no-install-recommends && \
rm -rf /var/lib/apt/lists/*
COPY file /home/ubuntu
#Dockerfile-2

FROM ubuntu:latest
COPY file /home/ubuntu
RUN apt update -y && \
apt install unzip -y --no-install-recommends && \
apt install curl --no-install-recommends -y && \
apt install python3 -y --no-install-recommends && \
rm -rf /var/lib/apt/lists/*

在上述情况下,dockerfile-1 将能够比 dockerfile-2 表现得更好

安装后删除软件包 

如果您需要在 docker 镜像中安装一些包,并且您是从外部下载它们,那么最好在安装后删除这些包。

例如,如果您希望从 zip 文件安装 AWS CLI V2,那么在成功安装后请记住也删除该 zip 文件。

FROM ubuntu:latest
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
sudo ./aws/install && \
rm awscliv2.zip

使用 Docker 镜像缩容工具

 

有几个dockerfile 优化工具可以帮助你减少 docker 镜像的大小。下面列出了其中一些。

Dive:Dive 是一个开源工具,用于探索 Docker 镜像及其层内容,然后发现缩小 Docker/OCI 镜像大小的方法。
https://github.com/wagoodman/dive

fromlatest.io:此工具将检查您的 Dockerfile 并检查可以执行的更多步骤以减小镜像大小。
https://www.fromlatest.io/

Docker Slim:它让你的容器更好、更小、更安全。您可以使用dockerslim 来最小化容器镜像。
https://github.com/slimtoolkit/slim
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值