Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何理解Docker镜像分层?且听百度高级研发工程师细细道来

如何理解Docker镜像分层?且听百度高级研发工程师细细道来

原创
作者头像
本人秃顶程序员
修改于 2019-05-17 01:47:03
修改于 2019-05-17 01:47:03
1.4K00
代码可运行
举报
文章被收录于专栏:Java架构筑基Java架构筑基
运行总次数:0
代码可运行

目录

  • 关于base镜像
  • 关于存储结构(About storage drivers)
    • 先来创建一个自己的镜像
    • docker镜像的分层结构
    • 容器的大小
    • 修改时复制策略 copy-on-write (CoW)
    • Copying makes containers efficient

关于base镜像

base 镜像有两层含义:

  • 不依赖其他镜像,从 scratch 构建。
  • 其他镜像可以之为基础进行扩展。

所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。

base 镜像提供的是最小安装的 Linux 发行版

我们大部分镜像都将是基于base镜像构建的。所以,通常使用的是官方发布的base镜像。可以在docker hub里找到。比如centos: https://hub.docker.com/_/centos

点击版本可以看到github里的Dockerfile

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM scratch
ADD centos-7-docker.tar.xz /

LABEL org.label-schema.schema-version="1.0" \
    org.label-schema.name="CentOS Base Image" \
    org.label-schema.vendor="CentOS" \
    org.label-schema.license="GPLv2" \
    org.label-schema.build-date="20181205"
    //欢迎加入Java高级架构进阶Qqun:963944895;免费分享Java架构学习资料、面试题、编程书籍
CMD ["/bin/bash"]

ADD命令将本地的centos7的tar包添加到镜像,并解压到根目录/下。生成/dev,/proc/,/bin等。

我们可以自己构建docker base镜像,也可以直接使用已有的base镜像。比如centos。我们可以直接从docker hub上拉取。

拉取

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
docker pull centos

查看

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# docker images centos 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              1e1148e4cc2c        2 months ago        202MB

可以看到最新的centos镜像只有200mb,是不是觉得太小了?这是因为docker镜像在运行的时候直接使用docker宿主机器的kernel。

Linux操作系统由内核空间和用户空间组成。

内核空间是kernel,用户空间是rootfs, 不同Linux发行版的区别主要是rootfs.比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。

所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。

需要注意的是

  • base镜像只是用户空间和发行版一致。kernel使用的是docker宿主机器的kernel。例如 CentOS 7 使用 3.x.x 的 kernel,如果 Docker Host 是 Ubuntu 16.04(比如我们的实验环境),那么在 CentOS 容器中使用的实际是是 Host 4.x.x 的 kernel。
  • ① Host kernel 为 4.4.0-31
  • ② 启动并进入 CentOS 容器
  • ③ 验证容器是 CentOS 7
  • ④ 容器的 kernel 版本与 Host 一致

关于存储结构(About storage drivers)

上文里展示了如何下载一个base镜像。我们通常是基于这份base镜像来构建我们自己的镜像。比如,在centos里添加一个nginx负载均衡。首先,得需要了解镜像的结构是什么。

官方文档: https://docs.docker.com/storage/storagedriver/

先来创建一个自己的镜像

首先,base镜像是基于docker宿主机器kernel之上的Linux发行版。

现在,我们给这台机器安装一个vim,一个httpd. 基于Dockerfile来创建一个新的镜像。

我们的Dockerfile

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM centos:7
RUN yum install -y vim
RUN yum install -y httpd
CMD ["/bin/bash"]

含义:

  • 基于centos7的base镜像构建
  • 安装vim
  • 安装httpd
  • 执行bash

在当前目录下新建一个文件Dockerfile, 填充上述内容。然后执行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# docker build -t ryan/httpd:v1.0 .
Sending build context to Docker daemon  6.144kB
Step 1/4 : FROM centos:7
 ---> 1e1148e4cc2c
Step 2/4 : RUN yum install -y vim
 ---> Using cache
 ---> 74bdbea98f73
Step 3/4 : RUN yum install -y httpd
 ---> Using cache
 ---> 17d8c4095dc4
Step 4/4 : CMD /bin/bash
 ---> Using cache
 ---> f2b58b1192de
Successfully built f2b58b1192de
Successfully tagged ryan/httpd:latest
//欢迎加入Java高级架构进阶Qqun:963944895;免费分享Java架构学习资料、面试题、编程书籍
  • -t 指定我们创建的镜像名称,镜像名称可以用组织/id:version的方式标记
  • 最后一个参数是Dockerfile所在的路径., 表示当前目录

然后我们添加一个tag latest

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
docker tag ryan/httpd:v1.0 ryan/httpd:latest
  • 即给镜像ryan/httpd:v1.0标记为ryan/httpd:latest

构建完成之后,查看

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# docker images  | grep -E '(ryan|centos)'
ryan/httpd                                                               latest                     f2b58b1192de        About an hour ago   444MB
ryan/httpd                                                               v1.0                       f2b58b1192de        About an hour ago   444MB
centos                                                                   7                          1e1148e4cc2c        2 months ago        202MB
centos                                                                   latest                     1e1148e4cc2c        2 months ago        202MB

可以运行我们创建的镜像:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# docker run -d  --privileged=true -it ryan/httpd:v1.0 /usr/sbin/init
48a4a128cd7b6924149cd97670919d4e2af6cb96c73c901af60d05fe4478225a
# docker ps | grep ryan
48a4a128cd7b        ryan/httpd:v1.0                                                          "/usr/sbin/init"         8 seconds ago       Up 8 seconds       

现在我们的基于原生base centos7的httpd服务器已经启动了。可以通过docker exec -it zealous_kirch /bin/bash来进入容器内部,查看启动httpd。

docker镜像的分层结构

我们可以查看镜像的历史,用上一步的镜像id f2b58b1192de

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# docker history f2b58b1192de
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
f2b58b1192de        About an hour ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
17d8c4095dc4        About an hour ago   /bin/sh -c yum install -y httpd                 110MB               
74bdbea98f73        About an hour ago   /bin/sh -c yum install -y vim                   133MB               
1e1148e4cc2c        2 months ago        /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           2 months ago        /bin/sh -c #(nop)  LABEL org.label-schema....   0B                  
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:6f877549795f479...   202MB   

启动镜像的时候,一个新的可写层会加载到镜像的顶部。这一层通常称为“容器层”, 之下是“镜像层”。

容器层可以读写,容器所有发生文件变更写都发生在这一层。镜像层read-only,只允许读取。

(上图来自官方文档,和本次实验内容略有不同,但原理一样)

第一列是imageid, 最上面的id就是我们新创建ryan/httpd:latest. 下面几行都是我们dockerfile里定义的步骤堆栈。由此可以看出,每个步骤都将创建一个imgid, 一直追溯到1e1148e4cc2c正好是我们的base镜像的id。关于<missing>的部分,则不在本机上。

最后一列是每一层的大小。最后一层只是启动bash,所以没有文件变更,大小是0. 我们创建的镜像是在base镜像之上的,并不是完全复制一份base,然后修改,而是共享base的内容。这时候,如果我们新建一个新的镜像,同样也是共享base镜像。

那修改了base镜像,会不会导致我们创建的镜像也被修改呢? 不会!因为不允许修改历史镜像,只允许修改容器,而容器只可以在最上面的容器层进行写和变更。

容器的大小

创建镜像的时候,分层可以让docker只保存我们添加和修改的部分内容。其他内容基于base镜像,不需要存储,读取base镜像即可。如此,当我们创建多个镜像的时候,所有的镜像共享base部分。节省了磁盘空间。

对于启动的容器,查看所需要的磁盘空间可以通过docker ps -s

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# docker run -d -it centos
4b0df4bc3e705c540144d545441930689124ade087961d01f56c2ac55bfd986d
# docker ps -s | grep -E '(ryan|centos)'
4b0df4bc3e70        centos                                                                   "/bin/bash"              23 seconds ago      Up 23 seconds                           vigorous_elion                                                                                                                           0B (virtual 202MB)
b36421d05005        ryan/httpd:v1.0                                                          "/usr/sbin/init"         32 minutes ago      Up 32 minutes                           gracious_swirles                                                                                                                         61.6kB (virtual 444MB)
  • 首先启动一个base镜像用来对比
  • 可以看到第一行就是base镜像centos,第2列的size是0和202MB, 0表示容器层可写层的大小,virtual则是容器层+镜像层的大小。这里对比可以看到一共202M,正好是最初centos镜像的大小。
  • 第二行是我们自己创建的镜像。virtual达到了444MB。对比前面的history部分,可以发现这个数字是每一层大小之和。同时,由于共享base,其中的202M是和第一行的镜像共享的。

修改时复制策略 copy-on-write (CoW)

docker通过一个叫做copy-on-write (CoW) 的策略来保证base镜像的安全性,以及更高的性能和空间利用率。

Copy-on-write is a strategy of sharing and copying files for maximum efficiency. If a file or directory exists in a lower layer within the image, and another layer (including the writable layer) needs read access to it, it just uses the existing file. The first time another layer needs to modify the file (when building the image or running the container), the file is copied into that layer and modified. This minimizes I/O and the size of each of the subsequent layers. These advantages are explained in more depth below. Copying makes containers efficient When you start a container, a thin writable container layer is added on top of the other layers. Any changes the container makes to the filesystem are stored here. Any files the container does not change do not get copied to this writable layer. This means that the writable layer is as small as possible. When an existing file in a container is modified, the storage driver performs a copy-on-write operation. The specifics steps involved depend on the specific storage driver. For the aufs, overlay, and overlay2 drivers, the copy-on-write operation follows this rough sequence: Search through the image layers for the file to update. The process starts at the newest layer and works down to the base layer one layer at a time. When results are found, they are added to a cache to speed future operations. Perform a copy_up operation on the first copy of the file that is found, to copy the file to the container’s writable layer. Any modifications are made to this copy of the file, and the container cannot see the read-only copy of the file that exists in the lower layer. Btrfs, ZFS, and other drivers handle the copy-on-write differently. You can read more about the methods of these drivers later in their detailed descriptions. Containers that write a lot of data consume more space than containers that do not. This is because most write operations consume new space in the container’s thin writable top layer.

简单的说,启动容器的时候,最上层容器层是可写层,之下的都是镜像层,只读层。

当容器需要读取文件的时候

从最上层镜像开始查找,往下找,找到文件后读取并放入内存,若已经在内存中了,直接使用。(即,同一台机器上运行的docker容器共享运行时相同的文件)。

当容器需要添加文件的时候

直接在最上面的容器层可写层添加文件,不会影响镜像层。

当容器需要修改文件的时候

从上往下层寻找文件,找到后,复制到容器可写层,然后,对容器来说,可以看到的是容器层的这个文件,看不到镜像层里的文件。容器在容器层修改这个文件。

当容器需要删除文件的时候

从上往下层寻找文件,找到后在容器中记录删除。即,并不会真正的删除文件,而是软删除。这将导致镜像体积只会增加,不会减少。

综上,Docker镜像通过分层实现了资源共享,通过copy-on-write实现了文件隔离。

对于文件只增加不减少问题,我们应当在同一层做增删操作,从而减少镜像体积。比如,如下测试。

Dockerfile.A: 分层删除文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM centos:7
RUN yum install -y vim
RUN yum install -y httpd
WORKDIR /home
RUN dd if=/dev/zero of=50M.file bs=1M count=50
#创建大小为50M的测试文件
RUN rm -rf 50M.file
CMD ["/bin/bash"]

构建

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
docker build -t test:a -f Dockerfile.A .

Dockerfile.B: 同层删除

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM centos:7
RUN yum install -y vim
RUN yum install -y httpd
WORKDIR /home
RUN dd if=/dev/zero of=50M.file bs=1M count=50 && rm -rf 50M.file

构建

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
docker build -t test:b -f Dockerfile.B .

比较二者大小

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[root@sh-k8s-001 tmp]# docker images | grep test
test                                                                     a                          ae673aa7db48        9 minutes ago       497MB
test                                                                     b                          21b2bc49f0bd        12 minutes ago      444MB

显然,分层删除操作并没有真正删除掉文件。

写在最后

点关注,不迷路;持续更新Java相关技术及资讯文章

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Docker 镜像打包构建启动实例
当启动容器时,镜像如果在本地中不存在,docker 就会从 docker 镜像仓库中下载,默认是从 Docker Hub 公共镜像源下载。
洛秋_
2023/11/25
5131
Docker 镜像打包构建启动实例
​Docker 数据卷的管理及自动构建docker镜像
https://github.com/CentOS/CentOS-Dockerfiles
码农编程进阶笔记
2021/07/20
7130
​Docker 数据卷的管理及自动构建docker镜像
Docker 镜像与容器管理
镜像(Image): Docker镜像类似于虚拟机镜像,可以将它理解为一个只读的模板.例如,一个镜像可以包含一个基本的操作系统环境,里面仅安装了一个应用程序,可以把它称为一个镜像,镜像是创建Docker容器的基础.通过版本管理和增量的文件系统,Docker提供了一套十分简单的机制来创建和更新现有的镜像,用户甚至可以从网上下载一个已经做好的应用镜像,并直接使用.
王瑞MVP
2022/12/28
8340
【docker深入浅出】一文学透Docker基础万字好文
Docker 最初是dotCloud公司创始人Solomon Hykes在法国期间发起的一个公司内部项目,它是基于dotCloud公司多年云服务技术的一次革新,并与2013年3月以Apache 2.0授权协议开源),主要项目代码在GitHub上进行维护。Docker项目后来还加入了Linux基金会,并成立推动开放容器联盟。
iOS Magician
2023/10/11
5590
【docker深入浅出】一文学透Docker基础万字好文
Docker容器学习梳理-Dockerfile构建镜像
在Docker的运用中,从下载镜像,启动容器,在容器中输入命令来运行程序,这些命令都是手工一条条往里输入的,无法重复利用,而且效率很低。所以就需要一 种文件或脚本,我们把想执行的操作以命令的方式写入其中,然后让docker读取并分析、执行,那么重复构建、更新将变得很方便,所以Dockerfile就此诞生了。Docker提供了Dockerfile作为构建Docker镜像脚本,避免人们一行一行的输入,真是善莫大焉。Dockerfile脚本可以做到随时维护修改,即可以分享,更有利于在模板化,更不用说传输了,好处那
洗尽了浮华
2018/01/23
1.6K0
Docker容器学习梳理-Dockerfile构建镜像
Docker2 docker commit方法镜像制作
一、前期准备 1.下载一个centos镜像,进入容器,安装wget docker pull centos docker run -it centos bash [root@web1 ~]# docker run -it centos bash #进入容器 [root@4f1f1ca319f2 /]# [root@4f1f1ca319f2 /]# [root@4f1f1ca319f2 /]# cd [root@4f1f1ca319f2 ~]# ls anaconda-ks.cfg [root@
Java帮帮
2019/11/25
5630
docker学习3-镜像的基本使用
Docker的三大核心概念:镜像、容器、仓库。初学者对镜像和容器往往分不清楚,学过面向对象的应该知道类和实例,这跟面向对象里面的概念很相似 我们可以把镜像看作类,把容器看作类实例化后的对象。
上海-悠悠
2019/07/04
5880
docker学习3-镜像的基本使用
Dockerfile语法及构建简单镜像
这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vi,还得重复前面的所有步骤。 更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。 既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?
全栈程序员站长
2021/06/10
5600
搭建docker之路-初识(1)
[root@host ~]# yum install -y docker 查看信息:
cuijianzhe
2022/06/14
7790
搭建docker之路-初识(1)
Docker 镜像使用
当运行容器时,使用的镜像如果在本地中不存在,docker 就会自动从 docker 镜像仓库中下载,默认是从 Docker Hub 公共镜像源下载。
用户8647142
2021/07/27
5710
这就是你日日夜夜想要的docker!!!---------Docker镜像制作与私有仓库建立
Docker 镜像是由文件系统叠加而成(是一种文件的存储形式)。最底端是一个文件引 导系统,即 bootfs,这很像典型的 Linux/Unix 的引导文件系统。Docker 用户几乎永远不会和 引导系统有什么交互。实际上,当一个容器启动后,它将会被移动到内存中,而引导文件系 统则会被卸载,以留出更多的内存供磁盘镜像使用。Docker 容器启动是需要一些文件的, 而这些文件就可以称为 Docker 镜像。
不吃小白菜
2020/09/22
6270
这就是你日日夜夜想要的docker!!!---------Docker镜像制作与私有仓库建立
DockerFile 编译语法详解
Dockerfile是一个文本格式的配置文件,用户可以使用Dockerfile来快速创建自定义的镜像,本小结首先介绍Dockerfile典型的基本结构和它支持的众多指令,并具体讲解通过这些指令来编写定制镜像的Dockerfile,以及如何生成镜像.最后介绍使用Dockerfile的一些最佳实践经验.
王瑞MVP
2022/12/28
4300
Docker学习以及镜像制作流程
一、何为Docker Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。 容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。 Docker的应用场景 Web 应用的自动化打包和发布。 自动化测试和持续集成、发布。 在服务型环境中部署和调整数据库或其他的后
互联网金融打杂
2018/06/13
1.5K0
【愚公系列】2022年01月 Docker容器 镜像的操作
镜像就相当于系统光盘,容器相当于已经安装好的系统,这也就是为啥docker可以在任何电脑上运行的原因,减少重复部署麻烦,在网关层能更好进行负载均衡和管理。
愚公搬代码
2022/12/01
3360
【愚公系列】2022年01月 Docker容器 镜像的操作
docker storage driver
使用docker目录创建一个volume,并将该volume挂载到容器的/my_Cvol目录下
charlieroro
2020/03/24
1.4K0
docker storage driver
Docker容器技术
Docker介绍 什么是容器 Linux容器是与系统其他部分隔离开的一系列进程,从另一个系统镜像运行,并由该镜像提供支持进程所需的全部文件。 容器镜像包含了应用的所有依赖项,因而在从开发到测试再到生产的整个过程中,它都具有可移植性和一致性。 来源:https://www.redhat.com/zh/topics/containers/whats-a-linux-container 容器就是虚拟化吗? 虚拟化使得许多操作系统可同时在单个系统上运行。 容器只能共享操作系统内核,将应用进程与系统其他部分,隔离开。
863987322
2018/03/29
3.5K0
Docker容器技术
Docker镜像管理基础
docker镜像含有启动容器所需要的文件系统及其内容,因此,其用于创建并启动容器。
Alone-林
2022/08/23
7290
Docker镜像管理基础
003.Docker镜像制作之基于Dockerfile制作镜像
1. 制作带有sshd服务的CentOS6.9镜像 # 1.编写dockerfile,文件名必须是Dockerfile或者dockerfile # FROM: 指定基础镜像,两种语法 # FROM centos:6.9 # FROM imageId FROM 2199b8eb8390 # RUN: 进入容器后执行的命令,尽量少的使用新的RUN命令行,因为每次执行一次RUN,都会生成一个新的临时容器 # RUN有两种语法 # RUN command && command # RUN ["command","
CoderJed
2020/11/12
4.1K0
003.Docker镜像制作之基于Dockerfile制作镜像
Docker 容器入门
1.1 容器简介 1.1.1 什么是 Linux 容器 Linux容器是与系统其他部分隔离开的一系列进程,从另一个镜像运行,并由该镜像提供支持进程所需的全部文件。容器提供的镜像包含了应用的所有依赖项,
惨绿少年
2018/03/30
2K0
(4/4) .NET Core Web API + Vue By Linux and Windows 部署方案知识点总结
开始新的方案前,先复习一下上面的内容建议用云处理器学习,重装什么都简单,按小时购买成本也不高
老张的哲学
2023/01/09
2.4K0
(4/4) .NET Core Web API + Vue By Linux and Windows 部署方案知识点总结
相关推荐
Docker 镜像打包构建启动实例
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验