docker镜像

1 最小的镜像

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

本章内容安排如下:

  1. 首先通过研究几个典型的镜像,分析镜像的内部结构。
  2. 然后学习如何构建自己的镜像。
  3. 最后介绍怎样管理和分发镜像。

镜像的内部结构

为什么我们要讨论镜像的内部结构?

如果只是使用镜像,当然不需要了解,直接通过 docker 命令下载和运行就可以了。

但如果我们想创建自己的镜像,或者想理解 Docker 为什么是轻量级的,就非常有必要学习这部分知识了。

我们从一个最小的镜像开始吧。

hello-world - 最小的镜像

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

我们先通过 docker pull 从 Docker Hub 下载它。

shell 复制代码
[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 命令查看镜像的信息。

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

hello-world 镜像竟然还不到 14KB!

通过 docker run 运行。

shell 复制代码
[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/

其实我们更关心 hello-world 镜像包含哪些内容。

Dockerfile 是镜像的描述文件,定义了如何构建 Docker 镜像。Dockerfile 的语法简洁且可读性强,后面我们会专门讨论如何编写 Dockerfile。

hello-world 的 Dockerfile 内容如下:

只有短短三条指令。

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

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

/hello 就是文件系统的全部内容,连最基本的 /bin,/usr, /lib, /dev 都没有。

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

我们下一节讨论 base 镜像。


2 base镜像

上一节我们介绍了最小的 Docker 镜像,本节讨论 base 镜像。

base 镜像有两层含义:

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

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

我们以 CentOS 为例考察 base 镜像包含哪些内容。

下载镜像:

docker pull centos:7

查看镜像信息:

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

镜像大小不到 300MB。

等一下!

一个 CentOS 才 204MB ?

平时我们安装一个 CentOS 至少都有几个 GB,怎么可能才 204MB !

相信这是几乎所有 Docker 初学者都会有的疑问,包括我自己。下面我们来解释这个问题。

Linux 操作系统由内核空间和用户空间组成。如下图所示:

rootfs

1

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

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

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

而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。

我们平时安装的 CentOS 除了 rootfs 还会选装很多软件、服务、图形桌面等,需要好几个 GB 就不足为奇了。

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

下面是 CentOS 镜像的 Dockerfile 的内容:

第二行 ADD 指令添加到镜像的 tar 包就是 CentOS 7 的 rootfs。在制作镜像时,这个 tar 包会自动解压到 / 目录下,生成 /dev, /porc, /bin 等目录。

注:可在 Docker Hub 的镜像描述页面中查看 Dockerfile 。

支持运行多种 Linux OS

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

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

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

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

这里需要说明的是:

  1. base 镜像只是在用户空间与发行版一致,kernel 版本与发型版是不同的。

    例如 ubuntu使用 3.x.x 的 kernel,如果 Docker Host 是 CentOS Stream 8(比如我们的实验环境),那么在 CentOS 容器中使用的实际是是 Host 4.18.0 的 kernel。

    bash 复制代码
    [root@docker ~]# uname -r
    4.18.0-553.6.1.el8.x86_64
    #Host kernel 为 4.18.0

    启动一个ubuntu,ubuntu内核正常与host os不一致

    bash 复制代码
    [root@docker ~]# docker run -it ubuntu
    root@4264749aa4af:/# uname -r
    4.18.0-553.6.1.el8.x86_64         #容器ubuntu用的内核就是docker host内核
  2. 容器只能使用 Host 的 kernel,并且不能修改。

    所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。

下一节我们讨论镜像的分层结构。


3 镜像的分层结构

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

实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,Dockerfile 如下:

① 新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。

② 安装 emacs 编辑器。

③ 安装 apache2。

④ 容器启动时运行 bash。

构建过程如下图所示:

可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。

问什么 Docker 镜像要采用这种分层结构呢?

最大的一个好处就是 - 共享资源

比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。

这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc 是否也会被修改?

答案是不会!

修改会被限制在单个容器内。

这就是我们接下来要学习的容器 Copy-on-Write 特性。

可写的容器层

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

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

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

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

下面我们深入讨论容器层的细节。

镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。

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

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

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

这样就解释了我们前面提出的问题:容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享

理解了镜像的原理和结构,下一节我们学习如何构建镜像。


4 构建镜像

对于 Docker 用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的 Docker 官方镜像或其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用。

使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为 Docker 的工程师知道如何更好的在容器中运行软件。

当然,某些情况下我们也不得不自己构建镜像,比如:

  1. 找不到现成的镜像,比如自己开发的应用程序。
  2. 需要在镜像中加入特定的功能,比如官方镜像几乎都不提供 ssh。

所以本节我们将介绍构建镜像的方法。同时分析构建的过程也能够加深我们对前面镜像分层结构的理解。

Docker 容器文件系统

描述:从下面的图片可以看见出以下几点:

  • Docker 镜像代表了容器的文件系统里的内容,是容器的基础,镜像一般是通过 Dockerfile 生成的;
  • Docker 的镜像是分层的,所有的镜像(除了基础镜像)都是在之前镜像的基础上加上自己这层的内容生成的;
  • Docker 中每一层镜像的元数据都是存在 json 文件中的,除了静态的文件系统之外,还会包含动态的数据;
  • Docker 镜像生产容器后会在此基础之上加入挂载点到安装Docker宿主机文件系统之中,并提供一个读写层(Read-Write Layer),所以容器进程的所有操作都在读写层进行;

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

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

docker commit

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

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

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

  1. 第一步, 运行容器

    shell 复制代码
    [root@docker ~]# docker run -it ubuntu
    root@8dbdff6d3d88:/#

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

  2. 安装 vim

    确认 vim 没有安装。

    shell 复制代码
    root@8dbdff6d3d88:/# vim          
    bash: vim: command not found

    安装 vim。

    shell 复制代码
    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. 保存为新镜像

在新窗口中查看当前运行的容器。

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

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

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

shell 复制代码
[root@docker ~]# docker commit cool_darwin ubuntu-with-vim
sha256:ba18ae460c068f9bdba060350e64dcec4bc4af05b9918602ee34e6350d0369a4

新镜像命名为 ubuntu-with-vim

查看新镜像的属性。

bash 复制代码
[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 已经可以使用。

shell 复制代码
[root@docker ~]# docker run -it ubuntu-with-vim
root@4d071cf3014f:/# which vim
/usr/bin/vim
root@4d071cf3014f:/# vim file1

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

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

既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?

原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。

下一节我们学习如何通过 Dockerfile 构建镜像。


5 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 命令构建镜像并详细分析每个细节。

bash 复制代码
[root@docker ~]# pwd
/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 .

① 当前目录为 /root。

② Dockerfile 准备就绪。

③ 运行 docker build 命令,-t 将新镜像命名为 ubuntu-with-vim-dockerfile,命令末尾的 . 指明 build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过 -f 参数指定 Dockerfile 的位置。

④ 从这步开始就是镜像真正的构建过程。 首先 Docker 将 build context 中的所有文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。

Dockerfile 中的 ADD、COPY 等命令可以将 build context 中的文件添加到镜像。此例中,build context 为当前目录 /root,该目录下的所有文件和子目录都会被发送给 Docker daemon。

所以,使用 build context 就得小心了,不要将多余文件放到 build context,特别不要把 //usr 作为 build context,否则构建过程会相当缓慢甚至失败。

⑤ Step 1:执行 FROM,将 ubuntu 作为 base 镜像。

⑥ Step 2:执行 RUN,安装 vim

⑦ 镜像构建成功。

⑧ 镜像重命名为ubuntu-with-vim-dockerfile

通过 docker images 查看镜像信息。

shell 复制代码
[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,与构建时的输出一致。

在上面的构建过程中,我们要特别注意指令 RUN 的执行过程。Docker 会在启动的临时容器中执行操作,并通过 commit 保存为新的镜像。

查看镜像分层结构

ubuntu-with-vim-dockerfile 是通过在 base 镜像的顶部添加一个新的镜像层而得到的。

这个新镜像层的内容由 RUN apt-get update && apt-get install -y vim 生成。这一点我们可以通过 docker history 命令验证。

docker history 会显示镜像的构建历史,也就是 Dockerfile 的执行过程。

ubuntu-with-vi-dockerfile 与 ubuntu 镜像相比,确实只是多了顶部的一层 bbc08145d011,由 apt-get 命令创建,大小为 111MB。docker history 也向我们展示了镜像的分层结构,每一层由上至下排列。

注: 表示无法获取 IMAGE ID,通常从 Docker Hub 下载的镜像会有这个问题。

下一节我们学习镜像的缓存特性。


6 镜像的缓存特性

上一节我们学习了镜像的分层结构,接下来讨论镜像的缓存特性。

Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。

举例说明。

在前面的 Dockerfile 中添加一点新内容,往镜像中复制一个文件:

bash 复制代码
[root@docker ~]# pwd
/root
[root@docker ~]# ls
Dockerfile
[root@docker ~]# touch testfile
[root@docker ~]# ls
Dockerfile  testfile
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
COPY testfile /
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-2 .

① 确保 testfile 已存在。可以通过touch创建

重点在这里:之前已经运行过相同的 RUN 指令,这次直接使用缓存中的镜像层

③ 执行 COPY 指令。

其过程是启动临时容器,复制 testfile,提交新的镜像层5561217926be,删除临时容器。

在 ubuntu-with-vi-dockerfile 镜像上直接添加一层就得到了新的镜像 ubuntu-with-vim-dockerfile-2。

如果我们希望在构建镜像时不使用缓存,可以在 docker build 命令中加上 --no-cache 参数。

Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。

也就是说,如果我们改变 Dockerfile 指令的执行顺序,或者修改或添加指令,都会使缓存失效。

举例说明,比如交换前面 RUN 和 COPY 的顺序:

虽然在逻辑上这种改动对镜像的内容没有影响,但由于分层的结构特性,Docker 必须重建受影响的镜像层。

bash 复制代码
[root@docker ~]# vim Dockerfile
FROM ubuntu
COPY testfile /
RUN apt-get update && apt-get install -y vim
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-3 .

从上面的输出可以看到[2/3],[3/3]都没有使用缓存,最后生成了新的镜像层 33f20b2ec8fd,缓存已经失效。

除了构建时使用缓存,Docker 在下载镜像时也会使用。例如我们下载 httpd 镜像。

docker pull 命令输出显示第一层(base 镜像)已经存在,不需要下载。

由 Dockerfile 可知 httpd 的 base 镜像为 debian,正好之前已经下载过 debian 镜像,所以有缓存可用。通过 docker history 可以进一步验证。


7 调试Dockerfile

包括 Dockerfile 在内的任何脚本和程序都会出错。有错并不可怕,但必须有办法排查,所以本节讨论如何 debug Dockerfile。

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

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

从这个过程可以看出,如果 Dockerfile 由于某种原因执行到某个指令失败了,我们也将能够得到前一个指令成功执行构建出的镜像,这对调试 Dockerfile 非常有帮助。我们可以运行最新的这个镜像定位指令失败的原因。

我们来看一个调试的例子。Dockerfile 内容如下:

执行 docker build

bash 复制代码
[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。虽然这是个极其简单的例子,但它很好地展示了调试 Dockerfile 的方法。

到这里相信大家对 Dockerfile 的功能和使用流程有了比较完整的印象,但还没有系统学习 Dockerfile 的各种指令和实际用法,下节会开始这个主题。


8 Dockerfile常用指令

是时候系统学习 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

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

dockerfile 复制代码
...

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:

dockerfile 复制代码
[root@docker ~]# cat Dockerfile
# my dockerfile
FROM busybox
MAINTAINER 6946630@qq.com
WORKDIR /testdir
RUN touch tmpfile1
COPY ["tmpfile2","."]
ADD ["passwd.tar.gz","."]
ENV WELCOME "You are in my container,welcome!"

注:Dockerfile 支持以"#"开头的注释。

构建镜像:

tmpfile2 用touch命令产生

bash 复制代码
[root@docker ~]# touch tmpfile2

passwd.tar.gz 用tar命令产生

bash 复制代码
[root@docker ~]# cp /etc/passwd .
[root@docker ~]# tar -cvzf passwd.tar.gz passwd
passwd

完整的操作步骤如下:

bash 复制代码
[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 6946630@qq.com
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

① 构建前确保 build context 中存在需要的文件。

② 依次执行 Dockerfile 指令,完成构建。

运行容器,验证镜像内容:

① 进入容器,当前目录即为 WORKDIR。

如果 WORKDIR 不存在,Docker 会自动为我们创建。

② WORKDIR 中保存了我们希望的文件和目录:

文件passwd:由 ADD 指令从 build context 复制的归档文件passwd.tar.gz,已经自动解压。

文件 tmpfile1:由 RUN 指令创建。

文件 tmpfile2:由 COPY 指令从 build context 复制。

③ ENV 指令定义的环境变量已经生效。

在上面这些指令中,RUN、CMD、ENTRYPOINT 很重要且容易混淆,下节专门讨论。


9 RUN vs CMD vs ENTRYPOINT

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

简单的说:

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

下面我们详细分析。

Shell 和 Exec 格式

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

Shell 格式

bash 复制代码
<instruction> <command>

例如:

dockerfile 复制代码
RUN apt-get install python3  

CMD echo "Hello world"  

ENTRYPOINT echo "Hello world" 

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

例如下面的 Dockerfile :

dockerfile 复制代码
FROM busybox
ENV name gqd 
ENTRYPOINT echo "Hello, $name" 

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

bash 复制代码
[root@docker ~]# docker build -t dockerfile1 .

执行 docker run dockerfile1:

bash 复制代码
[root@docker ~]# docker run dockerfile1
Hello,gqd

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

下面来看 Exec 格式。

Exec 格式

bash 复制代码
<instruction> ["executable", "param1", "param2", ...]

例如:

dockerfile 复制代码
RUN ["apt-get", "install", "python3"]  

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

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

当指令执行时,会直接调用 ,不会被 shell 解析。

例如下面的 Dockerfile :

dockerfile 复制代码
FROM busybox
ENV name gqd  
ENTRYPOINT ["/bin/echo", "Hello, $name"]

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

bash 复制代码
[root@docker ~]# docker build -t dockerfile2 .

执行 docker run dockerfile2:

bash 复制代码
[root@docker ~]# docker run dockerfile2
hello,$name

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

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

dockerfile 复制代码
FROM busybox
ENV name gqd  
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]

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

bash 复制代码
[root@docker ~]# docker build -t dockerfile3 .

执行 docker run dockerfile3:

bash 复制代码
[root@docker ~]# docker run dockerfile3
Hello, gqd

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

RUN

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

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

RUN 有两种格式:

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

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

dockerfile 复制代码
FROM ubuntu
RUN apt-get update && apt-get install -y \  
bzr \
cvs \
git \
mercurial \
subversion

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

bash 复制代码
[root@docker ~]# docker build -t dockerfile4 .

执行 docker run -it dockerfile4:

bash 复制代码
[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 如下:

dockerfile 复制代码
FROM busybox
CMD echo "Hello,world"

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

bash 复制代码
[root@docker ~]# docker build -t dockerfile5 .

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

bash 复制代码
[root@docker ~]# docker run -it dockerfile5
Hello,world

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

bash 复制代码
[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 :

dockerfile 复制代码
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]

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

bash 复制代码
[root@docker ~]# docker build -t dockerfile6 .

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

bash 复制代码
[root@docker ~]# docker run -it dockerfile6
Hello world

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

bash 复制代码
[root@docker ~]# docker run -it dockerfile6 gqd
Hello gqd

Shell 格式

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

比如下面的 Dockerfile :

dockerfile 复制代码
FROM busybox
ENTRYPOINT echo "Hello,"
CMD ["world"]

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

bash 复制代码
[root@docker ~]# docker build -t dockerfile7 .

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

bash 复制代码
[root@docker ~]# docker run -it dockerfile7
Hello,

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

bash 复制代码
[root@docker ~]# docker run -it dockerfile7 gqd
Hello,

Shell 格式

最佳实践

  1. 使用 RUN 指令安装应用和软件包,构建镜像。
  2. 如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。
  3. 如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。

到这里,我们已经具备编写 Dockerfile 的能力了。如果大家还觉得没把握,推荐一个快速掌握 Dockerfile 的方法:去 Docker Hub 上参考那些官方镜像的 Dockerfile

好了,我们已经学习完如何创建自己的 image,下一节讨论如何分发 image。

Dockerfile案例:配置SSH镜像

项目背景:官方下载的centos镜像默认不带ssh,管理起来不方便,自己制作一个带SSH功能的centos镜像

创建dockerfile

dockerfile 复制代码
[root@docker ~]# vim centos.ssh.dockerfile
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
RUN minorver=8.4.2105 && \
sed -e "s|^mirrorlist=|#mirrorlist=|g" \
-e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" \
-i.bak \
/etc/yum.repos.d/CentOS-*.repo
RUN yum install -y openssh-server
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
RUN echo "root:huawei" | chpasswd
EXPOSE 22
CMD ["/usr/sbin/sshd","-D"]

构建镜像

bash 复制代码
[root@docker ~]# docker build -t centos:ssh -f centos.ssh.dockerfile .

[+] Building 0.6s (10/10) FINISHED                                                                                                                 docker:default
 => [internal] load build definition from centos.ssh.dockerfile                                                                                              0.0s
 => => transferring dockerfile: 604B                                                                                                                         0.0s
 => [internal] load metadata for docker.io/library/centos:8.4.2105                                                                                           0.0s
 => [internal] load .dockerignore                                                                                                                            0.0s
 => => transferring context: 2B                                                                                                                              0.0s
 => [1/6] FROM docker.io/library/centos:8.4.2105                                                                                                             0.0s
 => CACHED [2/6] RUN minorver=8.4.2105 && sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=http://mirror.centos.org/$contentdir/$releasever|baseurl=h  0.0s
 => CACHED [3/6] RUN yum install -y openssh-server                                                                                                           0.0s
 => CACHED [4/6] RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key                                                                                          0.0s
 => [5/6] RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key                                                                                             0.3s
 => [6/6] RUN echo "root:huawei" | chpasswd                                                                                                                  0.3s
 => exporting to image                                                                                                                                       0.0s
 => => exporting layers                                                                                                                                      0.0s
 => => writing image sha256:cc138c4d3c36fe82eab32dd80549707c8bfe99ddcb6d3882319a10283bb1a864                                                                 0.0s
 => => naming to docker.io/library/centos:ssh                                                          

查看现象

bash 复制代码
[root@docker ~]# docker history centos:ssh
IMAGE          CREATED             CREATED BY                                      SIZE      COMMENT
cc138c4d3c36   36 seconds ago      CMD ["/usr/sbin/sshd" "-D"]                     0B        buildkit.dockerfile.v0
<missing>      36 seconds ago      EXPOSE map[22/tcp:{}]                           0B        buildkit.dockerfile.v0
<missing>      36 seconds ago      RUN /bin/sh -c echo "root:huawei" | chpasswd...   1.77kB    buildkit.dockerfile.v0
<missing>      36 seconds ago      RUN /bin/sh -c ssh-keygen -t ecdsa -f /etc/s...   695B      buildkit.dockerfile.v0
<missing>      47 minutes ago      RUN /bin/sh -c ssh-keygen -t rsa -f /etc/ssh...   3.18kB    buildkit.dockerfile.v0
<missing>      47 minutes ago      RUN /bin/sh -c yum install -y openssh-server...   51.9MB    buildkit.dockerfile.v0
<missing>      About an hour ago   RUN /bin/sh -c minorver=8.4.2105 && sed -e "...   17.6kB    buildkit.dockerfile.v0
<missing>      About an hour ago   MAINTAINER gaoqiaodong                          0B        buildkit.dockerfile.v0
<missing>      3 years ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>      3 years ago         /bin/sh -c #(nop)  LABEL org.label-schema.sc...   0B
<missing>      3 years ago         /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0...   231MB

测试

bash 复制代码
#基于刚才dockerfile创建的镜像centos:ssh创建容器sshtest
[root@docker ~]# docker run -d -p 2022:22 --name sshtest centos:ssh
73d963d15407a1e73097540bb320b9edf05b468001bd707abf01bc7be5e54bcb

#创建出来的容器
[root@docker ~]# docker ps
CONTAINER ID   IMAGE        COMMAND               CREATED         STATUS         PORTS                                   NAMES
73d963d15407   centos:ssh   "/usr/sbin/sshd -D"   6 seconds ago   Up 5 seconds   0.0.0.0:2022->22/tcp, :::2022->22/tcp   sshtest

#ssh登录容器测试ssh,能够成功登录
[root@docker ~]# ssh root@localhost -p 2022
The authenticity of host '[localhost]:2022 ([::1]:2022)' can't be established.
ECDSA key fingerprint is SHA256:z1owYLOuClnbPrZwXxgy1jcItQT1k+QX6LxosydT64A.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts.
root@localhost's password:
"System is booting up. Unprivileged users are not permitted to log in yet. Please come back later. For technical details, see pam_nologin(8)."
[root@73d963d15407 ~]#

Dockerfile案例:自定义httpd镜像

创建dockerfile

dockerfile 复制代码
[root@docker ~]# vim httpd.dockerfile
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
RUN minorver=8.4.2105 \
&& sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" -i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum install -y httpd
ADD index.html /var/www/html/
RUN systemctl enable httpd
EXPOSE 80
CMD /usr/sbin/httpd -DFOREGROUND
[root@docker ~]# echo Hello World > index.html

构建镜像

bash 复制代码
[root@docker ~]# docker build -t httpd:centos -f httpd.dockerfile .

查看现象

bash 复制代码
[root@docker ~]# docker history httpd:centos
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
e50670251b11   24 seconds ago   CMD ["/bin/sh" "-c" "/usr/sbin/httpd -DFOREG...   0B        buildkit.dockerfile.v0
<missing>      24 seconds ago   EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
<missing>      24 seconds ago   RUN /bin/sh -c systemctl enable httpd # buil...   0B        buildkit.dockerfile.v0
<missing>      25 seconds ago   ADD index.html /var/www/html/ # buildkit        12B       buildkit.dockerfile.v0
<missing>      25 seconds ago   RUN /bin/sh -c yum install -y httpd # buildk...   57MB      buildkit.dockerfile.v0
<missing>      13 minutes ago   RUN /bin/sh -c minorver=8.4.2105 && sed -e "...   17.6kB    buildkit.dockerfile.v0
<missing>      13 minutes ago   MAINTAINER gaoqiaodong                          0B        buildkit.dockerfile.v0
<missing>      3 years ago      /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>      3 years ago      /bin/sh -c #(nop)  LABEL org.label-schema.sc...   0B
<missing>      3 years ago      /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0...   231MB

测试

bash 复制代码
#基于刚才dockerfile创建的镜像httpd:centos创建容器myweb
[root@docker ~]# docker run -d -p 80:80 --name myweb httpd:centos
1e0b0631cf708bcc0a162d56b936a12cd06c9bcdebcc2594b23c2fbee3ed8894

#创建出来的容器
[root@docker ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED              STATUS              PORTS                                   NAMES
1e0b0631cf70   httpd:centos   "/bin/sh -c '/usr/sb..."   About a minute ago   Up About a minute   0.0.0.0:80->80/tcp, :::80->80/tcp       myweb

#访问测试
[root@docker ~]# curl localhost
Hello World

Dockerfile案例:自定义mycentosjava8

项目背景:java工程师需要我们给他发个带JAVA的centos

要求CentOS8镜像具备vim+ifconfig+jdk8

JDK的下载地址:https://www.oracle.com/java/technologies/downloads/#java8

编写Dockerfile文件

bash 复制代码
[root@docker myfile]# pwd
/myfile
[root@docker myfile]# vim Dockerfile
dockerfile 复制代码
FROM centos:8.4.2105
MAINTAINER gaoqiaodong<6946630@qq.com>
 
ENV MYPATH /usr/local
WORKDIR $MYPATH

#配置yum源
RUN minorver=8.4.2105 \
&& sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" -i.bak /etc/yum.repos.d/CentOS-*.repo
#安装vim编辑器
RUN yum -y install vim
#安装ifconfig命令查看网络IP
RUN yum -y install net-tools
#安装java8及lib库
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
#ADD 是相对路径jar,把jdk-8u461-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u461-linux-x64.tar.gz /usr/local/java/
#配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_461
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
 
EXPOSE 80
 
CMD echo $MYPATH
CMD echo "success--------------ok"
CMD /bin/bash
bash 复制代码
# 将jdk-8u461-linux-x64.tar.gz与Dockerfile放到同一目录
[root@docker myfile]# ls
Dockerfile  jdk-8u461-linux-x64.tar.gz
bash 复制代码
# 构建镜像为centosjava8:461
[root@docker myfile]# docker build -t centosjava8:461 .

查看构建的镜像

bash 复制代码
[root@docker myfile]# docker images
REPOSITORY    TAG        IMAGE ID       CREATED         SIZE
centosjava8   461        8097b762c0a5   4 minutes ago   537MB

用创建的镜像运行容器测试

bash 复制代码
[root@docker myfile]# docker run -it centosjava8:461 /bin/bash
[root@6b2767bcdf24 local]# pwd              #测试了ENV和WORKDIR
/usr/local
[root@6b2767bcdf24 local]# java -version         #测试java是否安装
java version "1.8.0_461"
Java(TM) SE Runtime Environment (build 1.8.0_461-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.461-b11, mixed mode)
[root@6b2767bcdf24 local]# ifconfig           #测试net-tools是否安装
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.3  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:03  txqueuelen 0  (Ethernet)
        RX packets 8  bytes 656 (656.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@6b2767bcdf24 local]#vim file1           #c
相关推荐
何故染尘優7 小时前
docker学习笔记,从入门开始!
笔记·学习·docker
不爱笑的良田8 小时前
从零开始的云原生之旅(一):把 Go 应用塞进 Docker
docker·云原生·golang
不爱笑的良田8 小时前
从零开始的云原生之旅(四):K8s 工作负载完全指南
云原生·容器·kubernetes
java_logo10 小时前
Docker 部署 Rocky Linux 全流程教程
linux·运维·服务器·docker·容器·1024程序员节
2501_9387918310 小时前
服务器镜像安全:Docker 镜像漏洞扫描(Trivy)与基础镜像优化
服务器·安全·docker
老司机张师傅10 小时前
【微服务实战之Docker容器】第十章-compose容器编排
docker·微服务·架构
ghie909010 小时前
利用 Docker 和 Kubernetes 实现微服务部署
docker·微服务·kubernetes
奔跑吧 android12 小时前
【Docker】【03.使用docker搭建ubuntu20.04 Qt5.12 开发环境】
qt·docker·ubuntu20.04·qt5.12
Gss77713 小时前
Kubernetes 实战入门核心内容总结
容器·kubernetes