Docker镜像怎么生成

发布时间:2022-05-26 15:31:21 作者:iii
来源:亿速云 阅读:289

这篇文章主要介绍“Docker镜像怎么生成”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Docker镜像怎么生成”文章能帮助大家解决问题。

 docker 有两种方式来创建一个容器镜像:

1. docker build 生成镜像

1.1 生成过程实例

 在使用 dockerfile 创建容器之前,需要先准备一个 dockerfile 文件,然后运行 docker build 命令来创建镜像。我们通过下面的例子来看看docker 创建容器的过程。

 from ubuntu:14.04

maintainer sammy "sammy@sammy.com"

run apt-get update

run apt-get -y install ntp

expose 5555

cmd ["/usr/sbin/ntpd"]

这是一个非常简单的dockerfile,它的目的是基于 ubuntu 14.04 基础镜像安装 ntp 从而生成一个新的镜像。看看其过程:

root@devstack:/home/sammy/ntponubuntu# docker build -t sammy_ntp2 .
sending build context to docker daemon 2.048 kb
step 1 : from ubuntu:14.04
 ---> 4a725d3b3b1c
step 2 : maintainer sammy "sammy@sammy.com"
 ---> using cache
 ---> c4299e3f774c
step 3 : run apt-get update
 ---> using cache
 ---> 694a19d54103
step 4 : run apt-get -y install ntp
 ---> running in 9bd153c65a76
reading package lists...
...
fetched 561 kb in 10s (51.1 kb/s)
selecting previously unselected package libedit2:amd64.
(reading database ... 11558 files and directories currently installed.)
...
processing triggers for libc-bin (2.19-0ubuntu6.9) ...
processing triggers for ureadahead (0.100.0-16) ...
 ---> 9cc05cf6f48d
removing intermediate container 9bd153c65a76
step 5 : expose 5555
 ---> running in eb4633151d98
 ---> f5c96137bec9
removing intermediate container eb4633151d98
step 6 : cmd /usr/sbin/ntpd
 ---> running in e81b1eae3678
 ---> af678df648bc
removing intermediate container e81b1eae3678
successfully built af678df648bc

dockerfile 中的每个步骤都会对应每一个 docker build 输出中的 step。

step 1:from ubuntu:14.04

获取基础镜像 ubuntu:14.04. docker 首先会在本地查找,如果找到了,则直接利用;否则从 docker registry 中下载。在第一次使用这个基础镜像的时候,docker 会从 docker hub 中下载这个镜像,并保存在本地:

step 1 : from ubuntu:14.04
14.04: pulling from library/ubuntu
862a3e9af0ae: pull complete
6498e51874bf: pull complete
159ebdd1959b: pull complete
0fdbedd3771a: pull complete
7a1f7116d1e3: pull complete
digest: sha256:5b5d48912298181c3c80086e7d3982029b288678fccabf2265899199c24d7f89
status: downloaded newer image for ubuntu:14.04
 ---> 4a725d3b3b1c

以后再使用的时候就直接使用这个镜像而不再需要下载了。

step 2:maintainer sammy ""

本例中依然是从 cache 中环境新的镜像。在第一次的时候,docker 会创建一个临时的容器 1be8f33c1846,然后运行 maintainer 命令,再使用 docker commit 生成新的镜像

step 2 : maintainer sammy "sammy@sammy.com"
 ---> running in 1be8f33c1846
 ---> c4299e3f774c

通过这个临时容器的过程(create -> commit -> destroy),生成了新的镜像 c4299e3f774c:

2016-09-16t21:58:09.010886393+08:00 container create 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=focused_poitras)
2016-09-16t21:58:09.060071206+08:00 container commit 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (comment=, image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=focused_poitras)
2016-09-16t21:58:09.071988068+08:00 container destroy 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=focused_poitras)

这个镜像是基于 ubuntu 14.04 基础镜像生成的,layers 没有变化,只是元数据 cmd 发生了改变:

"cmd": [
        "/bin/sh",
        "-c",
        "#(nop) ",
        "maintainer sammy \"sammy@sammy.com\""
      ]

因此可以认为只是镜像的元数据发生了改变。生成的新的镜像作为中间镜像会被保存在 cache 中。

 step 3: run apt-get update

本例中docker 仍然从缓存中获取了镜像。在第一次的时候,docker 仍然是通过创建临时容器在执行 docker commit 的方式来创建新的镜像:
step 3 : run apt-get update

 ---> running in 8b3b97af3bd7
ign http://archive.ubuntu.com trusty inrelease
get:1 http://archive.ubuntu.com trusty-updates inrelease [65.9 kb]
...
get:22 http://archive.ubuntu.com trusty/universe amd64 packages [7589 kb]
fetched 22.2 mb in 16min 21s (22.6 kb/s)
reading package lists...
 ---> 694a19d54103
removing intermediate container 8b3b97af3bd7

通过以上步骤,生成了新的中间镜像 694a19d54103,它也会被保存在缓存中。你可以使用 docker inspect 694a19d54103 命令查看该中间镜像,但是无法在docker images 列表中找到它,这是因为 docker images 默认隐藏了中间状态的镜像,因此你需要使用 docker images -a 来获取它:

root@devstack:/home/sammy# docker images -a | grep 694a19d54103
<none>         <none>       694a19d54103    11 hours ago    210.1 mb

该镜像和原始镜像相比,多了一个 layer,它保存的是 apt-get update 命令所带来的变化:

"rootfs": {
      "type": "layers",
      "layers": [
        "sha256:102fca64f92471ff7fca48e55807ae2471502822ba620292b0a06ebcab907cf4",
        "sha256:24fe29584c046f2a88f7f566dd0bf7b08a8c0d393dfad8370633b0748bba8cbc",
        "sha256:530d731d21e1b1bbe356d70d3bca4d72d76fed89e90faab271d29bd58c8ccea4",
        "sha256:344f56a35ff9fc747ada7d2b88bd21c49b2ec404872662cbaf0a65201873c0c6",
        "sha256:ffb6ddc7582aa7e2e73f102df3ffcd272e59b7cf3f7abefe08d11a7c85dea53a",
        "sha256:a1afe95c99b39c30b5c1d3e8fda451bd3f066be304616197f1046e64cf6cda93" #这一层是新加的
      ]
    }

step 4: run apt-get -y install ntp

和上面 step 3 过程一样,这个步骤也会通过创建临时容器,执行该命令,再使用 docker commit 命令生成一个中间镜像 9cc05cf6f48d 。和上面步骤生成的镜像相比,它又多了一层:

root@devstack:/home/sammy# docker images -a | grep 9cc05cf6f48d
<none>         <none>       9cc05cf6f48d    10 hours ago    212.8 mb
root@devstack:/home/sammy# docker inspect --format={{'.rootfs.layers'}} 9cc05cf6f48d
[sha256:102fca64f92471ff7fca48e55807ae2471502822ba620292b0a06ebcab907cf4 
sha256:24fe29584c046f2a88f7f566dd0bf7b08a8c0d393dfad8370633b0748bba8cbc 
sha256:530d731d21e1b1bbe356d70d3bca4d72d76fed89e90faab271d29bd58c8ccea4 
sha256:344f56a35ff9fc747ada7d2b88bd21c49b2ec404872662cbaf0a65201873c0c6 
sha256:ffb6ddc7582aa7e2e73f102df3ffcd272e59b7cf3f7abefe08d11a7c85dea53a 
sha256:a1afe95c99b39c30b5c1d3e8fda451bd3f066be304616197f1046e64cf6cda93 
sha256:a93086f33a2b7ee18eec2454b468141f95a403f5081284b6f177f83cdb3d54ba]

step 5: expose 5555

 这一步和上面的 step 2 一样,docker 生成了一个临时容器,执行 expose 55 命令,再通过 docker commit 创建了中间镜像 f5c96137bec9。该镜像的 layers 没有变化,但是元数据发生了一些变化,包括:

 "exposedports": {
        "5555/tcp": {}
      }
"cmd": [
        "/bin/sh",
        "-c",
        "#(nop) ",
        "expose 5555/tcp"
      ]

step 6: cmd ["/usr/sbin/ntpd"]

这一步和上面的步骤相同,最终它创建了镜像 af678df648bc,该镜像只是修改了 cmd 元数据:

 "cmd": [
        "/bin/sh",
        "-c",
        "#(nop) ",
        "cmd [\"/usr/sbin/ntpd\"]"
      ]

该镜像也是docker 根据本 dockerfile 生成的最终镜像。它也出现在了 docker images 结果中:

root@devstack:/home/sammy# docker images | grep af678df648bc
sammy_ntp2       latest       af678df648bc    11 hours ago    212.8 mb

我们可以使用 docker history 命令查看该镜像中每一层的信息:

root@devstack:/home/sammy/ntponubuntu# docker history af678df648bc
image        created       created by                   size        comment
af678df648bc    16 hours ago    /bin/sh -c #(nop) cmd ["/usr/sbin/ntpd"]    0 b
f5c96137bec9    16 hours ago    /bin/sh -c #(nop) expose 5555/tcp       0 b
9cc05cf6f48d    16 hours ago    /bin/sh -c apt-get -y install ntp        2.679 mb
694a19d54103    16 hours ago    /bin/sh -c apt-get update            22.17 mb
c4299e3f774c    17 hours ago    /bin/sh -c #(nop) maintainer sammy "sammy@sa  0 b
4a725d3b3b1c    3 weeks ago     /bin/sh -c #(nop) cmd ["/bin/bash"]       0 b
<missing>      3 weeks ago     /bin/sh -c mkdir -p /run/systemd && echo 'doc  7 b
<missing>      3 weeks ago     /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/  1.895 kb
<missing>      3 weeks ago     /bin/sh -c rm -rf /var/lib/apt/lists/*     0 b
<missing>      3 weeks ago     /bin/sh -c set -xe  && echo '#!/bin/sh' > /u  194.6 kb
<missing>      3 weeks ago     /bin/sh -c #(nop) add file:ada91758a31d8de3c7  187.8 mb

以上过程说明:

1.2 docker 镜像分层,cow 和 镜像大小(size)

1.2.1 镜像分层和容器层Docker镜像怎么生成
 

从上面例子可以看出,一个 docker 镜像是基于基础镜像的多层叠加,最终构成和容器的 rootfs (根文件系统)。当 docker 创建一个容器时,它会在基础镜像的容器层之上添加一层新的薄薄的可写容器层。接下来,所有对容器的变化,比如写新的文件,修改已有文件和删除文件,都只会作用在这个容器层之中。因此,通过不拷贝完整的 rootfs,docker 减少了容器所占用的空间,以及减少了容器启动所需时间。

1.2.2 cow 和镜像大小

  cow,copy-on-write 技术,一方面带来了容器启动的快捷,另一方也造成了容器镜像大小的增加。每一次 run 命令都会在镜像上增加一层,每一层都会占用磁盘空间。举个例子,在 ubuntu 14.04 基础镜像中运行 run apt-get upgrade 会在保留基础层的同时再创建一个新层来放所有新的文件,而不是修改老的文件,因此,新的镜像大小会超过直接在老的文件系统上做更新时的文件大小。因此,为了减少镜像大小起见,所有文件相关的操作,比如删除,释放和移动等,都需要尽可能地放在一个 run 指令中进行。

比如说,通过将上面的示例 dockerfile 修改为:

 from ubuntu:14.04
maintainer sammy "sammy@sammy.com"
run apt-get update && apt-get -y install ntp
expose 5555
cmd ["/usr/sbin/ntpd"]

结果产生的镜像,不仅层数少了一层(7 -> 6),而且大小减少了 0.001m :),因为这个例子比较特殊,文件都是添加,而没有更新,因此size 的下降非常小。

1.2.3 使用容器需要避免的一些做法

下面列举了一些在使用容器时需要避免的做法,包括:

2. dockerfile 语法

上面的步骤说明了 docker 可以通过读取 dockerfile 的内容来生成容器镜像。dockerfile 的每一行都是 instruction arguments 格式,即 “指令 参数”。关于 dockerfile 的预防,请参考 。下面只是就一些主要的指令做一些说明。

2.1 几个主要指令

2.1.1 add 和 copy

add:将 host 上的文件拷贝到或者将网络上的文件下载到容器中的指定目录

# usage: add [source directory or url] [destination directory]
add /my_app_folder /my_app_folder

例子:

from ubuntu:14.04
maintainer sammy liu <sammy.liu@unknow.com>
add temp dockfile
entrypoint top

add 指令会将本地 temp 目录中的文件拷贝到容器的 dockfile 目录下面,从而在镜像中增加一个 layer。在未指定绝对路径的时候,会放到 workdir 目录下面。

root@cc2a5605f905:/# ls dockfile/
dockerfile-add dockerfile-cmd dockerfile-env dockerfile-ports dockerfile-user dockerfile-user-h
root@cc2a5605f905:/# pwd
/

那两者有什么区别呢?

2.1.2 cmd

cmd:在容器被创建后执行的命令,和 run 不同,它是在构造容器时候所执行的命令

# usage 1: cmd application "argument", "argument", ..
cmd "echo" "hello docker!"

cmd 有三种格式:

一个dockerfile里只能有一个cmd,如果有多个,只有最后一个生效。

2.1.3 entrypoint

entrypoint :设置默认应用,会保证每次容器被创建后该应用都会被执行。cmd 和 entrypoint 的关系会在下面详细解释。

2.1.4 env:设置环境变量,可以使用多次

# usage: env key value
env server_works 4

设置了后,后续的run命令都可以使用,并且会作为容器的环境变量。举个例子,下面是 dockfile:

from ubuntu:14.04
env abc=1
env def=2
entrypoint top

生成镜像:docker build -t envimg4 -f dockerfile-env . 其元数据包括了这两个环境变量:

"env": [
        "path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "abc=1",
        "def=2"
      ],

启动容器:docker run -it --name envc41 envimg4。也能看到:

"env": [
        "path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "abc=1",
        "def=2"
      ]

进入容器:能看到定义的 abc 和 def 变量

root@devstack:/home/sammy/ntponubuntu# docker exec -it envc41 bash
root@ba460e0e9dc4:/# echo $abc
root@ba460e0e9dc4:/# echo $def

2.1.5 expose :向容器外暴露一个端口

# usage: expose [port]
expose 8080

2.1.6 from:指定进行的基础镜像,必须是第一条指令

# usage: from [image name]
from ubuntu

2.1.7 maintainer:可以在任意地方使用,设置镜像的作者

# usage: maintainer [name]
maintainer authors_name

2.1.8 run:运行命令,结果会生成镜像中的一个新层

# usage: run [command]
run aptitude install -y ntp

2.1.9 user:设置该镜像的容器的主进程所使用的用户,以及后续 run, cmd 和 entrypoint 指令运行所使用的用户

语法:

# usage: user [uid]
user 751

dockerfile 中的默认用户是基础镜像中所使用的用户。比如,你的镜像是从一个使用非 root 用户 sammy 的镜像继承而来的,那么你的 dockerfile 中 run 指定运行的命令的用户就会使用 sammy 用户。

举例:

(1)创建 dockerfile 文件

root@devstack:/home/sammy/dockerfile# cat dockerfile-user
from ubuntu:14.04
user 1000
entrypoint top

(2)创建镜像:docker build -t dockerfile-user-1000 -f dockerfile-user .

(3)启动容器:docker run -it --name c-user-1000-3 dockerfile-user-1000 top

能看出来当前用户id 为 1000:

pid user   pr ni  virt  res  shr s %cpu %mem   time+ command
  1 1000   20  0  4440  648  548 s 0.0 0.0  0:00.00 sh
  5 1000   20  0  19840  1296  984 r 0.0 0.1  0:00.00 top

(4)基于该镜像再创造一个镜像,然后再启动一个容器,可以发现容器中进程所使用的用户id 同样为 1000.

2.1.10 volume:允许容器访问host上某个目录

# usage: volume ["/dir_1", "/dir_2" ..]
volume ["/my_files"]

2.1.11 workdir:设置 cmd 所指定命令的执行目录

# usage: workdir /path
workdir ~/

2.1.12 healthcheck: 容器健康检查

这是 docker 1.12 版本中新引入的指令,其语法为 healthcheck [options] cmd command。 来看一个例子:

from ubuntu:14.04
maintainer sammy liu <sammy.liu@unknow.com>
run apt-get update
run apt-get -y install curl
expose 8888
cmd while true; do echo 'hello world' | nc -l -p 8888; done
healthcheck --interval=10s --timeout=2s cmd curl -f http://localhost:8888/ || exit 1

在启动容器后,其health 状态首先是 starting,然后在过了10秒做了第一次健康检查成功后,变为 healthy 状态。

root@devstack:/home/sammy/dockerfile# docker ps | grep c-health2
4c459eef1894    img-health2     "/bin/sh -c 'while tr"  7 seconds ago    up 6 seconds (health: starting)  8888/tcp         c-health2
root@devstack:/home/sammy/dockerfile# docker ps | grep c-health2
4c459eef1894    img-health2     "/bin/sh -c 'while tr"  9 seconds ago    up 8 seconds (health: starting)  8888/tcp         c-health2
root@devstack:/home/sammy/dockerfile# docker ps | grep c-health2
4c459eef1894    img-health2     "/bin/sh -c 'while tr"  11 seconds ago   up 11 seconds (healthy)   8888/tcp         c-health2

需要注意的是 cmd 是在容器之内运行的,因此,你需要确保其命令或者脚本存在于容器之内并且可以被运行。

2.2 几个比较绕的地方

2.2.1 expose 和 docker run -p -p 之间的关系

容器的端口必须被发出(publish)出来后才能被外界使用。dockerfile 中的 expose 只是“标记”某个端口会被暴露出来,只有在使用了 docker run -p 或者 -p 后,端口才会被“发出”出来,此时端口才能被使用。

举例:

(1)dockerfile

from ubuntu:14.04
maintainer sammy liu <sammy.liu@unknow.com>
cmd while true; do echo 'hello world' | nc -l -p 8888; done

(2)创建镜像:docker build -t no-exposed-ports -f dockerfile-ports .

(3)启动容器1:docker run -d --name no-exposed-ports1 no-exposed-ports。此容器没有 exposed 和 published 任何端口。

(4)启动容器2:docker run -d --name no-exposed-ports2 -p 8888:8888 no-exposed-ports

此时容器的 8888 端口被发布为主机上的 8888 端口:

"ports": {
        "8888/tcp": [
          {
            "hostip": "0.0.0.0",
            "hostport": "8888"
          }
        ]
      }


该端口会正确返回:

root@devstack:/home/sammy/dockerfile# telnet 0.0.0.0 8888
trying 0.0.0.0...
connected to 0.0.0.0.
escape character is '^]'.
hello world
connection closed by foreign host.

(5)使用 -p 参数:docker run -d --name no-exposed-ports3 -p no-exposed-ports

此时没有任何端口被 published,说明 docker 在使用了 “-p” 情形下只是自动将 exposed 的端口 published。

(6)使用 -p 加上一个不存在的端口:docker run -d --name no-exposed-ports4 -p 8889:8889 no-exposed-ports

此时,8889 端口会被暴露,但是没法使用。说明 -p 会将没有 exposed 的端口自动 exposed 出来。

(7)修改 dockerfile 为:

from ubuntu:14.04
maintainer sammy liu <sammy.liu@unknow.com>
expose 8888
cmd while true; do echo 'hello world' | nc -l -p 8888; done

创建镜像exposed-ports, 再运行 docker run -d --name exposed-ports1 -p exposed-ports 创建一个容器,此时 8888 端口自动被 published 为主机上的 32776 端口:

"ports": {
        "8888/tcp": [
          {
            "hostip": "0.0.0.0",
            "hostport": "32776"
          }
        ]
      }

可见:

2.2.2 cmd 和 entrypoint

这两个指令都指定了运行容器时所运行的命令。以下是它们共存的一些规则:


没有 entrypointentrypoint exec_entry p1_entryentrypoint [“exec_entry”, “p1_entry”]
没有 cmd错误,不允许/bin/sh -c exec_entry p1_entry exec_entry p1_entry
cmd [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_cmd p1_cmd exec_entry p1_entry exec_cmd p1_cmd
cmd [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry p1_cmd p2_cmd exec_entry p1_entry p1_cmd p2_cmd
cmd exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd
备注只有 cmd 时,执行 cmd 定义的指令 cmd 和 entrypoint 都存在时,cmd 的指令作为 entrypoint 的参数

 举例:

(1)同时有 cmd 和 entrypoint

from ubuntu:14.04
maintainer sammy liu <sammy.liu@unknow.com>
cmd top
entrypoint ps

此时会运行的指令为 /bin/sh -c ps /bin/sh -c top

但是实际上只是运行了 ps:

root@devstack:/home/sammy/dockerfile# /bin/sh -c ps /bin/sh -c top
 pid tty     time cmd
pts/3  00:00:00 su
pts/3  00:00:00 bash
pts/3  00:00:00 sh
pts/3  00:00:00 ps
root@devstack:/home/sammy/dockerfile# /bin/sh -c ps
 pid tty     time cmd
pts/3  00:00:00 su
pts/3  00:00:00 bash
pts/3  00:00:00 sh
pts/3  00:00:00 ps

(2)cmd 作为 entrypoint 的参数

from ubuntu:14.04
maintainer sammy liu <sammy.liu@unknow.com>
cmd ["-n", "10"]
entrypoint top

启动容器后运行的命令为 /bin/sh -c top -n 10.

关于“Docker镜像怎么生成”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。

推荐阅读:
  1. Docker 镜像操作
  2. 怎么创建Docker镜像

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

docker

上一篇:Mac系统上如何用Docker搭建lamp环境

下一篇:Docker如何实现浏览器里开发Android应用的功能

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》