Docker 是一个构建,发布和运行应用程序的开放平台。Docker 以容器为资源分隔和调度的基本单位,容器封装了整个项目运行时所须要的全部环境,经过 Docker 你能够将应用程序与基础架构分离,像管理应用程序同样管理基础架构,以便快速完成项目的部署与交付。java
Docker 使用 Go 语言进行开发,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于操做系统层面的虚拟化技术。最初实现是基于 LXC,从 0.7 版本之后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。linux
下图体现了 Docker 和传统虚拟化方式的不一样之处:传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操做系统,再在该系统上运行所需应用进程;而 Docker 容器内的应用进程则是直接运行于宿主的内核,容器内没有本身的内核,并且也没有进行硬件虚拟,所以要比传统虚拟机更为轻便。git
Docker 使用 client-server 架构, Docker 客户端将命令发送给 Docker 守护进程,后者负责构建,运行和分发 Docker 容器。 Docker 客户端和守护程序使用 REST API,经过 UNIX 套接字或网络接口进行通讯。核心概念以下:github
Docker 镜像(Image)是一个特殊的文件系统,包含了程序运行时候所须要的资源和环境。镜像不包含任何动态数据,其内容在构建以后也不会被改变。spring
由于镜像包含操做系统完整的 root
文件系统,其体积每每是庞大的,所以在 Docker 设计时,充分利用 Union FS (联合文件系统)的技术,将其设计为分层存储的架构,因此一个镜像其实是由多层文件系统联合组成。镜像构建时,会一层层构建,前一层是后一层的基础;每一层构建完就不会再发生改变,后一层上的任何改变只发生在本身这一层。好比,删除前一层文件的操做,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,可是实际上该文件会一直跟随镜像。所以,在构建镜像的时候,须要额外当心,每一层尽可能只包含该层须要添加的东西,任何额外的东西应该在该层构建结束前清理掉。docker
分层存储的特征使得镜像的复用、定制变的更为容易。甚至能够用以前构建好的镜像做为基础层,而后进一步添加新的层,以定制本身所需的内容,构建新的镜像。shell
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类
和 实例
同样,镜像是静态的定义,容器是镜像运行时的实体,容器能够被建立、启动、中止、删除、暂停等。centos
容器的实质是进程,但与直接在宿主执行的进程不一样,容器进程运行在属于本身的、独立的命名空间中。所以容器能够拥有本身的 root
文件系统、本身的网络配置、本身的进程空间,甚至本身的用户 ID 空间。容器内的进程运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操做同样,这种特性使得容器封装的应用比直接在宿主运行更加安全。缓存
前面讲过镜像使用的是分层存储,容器也是如此。每个容器运行时,是以镜像为基础层,在其上建立一个当前容器的存储层,咱们能够称这个为容器运行时读写而准备的存储层称为 容器存储层。容器存储层的生存周期和容器同样,容器消亡时,容器存储层也随之消亡。所以,任何保存于容器存储层的信息都会随容器删除而丢失。安全
按照 Docker 最佳实践的要求,容器不该该向其存储层内写入任何数据,容器存储层要保持无状态化。全部的文件写入操做,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡,所以,使用数据卷后,容器删除或者从新运行以后,数据都不会丢失。
镜像构建完成后,能够很容易的在当前宿主机上运行,但若是须要在其它服务器上使用这个镜像,就须要一个集中的存储、分发镜像的服务,这就是镜像仓库(Registry)。Docker Hub 是 Docker 官方提供的镜像公有仓库,提供了大量经常使用软件的镜像,固然出于安全和保密的须要,你也能够构建本身的私有仓库。
Docker daemon(dockerd)负责监听 Docker API 请求并管理 Docker 对象,如镜像,容器,网络和卷,守护程序彼此之间也能够进行通信。
Docker 客户端(docker)是用户与 Docker 交互的主要方式。当你使用 docker run 等命令时,客户端会将这些命令发送到 dockerd,dockerd 负责将其执行。一个 Docker客户端能够与多个 dockerd 进行通信。
Docker 提供了大量命令用于管理镜像、容器和服务,命令的统一使用格式为:docker [OPTIONS] COMMAND
,其中 OPTIONS 表明可选参数。须要注意的是 Docker 命令的执行通常都须要获取 root 权限,这是由于 Docker 的命令行工具 docker 与 docker daemon 是同一个二进制文件,docker daemon 负责接收并执行来自 docker 的命令,它的运行须要 root 权限。全部经常使用命令及其使用场景以下:
从官方镜像仓库 Docker Hub 查找指定名称的镜像。经常使用参数为 --no-trunc
,表明显示完整的镜像信息。
列出全部顶层镜像的相关信息。经常使用参数以下:
从官方仓库下载镜像,:TAG
为镜像版本,不加则默认下载最新版本。
删除指定版本的镜像,不加 :TAG
则默认删除镜像的最新版本。若是有基于该镜像的容器存在,则该镜像没法直接删除,此时可使用参数 -f
,表明强制删除。rmi 命令支持批量删除,多个镜像名之间使用空格分隔。若是想要删除全部镜像,则可使用命令 docker rmi -f $(docker images -qa)
。
run 是 docker 中最为核心的一个命令,用于新建并启动容器,其拥有众多可用参数,可使用 docker run --help
查看全部可用参数。经常使用参数以下:
-i
结合使用,表示使用伪终端与容器进行交互;-v
参数同时挂载多个 volume。volume 的格式为:[host-dir]:[container-dir]:[rw:ro]
,[rw:ro]
用于指定数据卷的模式,rw
表明读写模式,ro
表明只读模式。hostPort:containerPort
,经过端口的暴露,可让外部主机可以访问容器内的应用。列出当前全部正在运行的容器。经常使用参数以下:
与容器启动、中止相关的命令为:docker start|restart|stop|kill 容器名或ID
,start 命令用于启动已有的容器,restart 用于重启正在运行的容器,stop 用于中止正在运行的容器,kill 用于强制中止容器。
想要进入正在运行的容器,与容器主进程交互,有如下两种经常使用方法:
想要退出正在运行的容器,有如下两种经常使用方法:
删除已中止的容器,经常使用参数为-f
,表示强制删除容器,即使容器还在运行。想要删除全部容器,可使用 docker rm -f $(docker ps -aq)
命令。
可使用 docker inspect [OPTIONS] NAME|ID [NAME|ID...]
查看容器或者镜像的详细信息,想要查看指定的信息,可使用 -- format
参数来指定输出的模板格式,示例以下:
docker inspect --format='{{.NetworkSettings}}' 32cb3ace3279
复制代码
可使用 docker logs [OPTIONS] CONTAINER
查看容器中进程的运行日志,经常使用参数以下:
dockerfile 是 Docker 用来构建镜像的文本文件,包含自定义的指令和格式,能够经过 build 命令从 dockerfile 中构建镜像,命令格式为:docker build [OPTIONS] PATH | URL | -
。
dockerfile 描述了组装镜像的步骤,其中每条指令都是单独执行的。除了 FROM 指令,其余的每一条指令都会在上一条指令所生成镜像的基础上执行,执行完后会生成一个新的镜像层,新镜像层覆盖在原来的镜像之上从而造成新的镜像。为了提升镜像构建的速度, Docker Daemon 会缓存构建过程当中的中间镜像。在构建镜像时,它会将 dockerfile 中下一条指令和基础镜像的全部子镜像作比较,若是有一个子镜像是由相同的指令生成的,则命中缓存,直接使用该镜像,而不用再生成一个新的镜像。经常使用指令以下:
FROM 指令用于指定基础镜像,所以全部的 dockerfile 都必须使用 FROM 指令开头。FROM 指令能够出现屡次,这样会构建多个镜像,每一个镜像建立完成后,Docker 命令行界面会输出该镜像的 ID。经常使用指令格式为:FROM <image>[:<tag>] [AS <name>]
。
MAINTAINER 指令能够用来设置做者名称和邮箱,目前 MAINTAINER 指令被标识为废弃,官方推荐使用 LABEL 代替。
LABEL 指令能够用于指定镜像相关的元数据信息。格式为:LABEL <key>=<value> <key>=<value> <key>=<value> ...
。
ENV 指令用于声明环境变量,声明好的环境变量能够在后面的指令中引用,引用格式为 $variable_name
或 ${variable_name}
。经常使用格式有如下两种:
ENV <key> <value>
:用于设置单个环境变量;ENV <key>=<value> ...
:用于一次设置多个环境变量。EXPOSE 用于指明容器对外暴露的端口号,格式为:EXPOSE <port> [<port>/<protocol>...]
,您能够指定端口是侦听 TCP 仍是 UDP,若是未指定协议,则默认为 TCP。
WORKDIR 用于指明工做目录,它能够屡次使用。若是指明的是相对路径,则它将相对于上一个WORKDIR指令的路径。示例以下:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd # 此时pwd为:/a/b/c
复制代码
COPY 指令的经常使用格式为:COPY <src>... <dest>
,用于将指定路径中的文件添加到新的镜像中,拷贝的目标路径能够不存在,程序会自动建立。
ADD 指令的经常使用格式为:COPY <src>... <dest>
,做用与 COPY 指令相似,但功能更为强大,例如 Src
支持文件的网络地址,且若是 Src
指向的是压缩文件,ADD 在复制完成后还会自动进行解压。
RUN 指令会在前一条命令建立出的镜像基础上再建立一个容器,并在容器中运行命令,在命令结束后提交该容器为新的镜像。它支持如下两种格式:
RUN <command>
(shell 格式)RUN ["executable", "param1", "param2"]
(exec 格式)使用 shell 格式时候,命令经过 /bin/sh -c
运行,而当使用 exec 格式时,命令是直接运行的,容器不调用 shell 程序,这意味着不会发生正常的 shell 处理。例如,RUN ["echo","$HOME"]
不会对 $HOME
执行变量替换,此时正确的格式应为:RUN ["sh","-c","echo $HOME"]
。下面的 CMD 指令也存在一样的问题。
CMD ["executable","param1","param2"]
(exec 格式, 首选)CMD ["param1","param2"]
(做为 ENTRYPOINT 的默认参数)CMD command param1 param2
(shell 格式)CMD 指令提供容器运行时的默认值,这些默认值能够是一条指令,也能够是一些参数。一个 dockerfile 中能够有多条 CMD 指令,但只有最后一条 CMD 指令有效。CMD 指令与 RUN 指令的命令格式相同,但做用不一样:RUN 指令是在镜像的构建阶段用于产生新的镜像;而 CMD 指令则是在容器的启动阶段默认将 CMD 指令做为第一条执行的命令,若是用户在 docker run 时指定了新的命令参数,则会覆盖 CMD 指令中的命令。
ENTRYPOINT 指令 支持如下两种格式:
ENTRYPOINT ["executable", "param1", "param2"]
(exec 格式,首先)ENTRYPOINT command param1 param2
(shell 格式)ENTRYPOINT 指令 和 CMD 指令相似,均可以让容器在每次启动时执行相同的命令。但不一样的是 CMD 后面能够是参数也能够是命令,而 ENTRYPOINT 只能是命令;另外 docker run 命令提供的运行参数能够覆盖 CMD,但不能覆盖 ENTRYPOINT ,这意味着 ENTRYPOINT 指令上的命令必定会被执行。以下 dockerfile 片断:
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
复制代码
当执行 docker run -it image
后,此时输出为 hello world
,而当你执行 docker run -it image spring
,此时 CMD 中的参数会被覆盖,此时输出为hello spring
。
生产环境中的大多数项目一般都部署在 Linux 服务器上,这里咱们从基础的 Linux 镜像开始,并将咱们的项目(这里以 Spring Boot 项目为例)一块儿打包构建成为一个完整的可执行的镜像。首先须要建立Dockerfile文件,其内容以下:
# 以官方仓库的centos镜像为基础开始建立
FROM centos
# 做者信息
MAINTAINER heibaiying@heibaiying.com
# 把JDK安装包拷贝到容器中并自动进行解压
ADD jdk-8u211-linux-x64.tar.gz /usr/java/
# 拷贝项目Jar包到容器中
COPY spring-boot-base.jar /usr/app/
# 配置Java环境变量
ENV JAVA_HOME /usr/java/jdk1.8.0_211
ENV JRE_HOME ${JAVA_HOME}/jre
ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib
ENV PATH ${JAVA_HOME}/bin:$PATH
# 项目启动命令
ENTRYPOINT ["java", "-jar", "/usr/app/spring-boot-base.jar"]
复制代码
将 JDK 安装包,Spring Boot 项目的 Jar 包以及 Dockerfile 文件放在同一个目录,而后执行下面镜像构建命令:
docker build -t spring-boot-base-java:latest .
复制代码
镜像构建完成后,可使用如下命令进行启动:
docker run -it -p 8080:8080 spring-boot-base-java
复制代码
这里为了观察到启动效果,因此使用交互的方式启动,实际部署时可使用-d
参数来后台启动,输出以下:
上面的项目咱们是基于最基础的 Centos 镜像开始构建,但因为 Docker Hub 上已经提供了 JDK 的镜像,咱们也能够选择从 JDK 镜像开始构建,此时构建过程更加简单。构建步骤和上面的彻底一致,只是 Dockerfile 的内容有所不一样,以下:
# 因为只须要运行环境,这里咱们直接以官方仓库的jre镜像为基础开始建立
FROM openjdk:8u212-jre
# 做者信息
MAINTAINER heibaiying@heibaiying.com
# 拷贝项目Jar包到容器中
COPY spring-boot-base.jar /usr/app/
# 项目启动命令
ENTRYPOINT ["java", "-jar", "/usr/app/spring-boot-base.jar"]
复制代码
更多文章,欢迎访问 [全栈工程师手册] ,GitHub 地址:github.com/heibaiying/…