Docker镜像使用

镜像是 Docker 容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。

hello-world - 最小的镜像

hello-world 是 Docker 官方提供的一个镜像,通常用来验证 Docker 是否安装成功。

通过 docker pull 从 Docker Hub 下载。

[root@docker ~]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest

用 docker images 命令查看镜像的信息。

[root@docker ~]# docker images 
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
hello-world   latest    d2c94e258dcb   16 months ago   13.3kB

通过 docker run 运行。

[root@docker ~]# docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Dockerfile 是镜像的描述文件,定义了如何构建 Docker 镜像,Dockerfile 的语法简洁且可读性强。

后续会说明如何创建自己的dockerfile。

hello-world 的 Dockerfile 内容如下:

dockerhub hello-word网页:hello-world - Official Image | Docker Hub

github hello-wrold网页:hello-world/amd64/hello-world at master · docker-library/hello-world · GitHub
在这里插入图片描述

  1. FROM scratch
    此镜像从 0 开始构建。
  2. COPY hello /
    将文件“hello”复制到镜像的根目录。
  3. CMD [“/hello”]
    容器启动时,执行 /hello

镜像 hello-world 中就只有一个可执行文件 “hello”,其功能就是打印出 “Hello from Docker …” 等信息。

hello-world 虽然是一个完整的镜像,但它并没有什么实际用途。通常来说,我们希望镜像能提供一个基本的操作系统环境,用户可以根据需要安装和配置软件。这样的镜像我们称作 base 镜像。


base镜像

base 镜像有两层含义:

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

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

接下来以 CentOS 为例考察 base 镜像包含哪些内容。

下载镜像:

docker pull centos:7

查看镜像信息:

[root@docker ~]# docker images centos:7
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
centos       7         eeb6ee3f44bd   3 years ago   204MB

下面是 CentOS 镜像的 Dockerfile 的内容:
在这里插入图片描述
第二行 ADD 指令添加到镜像的 tar 包就是 CentOS 7 的 rootfs。在制作镜像时,这个 tar 包会自动解压到 / 目录下,生成 /dev, /porc, /bin 等目录。

什么是 rootfs

请添加图片描述

如图所示,Linux 的内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。

用户空间的文件系统是 rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。

对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。

Docker 支持运行多种 Linux OS

不同 Linux 发行版的区别主要就是 rootfs。

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

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

请添加图片描述
上图 Debian 和 BusyBox(一种嵌入式 Linux)上层提供各自的 rootfs,底层共用 Docker Host 的 kernel。


镜像的分层结构

Docker 支持通过扩展现有镜像,创建新的镜像。

实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。

以debian镜像为例,其Dockerfile 如下:
在这里插入图片描述
① 新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
② 安装 emacs 编辑器。
③ 安装 apache2。
④ 容器启动时运行 bash。

构建过程如下图所示:
请添加图片描述可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。

可写的容器层

当容器启动时,一个新的可写层被加载到镜像的顶部。

这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。

请添加图片描述
所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。

只有容器层是可写的,容器层下面的所有镜像层都是只读的。

镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。

如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。

对容器增删改查操作如下:

操作具体执行
创建文件新文件只能被添加在容器层中。
删除文件依据容器分层结构由上往下依次查找。找到后,在容器层中记录该删除操作。 具体实现是,UnionFS会在容器层创建一个”whiteout”文件,将被删除的文件“遮挡”起来。
修改文件依据容器分层结构由上往下依次查找。找到后,将镜像层中的数据复制到容器层进行修改,修改后的数据保存在容器层中。(copy-on-write)
读取文件依据容器分层结构由上往下依次查找。

只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改,而所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。


构建镜像

本节将介绍构建镜像的方法。同时分析构建镜像的过程。

Docker 容器文件系统

Docker 提供了两种构建镜像的方法:

  1. docker commit 命令
  2. Dockerfile 构建文件

docker commit

docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:

  1. 运行容器
  2. 修改容器
  3. 将容器保存为新的镜像

举例演示:在 ubuntu base 镜像中安装 vim并保存为新镜像。

  1. 第一步, 运行容器

    [root@docker ~]# docker run -it ubuntu
    root@8dbdff6d3d88:/#
    

    -it 参数的作用是以交互模式进入容器,并打开终端。8dbdff6d3d88 是容器的内部 ID。

  2. 安装 vim

    确认 vim 没有安装。

    root@8dbdff6d3d88:/# vim          
    bash: vim: command not found
    

    安装 vim。

    root@8dbdff6d3d88:/# apt-get update
    root@8dbdff6d3d8:/# apt-get install -y vim  
    1. Africa  2. America  3. Antarctica  4. Arctic  5. Asia  6. Atlantic  7. Australia  8. Europe  9. Indian  10. Pacific  11. Etc
    Geographic area: 5
    
    Please select the city or region corresponding to your time zone.
    
      1. Aden      12. Bangkok     23. Dili         34. Istanbul   45. Krasnoyarsk   56. Novosibirsk  67. Samarkand      78. Tokyo
      2. Almaty    13. Barnaul     24. Dubai        35. Jakarta    46. Kuala_Lumpur  57. Omsk         68. Seoul          79. Tomsk
      3. Amman     14. Beirut      25. Dushanbe     36. Jayapura   47. Kuching       58. Oral         69. Shanghai       80. Ulaanbaatar
      4. Anadyr    15. Bishkek     26. Famagusta    37. Jerusalem  48. Kuwait        59. Phnom_Penh   70. Singapore      81. Urumqi
      5. Aqtau     16. Brunei      27. Gaza         38. Kabul      49. Macau         60. Pontianak    71. Srednekolymsk  82. Ust-Nera
      6. Aqtobe    17. Chita       28. Harbin       39. Kamchatka  50. Magadan       61. Pyongyang    72. Taipei         83. Vientiane
      7. Ashgabat  18. Choibalsan  29. Hebron       40. Karachi    51. Makassar      62. Qatar        73. Tashkent       84. Vladivostok
      8. Atyrau    19. Chongqing   30. Ho_Chi_Minh  41. Kashgar    52. Manila        63. Qostanay     74. Tbilisi        85. Yakutsk
      9. Baghdad   20. Colombo     31. Hong_Kong    42. Kathmandu  53. Muscat        64. Qyzylorda    75. Tehran         86. Yangon
      10. Bahrain  21. Damascus    32. Hovd         43. Khandyga   54. Nicosia       65. Riyadh       76. Tel_Aviv       87. Yekaterinburg
      11. Baku     22. Dhaka       33. Irkutsk      44. Kolkata    55. Novokuznetsk  66. Sakhalin     77. Thimphu        88. Yerevan
    Time zone: 69
    
  3. 保存为新镜像

打开一个新窗口,查看当前运行的容器。

[root@docker ~]# docker ps
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS         PORTS     NAMES
8dbdff6d3d88   ubuntu    "/bin/bash"   2 minutes ago   Up 2 minutes             cool_darwin

上面查看结果的解释如下:

8dbdff6d3d88 是新创建容器的ID

cool_darwin 是 Docker 为我们的容器随机分配的名字。

执行 docker commit 命令将容器保存为镜像。

[root@docker ~]# docker commit cool_darwin ubuntu-with-vim    #cool_darwin是容器名,ubuntu-with-vim是新建的镜像名
sha256:ba18ae460c068f9bdba060350e64dcec4bc4af05b9918602ee34e6350d0369a4

新镜像命名为 ubuntu-with-vim。

查看新镜像的属性。

[root@docker ~]# docker images
REPOSITORY                   TAG       IMAGE ID       CREATED          SIZE
ubuntu-with-vim              latest    acefd029083b   27 minutes ago   189MB
ubuntu                       latest    edbfe74c41f8   5 weeks ago      78.1MB

从 SIZE 上看到镜像因为安装了软件而变大了。

从新镜像启动容器,验证 vim 已经可以使用。

[root@docker ~]# docker run -it ubuntu-with-vim    #ubuntu-with-vim是新创建的镜像名
root@4d071cf3014f:/# which vim
/usr/bin/vim
root@4d071cf3014f:/# vim file1

以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像。原因如下:

  1. 这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vim,还得重复前面的所有步骤。
  2. 更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。

接下来学习如何通过 Dockerfile 构建镜像。


Dockerfile构建镜像

Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。

Dockerfile基本说明:

  1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. #表示注释
  4. 每条指令都会创建一个新的镜像层并对镜像进行提交

常用参数:

docker build -f [Dockerfile路径] [构建上下文路径]
参数作用
-f 或 --file标志符,声明要使用自定义 Dockerfile
[Dockerfile路径]绝对路径或相对于构建上下文的路径(如 subdir/Dockerfile.dev)
[构建上下文路径]Docker 打包发送给守护进程的目录(通常用 . 表示当前目录)

第一个 Dockerfile

用 Dockerfile 创建上节的 ubuntu-with-vim,其内容为:

在这里插入图片描述
运行 docker build 命令构建镜像。

[root@docker ~]# cd /root
[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile .

通过 docker images 查看镜像信息。

[root@docker ~]# docker images
REPOSITORY                   TAG       IMAGE ID       CREATED          SIZE
ubuntu-with-vim-dockerfile   latest    bbc08145d011   10 minutes ago   189MB
ubuntu-with-vim              latest    acefd029083b   27 minutes ago   189MB
ubuntu                       latest    edbfe74c41f8   5 weeks ago      78.1MB

镜像 ID 为 bbc08145d011,与构建时的输出一致。


调试Dockerfile

包括 Dockerfile 在内的任何脚本和程序都会出错,因此,本节讨论如何 debug Dockerfile。

先回顾一下通过 Dockerfile 构建镜像的过程:

  1. 从 base 镜像运行一个容器。
  2. 执行一条指令,对容器做修改。
  3. 执行类似 docker commit 的操作,生成一个新的镜像层。
  4. Docker 再基于刚刚提交的镜像运行一个新容器。
  5. 重复 2-4 步,直到 Dockerfile 中的所有指令执行完毕。

从这个过程可以看出,就算 Dockerfile 由于某种原因执行到某个指令失败了,也能够得到由前一个指令成功执行构建出的镜像,这对调试 Dockerfile 非常有帮助。

构建一个dockerfile为例。Dockerfile 内容如下:
在这里插入图片描述
执行 docker build:

[root@docker ~]# ls             #查看有没有Dockerfile和testfile
Dockerfile  testfile
[root@docker ~]# vim Dockerfile            #编辑Dockerfile,写入上图的内容
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c "echo continue to build..."
COPY testfile /

[root@docker ~]# docker build -t image-debug .   #基于刚才写的Dockerfile构建镜像image-debug

在这里插入图片描述

Dockerfile 在执行第三步 RUN 指令时失败。我们可以利用busybox的镜像进行调试,方式是通过 docker run -it 启动镜像的一个容器。

在这里插入图片描述
手工执行 RUN 指令很容易定位失败的原因是 busybox 镜像中没有 bash,busybox中使用的是sh。

# 找出错误原因,修改错误
[root@docker ~]# vim Dockerfile
FROM busybox
RUN touch tmpfile
RUN /bin/sh -c "echo continue to builld..."          #将错误的/bin/bash修改为正确的/bin/sh
COPY testfile /

[root@docker ~]# docker build -t image-debug .   #基于刚才写的Dockerfile构建镜像image-debug

在这里插入图片描述
这次执行成功了。


Dockerfile常用指令

下面列出了 Dockerfile 中最常用的指令,完整列表和说明可参看官方文档。

FROM

指定 base 镜像。第一条必须是FROM

MAINTAINER

设置镜像的作者,可以是任意字符串。

COPY

将文件从 build context 复制到镜像。
COPY 支持两种形式:

  1. COPY src dest
  2. COPY [“src”, “dest”]

注意:src 只能指定 build context 中的文件或目录。

ADD

与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。

ENV

设置环境变量,环境变量可被后面的指令使用。例如:

...

ENV MY_VERSION 1.3

RUN apt-get install -y mypackage=$MY_VERSION

...
EXPOSE

指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。

VOLUME

将文件或目录声明为 volume。

WORKDIR

为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。

RUN

在容器中运行指定的命令。

CMD

容器启动时运行指定的命令。
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。

ENTRYPOINT

设置容器启动时运行的命令。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。

示例:一个较为完整的 Dockerfile

# my dockerfile
FROM busybox					  #从busybox开始构建
MAINTAINER frw       			   #声明作者信息
WORKDIR /testdir                    #设置工作目录为/testdir
RUN touch tmpfile1                  #在新镜像中创建tmpfille1
COPY ["tmpfile2","."]               #将Dockerfile文件所在目录中的tmpfile2文件拷贝到新镜像中
ADD ["passwd.tar.gz","."]           #将Dockerfile文件所在目录中的passwd.tar.gz拷贝到新镜像中并解压缩
ENV WELCOME "You are in my container,welcome!"        #设置环境变量WELOCME

完整的操作步骤如下:

[root@docker ~]# pwd                 #确定Dockerfile工作目录
/root
[root@docker ~]# ls                    #当前/root目录下空的
[root@docker ~]# touch tmpfile2             #创建空文档tmpfile2
[root@docker ~]# cp /etc/passwd .              #将/etc/passwd文件拷贝到/root
[root@docker ~]# tar -cvzf passwd.tar.gz passwd      #将passwd文件做出归档文件passwd.tar.gz
passwd
[root@docker ~]# rm passwd                  #删除passwd文件
rm: remove regular file 'passwd'? y
[root@docker ~]# vim Dockerfile          #编辑Dockerfile写入如下内容
# my dockerfile
FROM busybox
MAINTAINER frw
WORKDIR /testdir
RUN touch tmpfile1
COPY ["tmpfile2","."]
ADD ["passwd.tar.gz","."]
ENV WELCOME "You are in my container,welcome!"

[root@docker ~]# ls                       #最后目录中有三个文件
Dockerfile  passwd.tar.gz  tmpfile2

[root@docker ~]# docker build -t my-image .    #构建新镜像my-image

RUN / CMD / ENTRYPOINT

RUN、CMD 和 ENTRYPOINT 这三个 Dockerfile 指令看上去很类似,很容易混淆。本节将通过实践详细讨论它们的区别。

简单来说:

  1. RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
  2. CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参数替换。
  3. ENTRYPOINT 配置容器启动时运行的命令。

Shell 和 Exec 格式

可以用两种方式指定 RUN、CMD 和 ENTRYPOINT 要运行的命令:Shell 格式和 Exec 格式,二者在使用上有细微的区别。

Shell 格式

<instruction> <command>

例如:

RUN apt-get install python3  
CMD echo "Hello world"  
ENTRYPOINT echo "Hello world" 

当指令执行时,shell 格式底层会调用 /bin/sh -c 。

例如下面的 Dockerfile :

[root@docker ~]# vim Dockerfile
FROM busybox
ENV name frw
ENTRYPOINT echo "Hello, $name" 

用上面的Dockerfile创建镜像dockerfile1用于测试

[root@docker ~]# docker build -t dockerfile1 .

执行 docker run dockerfile1:

[root@docker ~]# docker run dockerfile1
Hello, frw

注意环境变量 name 已经被值 frw 替换。

下面来看 Exec 格式。

Exec 格式

<instruction> ["executable", "param1", "param2", ...]

例如:

RUN ["apt-get", "install", "python3"]  

CMD ["/bin/echo", "Hello world"]  

ENTRYPOINT ["/bin/echo", "Hello world"]

当指令执行时,会直接调用 ,不会被 shell 解析。
例如下面的 Dockerfile :

[root@docker ~]# vim Dockerfile
FROM busybox
ENV name frw  
ENTRYPOINT ["/bin/echo", "Hello, $name"]

用上面的Dockerfile创建镜像dockerfile2用于测试

[root@docker ~]# docker build -t dockerfile2 .

执行 docker run dockerfile2:

[root@docker ~]# docker run dockerfile2
hello, $name

注意环境变量“name”没有被替换。

如果希望使用环境变量,照如下修改

[root@docker ~]# vim Dockerfile
FROM busybox
ENV name frw  
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]

用上面的Dockerfile创建镜像dockerfile3用于测试

[root@docker ~]# docker build -t dockerfile3 .

执行 docker run dockerfile3:

[root@docker ~]# docker run dockerfile3
Hello, frw

CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。

RUN 则两种格式都可以。

RUN

RUN 指令通常用于安装应用和软件包。

RUN 在当前镜像的顶部执行命令,并通过创建新的镜像层。Dockerfile 中常常包含多个 RUN 指令。

RUN 有两种格式:

  1. Shell 格式:RUN
  2. Exec 格式:RUN [“executable”, “param1”, “param2”]

下面的Dockerfile是使用 RUN 安装多个包的例子:

[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y \  
bzr \
cvs \
git \
mercurial \
subversion

用上面的Dockerfile创建镜像dockerfile4用于测试

[root@docker ~]# docker build -t dockerfile4 .

执行 docker run -it dockerfile4:

[root@docker ~]# docker run -it dockerfile4
root@43894b9f29db:/# apt list install brz cvs git mercurial subversion
Listing... Done
brz/noble,now 3.3.5-6build2 amd64 [installed,automatic]
cvs/noble,now 2:1.12.13+real-30build1 amd64 [installed]
git/noble-updates,noble-security,now 1:2.43.0-1ubuntu7.3 amd64 [installed]
mercurial/noble-updates,now 6.7.2-1ubuntu2.2 amd64 [installed]
subversion/noble,now 1.14.3-1build4 amd64 [installed]

注意:apt-get update 和 apt-get install 被放在一个 RUN 指令中执行,这样能够保证每次安装的是最新的包。如果 apt-get install 在单独的 RUN 中执行,则会使用 apt-get update 创建的镜像层,而这一层可能是很久以前缓存的。

CMD

CMD 指令允许用户指定容器的默认执行的命令。

此命令会在容器启动且 docker run 没有指定其他命令时运行。

  1. 如果 docker run 指定了其他命令,CMD 指定的默认命令将被忽略。
  2. 如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 有效。

CMD 有三种格式:

  1. Exec 格式:CMD [“executable”,“param1”,“param2”] 这是 CMD 的推荐格式。
  2. CMD [“param1”,“param2”] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 Exec 格式。
  3. Shell 格式:CMD command param1 param2

Exec 和 Shell 格式前面已经介绍过了。
第二种格式 CMD [“param1”,“param2”] 要与 Exec 格式 的 ENTRYPOINT 指令配合使用,其用途是为 ENTRYPOINT 设置默认的参数。我们将在后面讨论 ENTRYPOINT 时举例说明。

下面看看 CMD 是如何工作的。Dockerfile 如下:

[root@docker ~]# vim Dockerfile
FROM busybox
CMD echo "Hello,world"

用上面的Dockerfile创建镜像dockerfile5用于测试

[root@docker ~]# docker build -t dockerfile5 .

运行容器docker run -it dockerfile5将输出:

[root@docker ~]# docker run -it dockerfile5
Hello,world

但当后面加上一个命令,比如docker run -it dockerfile5 /bin/sh,CMD 会被忽略掉,命令 sh 将被执行:

[root@docker ~]# docker run -it dockerfile5 /bin/sh
/ #

ENTRYPOINT

ENTRYPOINT 指令可让容器以应用程序或者服务的形式运行。

ENTRYPOINT 看上去与 CMD 很像,它们都可以指定要执行的命令及其参数。不同的地方在于 ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。

ENTRYPOINT 有两种格式:

  1. Exec 格式:ENTRYPOINT [“executable”, “param1”, “param2”] 这是 ENTRYPOINT 的推荐格式。
  2. Shell 格式:ENTRYPOINT command param1 param2

在为 ENTRYPOINT 选择格式时必须小心,因为这两种格式的效果差别很大。

Exec 格式

ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。

ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。

比如下面的 Dockerfile :

[root@docker ~]# vim Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]

用上面的Dockerfile创建镜像dockerfile6用于测试

[root@docker ~]# docker build -t dockerfile6 .

当容器通过 docker run -it dockerfile6 启动时,输出为:

[root@docker ~]# docker run -it dockerfile6
Hello world

而如果通过 docker run -it dockerfile6 frw 启动,则输出为:

[root@docker ~]# docker run -it dockerfile6 frw
Hello frw

Shell 格式

ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。

比如下面的 Dockerfile :

FROM busybox
ENTRYPOINT echo "Hello,"
CMD ["world"]

用上面的Dockerfile创建镜像dockerfile7用于测试

[root@docker ~]# docker build -t dockerfile7 .

当容器通过 docker run -it dockerfile7 启动时,输出为:

[root@docker ~]# docker run -it dockerfile7
Hello,

而如果通过 docker run -it dockerfile7 frw 启动,则输出为:

[root@docker ~]# docker run -it dockerfile7 frw
Hello,

为镜像命名

无论采用何种方式保存和分发镜像,首先都得给镜像命名。

当执行 docker build 命令时已经为镜像取了个名字,例如前面:

docker build -t ubuntu-with-vim

这里的 ubuntu-with-vim 就是镜像的名字。通过 dock images 可以查看镜像的信息。
在这里插入图片描述
这里注意到 ubuntu-with-vim 对应的是 REPOSITORY,而且还有一个叫 latest 的 TAG。

实际上一个特定镜像的名字由两部分组成:repository 和 tag。

[image name] = [repository]:[tag]

全称如下:

镜像名称格式:Image hub address/Namespace/Repository:tag即054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com/library/nginx:latest

如果执行 docker build 时没有指定 tag,会使用默认值 latest。其效果相当于:

docker build -t ubuntu-with-vim:latest

tag 常用于描述镜像的版本信息,比如 httpd 镜像:

在这里插入图片描述
当然 tag 可以是任意字符串,比如 ubuntu 镜像:

在这里插入图片描述

Docker镜像仓库:Registry

Docker仓库类似于代码仓库,它是Docker集中存放镜像文件的场所。仓库注册服务器存放着很多类镜像,每类镜像包括多个镜像文件,通过不同的标签(tag)来进行区分。

根据所存储的镜像公开分享与否,Docker仓库可以分为:

  • 公开仓库(Public)
  • 私有仓库(Private)

目前,最大的公开仓库是官方提供的Docker Hub,其中存放了数量庞大的镜像供用户下载。国内不少云服务提供商(如华为、阿里云等)也提供了仓库的本地源,可以提供稳定的国内访问。

保存和分发镜像的最直接方法就是使用 Docker Hub。

Docker Hub 是 Docker 公司维护的公共 Registry。用户可以将自己的镜像保存到 Docker Hub 免费的 repository 中。如果不希望别人访问自己的镜像,也可以购买私有 repository。

国内的公共 Registry 有华为云、阿里云等。

如需要私有镜像仓库,则可以使用Harbor。

Harbor是构建企业级私有docker镜像的仓库的开源解决方案,它是Docker Registry的更高级封装,除了提供友好的Web UI界面,角色和用户权限管理,用户操作审计等功能外,它还整合了K8s的插件(Add-ons)仓库。

harbor下载:https://github.com/goharbor/harbor/releases


导入导出本地镜像文件

保存本地镜像为文件-save

docker默认使用overlay2存储驱动存储镜像。

[root@docker ~]# docker info | grep 'Storage Driver'
 Storage Driver: overlay2

镜像存储在本地/var/lib/docker/overlay2,通过文件系统层面拷贝image,操作复杂。可以使用save命令,将本地镜像保存为单个文件,并分享给他人使用。

[root@docker ~]# docker save --help

Usage:  docker save [OPTIONS] IMAGE [IMAGE...]

Save one or more images to a tar archive (streamed to STDOUT by default)

Aliases:
  docker image save, docker save

Options:
  -o, --output string   Write to a file, instead of STDOUT

示例:

[root@docker ~]# docker save httpd -o httpd.tar         #httpd是镜像名  httpd.tar是要保存的文件名
[root@docker ~]# docker save httpd hello-world -o images.tar   #将httpd,hello-world镜像保存为images.tar
[root@docker ~]# ls
httpd.tar  images.tar
将本地镜像文件导入本地-load
[root@docker ~]# docker load --help

Usage:  docker load [OPTIONS]

Load an image from a tar archive or STDIN

Aliases:
  docker image load, docker load

Options:
  -i, --input string   Read from tar archive file, instead of STDIN
  -q, --quiet          Suppress the load output

示例:删除本地镜像,并导入本地镜像文件

[root@docker ~]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
httpd         latest    90f191b9781e   11 days ago    148MB
hello-world   latest    74cc54e27dc4   6 months ago   10.1kB
[root@docker ~]# docker rmi httpd:latest hello-world:latest
Untagged: httpd:latest
Untagged: httpd@sha256:f84fe51ff5d35124e024f51215b443b16c939b24eae747025a515200e71c7d07
Deleted: sha256:90f191b9781e01f5cd601af7b32d4ebb46770d1a98fa9170c328f9a78458b758
Deleted: sha256:8cbdaf91f3d9b2916bd85a36c608c8b652176b92266a6c51ead6c52f487b9eb5
Deleted: sha256:352b0ea3b9e318a2111200ed85540548a23873ce5ab5e6076b1dcef0110724ce
Deleted: sha256:7810a27010681e9307dae49c3673a021928add80b99e43937d79a3c5322cb862
Deleted: sha256:3c75e1b632ae15a9a14d7527fb134de5d45b2abcf3eef1cfaa057d702b957dba
Deleted: sha256:4eabd365f7baf2356d8d27adb4a182e45fca9dfc1d5533e93832c243135a5aa9
Deleted: sha256:1bb35e8b4de116e84b2ccf614cce4e309b6043bf2cd35543d8394edeaeb587e3
Untagged: hello-world:latest
Untagged: hello-world@sha256:ec153840d1e635ac434fab5e377081f17e0e15afab27beb3f726c3265039cfff
Deleted: sha256:74cc54e27dc41bb10dc4b2226072d469509f2f22f1a3ce74f4a59661a1d44602
Deleted: sha256:63a41026379f4391a306242eb0b9f26dc3550d863b7fdbb97d899f6eb89efe72
[root@docker ~]# docker images         #本地没有镜像
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE

删除本地正在使用的镜像需要选项-f

导入镜像

[root@docker ~]# docker load -i images.tar
1bb35e8b4de1: Loading layer  77.88MB/77.88MB
6c5ea8a96778: Loading layer   2.56kB/2.56kB
5f70bf18a086: Loading layer  1.024kB/1.024kB
785236f38f5b: Loading layer  11.41MB/11.41MB
5ca968893b91: Loading layer  62.99MB/62.99MB
b4558c4e936a: Loading layer  3.584kB/3.584kB
Loaded image: httpd:latest
63a41026379f: Loading layer  11.78kB/11.78kB
Loaded image: hello-world:latest
[root@docker ~]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
httpd         latest    90f191b9781e   11 days ago    148MB
hello-world   latest    74cc54e27dc4   6 months ago   10.1kB

说明:如果本地镜像名与导入的镜像重名,则本地的镜像会被覆盖。

[root@docker ~]# docker rm -f $(docker ps -aq)      #删除所有容器

[root@docker ~]# docker rmi -f $(docker images -aq)   #删除所有镜像

Docker镜像小结

本文首先讨论了镜像的分层结构,然后学习了如何构建镜像,包括Dockerfile的格式和各种常用指令。

镜像的常用操作子命令如下:

images 显示镜像列表

history 显示镜像构建历史

commit 从容器创建新镜像

build 从 Dockerfile 构建镜像

tag 给镜像打 tag

pull 从 registry 下载镜像

push 将 镜像 上传到 registry

rmi 删除 Docker host 中的镜像

search 搜索 Docker Hub 中的镜像

下面简单讲解前文未提到的子命令 rmi 和 search。

rmi

rmi 只能删除 host 上的镜像,不会删除 registry 的镜像。

如果一个镜像对应了多个 tag,只有当最后一个 tag 被删除时,镜像才被真正删除。例如 host 中 busybox镜像有三个 tag:

[root@docker ~]# docker images busybox
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
busybox      latest    6fd955f66c23   16 months ago   4.26MB

[root@docker ~]# docker tag busybox:latest busybox:v1

[root@docker ~]# docker tag busybox:latest busybox:v2

[root@docker ~]# docker images busybox
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
busybox      latest    6fd955f66c23   16 months ago   4.26MB
busybox      v1        6fd955f66c23   16 months ago   4.26MB
busybox      v2        6fd955f66c23   16 months ago   4.26MB

删除其中 busybox:latest 只是删除了 latest tag,镜像本身没有删除。

在这里插入图片描述
只有当 busybox:v1和busybox:v2也被删除时,整个镜像才会被删除。
在这里插入图片描述
search

search 可以直接在命令行中搜索 Docker Hub 中的镜像。

在这里插入图片描述
如果想知道镜像都有哪些 tag,还是得访问 Docker Hub。

更多推荐