Docker入门|第3章 镜像详解

Docker入门|第3章 镜像详解

摘要:本章深入讲解 Docker 镜像的核心原理与实战操作。首先通过 hello‑world 最小镜像认识 scratch 空白镜像,并剖析 base 镜像只提供 rootfs、复用宿主机内核的原理;接着介绍镜像分层只读结构与容器层 Copy‑on‑Write 写时复制机制。构建镜像方面,对比 docker commit 手工提交与 Dockerfile 标准构建两种方式,详解 FROM、RUN、CMD、ENTRYPOINT 等核心指令及 Shell/Exec 两种格式。最后讲解镜像 tag 语义化版本管理,以及 Docker Hub、华为云 SWR、Registry、Harbor 等公共与私有仓库的分发实践。

一、本章概述

1.1 学习目标

镜像是Docker容器的基石,容器是镜像的运行实例,只有拥有镜像才能够启动容器。 本章学习内容分为三大部分:

  1. 研究典型镜像,理解镜像的内部结构原理;
  2. 学习多种方式构建自定义镜像;
  3. 掌握镜像的管理、打标签、分发推送。

提示:日常直接下载使用公共镜像不需要理解底层结构;但如果需要自己制作镜像,弄懂镜像内部原理是非常必要的。

二、认识镜像:从最小镜像hello‑world开始

2.1 hello‑world最小镜像

2.1.1 下载并查看hello‑world镜像

hello‑world是Docker官方最小测试镜像,用于验证Docker环境是否正常。

bash 复制代码
docker pull hello-world
docker images hello-world

输出可以看到镜像大小只有13.3KB,体积极小。

运行镜像:

bash 复制代码
docker run hello-world
2.1.2 hello‑world的Dockerfile源码
bash 复制代码
FROM scratch
COPY hello /
CMD ["/hello"]

指令解读:

  1. FROM scratch:从0开始,空白基础镜像,没有任何系统文件;
  2. COPY hello /:将可执行程序复制到镜像根目录;
  3. CMD ["/hello"]:容器启动执行/hello程序,输出提示信息。

这个镜像里面仅仅只有一个可执行文件,连/bin、/usr、/lib、/dev这些基础目录都不存在。实际业务不会直接使用,用来理解scratch空白镜像。

2.2 Base基础镜像

2.2.1 base镜像两层含义
  1. 从scratch从零构建,不依赖其他上层镜像;
  2. 可以被其他镜像作为底层基础来扩展。

常见base镜像就是各类Linux发行版镜像:CentOS、Ubuntu、Debian、Alpine。

疑问:完整CentOS操作系统安装后几个GB,但是Docker的centos镜像只有200MB左右?

2.2.2 rootfs与bootfs原理

Linux操作系统分为两大部分:

  1. bootfs(引导文件系统):内核kernel,系统启动完成之后bootfs会被卸载;
  2. rootfs(根文件系统) :用户空间,包含/dev /proc /bin /etc /usr等目录。

✅Docker base镜像只提供rootfs,直接复用宿主机Host的kernel内核。 所以镜像不需要携带内核,体积可以做得很小。alpine基础镜像甚至不到10MB。

  • 宿主机是什么内核,容器就使用什么内核;
  • 容器内部不能修改、升级内核;
  • 如果业务应用对内核版本有硬性要求,不适合容器,优先选择虚拟机。

验证现象: 在CentOS Stream8宿主机(内核4.18),启动ubuntu/centos7容器,执行uname -r,显示的依然是宿主机4.18内核。

2.2.3 CentOS base镜像Dockerfile片段
bash 复制代码
FROM scratch
ADD centos-7-docker.tar.xz /
CMD ["/bin/bash"]

把centos的rootfs压缩包解压到根目录,就完成基础镜像构建。

三、Docker镜像分层结构原理

3.1 分层镜像思想

Docker Hub上绝大多数镜像,都是在base镜像之上一层层叠加软件构建而成。 每执行一条构建指令,就新增一层镜像。

示例Dockerfile:

bash 复制代码
FROM debian
RUN apt-get install emacs
RUN apt-get install apache2
CMD ["/bin/bash"]
  • 第一层:debian base镜像;
  • 第二层:安装emacs生成新层;
  • 第三层:安装apache2生成新层。
3.1.1 分层最大优势:资源共享

多个镜像如果基于同一个base镜像,宿主机磁盘只保存一份base镜像;内存也只加载一份,多个容器可以同时复用。

3.2 容器层:Copy‑on‑Write写时复制机制

所有镜像层全部是只读 ;容器启动的时候,会在所有只读镜像层之上新增一层可写容器层。

  • 只读层:镜像层,永远不会被修改;
  • 可写层:容器层,所有增删改操作全部发生在这里。
3.2.1 文件操作规则(写时复制Copy‑on‑Write)
操作 执行逻辑
读取文件 从上往下逐层查找,找到文件直接读取
新建文件 直接写入上层可写容器层
删除文件 在容器层生成whiteout遮挡文件,屏蔽下层镜像的文件,底层镜像不会改动
修改文件 找到下层镜像文件,复制一份到上层容器层,再修改副本,底层镜像原文件保持不变

镜像层只读,修改全部发生容器层,所以镜像可以被成千上百容器安全共享。

四、两种构建镜像方式

4.1 docker commit(手动提交构建)

4.1.1 操作步骤
  1. 运行基础镜像,进入容器;
  2. 在容器内部手工安装软件、修改配置;
  3. 使用docker commit把当前容器状态保存为新镜像。

实操示例:

bash 复制代码
#1.启动ubuntu容器
docker run -it ubuntu
#容器内部安装vim
apt-get update
apt-get install -y vim

#新开终端,查看容器ID/名字
docker ps
#提交容器生成新镜像
docker commit cool_darwin ubuntu-with-vim
#查看镜像
docker images
#从新镜像启动验证vim可用
docker run -it ubuntu-with-vim
4.1.2 docker commit缺点
  1. 手工操作,不可复现,环境迁移要重复手工操作;
  2. 镜像无法审计,外部不知道镜像内部做过哪些修改,存在安全风险;

虽然不推荐日常生产使用,但是底层Dockerfile构建本质依然是多次commit,学习commit可以加深分层原理理解。

4.2 Dockerfile文件构建(生产推荐)

4.2.1 Dockerfile基础语法规则
  1. 保留字指令大写,后面跟参数;
  2. 指令从上到下顺序执行;
  3. #代表注释;
  4. 每一条指令都会生成一层新镜像层。

基础构建命令格式:

bash 复制代码
docker build -f Dockerfile路径 -t 镜像名:标签 构建上下文目录
  • -f:指定Dockerfile文件;
  • -t:给新镜像打名字tag;
  • 末尾.代表构建上下文为当前目录;

⚠️构建上下文:docker build会把上下文目录全部文件发送给dockerd服务端,不要把/根目录作为上下文,避免传输海量文件,构建极慢。

示例Dockerfile(构建ubuntu-with-vim)

bash 复制代码
FROM ubuntu
RUN apt-get update && apt-get install -y vim

构建:

bash 复制代码
docker build -t ubuntu-with-vim-dockerfile .
4.2.2 镜像缓存特性

Docker会缓存已经生成的镜像层,构建镜像时如果指令不变,直接复用缓存,不用重新执行。

  • 只要某一条指令发生改动,该指令之后所有层缓存全部失效,全部重新构建。
  • 参数--no-cache,构建时强制不使用缓存。

docker pull拉取镜像同样会复用本地已经存在镜像层,只下载缺失层。

4.2.3 Dockerfile调试技巧

构建中途某条指令报错,Docker会保留上一步成功构建出来的镜像。 直接运行这个中间镜像,进入容器手工复现执行指令,定位错误原因。

示例错误Dockerfile(busybox没有bash)

bash 复制代码
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c "echo continue to build..."
COPY testfile /

构建会报错,原因busybox只有sh没有bash;修改为/bin/sh即可修复。

五、Dockerfile常用指令详解

5.1 基础指令清单

指令 说明
FROM 指定base基础镜像,Dockerfile第一条有效指令必须是FROM
MAINTAINER 设置镜像作者信息
COPY 把构建上下文文件复制到镜像内部,不会自动解压
ADD 复制文件,若源是压缩tar包会自动解压到目标路径
ENV 设置环境变量,后续指令、容器运行时都可以读取
EXPOSE 声明容器打算监听哪些端口,不会自动做端口映射
VOLUME 声明数据卷挂载点
WORKDIR 设置RUN/CMD/ENTRYPOINT的工作目录,目录不存在自动创建
RUN 构建镜像阶段执行命令,生成新镜像层,常用于安装软件包
CMD 容器启动时执行默认命令,可被docker run后面参数覆盖,多个CMD仅最后一条生效
ENTRYPOINT 容器启动入口命令,不会被run参数覆盖;run参数会当做ENTRYPOINT参数传入

5.2 Shell格式与Exec格式

两种书写格式,RUN / CMD / ENTRYPOINT都支持。

  1. Shell格式 :<指令> command param,底层调用/bin/sh -c,可以解析环境变量;
bash 复制代码
CMD echo "hello $name"
  1. Exec格式(数组格式,推荐CMD、ENTRYPOINT使用) :["可执行程序","参数1","参数2"],不经过shell,不会解析环境变量。
bash 复制代码
CMD ["/bin/echo","hello world"]

如果exec格式需要解析环境变量,需要手动调用shell:ENTRYPOINT ["/bin/sh","-c","echo hello $name"]

5.3 RUN、CMD、ENTRYPOINT三者对比

  1. RUN:镜像构建阶段执行,执行结果保存为镜像层;用于安装软件包。
  2. CMD :容器启动默认命令;docker run后面追加参数会完全替换掉CMD。
  3. ENTRYPOINT:容器启动入口,不会被run参数覆盖;run传入参数追加给ENTRYPOINT当参数。

组合示例(ENTRYPOINT + CMD)

bash 复制代码
FROM busybox
ENTRYPOINT ["/bin/echo","Hello"]
CMD ["world"]
  • docker run xxx →输出 Hello world
  • docker run xxx gqd →输出 Hello gqd

最佳实践:

  • RUN用来构建镜像安装软件;
  • 业务服务镜像优先使用Exec格式ENTRYPOINT;
  • CMD给ENTRYPOINT提供默认参数。

5.4 实战Dockerfile案例

5.4.1 案例1:制作带SSH的CentOS镜像
bash 复制代码
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
RUN minorver=8.4.2105 && \
sed -e "s|^mirrorlist=|#mirrorlist=|g" \
-e "s|^#baseurl=[http://mirror.centos.org/](http://mirror.centos.org/)\$contentdir/\$releasever|baseurl=[https://mirrors.aliyun.com/centos-vault/$minorver|g](https://mirrors.aliyun.com/centos-vault/$minorver|g)" \
-i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum install -y openssh-server
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
RUN echo "root:huawei" | chpasswd
EXPOSE 22
CMD ["/usr/sbin/sshd","-D"]

构建与测试:

bash 复制代码
docker build -t centos:ssh -f centos.ssh.dockerfile .
docker run -d -p 2022:22 --name sshtest centos:ssh
ssh root@localhost -p 2022
5.4.2 案例2:自定义httpd网页镜像
bash 复制代码
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
RUN minorver=8.4.2105 \
&& sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=[http://mirror.centos.org/](http://mirror.centos.org/)\$contentdir/\$releasever|baseurl=[https://mirrors.aliyun.com/centos-vault/$minorver|g](https://mirrors.aliyun.com/centos-vault/$minorver|g)" -i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum install -y httpd && yum clean all && rm -rf /var/cache/yum
COPY index.html /var/www/html/
EXPOSE 80
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
5.4.3 案例3:CentOS8 + vim + net‑tools + JDK8镜像
bash 复制代码
FROM centos:8.4.2105
MAINTAINER gaoqiaodong<6946630@qq.com>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN minorver=8.4.2105 \
&& sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=[http://mirror.centos.org/](http://mirror.centos.org/)\$contentdir/\$releasever|baseurl=[https://mirrors.aliyun.com/centos-vault/$minorver|g](https://mirrors.aliyun.com/centos-vault/$minorver|g)" -i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum -y install vim
RUN yum -y install net-tools
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
ADD jdk-8u461-linux-x64.tar.gz /usr/local/java/
ENV JAVA_HOME /usr/local/java/jdk1.8.0_461
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
CMD /bin/bash

六、镜像命名、标签tag最佳实践

6.1 镜像完整命名格式

bash 复制代码
[仓库地址]/命名空间/仓库名:tag标签

示例: swr.cn‑east‑3.myhuaweicloud.com/library/nginx:latest

  • 如果省略仓库地址,默认指向Docker Hub;
  • tag用来标记版本,latest只是默认标签,不保证是最新版本,生产环境禁止直接使用latest,必须明确指定版本号。

6.2 tag版本管理最佳实践

同一个镜像可以打多个tag,参考语义化版本:主版本.次版本.补丁版本 例如镜像版本v1.9.1,可以打标签:1.9.1、1.9、1、latest。 当发布v1.9.2,把1.9、1、latest移动指向新版本。

打标签命令:

bash 复制代码
docker tag 源镜像:tag 目标镜像:tag

七、镜像分发:公共镜像仓库

7.1 Docker Hub公共仓库

  1. 在Docker Hub官网注册账号;
  2. 本地docker客户端登录:docker login -u 用户名;
  3. 修改镜像名字格式:用户名/镜像名:版本;
  4. docker push 用户名/镜像名:版本上传;
  5. 其他机器使用docker pull 用户名/镜像名:版本下载镜像。

Docker Hub官方镜像不需要加用户名前缀,如httpd、centos。

7.2 华为云SWR容器镜像服务

  1. 登录华为云控制台,进入【容器镜像服务SWR】;
  2. 创建组织,获取临时登录指令;
  3. 本地执行docker login登录SWR仓库;
  4. docker tag把镜像重命名为SWR仓库地址格式;
  5. docker push上传镜像;
  6. 其他主机docker pull拉取镜像;
  7. 可以在控制台设置镜像公开/私有权限。

八、镜像分发:私有镜像仓库

8.1 Docker官方Registry(简易私有仓库)

直接运行registry容器快速搭建私有仓库,端口5000。

bash 复制代码
#启动registry容器,数据持久化到宿主机/myregistry
docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:2
#打标签,格式 localhost:5000/镜像名:tag
docker tag httpd:latest localhost:5000/httpd:v1
#上传镜像到本地私有仓库
docker push localhost:5000/httpd:v1
#测试拉取镜像
docker rmi localhost:5000/httpd:v1
docker pull localhost:5000/httpd:v1

registry功能简单,无web界面,无用户权限管理,适合学习测试。

8.2 Harbor企业级私有镜像仓库

Harbor是Registry上层封装的企业级私有仓库,提供WebUI界面、用户权限管理、审计、项目隔离。

  1. 下载Harbor离线安装包;
  2. 修改harbor.yml配置hostname、admin密码;
  3. 执行./prepare、./install.sh完成部署;
  4. 浏览器访问Harborweb页面;
  5. 创建项目、创建用户,给用户分配项目权限;
  6. docker配置daemon.json增加insecure‑registries支持http非安全仓库;
  7. docker login登录Harbor,完成镜像tag、push上传、pull下载。

九、第三章小结

  1. hello‑world演示scratch空白镜像;base镜像只提供rootfs,复用宿主机Linux内核,容器不能修改内核。
  2. Docker镜像采用分层只读结构,容器新增可写容器层,依靠Copy‑on‑Write写时复制实现修改,镜像层可以多容器共享。
  3. 构建镜像两种方式:docker commit手工提交(底层原理,不推荐生产);Dockerfile + docker build(生产标准)。
  4. 掌握Dockerfile核心指令,区分RUN / CMD / ENTRYPOINT,区分Shell格式与Exec数组格式,理解镜像缓存机制与调试手段。
  5. 镜像tag标签管理,不要生产直接使用latest标签,采用语义化版本打多tag。
  6. 镜像分发途径:Docker Hub、公有云镜像仓库(华为SWR)、简易registry私有仓库、企业Harbor私有仓库。

相关推荐
Ruiery4 小时前
Linux 6.6内核 CPU 深度解析(七):调度器启动 — 从单核到多核,调度器分阶段点亮
linux·运维·服务器
50万马克的面包4 小时前
CSDN-栈队列数组-知识点整理
linux·运维·服务器
梦想不只是梦与想4 小时前
docker-compose.yml
docker·docker-compose·docker run
国科安芯5 小时前
一颗当两颗用:ASC4T245S 的“两组独立 2 位 + 控制挂靠 VCCA“账
网络·电平转换·抗辐射·在轨服务
冯胤清5 小时前
《Jev-IDS:网络入侵检测一种系统模型》论文深度分析总结
网络
wjkjpcba5 小时前
PCBA烧录程序是什么:PCBA包工包料厂家解析烧录与测试
linux·数据库·人工智能·smt贴片加工·pcba贴片加工厂
꯭自꯭闭꯭5 小时前
达梦事物特性及MVCC
linux·运维·数据库
郑州光合科技余经理6 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
91刘仁德6 小时前
HTTP协议详解:从URL到HTTP服务器的完整实战
服务器·网络·笔记·网络协议·http
专业程序开发源6 小时前
springboot简历管理系统81389-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计