2017-02-13Chang Wei

Docker简介

(本文由GeneDock 后端研发实习生魏畅撰写。转载请保留作者信息和原文链接)

Docker简介

Docker是什么?

Docker is the world’s leading software containerization platform.

根据Docker的官方解释,Docker是一个世界领先的软件“集装箱化”平台。

那么“集装箱化”又是什么呢?下面这张图可以帮助我们直观地理解这一概念。



如果把大鲸鱼看成操作系统,把要交付的应用程序看成是各种货物。原本要将各种各样形状、尺寸不同的货物放到大鲸鱼上,要为每件货物考虑怎么安放(就是应用程序配套的环境),还得考虑货物和货物是否会叠起来(应用程序依赖的环境是否会冲突)。现在使用了集装箱(容器)把每件货物都放到集装箱里(把代码,运行环境,系统工具,系统库打包),这样大鲸鱼可以用同样地方式安放、堆叠集装了,省事省力。

Docker的特点

轻量

Docker容器运行在一个机器上,共享一个操作系统内核;启动速度快,占用内存少。

开放

Docker容器可以运行在主流的Linux发行版还有Windows系统上。

安全

容器把应用与其他应用以及底层隔离开来,同时提供一个额外的保护层来保护程序。

Docker V.S Virtual Machine

了解了Docker解决的问题以及它的特点,相信很多人都会想Docker和Virtural Machine(虚拟机)之间有什么异同。下面这张图反映了Docker和Virtual Machine在实现上的不同。

Docker和Virtual Machine实现原理的区别

首先,在图中我们可以看到Docker和Virtual Machine在实现资源隔离和环境隔离的方式完全不同。

虚拟机实现资源隔离和环境隔离的方法是利用独立的OS,并利用Hypervisor(Hypervisor是一种运行在物理服务器和操作系统之间的中间软件层,可允许多个操作系统和应用共享一套基础物理硬件。当服务器启动并执行Hypervisor时,它会给每一台虚拟机分配适量的内存、CPU、网络和磁盘,并加载所有虚拟机的客户操作系统。)虚拟化CPU、内存、IO设备等实现的。

例如,为了虚拟CPU,Hypervisor会为每个虚拟的CPU创建一个数据结构,模拟CPU的全部寄存器的值,在适当的时候跟踪并修改这些值。需要指出的是在大多数情况下,虚拟机软件代码是直接跑在硬件上的,而不需要Hypervisor介入。只有在一些权限高的请求下,Guest OS需要运行内核态修改CPU的寄存器数据,Hypervisor会介入,修改并维护虚拟的CPU状态。 Hypervisor虚拟化内存的方法是创建一个shadow page table。正常的情况下,一个page table可以用来实现从虚拟内存到物理内存的翻译。在虚拟化的情况下,由于所谓的物理内存仍然是虚拟的,因此shadow page table就要做到:虚拟内存->虚拟的物理内存->真正的物理内存。 对于IO设备虚拟化,当Hypervisor接到page fault,并发现实际上虚拟的物理内存地址对应的是一个I/O设备,Hypervisor就用软件模拟这个设备的工作情况,并返回。比如当CPU想要写磁盘时,Hypervisor就把相应的数据写到一个host OS的文件上,这个文件实际上就模拟了虚拟的磁盘。

对比虚拟机实现资源和环境隔离的方案,Docker就显得简练很多。Docker Engine可以简单看成对Linux的NameSpace、Cgroup、镜像管理文件系统操作的封装。Docker并没有和虚拟机一样利用一个完全独立的Guest OS实现环境隔离,它利用的是目前Linux内核本身支持的容器方式实现资源和环境隔离。简单的说,Docker利用namespace实现系统环境的隔离;利用Cgroup实现资源限制;利用镜像实现根目录环境的隔离。

Docker和Virtual Machine的优劣

Docker有着比虚拟机更少的抽象层。由于Docker不需要Hypervisor实现硬件资源虚拟化,运行在Docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上Docker将会在效率上有优势。

Docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,Docker不需要和虚拟机一样重新加载一个操作系统内核。我们知道,引导、加载操作系统内核是一个比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载Guest OS,这个新建过程是分钟级别的。而Docker由于直接利用宿主机的操作系统,则省略了这个过程,因此新建一个Docker容器只需要几秒钟。另外,现代操作系统是复杂的系统,在一台物理机上新增加一个操作系统的资源开销是比较大的,因此,Docker对比虚拟机在资源消耗上也占有比较大的优势。

Docker基本操作

Docker基本命令

获取Docker镜像-docker pull命令

docker pull [OPTIONS] NAME[:TAG|@DIGEST]

从docker registry获取docker image。

registry可以是共有的docker hub或者私有的(例如GeneDock docker registry)。

运行Docker容器-docker run命令

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

OPTIONS:

-d 让容器在后台运行

-p hostPort:containerPort 显式将一个或者一组端口从容器里绑定到主机上

-P 将Dockerfile里暴露的所有容器端口映射到动态分配的宿主机端口上(之后可以通过docker port命令查看分配到的端口)

制作Docker镜像-docker build命令

docker build OPTIONS PATH | URL | -

OPTIONS:

-t \<tagname>指定新镜像的标签名

-f 指定Dockerfile的名字(默认是PATH/Dockerfile)

PATH指定Dockerfile所在位置(eg:.)

注意不要使用根目录/作为docker build的path,不然会把整个硬盘的内容转移到docker daemon(守护进程进程)上。

发布Docker镜像-docker push命令

docker push [OPTIONS] NAME[:TAG]

可以将制作好的镜像发布到DockerHub或者私有的registry上。如果要发布到私有registry上,应先使用docker tag命令将镜像的标签改为registry所在ip:开放端口/镜像名。

Docker常用命令

Docker镜像命令

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

将容器状态保存为镜像

OPTIONS:

-a 作者名称

-m 提交信息

-p 当commit时暂停正在运行的容器

docker images [OPTIONS] [REPOSITORY[:TAG]]

显示本地的镜像。可以输入[REPOSITORY][:TAG]]完全匹配想要查看的镜像。

OPTIONS:

-a 显示全部镜像

-f 过滤器 可以添加过滤条件

-q 只显示镜像的id

docker rmi [OPTIONS] IMAGE [IMAGE…]

删除一个或多个镜像(通过它的id、tag)

OPTIONS:

-f 强制删除

docker tag  IMAGE [:TAG] IMAGE[:TAG]

为镜像打标签(创建名字或重命名)

OPTIONS:

-f 强制覆盖

Docker容器命令

docker ps [OPTIONS]

默认显示正在运行的容器

OPTIONS:

-a 显示所用容器

-f 过滤器 可以添加过滤条件

-q 只显示容器的id

docker rm [OPTIONS] CONTAINER [CONTAINER…]

删除一个或多个容器(通过它的id、tag)

OPTIONS:

-f 强制删除

docker port CONTAINER [PRIVATE_PORT[/PROTO]]

查看容器与主机端口映射情况

OPTIONS:

-f 强制删除

docker start [OPTIONS] CONTAINER [CONTAINTER…]

启动一个或多个容器

OPTIONS:

-a 打开输出流

-i 打开输入流

docker stop [OPTIONS] CONTAINER [CONTAINTER…]

停止一个或多个正在运行的容器

docker kill [OPTIONS] CONTAINER [CONTAINTER…]

立即停止一个或多个正在运行的容器

注:docker stop先发送SIGTERM信号,再发送SIGKILL信号,这样容器在退出前会做一些保存工作。而docker kill直接发送SIGKILL信号,将会立即停止容器,一般情况下均使用docker stop来停止容器。但是也可以用docker kill命令通过传入-signal参数来向容器发送一个自定义的信号。

Docker管理命令

docker info

显示系统信息(容器数量,镜像数量,版本信息,机器CPU,内存信息)

docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK…]

显示一个容器,镜像或者任务的底层信息

OPTIONS:

–format 给定一个go template来获取模板指定的信息

例如:

获取实例的IP地址:

docker inspect --format=‘{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}’ $INSTANCE_ID 

获取实例的MAC地址:

docker inspect --format=‘{{range .NetworkSettings.Networks}}{{.MacAddress}}{{end}}’ $INSTANCE_ID

获取实例的日志路径:

docker inspect --format=‘{{.LogPath}}’ $INSTANCE_ID

Dockerfile

Docker can build images automatically by reading the instructions from a Dockerfile, a text file that contains all the commands, in order, needed to build a given image.

Dockerfile包含构建一个Docker镜像的所有命令。有关Dockerfile命令的详细规范,可以在Docker的官方文档中找到。下面介绍一下Dockerfile的书写。

Dockerfile基本格式

# Comment 
INSTRUCTION arguments

INSTRUCTION是不区分大小写的,不过建议大写。

Dockerfile中以#开头的行都将视为注释,除非是Parser directives()解析器指令。其他地方的#都会被视为参数。

解析器指令

解析器指令是可选的,并且影响处理Dockerfile中后续行的方式。是以# directive = value形式写成一种特殊类型的注释。

书写规则

解析器指令不区分大小写。然而,约定是他们是小写的。公约还要包括一个空白行,遵循任何解析器指令。

解析器指令不支持行连续字符。

单个指令只能使用一次。

一旦处理过注释,空行或构建器指令,Docker不再寻找解析器指令。相反,它将任何格式化为解析器指令作为注释,并且不尝试验证它是否可能是解析器指令。因此,所有解析器指令必须位于Dockerfile的最顶端。

解析器指令中允许使用非换行符空格,下面这几行被视为相同:

#directive=value 
#  directive =value
#    directive= value 
# directive = value 
#   dIrEcTiVe=value

错误的书写:

#direc \
tive = value

不支持行连续字符,无效!

# directive=value1 
# directive=value2

出现两次,无效!

# About my Dockerfile 
# directive=value

写在注释后,无效!

FROM ImageName 
# directive=value

写在构建器指令后,无效!

# unknowndirective=value 
# knowndirective=value

写在未知的解析器指令之后,被视为注释。

常用解析器指令escape

# escape=\ (backslash)
OR
# escape=` (backtick)

设置用于在Dockerfile中转义字符的字符。如果未指定,则缺省转义字符为\。

注:不管escape解析器指令是否包括在Dockerfile中,在RUN命令中不执行转义,除非在行的末尾。

将转义字符设置为 ` 在Windows上特别有用,其中\是目录路径分隔符,例如:

FROM windowsservercore 
COPY testfile.txt c:\\ 
RUN dir c:\

第二行末尾的第二个将被解释为换行符,而不是从第一个转义的目标。类似地,假设第三行结尾处的实际上作为一条指令处理,它将被视为行继续。这个Dockerfile的结果是第二行和第三行被认为是单个指令。如果使用escape指令就可以解决这个问题,将上述代码改为:

# escape=`

FROM windowsservercore 
COPY testfile.txt c:\
RUN dir c:\

常用Dockerfile指令

FROM <image> | <image>:<tag> | <image>@<digest>
指定需要构建镜像的基镜像(baseimage)。

FROM必须是Dockerfile中的第一个非注释指令。

FROM可以在单个Dockerfile中多次出现,以创建多个镜像。但要在每个新的FROM命令之前提交输出的最后一个 image ID。

tag或digest是可选的。如果省略其中任何一个,构建器将默认使用latest。

MAINTAINER <name>

设置生成的images的作者字段

LABEL <key>=<value> <key>=<value> <key>=<value> ...

LABEL指令向image添加元数据。

image可以有多个label。要指定多个label,Docker建议在可能的情况下将标签合并到单个LABEL指令中。因为每个LABEL指令产生一个新层,如果使用许多标签,可能会导致镜像效率下降。

RUN <command>  
OR
RUN [“executable”,“param1”,“param2”]

前者是shell形式,Linux默认是/bin/sh -c ,windows默认是cmd /S/C。

后者是exec 形式。

RUN指令将在当前image之上的新层中执行任何命令,并提交结果。生成的已提交image将用于Dockerfile中的下一步。

注:

shell形式下可以使用\将单个RUN指令连接到下一行。

exec形式使得可以避免shell字符串杂乱(string munging),以及使用不包含指定的shell可执行文件的baseimage来运行RUN命令。

用于RUN指令的高速缓存在下一次构建期间不会自动失效。

诸如RUN apt-get dist-upgrade之类的指令的高速缓存将在下一次构建期间被重用。

可以通过使用–no-cache标志来使用于RUN指令的高速缓存无效,例如docker build –no-cache。

CMD [“executable”,“param1”,“param2”]
OR
CMD [“param1”,“param2”]
OR
CMD command param1 param2

CMD指令为执行容器提供默认值(这些默认值可以包括可执行文件,或者它们可以省略可执行文件,在这种情况下, 必须指定ENTRYPOINT指令),但是会被docker run提供的参数覆盖。

第一种形式是exec形式,首选。

第二种形式作为ENTRYPOINT的默认参数。

第三种形式是shell形式,command将在/bin/sh -c中执行。

注:
RUN指令实际上运行一个命令并提交结果;CMD指令在构建时不执行任何操作,但指定了image的预期命令。

ENTRYPOINT [“executable”, “param1”, “param2”]
OR
ENTRYPOINT command param1 param2

配置容器启动后执行的命令,将容器变为可执行的,并且不可被 docker run 提供的参数覆盖。

每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。

CMD指令与ENTRYPOINT指令的协作:

Dockerfile中应该至少指定一个CMD或ENTRYPOINT指令。

当要将容器作为可执行的需要指定ENTRYPOINT。

CMD应该用来定义ENTRYPOINT指令的默认参数,或者用来定义容器需要执行的临时命令。

下图反映了两个指令间的协作。

ENV <key> <value> 
OR
ENV <key>=<value> ...

设置环境变量。

前者设置单个环境变量,第一个空格后面的整个字符串将被视为\<value>,包括空格和引号等字符。

后者同时设置多个环境变量。

注:

ENV指令设置的环境变量将在后续的Dockerfile中生效。

使用ENV设置的环境变量将在从生成的image运行容器时保留。

ADD <src>... <dest>
OR
ADD [“<src>”,... “<dest>”]

ADD指令从src复制新文件,目录或远程文件URL,并将它们添加到镜像的文件系统路径dest。(对于包含空格的路径,必须使用第二种形式)

dest可以是一个绝对路径,也可以是一个相对于WORKDIR的路径。

COPY <src>... <dest>
OR
COPY ["<src>",... "<dest>"]

COPY指令从\<src>复制新文件,目录或远程文件URL,并将它们添加到镜像的文件系统路径\<dest>。

与ADD指令的区别是COPY指令不会自动解压压缩文件。

VOLUME ["/data"]

VOLUME指令创建具有指定名称的挂载点,并将其标记为从本机主机或其他容器保留外部挂载的卷。

如果任何构建步骤在声明后更改卷中的数据,那么这些更改将被丢弃。

WORKDIR

WORKDIR指令为Dockerfile中的任何RUN,CMD,ENTRYPOINT,COPY和ADD指令设置工作目录。

ONBUILD [INSTRUCTION]

ONBUILD指令可以为镜像添加触发器。其参数是任意一个Dockerfile 指令。

注:

当在一个Dockerfile文件中加上ONBUILD指令,该指令对利用该Dockerfile构建镜像(比如为A镜像)不会产生实质性影响。

但是当编写一个新的Dockerfile文件来基于A镜像构建一个镜像(比如为B镜像)时,这时构造A镜像的Dockerfile文件中的ONBUILD指令就生效了,在构建B镜像的过程中,首先会执行ONBUILD指令指定的指令,然后才会执行其它指令。

利用ONBUILD指令,实际上就是相当于创建一个模板镜像,后续可以根据该模板镜像创建特定的子镜像,需要在子镜像构建过程中执行的一些通用操作就可以在模板镜像对应的Dockerfile文件中用ONBUILD指令指定。 从而减少Dockerfile文件的重复内容编写。