Docker 镜像构建与优化
一、Dockerfile 构建镜像
1.1.拉取所需镜像
首先 docker pull 拉取一个 centos7 的镜像。
docker pull centos:7
下载 nginx 源码包。
官网:nginx: download
wget https://nginx.org/download/nginx-1.26.3.tar.gz
1.2.解决 CentOS 7 安装源问题
因为原本的 centos 7 安装源有问题不能下载软件,所以需要更换容器的安装源。以下是根据宿主机本机搭建共享安装源供容器使用,可也自行更换阿里或者其他平台的开源镜像源。
1.2.1.添加新光盘
搭建本地 Apache 服务,共享本地 RHEL 7 安装源供容器 CentOS 7 使用。
添加新 CD/DVD 设置 RHEL 7.9 的镜像文件。
1.2.2.配置 Apache 服务
安装 httpd ,配置服务。
yum install httpd -y
vim /etc/httpd/conf/httpd.conf
修改 httpd 主配置文件监听端口为 8888,为了与容器的端口不冲突。
构建访问页面,共享 RHEL 7.9 信息。
mkdir /var/www/html/rhel7.9
mount /dev/sr1 /var/www/html/rhel7.9/
systemctl enable --now httpd
1.2.3.测试
浏览器可访问到共享的光盘信息。
1.3.运行 CentOS 容器,修改 yum 源
查看 centos 容器网络(此时 IP :172.17.0.2)。
docker inspect centos
容器看到主机的网络为与容器同一网段的 IP 地址,所以,接下来在容器里配置 yum 源,需要设置容器连接的本机 IP(172.17.0.1),而不是本机真实 IP(192.168.67.140)。
运行 CentOS 容器,配置 yum 源。
docker run -it --name centos centos:7
# 进入容器,执行以下命令
cd /etc/yum.repos.d/
rm -rf *.repo
cat >> centos7.repo << EOF
[centos7]
name=centos7.9
baseurl=http://172.17.0.1:8888/rhel7.9/
gpgcheck=0
EOF
# 退出容器,但不关闭容器,使用命令 ctrl+p+q
# 提交 centos 现有状态
docker commit -m "add repo" centos centos:repo
SIZE 没大的变化。
因为原本的 centos 镜像安装源不能安装软件,所以需要搭建能安装软件的 centos 镜像,为了下面编写构建文件做准备。
1.4.编写构建文件
1.4.1.构建参数
FROM | 指定 base 镜像 eg:FROM busybox:version |
---|---|
COPY | 复制文件 eg:COPY file/file 或者 COPY ["file","/"] |
MAINTAINER | 指定作者信息,比如邮箱 eg:MAINATINER user@example.com 在最新版的 docker 中用 LABEL KEY=“VALUE” 代替 |
ADD | 功能和 copy 相似,指定压缩文件或 url eg:ADD test.tar /mnt 或者 eg:ADD http://ip/test.tar /mnt |
ENV | 指定环境变量 eg:ENV FILENAME test |
EXPOSE | 暴露容器端口 eg:EXPOSE 80 |
VOLUME | 申明数据卷,通常指数据挂载点 eg:VOLUME["/var/www/html"] |
WORKDIR | 切换路径 eg:WORKDIR /mnt |
RUN | 在容器中运行的指令 eg:touch file |
CMD | 在容器启动时自动运行的动作可以被覆盖 eg:CMD echo $FILENAME 会调用 shell 解析 eg:CMD["/bin/sh","-c","echo $FILENAME"]不调用 shell 解析 |
ENTRYPOINT | 和 CMD 功能和用法类似,但动作不可被覆盖 |
镜像层机制解析
-
Docker 镜像层的工作原理:
-
Docker 镜像是由多个只读层(Layer)叠加而成的。每个 RUN、COPY、ADD 指令都会生成一个新层。镜像层具有共享特性,相同的文件内容在多个镜像中只存储一次。但过多的镜像层会增加镜像体积和构建时间,因此合并层是优化的关键。
-
缓存机制说明
-
Docker 构建缓存的机制和注意事项:
-
Docker 在构建时会缓存每一层的结果。若某层指令未改变,后续构建会复用缓存。但修改上层指令会导致下层缓存失效。例如,将 ADD 指令放在 RUN 之前可以减少缓存失效的概率。
-
1.4.2.创建 Dockerfile
在家目录下建立文件。
mkdir /root/docker
cd /root/docker/
cp /root/nginx-1.26.3.tar.gz /root/docker/ # 注意:构建所需的所有文件多要与 Dockerfile 在同一个文件夹里
# 编写构建文件
cat >> Dockerfile << EOF
FROM centos:repo # 指定基础镜像,不过'repo'不是有效的CentOS镜像标签,应替换为如'centos:7'等有效标签
LABEL Mail=haha@qq.com # 为镜像添加元数据标签,标注维护者邮箱为haha@qq.com
ADD nginx-1.26.3.tar.gz /mnt # 将当前目录下的nginx-1.26.3.tar.gz文件添加到镜像的/mnt目录,ADD可自动解压压缩包
EXPOSE 80 # 声明容器运行时监听的端口为80,仅作声明,不进行实际端口映射
WORKDIR /mnt/nginx-1.26.3 # 设置后续命令执行的工作目录为/mnt/nginx-1.26.3
RUN yum install gcc make pcre-devel openssl-devel -y # 使用yum包管理器安装编译Nginx所需的依赖包
RUN ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module # 执行Nginx配置脚本,指定安装目录和开启相关模块
RUN make # 执行make命令,编译Nginx源代码
RUN make install # 执行make install命令,将编译好的Nginx安装到指定目录
VOLUME ["/usr/local/nginx/html"] # 创建数据卷,用于持久化存储数据和容器间共享
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"] # 定义容器启动时执行的命令,以非守护进程模式启动Nginx
EOF
1.4.3.通过 Dockerfile 构建镜像
docker build -t nginx:v1 /root/docker/
以下是通过 CentOS7 构建出来的 nginx 镜像。
问题:
-
镜像层冗余问题:多个 RUN 指令会生成多个镜像层,这会让镜像体积增大,还可能产生不必要的缓存。
-
包安装缓存问题:yum install 后没有清理缓存,这会让镜像体积增大。
二、镜像优化方案
2.1.镜像优化策略
-
镜像层合并:把多个 RUN 指令合并成一个,以此减少镜像层数量,进而减小镜像体积。
-
包安装缓存清理:在 yum install 之后添加 yum clean all 和 rm -rf /var/cache/yum 来清理缓存。
2.2.镜像优化示例
2.2.1.缩减镜像层
cd /root/docker/
# 修改构建文件
cat >> Dockerfile << EOF
FROM centos:repo
LABEL Mail=haha@qq.com
ADD nginx-1.26.3.tar.gz /mnt
EXPOSE 80
WORKDIR /mnt/nginx-1.26.3
RUN yum install gcc make pcre-devel openssl-devel -y && ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -rf /mnt/nginx-1.26.3 && yum clean all
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
EOF
docker build -t nginx:v2 /root/docker/
查看建立的镜像,v2 比 v1 小了一些。
解决的问题:
-
镜像层冗余问题
-
原问题
-
原 Dockerfile 中使用了多个 RUN 指令,每个 RUN 指令都会在镜像构建过程中创建一个新的镜像层,这会导致镜像层数增多,最终使得镜像体积变大,同时也可能造成不必要的缓存问题。
-
解决方式
-
修改后的 Dockerfile 将多个 RUN 指令合并为一个,即 RUN yum install gcc make pcre - devel openssl - devel -y && ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -rf /mnt/nginx - 1.26.3 && yum clean all。这样只创建一个镜像层,减少了镜像层数,有效减小了镜像体积。
-
-
包安装缓存问题
-
原问题
-
原 Dockerfile 里 yum install 后没有清理缓存,这些缓存会被包含在镜像中,从而增加镜像体积。
-
解决方式
-
修改后的 Dockerfile 在 yum install 之后添加了 yum clean all 命令,它会清理 yum 的缓存文件,进一步减小了镜像体积。
-
-
临时文件残留问题
-
原问题
-
原 Dockerfile 构建完成后,/mnt/nginx - 1.26.3 目录下的文件仍然存在于镜像中,这会增加镜像的冗余数据。
-
解决方式
-
修改后的 Dockerfile 在安装完成 Nginx 后添加了 rm -rf /mnt/nginx - 1.26.3 命令,将解压的 Nginx 源码目录删除,避免了临时文件占用镜像空间。
-
-
构建效率问题
-
原问题
-
多个 RUN 指令可能会导致每次构建时都需要重新执行多个步骤,影响构建效率。
-
解决方式
-
合并 RUN 指令后,Docker 可以一次性执行多个操作,减少了中间层的创建和处理,在一定程度上提高了构建效率。
-
2.2.2.多阶构建
cd /root/docker/
# 修改构建文件
cat >> Dockerfile << EOF
FROM centos:repo AS build
ADD nginx-1.26.3.tar.gz /mnt
WORKDIR /mnt/nginx-1.26.3
RUN yum install gcc make pcre-devel openssl-devel -y && ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -rf /mnt/nginx-1.26.3 && yum clean all
FROM centos:repo
LABEL Mail=haha@qq.com
COPY --from=build /usr/local/nginx /usr/local/nginx
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
EOF
docker build -t nginx:v3 /root/docker/
查看建立的镜像,v3 比 v2 小了一些。
解决的问题:
-
镜像体积显著减小
-
原问题
-
在单阶段构建时,构建过程中用到的所有工具(如编译工具、依赖库等)以及临时文件都会被打包进最终镜像,导致镜像体积庞大。例如,在安装和编译 Nginx 时,需要安装 gcc、make、pcre - devel、openssl - devel 等编译工具和依赖库,但在运行 Nginx 时,这些编译工具和依赖库并非必需,却依然存在于最终镜像中,增加了镜像的冗余数据。
-
解决方式
-
多阶段构建将构建过程分为多个阶段。在第一个阶段(build 阶段),使用包含编译工具的基础镜像进行编译和安装操作,完成后可以丢弃该阶段中使用的所有编译工具和临时文件。第二个阶段从一个新的基础镜像开始,仅将第一个阶段构建好的可执行文件和必要的配置文件复制到新的镜像中。在这个例子中,仅将 /usr/local/nginx 目录从 build 阶段复制到最终镜像,避免了编译工具和临时文件进入最终镜像,从而显著减小了镜像体积。
-
-
提高镜像安全性
-
原问题
-
单阶段构建的镜像中包含了构建过程中使用的所有工具和依赖,这些工具和依赖可能存在安全漏洞。例如,编译工具可能存在已知的安全隐患,将其留在最终镜像中会给容器的运行带来安全风险。
-
解决方式
-
多阶段构建的最终镜像只包含运行应用所需的最小化依赖。在第二个阶段,使用一个干净、轻量级的基础镜像,不包含构建阶段的编译工具和其他不必要的软件包,减少了潜在的安全漏洞,提高了镜像的安全性。
-
-
构建过程的独立性和可维护性增强
-
原问题
-
单阶段构建的 Dockerfile 中,构建步骤和运行步骤混杂在一起,当需要修改构建过程或者运行配置时,可能会相互影响,导致维护困难。而且,单阶段构建的逻辑不够清晰,难以理解每个步骤的目的。
-
解决方式
-
多阶段构建将构建过程和运行过程分离,每个阶段都有明确的职责。build 阶段专注于编译和安装应用,而第二个阶段专注于运行应用。这种分离使得 Dockerfile 的逻辑更加清晰,易于理解和维护。如果需要修改构建过程,只需要修改 build 阶段;如果需要调整运行配置,只需要修改第二个阶段,相互之间的影响较小。
-
-
构建效率提升
-
原问题
-
单阶段构建每次构建都需要重新执行所有步骤,即使某些步骤的结果在之前的构建中已经存在。例如,每次构建都需要重新安装编译工具和依赖库,浪费了时间和资源。
-
解决方式
-
多阶段构建可以利用 Docker 的缓存机制。如果 build 阶段的代码和配置没有发生变化,Docker 可以复用之前构建的结果,只需要重新构建有变化的部分。在第二个阶段,由于只复制必要的文件,复制操作的时间也会减少,从而提高了整体的构建效率。
-