2023-08-23:Docker镜像
学习自:03 镜像使用:Docker 环境下如何配置你的镜像?
1、镜像概念
在之前曾经说过,镜像是一个只读的Docker容器模板,包含了容器所需的所有文件系统结构和内容。
简单来说,镜像是一个特殊的文件系统,它提供了容器运行所需的程序、软件库、资源、配置等静态数据。即镜像不包含任何动态数据,镜像内容在构建后不会被改变。
2、镜像操作
上图给出了镜像的操作方法:
操作 |
命令docker xxx |
说明 |
拉取镜像 | pull | 拉取远程仓库的镜像到本地 |
重命名镜像 | tag | 重命名 |
查看镜像 |
image ls images |
查看本地已经存在的镜像 |
删除镜像 | rmi | 删除无用镜像 |
构建镜像 |
build commit |
基于Dockerfile构建镜像 基于已运行的容器提交为镜像 |
1)拉取镜像
用法:docker pull [Registry]/[Repository]/[Image:[Tag]
说明:
- Registry:注册服务器,拉取镜像的来源。Docker默认从docker.io拉取镜像,如果你有自己的镜像仓库,可以把Registry替换为自己的注册服务器;
- Repository:镜像仓库,通常将一组相关联的镜像归为一个镜像仓库,library是Docker默认的镜像仓库;
- Image:镜像名称
- Tag:镜像标签,默认latest
例子:
获取一个busybox镜像,可以执行以下命令:
1 | docker pull busybox |
实际上在执行docker pull busybox命令,都是先从本地搜索,如果本地搜索不到busybox,镜像就从Docker Hub下载镜像。
拉取完镜像,如果要查看镜像,该怎么做?
2)查看镜像
用法:
-
docker image
-
docker image ls
-
docker image ls 镜像名
例子:
列出本地所有的镜像
1 | docker images |
查询指定的镜像
1 2 | docker image ls busybox docker images | grep busybox |
3)重命名镜像
用法:docker tag [Source_Image][:Tag] [Target_Image][:Tag]
说明:把镜像名:标签从Source_Image:Tag重命名为Target_Image:Tag
例子
1 | docker tag busybox:latest mybusybox:latest |
可以看到,镜像列表中多了一个mybusybox镜像,它与busybox的IMAGE ID是完全一样的,因为它们实际上指向了同一个镜像文件,只是别名不同而已。
如果我不需要mybusybox镜像了,要怎么删除呢?
4)删除镜像
用法:
-
docker rmi 镜像
-
docker image rm
例子
删除mybusybox镜像
1 | docker rmi mybusybox |
此时再用docker images命令查看一下我们机器上的镜像列表:
如果我们要构建属于自己的镜像,应该怎么做?
5)构建镜像
方式:
-
docker commit:从运行中的容器提交为镜像
-
docker build:从Dockerfile构建镜像
例子
1.如何从运行中的容器提交为镜像——commit
使用如下命令创建一个名为busybox的容器并进入busybox容器:
1 | docker run -- rm --name=busybox -it busybox sh |
执行完该命令后,当前窗口会启动一个busybox并进入容器中。
在容器中,执行以下命令创建一个文件并写入内容:
1 2 | / # touch hello.txt && echo "I love docker. " > hello.txt / # |
此时在容器的根目录下,就创建了一个hello.txt文件,并写入了内容I love Docker。
再打开另一个命令行窗口,运行以下命令提交镜像:
1 | docker commit busybox busybox:hello |
再用docker image ls busybox查看镜像:
可以看到,主机上新生成了busybox:hello这个镜像。
2.用Dockerfile构建镜像——build(最常用)
使用Dockerfile构建镜像具有以下特性:
- Dockerfile的每行命令都会生成一个独立的镜像层,并且拥有唯一ID;
- Dockerfile的命令是完全透明的,通过查看Dockerfile的内容,就能知道镜像是如何一步步创建的;
- Dockerfile是纯文本的,方便跟随代码一起存放在代码仓库并做版本管理。
Dockerfile常用指令
指令 |
说明 |
FROM | 除了注释之外,第一行必须是FROM,后跟容器名称,代表我们要基于哪个基础镜像构建我们的容器。 |
RUN | 后跟具体指令,类似Linux命令行执行命令 |
ADD | 拷贝本机文件或远程文件到镜像 |
COPY | 拷贝本机文件到镜像 |
USER | 容器启动的用户 |
ENTRYPOINT | 容器的启动命令 |
CMD | CMD为ENTRYPOINT指令提供默认参数,也可以单独使用CMD指定容器启动参数 |
ENV | 指定容器运行时的环境变量,格式KEY=VALUE |
ARG | 定义外部变量,构建镜像时可以使用build-arg = xxx的格式传递参数用于构建 |
EXPOSE | 指定容器监听的端口,格式为[port]/tcp或[port]/udp |
WORKDIR | 为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY、ADD设置工作目录 |
下面借助一个Dockerfile的例子来解释说明:
1 2 3 4 5 6 | FROM centos:7 COPY nginx.repo /etc/yum .repos.d /nginx .repo RUN yum install -y nginx EXPOSE 80 ENV HOST=mynginx CMD [ "nginx" , "-g" , "daemon off;" ] |
- 第一行:基于centos:7这个镜像来构建自定义镜像。每个Dockerfile的第一行除了注释都必须以FROM开头;
- 第二行:拷贝本地文件nginx.repo到容器的/etc/yum.repos.d目录下。这里拷贝nginx.repo文件是为了添加nginx安装源;
- 第三行:在容器内运行yum install -y nginx命令,安装nginx服务到容器内,执行完该行命令,容器内已经将nginx安装完毕;
- 第四行:声明容器业务(nginx)使用80端口对外提供服务;
- 第五行:容器启动时的环境变量HOST=mynginx,容器启动后可以获取到环境变量HOST的值为mynginx;
- 第六行:容器的启动命令,格式为json数组。这里设置了容器的启动命令为nginx,并且添加了nginx启动参数-g 'daemon off;',使nginx以前台方式启动。
这个Dockerfile的例子涵盖了常用的镜像构建指令。
下面深入了解一下镜像的实现原理。
3、镜像的实现原理
其实Docker镜像是由一系列镜像层(layer)组成的,每层代表了镜像构建过程中的一次提交(Dockerfile中的一行)。
下面以一个镜像构建的Dockerfile来说明镜像是如何分层的:
1 2 3 | FROM busybox COPY test /tmp/test RUN mkdir /tmp/testdir |
将该文件保存为Dockerfile,注意Dockerfile就是文件名,不能修改,大小写敏感,无后缀
该Dockerfile由3步组成:
- 基于busybox创建一个镜像层;
- 拷贝本机test文件到镜像内;
- 在/mp目录下创建一个目录testdir。
为了验证镜像的存储,可以用docker build命令在上边的Dockerfile目录构建一个镜像(不要忘记最后的.,代表构建目录):
1 | docker build -t mybusybox . |
这里Docker使用的是overlay2文件驱动,进入到/var/lib/docker/overlay2目录下使用tree .命令查看产生的镜像文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | tree . # 以下为 tree . 命令输出内容 |-- 3e89b959f921227acab94f5ab4524252ae0a829ff8a3687178e3aca56d605679 | |-- diff # 这一层为基础层,对应上述 Dockerfile 第一行,包含 busybox 镜像所有文件内容,例如 /etc,/bin,/var 等目录 ... 此次省略部分原始镜像文件内容 | `-- link |-- 6591d4e47eb2488e6297a0a07a2439f550cdb22845b6d2ddb1be2466ae7a9391 | |-- diff # 这一层对应上述 Dockerfile 第二行,拷贝 test 文件到 /tmp 文件夹下,因此 diff 文件夹下有了 /tmp/test 文件 | | `-- tmp | | `-- test | |-- link | |-- lower | `-- work |-- backingFsBlockDev |-- bec6a018080f7b808565728dee8447b9e86b3093b16ad5e6a1ac3976528a8bb1 | |-- diff # 这一层对应上述 Dockerfile 第三行,在 /tmp 文件夹下创建 testdir 文件夹,因此 diff 文件夹下有了 /tmp/testdir 文件夹 | | `-- tmp | | `-- testdir | |-- link | |-- lower | `-- work ... |
如果这里查起来比较困难的话,可以先把指令结果输出到文件,再用vim去文件中查找
1 | tree . > tree.txt |
通过这一目录结构可以看到,Dockerfile的每行命令,都生成了一个镜像层,每层的diff文件夹下都只存放了增量数据,如下图所示:
分层的结构使得Docker镜像非常轻量,每层根据镜像内容都有一个唯一的ID值,当不同的镜像之间有相同的镜像层时,便可以实现不同镜像之间共享镜像层的效果。
小结
Docker镜像是静态、分层管理的文件组合,镜像底层实现依赖于联合文件系统(UnionFS)。充分掌握镜像原理,可以帮助我们在生产实践中构建出最优的镜像,同时也可以帮助我们更好地理解容器和镜像的关系。
镜像的实现原理:镜像是由一系列镜像层(layer)组成,每层代表了镜像构建过程中的一次提交(Dockerfile中的一行),当我们需要修改镜像内某个文件时,只需要在当前镜像层的基础上新建一个镜像层,并且只存放修改过的文件内容。分层使得镜像共享镜像层变得非常简单方便。
回顾
1、镜像
- 镜像是只读的Docker容器模板,包含了容器所需的文件系统结构和内容;
- 它是静态的,其内容在构建之后就无法改变;
- 镜像底层实现依赖于联合文件系统(UnionFs),这是一种文件分层机制;
- 镜像由一系列的镜像层组成,每次代表了Dockerfile中的一次提交;
- 当我们要修改镜像内的某个文件时,只需要在当前镜像层的基础上新建一个镜像层,并且只存放修改的内容
- 分层使镜像共享镜像层非常方便简单。
2、镜像操作:构建、拉取、重命名、查看、删除
3、拉取镜像:pull
拉取时,先从本地搜索,如果搜索不到,就从仓库下载。
4、查看镜像:image
5、重命名:tag
6、删除:rmi
7、构建:commit(将运行的容器保存为镜像文件)、build(从Dockerfile构建镜像,最常用)通过buid从Dockerfile构建镜像是最常用的方式
8、Dockerfile特性:
- 每行都会执行一个命令,每行命令都会生成一个独立镜像层,并具有唯一ID;
- 这些命令的执行对容器外透明,只需要看这些命令就知道这个镜像是怎么创建起来的;
- Dockerfile是纯文本的,方便与代码一起存放在代码仓库并做版本管理
9、一个Dockerfile文件会用到的指令(常用,非全部)
指令 |
说明 |
FROM | 除了注释之外,第一行必须是FROM,后跟容器名称,代表我们要基于哪个基础镜像构建我们的容器。 |
RUN | 后跟具体指令,类似Linux命令行执行命令 |
ADD | 拷贝本机文件或远程文件到镜像 |
COPY | 拷贝本机文件到镜像 |
USER | 容器启动的用户 |
ENTRYPOINT | 容器的启动命令 |
CMD | CMD为ENTRYPOINT指令提供默认参数,也可以单独使用CMD指定容器启动参数 |
ENV | 指定容器运行时的环境变量,格式KEY=VALUE |
ARG | 定义外部变量,构建镜像时可以使用build-arg = xxx的格式传递参数用于构建 |
EXPOSE | 指定容器监听的端口,格式为[port]/tcp或[port]/udp |
WORKDIR | 为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY、ADD设置工作目录 |
10、镜像的实现过程及原理
Docker镜像由一系列镜像层构成,每层代表了Docker文件中的一次提交(按行)
1 2 3 | FROM busybox COPY test /tmp/test RUN mkdir /tmp/testdir |
该Dockerfile有3步:
- 构建的镜像来自基础镜像busybox;
- copy本机本目录test文件至镜像中的/tmp/test文件
- 在镜像内创建目录/tmp/testdir
用docker build依据该Dockerfile构建为镜像:
1 | docker build -t mybusybox . |
进入到/var/lib/docker/overlay2目录下使用tree .命令查看产生的镜像文件:
1 | tree . |
通过这一目录结构可以看出,Dockerfile每行命令都会产生一个镜像层(上图的2aq和nyt开头的ID),每层的diff文件下都只存放了增量数据:
11、分层的优点
使Docker镜像非常轻量;
每层根据镜像内容都有一个唯一ID,当不同镜像之间有相同镜像层时,可以实现不同镜像之间共享镜像层的效果。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性