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提供了多种存储驱动来实现不同的方式存储镜像,下面是常用的几种存储驱动:
- AUFS
- OverlayFS
- Devicemapper
- Btrfs
- 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
- Repository可分为“顶层仓库”和“用户仓库”
- 用户仓库名称格式为“用户名/仓库名”
每个仓库可包含多个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分为四部分:
- 基础镜像信息 :通过FROM指令指定基础镜
- 维护者信息 :通过LABEL MAINTAINER编辑维护者信息
- 镜像操作指令
- 容器启动时默认要执行的指令
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