Docker学习笔记(2)-镜像,容器数据卷与DockerFile

Docker镜像

简介

Docker Image镜像是一种轻量级的、可执行的独立软件包,它包含了运行某个软件所需要的所有内容。应用程序和配置依赖被打包好形成一个可交付的运行环境,这个打包好的运行环境就是Image镜像文件,通过Image镜像文件才能生成Docker容器实例。

Docker镜像是分层构建的,其中使用了UnionFS(联合文件系统)。UnionFS是一种分层、轻量级、高性能的文件系统。它将对文件系统的修改看作一次次的提交,最终的状态则是一层层叠加之后的结果。同时UnionFS支持将不同目录挂载到同一个虚拟文件系统下。UnionFS是Docker Image的基础,Image可以通过分层来进行继承,基于基础镜像,可以制作各种具体的应用镜像。Image分层的一个最大好处就是能够做到共享资源,方便复用和迁移。

Docker镜像是分层的文件系统,其中最底层是启动层BootFS(Boot File System),其中主要包含了BootLoader以及Kernel,BootLoader主要用于引导加载文件系统,该层与典型的Unix系统是一致的。当加载完成之后,整个Kernel就都存放在内存当中了。此时内存的使用权就会从BootFS转交给Kernel,系统也会卸载BootFS。

而在BootFS之上是RootFS层,包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

在Docker的分层结构当中,又可以分为镜像层和容器层,这两种类型最大的区别就是镜像层是只读的,而容器层是可写的。当从一个镜像中启动出容器之后,一个新的可写的容器层就会被加载到现有分层结构的最顶层,之后所有的针对容器的修改都会记录到该层中。而在容器层之下的其他所有层次,则都属于镜像层,都是只读的。

commit & push

前面提到容器层可以进行修改,我们可以将修改后的容器重新打包成为一个镜像,这就需要使用到commit命令。下面的命令可以将contianer_id对应的容器提交成为一个新的镜像,新的镜像为target_image:tag。

1
2
# 提交容器副本使之成为一个新的镜像
docker commit -m="commit message" -a="author" container_id target_image:tag

生成的新镜像目前只在本地存在,可以通过push命令将本地镜像推送到Docker仓库中。当然还可以部署私有仓库,此时需要借助Docker Registry镜像,相关操作可以参考官方文档Deploy a registry server | docker docs,或者相关博客A Guide to Docker Private Registry | Baeldung

虚悬镜像,danling image,指的是仓库名,标签都是<none>的镜像。

1
2
3
4
5
# 查看虚悬镜像
docker image ls -f dangling=true

# 删除虚悬镜像
docker image prune

容器数据卷

使用

一个容器如果被删除了,那么容器内部的数据也都被删除了。但是在一些场景下,我们希望容器内部的数据能够被保存下来,这就需要使用到容器数据卷,起到一个数据备份的效果。

容器数据卷的挂载在容器启动的时候完成,通过-v属性。下面的命令可以挂载一个容器数据卷:

1
2
# 启动一个容器,并且完成数据卷挂载
docker run -it -privileged=true -v /宿主机绝对路径目录:/容器内目录 image_name[:tag]

--privileged=true表示扩大容器的权限,否则可能出现无法操作对应目录的情况。

该命令完成的功能是在启动容器的时候完成数据卷挂载,如果不使用数据卷,则容器内的目录是虚拟出来的,与宿主机是隔离的;如果使用了数据卷,那么容器内的对应目录直接映射到了宿主机的对应目录,能够达到一个类似双向绑定的效果。容器内目录改动能够传到宿主机中,反之亦然。并且此时如果我们删除了容器,相关数据仍然能够在宿主机中查看到。

如果挂载了数据卷,我们可以通过docker inspect命令在详细信息中查看到。

在挂载数据卷的时候,可以通过相关属性来配置容器对应该目录的读写权限,默认是读写权限都具备,当然也可以配置成只读。

1
2
3
4
# 默认读写权限都具备 (read/write)
-v /宿主机绝对路径目录:/容器内目录:rw
# 配置成只读权限 (read only)
-v /宿主机绝对路径目录:/容器内目录:ro

另外,容器数据卷可以在容器之间进行继承,通过--volumes-from属性完成。此时启动的容器与父容器除了具有相同的数据卷规则之外没有任何联系,一方的删除并不会导致另一方数据卷规则失效。

1
2
# 在启动容器的时候继承父容器的容器数据卷规则
docker run -it --privileged=true --volumes-from 父容器 --name xxx image_name[:tag]

卷挂载

在使用数据卷的时候,我们也可以只提供容器内的路径,而不提供宿主机的路径,这种挂载方式被称为卷挂载Volume Mount。

1
2
-v [name:]/容器内路径 
# name可选,它代替了宿主机路径,作为新的参数传递

卷挂载只提供容器内路径,但是可以传入一个name来代替之前的宿主机路径。如果没有name,则属于匿名挂载;如果提供了name,则属于具名挂载。

虽然卷挂载不提供宿主机目录,但是实际上还是有这个概念的。在卷挂载的情况下,对应的宿主机目录都是/var/lib/docker/volumes/xxx。如果是匿名挂载,后面的名字就是一串哈希值;如果是具名挂载,后面的名字就是指定的name。

DockerFile

简介

想要获取一个Image镜像,可以通过从仓库中拉取,也可以通过DockerFile来进行构建。DockerFile就是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的参数构成的脚本。首先编写一个DockerFile文件,之后可以通过docker build 命令来构建镜像。

保留字介绍

命令的详细介绍可以参考官方文档Dockerfile reference

首先有一些基础知识:

  1. 每条保留字指令都是大写字母,并且后面至少跟随一个参数
  2. 指令按照从上到下的顺序执行
  3. #表示注释

下面介绍常用的相关保留字:

FROM:指定基础镜像。指定一个已经存在的镜像作为模板,每个Dockerfile的第一条必须是FROM。

MANINTAINER:指定镜像维护者的姓名和邮箱地址。

RUN:在容器构建过程中需要运行的命令,命令可以用两种形式进行书写。

1
2
3
4
5
6
# shell格式,等同于在终端操作的shell命令
RUN yum -y install vim

# exec格式,类似于python中subprocess
# RUN ["可执行文件", "arg1", "arg2"]
RUN ["./test.sh", "arg1", "arg2"]

EXPOSE:指定当前容器向外暴露的端口。

WORKDIR:指定在创建容器之后,终端默认登陆进来的工作目录。

USER:指定该镜像该以哪个用户去执行,一般不指定,默认为root用户。

ENV:用来在镜像构建过程中设置环境变量。在该命令后面的其他指令可以直接使用已经定义了的环境变量。

ADD:将宿主机目录下的文件拷贝到镜像中,会自动处理URL以及解压tar压缩包。

COPY:类似ADD,但是不会进行解压处理,只是单纯的拷贝。

VOLUME:指定容器数据卷,用于数据保存和持久化的工作。

CMD:指定容器启动之后需要运行的命令。需要注意的是,Dockerfile中可以有多个CMD指令,但是只有最后一个生效,并且CMD会被docker run之后的参数替换。

ENTRYPOINT:同样是指定容器之后需要运行的命令。不过与CMD不同的是,ENTRYPOINT并不会被docker run后面的命令覆盖,并且传入的命令行参数会被当作提供给ENTRYPOINT对应命令的参数。当然在Dockerfile中同样可以有多个ENTRYPOINT指令,也只有最后一个生效。

CMD如果单独使用,则表示是运行的命令,同时可以被docker run参数覆盖。CMD如果和ENTRYPOINT一起使用,则CMD相当于在给ENTRYPOINT传参,此时CMD仍然可以被docker run参数覆盖。

因此,我们可以利用CMD来达到可变参数的目的:

假设有如下的Dockerfile,然后利用该Dockerfile构建出镜像,假设名为nginx:test

1
2
3
4
FROM nginx

ENTRYPOINT ["nginx", "-c"]
CMD ["/etc/nginx/nginx.conf"]

于是在利用docker run运行镜像的时候,可以通过是否提供命令行参数来达到控制参数的目的:

1
2
3
4
5
6
7
8
9
# 不提供命令参数
docker run nginx:test
# 容器内部相当于运行
nginx -c /etc/nginx/nginx.conf

# 提供命令参数
docker run nginx:test /etc/nginx/new.conf
# 容器内部相当于运行
nginx -c /etc/nginx/new.conf

案例

下面的案例Dockerfile完成了从centos基础镜像出发,设置好java8的环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 需要在Dockerfile的同级目录中准备jdk8的压缩包
FROM centos
 
ENV MYPATH /usr/local
WORKDIR $MYPATH
 
#安装vim编辑器
RUN yum -y install vim
#安装java8及lib库
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
#ADD 是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u171-linux-x64.tar.gz /usr/local/java/
#配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_171
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

之后运行docker build命令进行镜像构建:

1
2
# 在Dockerfile同级目录下运行命令
docker build -t new_image:tag

Docker学习笔记(2)-镜像,容器数据卷与DockerFile
http://example.com/2023/07/12/Docker学习笔记-2-镜像-容器数据卷与DockerFile/
作者
EverNorif
发布于
2023年7月12日
许可协议