Docker镜像构建全解析:从原理到实战优化

1.1 Docker 镜像结构

Docker 镜像共享宿主机内核,base 基础镜像只提供一个最小化的 Linux 发行版环境;同一台 Docker 主机上,可以同时运行多个不同发行版的 Linux 容器。

镜像采用分层只读结构,最大优势是多层镜像之间可以共享资源,减少磁盘占用。

1.2 镜像运行的基本原理

  • 容器启动后会在所有只读镜像层之上新增一层可写容器层(Copy-on-Write 写时复制)
  • 镜像层全部为只读,只有最上层容器层可读写
  • Docker 查找文件顺序:从上(容器层)往下逐层查找,找到即返回
  • 所有修改、新增文件都保存在容器层,不会改动底层镜像
  • 镜像层数上限:最多 127 层

写时复制 CoW:修改文件时,先把文件复制到上层可写层再修改,底层原文件保持不变。容器删除后,容器层数据随之消失,镜像不受影响。

1.3 镜像获得方式

  1. 官方基础镜像:由软件官方维护发布(nginx、centos、busybox 等)
  2. 企业自制镜像:基于基础镜像,通过编写 Dockerfile 自定义构建
  3. 拉取远程仓库镜像:docker pull 镜像地址
  4. 导入本地镜像包:docker load --i 本地镜像文件

1.4 镜像构建

1.4.1 Dockerfile 指令详解

Dockerfile 是镜像构建的描述文件,每一条指令生成一个镜像层。

指令 作用 示例说明
FROM 指定基础镜像,Dockerfile 第一条有效指令 FROM busybox:latest
COPY 复制本地文件到镜像,不会自动解压压缩包 COPY dragon /root
LABEL 添加镜像元数据(作者、版本、描述),替代旧 MAINTAINER LABEL create=doubledragon
ADD 类似 COPY,额外支持自动解压本地压缩包、支持远程 URL ADD test.tar /mnt 压缩包自动解压
ENV 设置环境变量,构建和容器运行阶段都可读取 ENV NAME=doubledragon
RUN 构建镜像阶段执行命令,执行结果写入镜像层 RUN touch /root/$NAME
EXPOSE 声明容器打算对外开放的端口,仅为文档说明,不会实际开放端口 EXPOSE 8080
VOLUME 声明数据卷挂载点,容器启动自动生成匿名卷挂载到此目录 VOLUME ["/mnt"]
WORKDIR 设置后续指令的工作目录,自动创建目录 WORKDIR "/mnt"
CMD 容器启动默认执行命令,可以被 docker run 后面的命令覆盖 CMD ["/bin/sh","-c","echo $NAME"]
ENTRYPOINT 容器入口命令,默认无法被覆盖,追加参数传入 ENTRYPOINT echo $NAME

重点区分:

  • RUN:构建镜像的时候执行
  • CMD / ENTRYPOINT:容器启动的时候执行
实操演示
复制代码
# 准备构建目录
[root@Docker-node1 ~]# mkdir docker/
[root@Docker-node1 ~]# cd docker/
[root@docker-node1 docker]# echo dragon > dragon
[root@Docker-node1 docker]# vim Dockerfile

Dockerfile 内容:

复制代码
FROM busybox:latest
COPY dragon /root

执行构建:

复制代码
[root@docker-node1 docker]# docker build -t dragon:v1 .

继续编写多指令 Dockerfile:

复制代码
FROM busybox:latest
LABEL create=doubledragon
COPY dragon /root
ADD test.tar /root
ENV NAME=doubledragon
RUN ["/bin/sh","-c","touch /root/$NAME"]
EXPOSE 8080
VOLUME "/mnt"
WORKDIR "/mnt"
CMD ["/bin/sh","-c","echo $NAME"]

# 构建镜像
[root@docker-node1 docker]# docker build -t dragon:v2 .
# 查看镜像分层历史
[root@docker-node1 docker]# docker history dragon:v2

VOLUME 挂载验证:

复制代码
# 启动容器
[root@docker-node1 docker]# docker run -it --name test --rm lee:v6
# 查看挂载信息
[root@docker-node1 ~]# docker inspect test | grep -i mounts -A10

输出可以看到 Docker 自动在宿主机/var/lib/docker/volumes/生成匿名卷目录,和容器内/mnt双向同步。

CMD 覆盖测试:

复制代码
# Dockerfile里CMD默认输出变量
[root@Docker-node1 docker]# docker run -it --rm --name test example:v3
lee
# run后面追加命令,直接覆盖CMD
[root@Docker-node1 docker]# docker run -it --name test --rm lee:v8 echo haha
haha

ENTRYPOINT 特点(不会被覆盖,参数追加)

复制代码
FROM busybox
ENV NAME lee
ENTRYPOINT echo $NAME

[root@docker-node1 docker]# docker run -it --rm --name test example:v3
lee
# 后面的haha不会覆盖,会作为参数传给ENTRYPOINT
[root@docker-node1 docker]# docker run -it --name test --rm lee:v8 haha
lee haha

快速批量删除带指定名称镜像

复制代码
[root@docker-node1 docker]# docker images | awk '/dragon/{system("docker rmi " $1)}'

1.4.2 Dockerfile 实战案例:编译 Nginx 镜像

1.4.2.1 准备文件,编写 Dockerfile
复制代码
[root@server1 ~]# mkdir docker
[root@server1 ~]# cd docker/
[root@server1 docker]# cp ~/nginx-1.23.3.tar.gz .
[root@server1 docker]# vim Dockerfile

Dockerfile:

复制代码
FROM centos:7
ADD nginx-1.23.3.tar.gz /mnt
WORKDIR /mnt/nginx-1.23.3
# 安装编译依赖
RUN yum install -y gcc make pcre-devel openssl-devel
# 去掉-g调试信息,减小程序体积
RUN sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc
# 编译配置,开启ssl和状态页模块
RUN ./configure --with-http_ssl_module --with-http_stub_status_module
RUN make
RUN make install
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
# nginx前台运行,容器必须前台进程
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
1.4.2.2 构建镜像
复制代码
[root@server1 docker]# docker build -t webserver:v1 .
1.4.2.3 测试镜像
复制代码
# 查看镜像
[root@server1 docker]# docker images webserver
# 查看分层构建历史
[root@server1 docker]# docker history webserver:v1
# 启动容器
[root@server1 docker]# docker run -d --name checkimage webserver
# 查看容器详细信息
[root@server1 docker]# docker inspect checkimage

1.5 镜像优化方案

镜像优化核心目标:缩小镜像体积、减少漏洞面、提升分发速度

1.5.1 优化策略

  1. 优先选用精简基础镜像(alpine、distroless),不要用完整版系统
  2. 合并 RUN 指令,减少镜像层数(层数上限 127,层数越多镜像越大)
  3. 构建完成及时清理缓存、源码包、yum/apt 缓存、编译工具等中间产物
  4. 使用多阶段构建:编译阶段单独作为临时镜像,最终镜像只拷贝运行所需程序,丢弃全套编译环境

1.5.2 优化示例

优化 1:合并 RUN 指令,清理缓存(v2 版本)
复制代码
FROM centos:7 AS build
ADD nginx-1.23.3.tar.gz /mnt
WORKDIR /mnt/nginx-1.23.3
RUN yum install -y gcc make pcre-devel openssl-devel && \
sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc && \
./configure --with-http_ssl_module --with-http_stub_status_module && \
make && make install && \
cd .. && rm -fr nginx-1.23.3 && yum clean all
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]

docker build -t webserver:v2 .

对比:v1:494MB → v2:317MB

优化 2:多阶段构建(v3 版本,效果最明显)

编译环境只在第一阶段 build,最终镜像只拷贝编译好的 nginx 程序,不带 gcc、make 等编译工具

复制代码
# 第一阶段:编译构建阶段(临时镜像)
FROM centos:7 AS build
ADD nginx-1.23.3.tar.gz /mnt
WORKDIR /mnt/nginx-1.23.3
RUN yum install -y gcc make pcre-devel openssl-devel && \
sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc && \
./configure --with-http_ssl_module --with-http_stub_status_module && \
make && make install && \
cd .. && rm -fr nginx-1.23.3 && yum clean all

# 第二阶段:最终运行镜像
FROM centos:7
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;"]

docker build -t webserver:v3 .

对比:v2:317MB → v3:205MB

优化 3:distroless 极简基础镜像(v4 版本)

distroless 镜像几乎没有 shell、包管理器,只包含程序运行必要库,安全性极高,体积极小

distroless 项目地址:GitHub - GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system. · GitHub

复制代码
docker pull gcr.io/distroless/base

Dockerfile:

复制代码
FROM nginx:1.23 AS base
ARG TIME_ZONE
RUN mkdir -p /opt/var/cache/nginx && \
cp -a --parents /usr/lib/nginx /opt && \
cp -a --parents /usr/share/nginx /opt && \
cp -a --parents /var/log/nginx /opt && \
cp -aL --parents /var/run /opt && \
cp -a --parents /etc/nginx /opt && \
cp -a --parents /etc/passwd /opt && \
cp -a --parents /etc/group /opt && \
cp -a --parents /usr/sbin/nginx /opt && \
cp -a --parents /usr/sbin/nginx-debug /opt && \
cp -a --parents /lib/x86_64-linux-gnu/ld-* /opt && \
cp -a --parents /usr/lib/x86_64-linux-gnu/libpcre* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libz.so.* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libc* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libdl* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libpthread* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libcrypt* /opt && \
cp -a --parents /usr/lib/x86_64-linux-gnu/shturl.cc.* /opt && \
cp -a --parents /usr/lib/x86_64-linux-gnu/libcrypto.so.* /opt && \
cp /usr/share/zoneinfo/${TIME_ZONE:-ROC} /opt/etc/localtime

FROM gcr.io/distroless/base-debian11
COPY --from=base /opt /
EXPOSE 80 443
ENTRYPOINT ["nginx", "-g", "daemon off;"]

docker build -t webserver:v4 .

对比:v3:205MB → v4:34MB

相关推荐
小豆庄2 小时前
会议录屏忘打码后的技术复盘:本地自动化隐私脱敏方案
运维·opencv·目标检测·自动化·视频·纹理贴图
码途行者2 小时前
Linux 服务器 Golang 安装及环境变量配置
linux·运维·服务器
爱和冰阔落2 小时前
【Linux网络】UDP 聊天室怎么实现?从在线用户管理、消息广播到双线程收发与线程池
linux·运维·网络·udp
wzq11_6662 小时前
Kubernetes集群——工作负载
云原生·容器·kubernetes
雪落漂泊2 小时前
Linux进程(三)概念:进程优先级与进程切换篇
linux·运维·服务器
拂拉氏2 小时前
【知识讲解】 Linux信号与可重入函数的认识
linux·运维·服务器
Sayai2 小时前
Kafka 跨数据中心日志同步实战:MirrorMaker 2 选型对比 + Flink 中继任务落地(双中心互备与消费者组隔离避坑)
大数据·运维·分布式·flink·kafka·消息队列·高可用
djjjx.2 小时前
Linux进程篇(十二):从零构建属于你的自定义 Shell 解释器
linux·运维·服务器