docker镜像制作

1.docker镜像

1.1 镜像分层机制

docker镜像含有启动容器时所需要的文件系统及其内容,采用分层构建机制,最低层为bootfs,其上为rootfs

bootfs

用于系统引导的文件系统,包括bootloader和kernel,容器启动完成后会被卸载以节约内存资源

[root@vm1 boot]# ls
config-4.18.0-193.el8.x86_64                             loader
efi                                                      lost+found
grub2                                                    System.map-4.18.0-193.el8.x86_64
initramfs-0-rescue-29ff59f726a942df9e1d53debac63dda.img  vmlinuz-0-rescue-29ff59f726a942df9e1d53debac63dda
initramfs-4.18.0-193.el8.x86_64.img                      vmlinuz-4.18.0-193.el8.x86_64

##vmlinuz-* 为linux内核文件

rootfs

rootfs位于bootfs之上,表现为docker容器的根文件系统

  • 传统模式中,系统启动之时,内核挂载rootfs会首先将其挂载为“只读”模式,完整性自检完成后将其重新挂载为读写模式

  • docker中,rootfs由内核挂载为“只读”模式,而后通过“联合挂载”技术额外挂载一个“可写”层(这个可写层随着容器删除而删除)

docker镜像层

rootfs中最底层为基础镜像(base image);最上层为容器创建时挂载的一个可写层,其下所有的层均为只读层。

事实上容器和镜像的最主要区别就是容器加上了顶层的读写层。所有对容器的修改都发生在此层,而镜像并不会被修改。这用到了COW(copy-on-write)技术,容器需要读取某个文件时,直接从底部只读层去读即可,而如果需要修改某文件,则将该文件拷贝到顶部读写层进行修改,只读层保持不变。

docker ps -s 命令看到的 size 和 virtual size,其中size就是容器读写层占用的磁盘空间,而 virtual size就是读写层加上对应只读层所占用的磁盘空间。

至于这些层的交互、管理就需要存储驱动程序,也即联合文件系统(UnionFS)

1.2 存储驱动

docker提供了多种存储驱动来实现不同的方式存储镜像,下面是常用的几种存储驱动:

  1. AUFS
  2. OverlayFS
  3. Devicemapper
  4. Btrfs
  5. VFS

AUFS

AUFS(AnotherUnionFS)是一种Union FS,是文件级的存储驱动。这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS创建该文件的一个副本,使用CoW将文件从只读层复制到可写层进行修改,结果也保存在可写层。在Docker中,底下的只读层就是image,可写层就是Container。

overlayFS

Overlay是Linux内核3.18后支持的,也是一种Union FS,和AUFS的多层不同的是Overlay只有两层:一个upper文件系统和一个lower文件系统,分别代表Docker的镜像层和容器层。而且Overlay并入了linux kernel mainline,AUFS没有。目前AUFS已基本被淘汰。

Devicemapper

Device mapper是块级存储,所有的操作都是直接对块进行操作,而不是文件。Device mapper驱动会先在块设备上创建一个资源池,然后在资源池上创建一个带有文件系统的基本设备,所有镜像都是这个基本设备的快照,而容器则是镜像的快照。

2.docker registry

  • docker registry由Repository,Index组成

2.1 Repository

由某特定的docker镜像的所有迭代版本组成的镜像仓库

一个Registry中可以存在多个Repository

  1. Repository可分为“顶层仓库”和“用户仓库”
  2. 用户仓库名称格式为“用户名/仓库名”

每个仓库可包含多个Tag(标签),每个标签对应一个镜像

2.2 Index

维护用户帐户、镜像的检验以及公共命名空间的信息,相当于为Registry提供了一个完成用户认证等功能的检索接口

3.镜像制作

多数情况下,我们做镜像是基于别人已存在的某个基础镜像来实现的,我们把它称为base image。比如一个纯净版的最小化的centos、ubuntu或debian。这个基础镜像一般是由Docker Hub的相关维护人员,也就是Docker官方手动制作的。

  • 镜像的生成途径:Dockerfile、基于容器制作、Docker Hub automated builds

3.1 基于容器制作

  • 需要docker commit命令

使用 docker commit意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为 黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。

[root@vm1 ~]# docker commit --help
Usage:  docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
Options:
  -a, --author string    Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
  -c, --change list      Apply Dockerfile instruction to the created image
  -m, --message string   Commit message
  -p, --pause            Pause container during commit (default true)

运行容器模拟修改操作

[root@vm1 ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
busybox      latest    a77dce18d0ec   5 days ago    1.24MB
nginx        latest    ae2feff98a0c   2 weeks ago   133MB
httpd        latest    dd85cdbb9987   3 weeks ago   138MB
ubuntu       latest    f643c72bc252   5 weeks ago   72.9MB
[root@vm1 ~]# docker run -it --name mybusybox busybox 
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # mkdir data
/ # cd data
/data # cat > index.html <<EOF
> hello word!
> EOF
/data # ls
index.html
/data # cat index.html
hello word!

docker commit生成镜像

##另外开一终端
[root@vm1 ~]# docker ps 
CONTAINER ID   IMAGE     COMMAND   CREATED         STATUS         PORTS     NAMES
c1f83933ada7   busybox   "sh"      8 minutes ago   Up 6 minutes             mybusybox
[root@vm1 ~]# docker commit -a "wisan:619585019@qq.com" -p mybusybox xpengzong/busybox:v2
sha256:6d063fb1d14eb106f2b6c185c2ccb63ac2f5b68b69b1d67f2905e6a637f21e93
[root@vm1 ~]# docker image ls
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
xpengzong/busybox   v2        6d063fb1d14e   15 seconds ago   1.24MB
busybox             latest    a77dce18d0ec   5 days ago       1.24MB
nginx               latest    ae2feff98a0c   2 weeks ago      133MB
httpd               latest    dd85cdbb9987   3 weeks ago      138MB
ubuntu              latest    f643c72bc252   5 weeks ago      72.9MB

推送新生成的镜像

##先登录hub.docker.com的账号
[root@vm1 ~]# docker login -u xpengzong
Password: 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

##推送制作的镜像
[root@vm1 ~]# docker push xpengzong/busybox:v2
The push refers to repository [docker.io/xpengzong/busybox]
12d5e0a8a313: Pushed 
1dad141bdb55: Mounted from library/busybox 
v2: digest: sha256:5a05aa2cf966444a2bf789699e4f7987b41a2292dd84ede17f07249e64b69243 size: 734

  • 可以看到,网站上自动生成了一个public用户仓库:xpengzong/busybox,同时还有一个标签为V2的镜像

验证这个新生成的镜像

##删除之前制作的镜像
[root@vm1 ~]# docker rmi xpengzong/busybox:v2
Untagged: xpengzong/busybox:v2
Untagged: xpengzong/busybox@sha256:5a05aa2cf966444a2bf789699e4f7987b41a2292dd84ede17f07249e64b69243
Deleted: sha256:6d063fb1d14eb106f2b6c185c2ccb63ac2f5b68b69b1d67f2905e6a637f21e93
Deleted: sha256:96cc390d5818e6659b47c30515cd5a1a21cf4602f67b4add7446a6f86dfd1fe2
[root@vm1 ~]# docker images 
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
busybox      latest    a77dce18d0ec   5 days ago    1.24MB
nginx        latest    ae2feff98a0c   2 weeks ago   133MB
httpd        latest    dd85cdbb9987   3 weeks ago   138MB
ubuntu       latest    f643c72bc252   5 weeks ago   72.9MB

##重新拉取新镜像
[root@vm1 ~]# docker pull xpengzong/busybox:v2
v2: Pulling from xpengzong/busybox
d60bca25ef07: Already exists 
6bd83d31c568: Pull complete 
Digest: sha256:5a05aa2cf966444a2bf789699e4f7987b41a2292dd84ede17f07249e64b69243
Status: Downloaded newer image for xpengzong/busybox:v2
docker.io/xpengzong/busybox:v2
[root@vm1 ~]# docker images 
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
xpengzong/busybox   v2        6d063fb1d14e   14 minutes ago   1.24MB
busybox             latest    a77dce18d0ec   5 days ago       1.24MB
nginx               latest    ae2feff98a0c   2 weeks ago      133MB
httpd               latest    dd85cdbb9987   3 weeks ago      138MB
ubuntu              latest    f643c72bc252   5 weeks ago      72.9MB

##运行容器
[root@vm1 ~]# docker run --name test -it xpengzong/busybox:v2 /bin/sh
/ # ls
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ # cd data
/data # ls
index.html
/data # cat index.html 
hello word!

##访问容器httpd服务
[root@vm1 ~]# docker exec  test /bin/httpd -f -h /data
##另外开启一个终端
[root@vm1 ~]# docker inspect test
.......................................
                   "IPAddress": "172.17.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
...........................................
[root@vm1 ~]# curl 172.17.0.3
hello word!

3.2 基于Dockerfile

Dockerfile是一个文本格式的配置文件,用户可以使用Dockerfile 快速创建自定义镜像。

一般来说,应该会将 Dockerfile置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .dockerignore文件剔除不需要作为上下文传递给 Docker 引擎的。

Dockerfile是由一行行命令语句组成,并且支持以 # 开头的注释行,以\为换行。Docker分为四部分:

  1. 基础镜像信息 :通过FROM指令指定基础镜
  2. 维护者信息 :通过LABEL MAINTAINER编辑维护者信息
  3. 镜像操作指令
  4. 容器启动时默认要执行的指令

3.2.1 Dockerfile指令

  • FROM
##第一条指令必须为FROM指令。如果在同一个Dockerfile中创建多个镜像时,可以使用多个FROM指令(每个镜像一次)。
##格式:FROM <image>[:<tag>]
  • LABEL MAINTAINER
##指定维护者信息
##格式:LABEL MAINTAINER=<name email_address>
  • ENV
##设置环境变量,无论是后面的其它指令(如RUN),还是运行时的应用,都可直接使用定义的环境变量。
##格式:ENV <key> <value> 或者 ENV <key1>=<value1> <key2>=<value2>...
  • EXPOSE
##EXPOSE声明运行时容器提供服务端口,在运行时并不会因为这个声明应用就会开启这个端口的服务。
##格式:EXPOSE <port1> [<port2>...]
##使用docker run -P,会自动随机映射EXPOSE声明的端口
  • RUN
##用于执行命令行命令
##格式:
        shell: RUN <COMMAND>
        exec:  RUN ["可执行文件","参数1","参数2"]
##RUN指令将对镜像执行跟随的命令。每运行一条RUN指令,镜像添加新的一层,并提交。
  • COPY
##复制本地主机的<src>(为Dockerfile所在目录的相对路径,文件或目录)为容器中的<dest>。目标路径不存在时会自动创建。
##格式:
        COPY [--chown=<user>:<group>] <源路径>... <目标路径>
        COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
##当使用本地目录为源目录时,推荐使用COPY。
  • ADD
##ADD指令和COPY的格式和性质基本一致,其中<src>可以是Dockerfile所在目录的一个相对路径(文件或目录);也可以是一个URL;还可以是一个tar文件(会自动解压为目录)。
##格式:
        ADD [--chown=<user>:<group>] <源路径>... <目标路径>
        ADD [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
##建议遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。
  • CMD
##CMD用于指定启动容器时默认要执行的命令,每个Dockerfile只能有一条CMD命令。如果指定了多条命令,只有最后一条会被执行。
##格式:
        shell:     CMD <COMMMAND> 在/bin/sh中执行命令
        exec:      CMD ["可执行文","参数1","参数2".....]
        参数列表: CMD ["参数1","参数2"......]在指定了ENTRYPOINT指令后,用CMD指定具体的参数

##如果用户启动容器时指定了运行的命令,则会覆盖掉CMD指定的命令。

Docker不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd 去启动后台服务,容器内没有后台服务的概念。对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。比如:CMD service nginx start 会被理解为 CMD [ "sh", "-c", "service nginx start"],因此主进程实际上是 sh。那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。正确的做法是直接执行nginx可执行文件,并且要求以前台形式运行。CMD ["nginx", "-g", "daemon off;"]。

  • ENTRYPOINT
##ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及参数。
##ENTRYPOINT在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run的参数--entrypoint 来指定.
##格式:
        shell:     ENTRYPOINT <COMMMAND> 在/bin/sh中执行命令
        exec:      ENTRYPOINT ["可执行文","参数1","参数2".....]
##当指定了ENTRYPOINT后,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给 ENTRYPOINT 指令。

  • VOLUME
##创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
##格式:VOLUME ["<路径1>", "<路径2>"...]
##在 Dockerfile 中,我们可以事先指定某些目录为数据卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
  • USER
##指定运行容器时的用户名或UID,也为后续的RUN,CMD,ENTRYPOINT也会使用指定用户。
##格式: USER <用户名>[:<用户组>]
##注意USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
创建用户:RUN groupadd -r redis && useradd -r -g redis redis

##要临时获取管理员权限可以使用gosu,而不推荐sudo。如果不指定,容器默认是root运行。
  • WORKDIR
##指定工作目录,以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR会帮你建立目录。
##格式: WORKDIR <工作目录路径>

##注意:WORKDIR 指令使用的相对路径,那么所切换的路径与之前的 WORKDIR 有关。
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd   则最终路径为/a/b/c
  • ONBUILD
##ONBUILD在当前镜像构建时并不会被执行。只有在这个构建好的当前镜像被用作构建其他镜像的基础镜像时,才会在其他镜像中执行
##格式: ONBUILD [INSTRUCTION]
  • HEALTHCHECK
##HEALTHCHECK 指令是告诉Docker应该如何进行判断容器的状态是否正常,这是Docker1.12引入的新指令
##格式:
       HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
       HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
       选项:
--interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
--timeout=<时长>:健康检查命令运行超时时间,超时则本次健康检查就被视为失败,默认 30 秒;
--retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。

##HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。

3.2.2 build创建镜像

  • 格式: docker build [选项] <上下文路径/URL/->

默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。实际上Dockerfile的文件名并不要求必须为Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile.

build用法

  • 直接用 Git repo 进行构建

下面命令指定了构建所需的 Git repo,并且指定分支为 master,构建目录为 /amd64/hello-world/,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建

docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world
  • 用给定的 tar 压缩包构建

如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。

docker build http://server/context.tar.gz
  • 从标准输入中读取 Dockerfile 进行构建

如果标准输入传入的是文本文件,则将其视为 Dockerfile,并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情。

docker build - < Dockerfile
  • 从标准输入中读取上下文压缩包进行构建

如果发现标准输入的文件格式是 gzip、bzip2 以及 xz 的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。

docker build - < context.tar.gz
posted @ 2021-01-05 19:39  小芃总  阅读(192)  评论(0编辑  收藏  举报