每天5分钟玩转 Docker 容器技术之镜像
wptr33 2024-11-13 15:03 32 浏览
镜像是 Docker 容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。为什么我们要讨论镜像的内部结构?
如果只是使用镜像,当然不需要了解,直接通过 docker 命令下载和运行就可以了。
但如果我们想创建自己的镜像,或者想理解 Docker 为什么是轻量级的,就非常有必要学习这部分知识了。
一、最小的镜像
1、运行hello-world镜像
hello-world 是 Docker 官方提供的一个镜像,通常用来验证 Docker 是否安装成功。
我们先通过 docker pull 从 Docker Hub 下载它。
用 docker images 命令查看镜像的信息。
其实我们更关心 hello-world 镜像包含哪些内容。
其实我们更关心 hello-world 镜像包含哪些内容。
2. hello-world镜像内容
Dockerfile 是镜像的描述文件,定义了如何构建Docker镜像。Dockerfile的语法简洁且可读性强,后面我们会专门讨论如何编写Dockerfile。
hello-world 的 Dockerfile 内容如下:
只有短短三条指令。
#1、此镜像是从白手起家,从 0 开始构建。
FROM scratch
#2、将文件“hello”复制到镜像的根目录。
COPY hello /
#3、容器启动时,执行 /hello
CMD ["/hello"]
镜像 hello-world 中就只有一个可执行文件 “hello”,其功能就是打印出 “Hello from Docker ......” 等信息。
/hello 就是文件系统的全部内容,连最基本的 /bin,/usr, /lib, /dev 都没有。
hello-world 虽然是一个完整的镜像,但它并没有什么实际用途。通常来说,我们希望镜像能提供一个基本的操作系统环境,用户可以根据
需要安装和配置软件。这样的镜像我们称作 base 镜像。我们下一节讨论 base 镜像。
二、base 镜像
1.base镜像含义
base 镜像有两层含义:
1、不依赖其他镜像,从 scratch 构建。
2、其他镜像可以之为基础进行扩展。
所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。
2.base镜像内容
我们以 CentOS 为例考察 base 镜像包含哪些内容。
下载镜像:docker pull centos
查看镜像信息
镜像大小 231MB。等一下!一个 CentOS 才 200MB ?平时我们安装一个 CentOS 至少都有几个 GB,怎么可能才 200MB !
相信这是几乎所有 Docker 初学者都会有的疑问,包括我自己。下面我们来解释这个问题。
Linux 操作系统由内核空间和用户空间组成。如下图所示:
内核空间是kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。用户空间的文件系统是 rootfs,包含我们熟悉的
/dev, /proc, /bin 等目录。对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs
已经算臃肿的了,alpine 还不到 10MB。我们平时安装的 CentOS 除了 rootfs 还会选装很多软件、服务、图形桌面等,需要好几个 GB 就
不足为奇了。下面是 CentOS 镜像的 Dockerfile 的内容:
这里需要说明的是:
不同 Linux 发行版的区别主要就是 rootfs。
比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,
Linux kernel 差别不大。所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。
上图 Debian 和 BusyBox(一种嵌入式 Linux)上层提供各自的 rootfs,底层共用 Docker Host 的 kernel。
这里需要说明的是:
容器只能使用 Host 的 kernel,并且不能修改。
所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。
下一节我们讨论镜像的分层结构。
三、镜像的分层结构
Docker 支持通过扩展现有镜像,创建新的镜像。
1、镜像分层示例
实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,Dockerfile 如下:
① 新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
② 安装 emacs 编辑器。
③ 安装 apache2。
④ 容器启动时运行 bash。
构建过程如下图所示:
可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
2、镜像分层好处
问什么 Docker 镜像要采用这种分层结构呢?
最大的一个好处就是 - 共享资源。
比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base
镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。
这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc
是否也会被修改?
答案是不会!
修改会被限制在单个容器内。
这就是我们接下来要学习的容器 Copy-on-Write 特性。
3、Copy-on-Write 特性
可写的容器层
当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。
所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。
只有容器层是可写的,容器层下面的所有镜像层都是只读的。
下面我们深入讨论容器层的细节。
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会
覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
添加文件
在容器中创建文件时,新文件被添加到容器层中。
读取文件
在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,打开并读入内存。
修改文件
在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
删除文件
在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
这样就解释了我们前面提出的问题:*容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。
理解了镜像的原理和结构,下一节我们学习如何构建镜像。
四、构建镜像
1、为何要构建镜像
对于 Docker 用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的 Docker 官方镜像或
其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用。
使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为 Docker 的工程师
知道如何更好的在容器中运行软件。
当然,某些情况下我们也不得不自己构建镜像,比如:
1. 找不到现成的镜像,比如自己开发的应用程序。
2. 需要在镜像中加入特定的功能,比如官方镜像几乎都不提供 ssh。
所以本节我们将介绍构建镜像的方法。同时分析构建的过程也能够加深我们对前面镜像分层结构的理解。
2、构建镜像方法
Docker 提供了两种构建镜像的方法:
1. docker commit 命令
2. Dockerfile 构建文件
3、docker commit构建镜像
docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:
1. 运行容器
2. 修改容器
3. 将容器保存为新的镜像
举个例子:在 ubuntu base 镜像中安装 vi 并保存为新镜像。
1)、第一步:运行容器
-it 参数的作用是以交互模式进入容器,并打开终端。6e2d389d4576 是容器的内部 ID。
2)、第二步:安装 vi
确认 vi 没有安装。开始安装 apt-get install -y vim
3)、第三步:保存新镜像
在新窗口中查看当前运行的容器。
新镜像命名为 ubuntu-with-vi。
执行 docker commit 命令将容器保存为镜像。
新镜像命名为 ubuntu-with-vi。
查看新镜像的属性。
从 size 上看到镜像因为安装了软件而变大了。从新镜像启动容器,验证 vi 已经可以使用。
以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像。原因如下:
1. 这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vi,还得重复前面的所有步骤。
2. 更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。
既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?
原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们
更加深入地理解构建过程和镜像的分层结构。
下一节我们学习如何通过 Dockerfile 构建镜像。
五、Dockerfile 构建镜像
Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。
1、Dockerfile 构建镜像
1)创建Dockerfile文件
touch Dockerfile
2)用 Dockerfile 创建上节的 ubuntu-with-vi,其内容则为:
3)构建镜像
docker build -t ubuntu-with-vi-dockerfile .
ubuntu-with-vi-dockerfile是构建镜像所取的名字
运行 docker build 命令,-t 将新镜像命名为 ubuntu-with-vi-dockerfile,命令末尾的 . 指明 build context 为当前目录。
Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过 -f 参数指定 Dockerfile 的位置。
4)镜像构建成功
通过 docker images 查看镜像信息。
可以看到新镜像已经构建成功,而且大小跟之前docker commit 构建的大小是一样大的。
构建成功
通过 docker images 查看镜像信息。
可以看到新镜像已经构建成功,而且大小跟之前docker commit 构建的大小是一样大的。
建的大小是一样大的。
建的大小是一样大的。
相关推荐
- MySQL进阶五之自动读写分离mysql-proxy
-
自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...
- 3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?
-
引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...
- 一文由浅入深带你完全掌握MySQL的锁机制原理与应用
-
本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...
- 验证Mysql中联合索引的最左匹配原则
-
后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...
- MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)
-
目录1.索引基础...
- 你会看 MySQL 的执行计划(EXPLAIN)吗?
-
SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...
- MySQL 从入门到精通(四)之索引结构
-
索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...
- mysql总结——面试中最常问到的知识点
-
mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...
- mysql总结——面试中最常问到的知识点(2)
-
首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...
- MySQL基础全知全解!超详细无废话!轻松上手~
-
本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...
- 深入剖析 MySQL 中的锁机制原理_mysql 锁详解
-
在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...
- Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析
-
引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...
- MySQL基础篇:DQL数据查询操作_mysql 查
-
一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...
- MySql:索引的基本使用_mysql索引的使用和原理
-
一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
程序员的开源月刊《HelloGitHub》第 71 期
-
详细介绍一下Redis的Watch机制,可以利用Watch机制来做什么?
-
假如有100W个用户抢一张票,除了负载均衡办法,怎么支持高并发?
-
Java面试必考问题:什么是乐观锁与悲观锁
-
如何将AI助手接入微信(打开ai手机助手)
-
SparkSQL——DataFrame的创建与使用
-
redission YYDS spring boot redission 使用
-
一文带你了解Redis与Memcached? redis与memcached的区别
-
如何利用Redis进行事务处理呢? 如何利用redis进行事务处理呢英文
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)