1
2
3
4
5
作者:李晓辉

微信联系:lxh_chat

联系邮箱: 939958092@qq.com

你是不是也和我一样,刚学 Docker 的时候看到“镜像”两个字脑子里全是问号?什么是镜像?为什么每次 docker run 都要“拉一下”?这些镜像到底藏在哪里?能不能自己整一个?

别急,这篇文章就是来帮你一一解答这些问题的!我们会从镜像的概念讲起,再带你看看怎么拉取、查看、制作、导出和导入镜像,还会顺手写个 Dockerfile 玩一下。全程没有废话,只有实战,包你看完后立刻上手不迷路!

准备好了?那就一起搞懂镜像这回事吧~

镜像的概念

当你听到“容器镜像”这几个字时,可能会想:“这到底是个啥?怎么感觉又神秘又复杂?”其实,容器镜像就像是容器的“照片”或者“模板”,它保存了一个应用程序所需的所有东西,简单来说,它就是用来跑容器的“食谱”!

1. 镜像是不可变的

镜像的特别之处在于,它是不可变的。一旦镜像创建好了,它的内容就定型了,不会改变。所以你每次运行一个容器,都是从这个固定的镜像创建出来的。而容器的运行时状态是可以变动的,比如运行中的数据、文件等,反正不会影响到镜像本身。

2. 镜像是分层的

镜像不像一个大文件那样堆在一起,而是分成了好几层!每一层都代表了镜像做的一个更改或增加的内容。例如,你可以从一个空白的操作系统镜像开始,然后安装一些工具,再安装你的应用。这种分层的方式让镜像更加高效,省空间,也让更新更容易(只需要更新特定的层而不是整个镜像)。

3. 怎么构建镜像?

镜像的创建通常是通过一个叫做 Dockerfile 的文件来完成的。你可以把它看成是一个“食谱”,告诉 Docker 该如何从零开始构建镜像。你在 Dockerfile 中写下基础操作系统、需要安装的软件、配置等内容,然后 Docker 会按照这些指令一步步构建出镜像。构建好之后,你就可以拿这个镜像跑容器了!

4. 镜像仓库

镜像可不是一口气都丢进本地硬盘的,它们会被存储在一个叫做“镜像仓库”的地方。最常用的仓库是 Docker Hub,这就像一个大型的应用商店,提供各种常用的镜像。而如果你不想让别人随便拿你的镜像,也可以设置私有仓库。

5. 拉取和推送镜像

通常,我们都从远程仓库(像 Docker Hub)拉取镜像,也就是下载到本地。而当你创建了自己的镜像后,就可以把它推送到仓库,分享给其他人。操作很简单:

  • 拉取镜像:docker pull <镜像名>

  • 推送镜像:docker push <镜像名>

6. 镜像优化小技巧

镜像虽然方便,但有时候会有点大。尤其在网络慢或者存储有限的情况下,镜像太大可能会让你抓狂。所以,这里有几个小技巧能帮你让镜像更轻便:

  • 用更小的基础镜像,比如 alpine 镜像。

  • 合并一些镜像创建步骤,减少层数。

  • 删除一些没必要的文件,尤其是缓存文件。

  • .dockerignore 文件排除不需要的文件。

这样一来,你的镜像既轻巧又高效,构建和下载也能更快。

容器和镜像有啥区别和联系?

简单来说,镜像就像是容器的“蓝图”或“模板”,它是静态的、不可变的。每次你需要启动容器时,Docker 会根据这个镜像生成一个可运行的实例,这个实例就是容器。

镜像是我们构建和分发应用的基础,可以保存在本地或者远程仓库。镜像本身不会在运行时改变,而容器则是根据镜像创建并启动的一个动态实体。容器是运行在主机上的进程,启动后可以在其中执行命令、修改文件或保存数据等。容器的生命周期是从启动到运行,再到停止或销毁。

这两者的关系可以理解为:镜像是容器的父本,容器是镜像的子本。每个容器都是基于某个镜像创建出来的,但容器的状态是可以变化的,而镜像则始终保持不变。

概念容器镜像(Image)容器(Container)
是什么运行容器的“模板”,类似应用的快照运行时的实例,基于镜像启动的进程
是否可变不可变,构建后内容固定可变,运行时可以修改内部状态
存储位置本地或远程仓库运行在宿主机上的进程
生命周期构建 -> 拉取 -> 用来生成容器启动 -> 运行 -> 停止/销毁

镜像从哪儿来?

获取镜像其实并不复杂,Docker 给我们提供了几种非常方便的方式来获取和管理镜像。你可以从官方镜像库、第三方镜像仓库,甚至是自己构建镜像。下面,我会给你介绍几种常见的镜像获取方式。

1. 从 Docker Hub 拉取镜像

最常见的方式就是从 Docker Hub 拉取镜像。Docker Hub 就像是一个应用商店,里面有成千上万的开源镜像,涵盖了各种各样的操作系统、应用程序和工具。你只需要知道镜像的名字,使用 docker pull 命令就能下载到本地,像这样:

1
docker pull ubuntu

其实这条命令全称是:

1
docker pull docker.io/library/ubuntu:latest

但是由于docker和dockerhub都是一家公司的,它隐藏了全路径,但是我们要知道这个格式:

1
docker pull 可解析的域名或IP/命名空间/镜像名称:标签

没指定标签的时候,默认会自动使用latest标签

上面这个命令会从 Docker Hub 下载最新的 Ubuntu 镜像。你也可以指定版本号或者标签来拉取不同版本的镜像:

1
docker pull ubuntu:20.04

如果你镜像从加速器拉取镜像,又不想在pull镜像的时候每次加前缀,那可以把容器镜像加速器放到docker daemon中,每次pull的时候,docker就会自动从特定的加速器拉取镜像,而不是在中国无法访问的dockerhub拉取了哦

1
2
3
4
5
6
7
8
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://<加速器地址>"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

2. 从第三方镜像仓库拉取

除了 Docker Hub,网络上还有很多其他的镜像仓库,像 阿里云腾讯云华为云 等也提供了 Docker 镜像服务。如果你身处国内,可能访问 Docker Hub 速度比较慢或者无法,那么你可以选择使用这些国内镜像仓库或者从你公司自己的仓库里下载,获得更快的下载速度。

比如我就在互联网上提供了容器镜像仓库:registry.myk8s.cn,如果你从我的仓库下载,那就这样加上前缀即可

1
docker login registry.myk8s.cn -u USERNAME -p PASSWORD
1
docker pull registry.myk8s.cn/library/ubuntu

如果你也想在公司里搭建一个容器镜像仓库给内网用,那就看看我的这篇文章吧,一步一步的教你搭建harbor容器镜像仓库,点此打开Harbor仓库搭建教程

构建自己的镜像

构建容器镜像有两个主流方法:

  1. Docker commit容器

  2. Dockerfile(最推荐,最主流)

Docker commit

docker commit 是 Docker 提供的一个命令,用来创建一个新的镜像,它是基于现有的容器状态来生成的。简单来说,它允许你将正在运行的容器的变化(例如文件的修改、包的安装、配置的改变等)保存为一个新的镜像。

通常情况下,docker commit 适用于你手动操作容器后,希望将这些操作结果保存下来,生成一个新的镜像。比如,你进入容器内部做了一些修改,然后想要把这些修改保存成镜像以备后续使用,或者分享给其他人。

假设你有一个运行中的容器(比如 container_idabc123),你在里面做了一些修改(比如安装了软件、修改了配置文件等),现在你希望把这些修改保存到一个新镜像中,可以运行:

1
docker commit abc123 my-new-image:latest

这条命令会将 abc123 这个容器的当前状态保存为一个新的镜像,镜像名为 my-new-image:latest

docker commit 通常用于一些临时的修改,或者你在测试一个容器时,做了一些想要保存的改变。不过,如果你要长期管理和复用镜像,最好还是写一个 Dockerfile 来描述镜像的构建过程,这样更清晰,也方便版本控制。

注意事项

  • docker commit 并不会保存容器的日志、网络设置等,它只保存容器的文件系统(包括文件和已安装的包等)。

  • 频繁使用 docker commit 可能会导致镜像管理变得混乱,因为每次提交都会生成一个新的镜像,可能不容易追踪和管理。

Dockerfile

Dockerfile 是一个文本文件,包含了一系列指令,用于自动化地构建 Docker 镜像。它就像是镜像的“配方”,你通过编写 Dockerfile 指定镜像的基础环境、所需的依赖、配置、执行命令等,Docker 根据这些指令来一步一步地构建出一个新的镜像。

简单来说,Dockerfile 就是用来告诉 Docker,怎么从零开始构建一个镜像。这个过程包括选择基础镜像、安装软件、拷贝文件、设置环境变量、运行程序等。

一个 Dockerfile 由多个指令(每个指令一行)组成,Docker 会按顺序执行这些指令来构建镜像。每个指令都有特定的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 使用官方 nginx 镜像作为基础镜像
FROM nginx:latest

# 维护者信息(可以不写)
LABEL maintainer="your_name@example.com"

# 设置工作目录为 Nginx 网站根目录
WORKDIR /usr/share/nginx/html

# 将本地的网页内容复制到容器的工作目录(即网站根目录)
COPY fstab /usr/share/nginx/html

# 暴露 nginx 服务的 80 端口
EXPOSE 80

# 默认启动 nginx 服务
CMD ["nginx", "-g", "daemon off;"]

除了上面的之外,还有一些别的常见指令,都可以看看哈

  • FROM: 指定镜像的基础镜像,用于构建新镜像。
  • LABEL: 添加元数据(如作者信息或版本号)到镜像中。
  • RUN: 在镜像构建过程中执行命令,通常用于安装软件。
  • COPY: 将本地文件或目录复制到容器中的指定路径。
  • ADD: 类似于 COPY,但支持从 URL 下载文件,并自动解压 .tar 文件。
  • WORKDIR: 设置容器内的工作目录,后续的指令会在此目录下执行。
  • EXPOSE: 声明容器监听的端口,但不会实际开放端口。
  • CMD: 设置容器启动时执行的默认命令。
  • ENTRYPOINT: 设置容器启动时执行的主命令,不能被覆盖。
  • ENV: 设置环境变量,在容器内有效。
  • ARG: 定义构建时使用的变量,可以在 docker build 时传入。
  • VOLUME: 创建一个挂载点,用于容器与宿主机之间的数据共享。
  • USER: 设置在容器内运行命令时使用的用户。
  • WORKDIR: 设置工作目录,指定后续操作路径。

写好了文件之后,用下面的命令来构建

我们要拷贝文件进去,所以先复制过来

1
cp /etc/fstab .

构建出lxh:v1这个镜像

1
docker build -t lxh:v1 .

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[+] Building 8.3s (9/9) FINISHED                                                                                                                                         docker:default
=> [internal] load build definition from dockerfile 0.0s
=> => transferring dockerfile: 571B 0.0s
=> [internal] load metadata for registry.myk8s.cn/library/nginx:latest 0.5s
=> [auth] sharing credentials for registry.myk8s.cn 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/3] FROM registry.myk8s.cn/library/nginx:latest@sha256:5ed8fcc66f4ed123c1b2560ed708dc148755b6e4cbd8b943fab094f2c6bfa91e 7.7s
=> => resolve registry.myk8s.cn/library/nginx:latest@sha256:5ed8fcc66f4ed123c1b2560ed708dc148755b6e4cbd8b943fab094f2c6bfa91e 0.0s
=> => sha256:5ed8fcc66f4ed123c1b2560ed708dc148755b6e4cbd8b943fab094f2c6bfa91e 10.25kB / 10.25kB 0.0s
=> => sha256:8a628cdd7ccc83e90e5a95888fcb0ec24b991141176c515ad101f12d6433eb96 28.23MB / 28.23MB 5.8s
=> => sha256:b0c073cda91fdc0aac18d3b7ad19faeea4c3e1f1e45c8e97256410a580d48bb4 44.15MB / 44.15MB 6.9s
=> => sha256:e6557c42ebeaea010d8a8883fdacdc5a17dea1221416d0d980e206dd42dc7e29 627B / 627B 1.5s
=> => sha256:69747b174b56bc170ca99a6d8e7a80e77a020c9702dda7e44a2154066a7a4dbe 2.29kB / 2.29kB 0.0s
=> => sha256:4e1b6bae1e48cdbde8e6ec3e6ee42d86ad4780156e75790465bf6fb16c551c27 8.58kB / 8.58kB 0.0s
=> => sha256:ec74683520b9c235ea1336a5165c6118a35cacd4d9878893a36b506b89334c50 956B / 956B 2.9s
=> => sha256:6c95adab80c5df4140fa3f348e07f127ba6f5c2a9ed8637cd27d5a5be5010330 405B / 405B 3.4s
=> => sha256:ad8a0171f43ec421ec572495b70d68bfd77b63acd094e4a4af794cb427516246 1.21kB / 1.21kB 4.5s
=> => sha256:32ef64864ec3b7cd667027631b05dc66a5fa40b05affe0e74a1bd01d1d57d6e5 1.40kB / 1.40kB 5.0s
=> => extracting sha256:8a628cdd7ccc83e90e5a95888fcb0ec24b991141176c515ad101f12d6433eb96 0.9s
=> => extracting sha256:b0c073cda91fdc0aac18d3b7ad19faeea4c3e1f1e45c8e97256410a580d48bb4 0.8s
=> => extracting sha256:e6557c42ebeaea010d8a8883fdacdc5a17dea1221416d0d980e206dd42dc7e29 0.0s
=> => extracting sha256:ec74683520b9c235ea1336a5165c6118a35cacd4d9878893a36b506b89334c50 0.0s
=> => extracting sha256:6c95adab80c5df4140fa3f348e07f127ba6f5c2a9ed8637cd27d5a5be5010330 0.0s
=> => extracting sha256:ad8a0171f43ec421ec572495b70d68bfd77b63acd094e4a4af794cb427516246 0.0s
=> => extracting sha256:32ef64864ec3b7cd667027631b05dc66a5fa40b05affe0e74a1bd01d1d57d6e5 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 760B 0.0s
=> [2/3] WORKDIR /usr/share/nginx/html 0.1s
=> [3/3] COPY ./fstab /usr/share/nginx/html 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:4819a7c584edc2d6cfa19f81d833431610c4a90e75404f86e3e6dc59d44eafcd 0.0s
=> => naming to docker.io/library/lxh:v1 0.0s

我们注意最后的输出,直接把我们的镜像生成了docker.io/library/lxh:v1,我们看看本地有哪些镜像

1
docker images

对于docker来说,没有域名前缀和命名空间,默认就是docker.io/library

1
2
REPOSITORY                              TAG       IMAGE ID       CREATED         SIZE
lxh v1 4819a7c584ed 2 minutes ago 192MB

Dockerfile可以作为源代码,直接上传到Git仓库中做版本控制哦,这样每次变化都可以追踪和回溯。

推送镜像到仓库

如果对名称不满意,或者需要改成自己的前缀,方便推送到自己的仓库,就用docker tag给容器镜像改名

1
docker tag lxh:v1 registry.myk8s.cn/library/lxh:v1

改名后,用docker push推送到远程仓库,在推送时,会自动解析你镜像前的域名,自动像那个地方推送

1
2
docker tag lxh:v1 registry.myk8s.cn/library/lxh:v1
docker push registry.myk8s.cn/library/lxh:v1

输出

1
2
3
4
5
6
7
8
9
10
11
The push refers to repository [registry.myk8s.cn/library/lxh]
b90af287caf6: Pushed
5f70bf18a086: Pushed
d1e3e4dd1aaa: Mounted from library/nginx
ccc5aac17fc4: Mounted from library/nginx
8d83f6b79143: Mounted from library/nginx
9e3c6e8c1e25: Mounted from library/nginx
9aad78ecf380: Mounted from library/nginx
bd903131a05e: Mounted from library/nginx
ea680fbff095: Mounted from library/nginx
v1: digest: sha256:9a1181a7dd9308f1e53fcfeaef742ddf51080b1135398fd96ee7ccf24177ad18 size: 2172