Docker镜像结构全解析
Docker镜像结构全解析
一、课程目标概述
本次课程围绕Docker镜像展开,旨在帮助学习者全面掌握Docker镜像的核心知识,具体达成以下三大目标:
- 深入理解镜像与容器之间的本质关系,明确二者在Docker生态中的角色与交互机制。
- 剖析镜像的分层结构,包括Base镜像的定义、分层原理以及容器层与镜像层的区别和联系。
- 掌握Docker镜像构建的三种方法,重点精通Dockerfile的编写规范、指令用法及镜像构建流程,实现镜像的定制化与自动化构建。
二、镜像与容器的关系
(一)核心定位
Docker镜像作为Docker容器运行的基础,是容器启动的“蓝图”——没有Docker镜像,就无法创建Docker容器,这是Docker设计的核心原则之一。二者本质区别在于:
- 镜像:静态内容,本质是一个特殊的文件系统,包含运行应用所需的代码、二进制文件、运行时、依赖库、配置文件以及为运行时准备的参数(如环境变量、匿名卷、用户等),不包含Linux内核。
- 容器:动态实体,本质是一个或多个正在运行的进程,这些进程被赋予了独立的资源(内存、CPU、虚拟网络设备)和隔离的文件系统,而容器的文件系统资源完全由镜像提供。
(二)静态到动态的转化机制
镜像(静态)要转化为容器(动态),需依赖以下关键环节:
- 镜像JSON文件的作用:每个Docker镜像都包含一个JSON文件,该文件记录了镜像的核心配置——如应运行的进程、进程所需的环境变量、文件系统挂载点等。Docker通过解析此JSON文件,获取容器启动的“指令集”。
- Docker守护进程(Docker Daemon)的角色:
Docker守护进程是转化工作的执行者,具体流程为:- 读取镜像的JSON文件,提取容器启动配置(如运行进程
/bin/bash
、环境变量PATH=/usr/local/sbin:/usr/local/bin
)。 - 为容器分配独立的资源(如内存配额、虚拟网络接口),并基于镜像的文件系统创建隔离的文件环境。
- 执行JSON文件指定的进程,此时容器正式启动(容器进程的父进程为Docker守护进程)。
- 读取镜像的JSON文件,提取容器启动配置(如运行进程
- 容器启动后的镜像角色:容器运行后,镜像的JSON文件不再发挥作用,此时镜像的核心功能仅为容器提供只读的文件系统,供容器内进程访问文件资源(如读取
/etc/profile
配置、调用/bin/ls
命令)。
(三)形象类比
可将Docker镜像类比为“操作系统安装光盘”——光盘本身是静态的,仅提供安装所需的文件和配置;而Docker容器则是“安装后的操作系统”——基于光盘(镜像)启动,成为可交互、可运行程序的动态系统,且运行过程中产生的临时数据(如新建文件、修改配置)仅保存在容器自身,不影响原始光盘(镜像)。
三、镜像的分层结构
(一)分层结构的核心原理
Docker镜像采用分层存储设计,绝大部分镜像(Docker Hub中99%以上)都是在其他镜像的基础上,通过添加新的“层”(Layer)构建而成。这种设计的优势在于:
- 复用性:不同镜像可共享基础层,减少存储空间占用(如多个基于
centos:7
的镜像,可共用centos:7
的基础层)。 - 可追溯性:每一层对应一条构建指令,便于查看镜像的构建历史(通过
docker history
命令)。 - 增量构建:修改镜像时仅需重新构建变化的层,而非整个镜像,提升构建效率。
(二)Base镜像(基础镜像)
Base镜像是分层结构的“根基”,具有两层核心含义:
- 独立性:不依赖任何其他镜像,从空白镜像(
scratch
)开始构建,包含一个完整的Linux发行版的用户空间(rootfs
)。 - 可扩展性:作为其他镜像的“基础模板”,供用户在此之上安装软件、配置环境,扩展出满足特定需求的镜像(如在
centos:7
基础上安装httpd
形成httpd:cy
镜像)。
Base镜像的本质:rootfs与内核的分离
Linux操作系统由“内核空间(Kernel)”和“用户空间(rootfs)”组成:
- 内核空间:由Docker主机(Docker Host)提供,容器无法修改内核版本或内核配置,仅能使用主机的内核(如主机为Linux 5.4内核,所有容器均使用该内核)。
- 用户空间(rootfs):由Base镜像提供,不同Linux发行版的区别主要体现在
rootfs
上——例如:Ubuntu 14.04
的rootfs
使用upstart
管理服务、apt
管理软件包;CentOS 7
的rootfs
使用systemd
管理服务、yum
管理软件包。
因此,Docker可通过不同的Base镜像,模拟出多种Linux操作系统环境,而无需为每个容器单独提供内核。
(三)镜像层与容器层
1. 分层结构示例
以基于centos:7
构建自定义镜像为例,分层过程如下:
构建指令 | 对应层级 | 层功能描述 |
---|---|---|
FROM centos:7 | CentOS 7 Base层 | 提供centos:7 的基础rootfs (/bin 、/etc 、/usr 等目录) |
RUN mkdir /galaxy | mkdir /galaxy层 | 在Base层之上新增/galaxy 目录 |
RUN touch /galaxy/cy | touch /galaxy/cy层 | 在/galaxy 目录下创建cy 文件 |
容器启动时 | 容器层(可写层) | 容器运行时的临时可写层,保存容器内的动态数据 |
2. 容器层的特性
当容器启动时,Docker会在所有镜像层(只读)的顶部加载一个新的可写层,即“容器层”,其核心特性包括:
- 读写权限:容器内的所有文件操作(如新建、修改、删除文件)均发生在容器层,镜像层保持只读状态,避免镜像被污染。
- 文件覆盖机制:若不同层存在相同路径的文件(如镜像层有
/galaxy/cy
,容器层修改该文件),容器层的文件会覆盖镜像层的文件,用户在容器内仅能看到容器层的文件。 - 生命周期绑定:容器停止或删除后,容器层会被销毁,其中的动态数据(如临时文件、修改的配置)也会随之消失;若需持久化数据,需通过“数据卷(Volume)”挂载到宿主机。
四、Docker镜像构建方法
Docker提供三种镜像构建方法,适用于不同场景,各有优劣,具体如下:
(一)方法一:docker commit
命令(基于容器构建)
1. 核心原理
通过在运行的容器内安装软件、配置环境,再将容器的“当前状态”打包为新镜像,本质是“快照式构建”。
2. 操作步骤(示例:在centos:7
中安装vim-common
)
- 启动基础容器:基于
centos:7
镜像启动容器,命名为cy
,并进入交互终端:[root@docker ~]# docker run --name cy -it centos:7 /bin/bash
- 在容器内配置环境:使用
yum
安装vim-common
:[root@2dc0f7d06aa8 /]# yum -y install vim-common
- 提交为新镜像:退出容器后,使用
docker commit
将容器cy
打包为新镜像centoscy:7
:[root@docker ~]# docker commit cy centoscy:7
- 验证镜像:查看镜像列表,确认新镜像创建成功:
[root@docker ~]# docker images | grep centoscy centoscy 7 xxxxxxxx 1 minute ago 450MB # 新镜像体积比centos:7大(因新增vim-common)
3. 优缺点
优点 | 缺点 |
---|---|
操作简单,适合快速验证临时需求 | 1. 手工操作,效率低、易出错,可重复性差; 2. 镜像构建过程不透明(无法追溯安装了哪些软件、修改了哪些配置); 3. 存在安全隐患(可能包含恶意程序,无法审计); 4. Docker官方不推荐用于生产环境。 |
(二)方法二:基于本地模板导入
1. 核心原理
直接从操作系统模板文件(如OpenVZ模板)导入镜像,模板文件通常为压缩包(如tar.gz
),包含完整的rootfs
和基础配置。
2. 操作步骤(示例:导入ubuntu:12.04
模板)
- 下载模板文件:从OpenVZ官方地址(http://openvz.org/Download/templates/precreated)下载
ubuntu-12.04-x86-minimal.tar.gz
。 - 导入为镜像:使用
docker import
命令将模板文件导入,命名为ubuntu:12.04
:[root@docker ~]# cat ubuntu-12.04-x86-minimal.tar.gz | docker import - ubuntu:12.04 sha256:88216a4bfde304bde7e1195756880acab7921b368da20e3a8591e71afc81b11e # 镜像ID
- 验证镜像:查看镜像列表,确认导入成功:
[root@docker ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu 12.04 88216a4bfde3 5 seconds ago 146MB
3. 适用场景
- 快速导入特定版本的操作系统镜像(如老旧系统
ubuntu:12.04
,官方镜像已下架)。 - 迁移现有虚拟机模板到Docker环境。
(三)方法三:Dockerfile构建(推荐)
1. Dockerfile的核心概念
Dockerfile是一个文本文件,包含一系列“指令(Instruction)”,每一条指令对应镜像的一层——指令描述了该层的构建操作(如安装软件、复制文件、配置环境)。通过docker build
命令执行Dockerfile,可自动化、可重复地构建镜像,解决了docker commit
的“不透明、不可追溯”问题。
2. Dockerfile的文件结构
Dockerfile分为四部分,按固定顺序排列:
部分 | 作用 | 示例指令 |
---|---|---|
基础镜像信息 | 指定构建镜像的基础镜像,必须为首个非注释行 | FROM centos:7 |
维护者信息 | 记录镜像作者的联系方式(可选,新版本推荐用LABEL 替代) | MAINTAINER chenyu@example.com |
镜像操作指令 | 配置镜像环境(安装软件、复制文件、暴露端口等) | RUN yum install -y httpd 、COPY index.html /var/www/html/ 、EXPOSE 80 |
容器启动执行指令 | 指定容器启动时默认运行的进程 | CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"] |
3. 构建示例(构建httpd
镜像)
(1)编写Dockerfile
[root@docker ~]# vim Dockerfile # 文件名固定为Dockerfile,不可修改
# 1. 基础镜像信息
FROM centos:7
# 2. 维护者信息
MAINTAINER chenyu@example.com
# 3. 镜像操作指令:安装httpd、暴露80端口
RUN yum install -y httpd
EXPOSE 80
# 4. 容器启动执行指令:启动httpd服务
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
(2)执行构建命令
使用docker build
命令构建镜像,-t
指定镜像名(httpd:cycy
),.
表示Dockerfile在当前目录(若Dockerfile在其他路径,用-f /path/to/Dockerfile
指定):
[root@docker ~]# docker build -t httpd:cycy .
(3)构建过程解析
docker build
会按Dockerfile指令顺序逐层构建,每一步生成一个临时镜像层,具体流程:
- 检查本地是否有
centos:7
镜像,若无则从Docker Hub拉取。 - 基于
centos:7
启动临时容器,执行RUN yum install -y httpd
,安装完成后将容器打包为新层。 - 基于上一层启动临时容器,执行
EXPOSE 80
(标记暴露80端口),打包为新层。 - 基于上一层启动临时容器,执行
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
(设置启动命令),打包为最终层,生成httpd:cycy
镜像。
(4)查看构建历史
通过docker history
命令可追溯镜像的每一层构建过程,实现“透明化审计”:
[root@docker ~]# docker history httpd:cycy
IMAGE | CREATED | CREATED BY | SIZE | COMMENT |
---|---|---|---|---|
b44f420b6834 | 10 minutes ago | /bin/sh -c #(nop) CMD [“/usr/sbin/httpd”… | 0B | |
65c05b81e846 | 10 minutes ago | /bin/sh -c #(nop) EXPOSE 80 | 0B | |
e7e8c35f9b15 | 10 minutes ago | /bin/sh -c yum -y install httpd | 203MB | |
a9c67ae8f88e | 13 minutes ago | /bin/sh -c #(nop) MAINTAINER chenyu@example.com | 0B | |
eeb6ee3f44bd | 9 months ago | /bin/sh -c #(nop) CMD [“/bin/bash”] | 0B |
4. 镜像缓存机制
docker build
会缓存构建过程中生成的每一层镜像,后续构建时若指令未修改,直接复用缓存层,大幅提升构建效率。
(1)缓存示例
修改上述Dockerfile,新增一条RUN mkdir /galaxy
指令:
FROM centos:7
MAINTAINER chenyu@example.com
RUN mkdir /galaxy # 新增指令
RUN yum install -y httpd
EXPOSE 80
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
重新构建时,Docker会复用前两层(FROM centos:7
、MAINTAINER
)的缓存,仅重新构建RUN mkdir /galaxy
及后续层:
[root@docker ~]# docker build -t httpd:cycy2 .
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM centos:7---> eeb6ee3f44bd # 复用缓存
Step 2/5 : MAINTAINER chenyu@example.com---> Using cache # 复用缓存---> a9c67ae8f88e
Step 3/5 : RUN mkdir /galaxy # 指令修改,重新构建---> Running in xxxxxxxx
Removing intermediate container xxxxxxxx---> yyyyyyyy
Step 4/5 : RUN yum install -y httpd # 后续层需重新构建(依赖上一层)---> Running in zzzzzzzz
...
(2)禁用缓存
若需强制重新构建所有层(如基础镜像更新后),在docker build
命令中添加--no-cache
参数:
[root@docker ~]# docker build --no-cache -t httpd:cycy2 .
5. Dockerfile常用指令详解
Dockerfile指令是构建镜像的“核心语法”,以下为常用指令的格式、功能及示例:
指令 | 格式 | 功能描述 | 示例 |
---|---|---|---|
FROM | FROM <Repository>[:<Tag>] 或 FROM <Repository>@<Digest> | 指定基础镜像,必须为Dockerfile首个非注释行;Digest 为镜像哈希,防止冒名顶替。 | FROM centos:7 (基于centos:7)、FROM ubuntu@sha256:xxxx (基于指定哈希的ubuntu) |
MAINTAINER | MAINTAINER "作者邮箱" | 记录镜像作者信息,已被LABEL 替代(可选)。 | MAINTAINER "cy@example.com" |
LABEL | LABEL <key>=<value> <key>=<value>... | 为镜像添加元数据(键值对),如作者、版本、描述等,便于镜像管理。 | LABEL author="cy" version="1.0" description="httpd image based on centos7" |
COPY | COPY <src>... <dest> 或 COPY ["<src>",... "<dest>"] | 复制宿主机的文件/目录到镜像中;src 支持通配符,dest 建议用绝对路径;若路径含空格,用第二种格式。 | COPY index.html /var/www/html/ (复制index.html到镜像的/var/www/html/)、COPY ["my file.txt", "/tmp/"] (复制含空格的文件) |
ADD | 同COPY ,但支持两种额外功能:1. 复制 tar 文件时自动解压(仅本地tar );2. 支持通过URL下载文件到镜像。 | 同COPY ,但支持tar 自动解压和URL下载。 | ADD app.tar.gz /opt/ (解压app.tar.gz到/opt/)、ADD https://example.com/config.ini /etc/ (下载config.ini到/etc/) |
WORKDIR | WORKDIR <路径> (绝对路径或相对路径) | 指定后续指令的工作目录;可多次使用,每层指令仅受当前WORKDIR 影响;支持引用ENV 定义的变量。 | WORKDIR /usr/local (设置工作目录为/usr/local)、ENV DIR /opt + WORKDIR $DIR (引用ENV变量) |
VOLUME | VOLUME <mountpoint> 或 VOLUME ["<mountpoint>"] | 在镜像中创建挂载点目录,用于持久化数据;仅支持“Docker管理的卷”(无法指定宿主机路径)。 | VOLUME /data (创建/data为挂载点,容器启动时自动关联Docker管理的卷) |
EXPOSE | EXPOSE <port>[/<protocol>]... (protocol 默认为tcp) | 声明容器待暴露的端口(仅为“声明”,不实际映射到宿主机);启动容器时需用-P (随机映射)或-p 宿主机端口:容器端口 (指定映射)。 | EXPOSE 80/tcp 443/udp (声明暴露80 tcp端口和443 udp端口) |
ENV | ENV <key> <value> 或 ENV <key>=<value>... | 定义环境变量,后续指令可引用($key 或 ${key} );容器启动时可用-e key=value 覆盖变量值(不影响镜像中已引用变量的文件)。 | ENV PATH=/usr/local/bin:$PATH (添加环境变量)、ENV DB_HOST=localhost DB_PORT=3306 (定义多个变量) |
RUN | RUN <command> (shell格式,默认/bin/sh -c 执行) 或 RUN ["executable", "param1", "param2"] (exec格式) | 在镜像构建阶段执行命令(如安装软件),每条RUN 生成一层镜像。 | RUN yum install -y gcc (shell格式)、RUN ["pip", "install", "flask"] (exec格式,避免shell解析) |
CMD | 1. CMD command param1 param2 (shell格式);2. CMD ["executable", "param1", "param2"] (exec格式);3. CMD ["param1", "param2"] (配合ENTRYPOINT ,作为参数) | 在容器启动阶段执行默认进程(PID=1);容器启动时,docker run 的命令行参数会覆盖CMD ;Dockerfile中仅最后一条CMD 生效。 | CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"] (exec格式,启动httpd)、CMD ["-c", "/etc/config.conf"] (配合ENTRYPOINT作为参数) |
ENTRYPOINT | 同CMD 的两种格式(shell/exec) | 与CMD 类似,指定容器启动时的默认进程;但docker run 的命令行参数不会覆盖ENTRYPOINT ,而是作为参数传递给它;仅最后一条ENTRYPOINT 生效;可通过--entrypoint 选项覆盖。 | ENTRYPOINT ["python"] + CMD ["app.py"] (容器启动时执行python app.py ,docker run 镜像 python test.py 会执行python test.py ) |
USER | USER <user>[:<group>] 或 USER <UID>[:<GID>] | 指定docker build 阶段(RUN 、CMD 等指令)的运行用户(默认root);确保镜像中存在该用户/UID。 | USER apache (后续指令以apache用户执行)、USER 1000:1000 (以UID 1000、GID 1000执行) |
五、核心总结
- 镜像与容器的依赖关系:镜像是容器的静态基础(文件系统+配置),容器是镜像的动态运行实例(进程+独立资源);无镜像则无容器,容器启动后镜像仅提供只读文件系统。
- 镜像分层与Base镜像:镜像采用分层存储,Base镜像从
scratch
构建,提供完整rootfs
;容器启动时添加可写层(容器层),所有动态操作仅发生在容器层。 - 镜像构建方法对比:
docker commit
:简单但不透明、不可重复,适合临时验证;- 本地模板导入:快速导入特定系统镜像,适合迁移场景;
- Dockerfile:自动化、可追溯、可复用,是生产环境推荐的构建方式。
- Dockerfile核心要点:每一条指令对应一层镜像;
RUN
(构建时执行)与CMD
(启动时执行)需区分;利用缓存提升构建效率,必要时禁用缓存;通过docker history
审计镜像构建过程。
通过掌握以上知识,可实现Docker镜像的定制化构建、高效管理及容器的稳定运行,为Docker在开发、测试、生产环境中的应用奠定基础。