Docker是一个用于开发、打包、运行应用的开放平台。Docker使得我们能够将我们的应用从基础设施中分离出来。基础设施就是指的服务器等基础硬件设施,怎么理解分离呢?就像Java一样,我们写的Java程序可以借助Java虚拟机实现一次编写、处处运行。而对于Docker,则可以实现一次构建,处处运行。
通过使用Docker,我们可以像管理我们的应用一样管理基础设施,可以快速完成代码的打包、测试、部署,从而显著缩短编写代码和交付到生产环境的周期。
Docker提供了在被称为容器的松散隔离环境中打包并运行应用的能力。Docker提供的隔离性和安全性使得在一个主机上可以同时运行多个容器。容器是轻量级的,并且包含运行应用所需要的所有东西,所以不需要依赖于当前主机上安装的东西,因此也就实现了将应用与基础设施分离的功能。我们工作时可以很轻松的分享镜像,需要注意的是:要确保每个使用这个镜像的人都按相同的方法使用。
Docker提供了工具和平台来管理容器的生命周期:
使用容器来开发我们的应用及其支持组件;
容器成为分发和测试应用的单元;
当应用开发完成后,将应用作为容器或一组服务部署到生产环境,这样,不管生产环境是一个本地数据中心还是云服务提供商,或者两者的混合,应用都将以相同的方式工作。
Docker简化了开发的生命周期,允许开发者在标准化的环境中使用本地容器来提供应用程序和服务。容器对于持续集成和持续交付(CI/CD)工作流非常有用。
考虑下面的案例场景:
开发人员在本地编写代码,然后把工作成果通过Docker容器分享给同事;
开发人员使用Docker将应用程序推送到测试环境,执行自动和手动测试;
开发人员发现bug后,他们可以在开发环境修复,然后重新部署到测试环境进行测试和验证;
当测试完成后,将这个修复交付给客户就像将修改后的镜像推送到生产环境一样简单。
Docker基于容器的平台允许高度可移植的工作负载。Docker容器可以运行在开发者的本地电脑,可以运行在数据中心的物理主机或虚拟主机,或者混合环境中。
Docker天然的可移植性和轻量级特性使得动态管理工作负载非常容易,可以根据业务需要,几乎实时性的对应用和服务进行扩展和缩减。
Docker是轻量级和快速的。它为基于管理程序的虚拟机提供了一套可行的、经济划算的替代方案,因此,我们可以使用更多的计算能力来实现我们的业务目标。Docker非常适合高密度环境和中小型部署,使用Docker,我们可以使用更少的资源做更多的事。
Docker使用客户端/服务端架构(也就是B/S架构)。Docker客户端向Docker守护进程下达指令,守护进程完成构建、运行和分发容器的繁重工作。Docker客户端和守护进程可以运行在同一个操作系统上,或者也可以使用Docker客户端连接到远程的守护进程。Docker客户端和守护进程使用REST API通过unix套接字或网络接口进行通信。另一种Docker客户端是Docker compose,它可以实现一组容器在一起工作。
Docker守护进程监听Docker API请求并管理镜像、容器、网络、卷等Doker对象。一个守护进程也可以和其他守护进程进行通信来管理Docker服务。
Docker客户端是Docker用户与Docker进行交互的主要途径。当使用像 docker run 这样的Docker命令时,Docker客户端将命令发给守护进程,守护进程执行收到的命令。实际上,Docker客户端命令就是调用的Docker API,一个Docker客户端可以与多个守护进程进行通信。
Docker仓库是用来存储镜像的。Docker Hub是一个所有人都可以使用的公共仓库,Docker默认是去Docker Hub上查找镜像的。用户也可以搭建自己的私有仓库(例如使用Harbor、JFrog等仓库管理工具)。
当使用Docker的时候,实际上就在创建和使用Docker对象,例如:镜像、容器、网络、卷、插件等对象。这一节主要简要介绍Docker的这些对象。
镜像
镜像是一个只读的模板,它包含创建一个容器的说明。通常,一个镜像是基于其他镜像的,并进行一些额外的定制。例如:我们可以基于ubuntu来构建一个镜像,在这个镜像里安装Apache服务器和我们自己的应用,然后进行一些配置来确保应用可以运行。
我们可能使用自己构建的镜像,也可能仅仅使用别人发布在仓库里的镜像。要构建自己的镜像,需要使用简单的语法来创建Dockerfile文件,在这个文件里定义创建镜像和运行镜像的步骤。Dockerfile文件里的每个指令都会给镜像创建一个层。当修改了Dockerfile并重新构建镜像时,只有那些改变的层才会被重新构建。与其他虚拟化技术相比,这是使镜像如此轻量级、小型和快速的部分原因。
容器
容器是镜像的可运行实例。我们可以使用Docker API或CLI命令来创建、启动、运行、移动、删除容器。我们可以将一个容器连接到一个或多个网络,可以给容器附加存储,甚至可以基于容器当前的状态创建一个新的镜像。
默认情况下,一个容器和其他的容器以及它所在的主机是相对隔离的。我们可以控制容器的网络、存储以及底层子系统与其他容器以及它所在的主机的隔离程度。
容器由它的镜像以及在创建或启动它时提供给它的任何配置选项定义。当容器被删除后,任何没有存储在持久化存储中的对于容器状态的修改将会消失。
docker run命令使用案例:
下面的命令运行了一个Ubuntu容器,附加了交互到本地命令行会话,然后运行了/bin/bash。
docker run -i -t ubuntu /bin/bash
当运行这个命令时,将发生下面的情况(假设使用的是默认的镜像仓库配置)。
如果本地没有Ubuntu镜像,Docker将从配置的镜像仓库中拉取镜像,就像手动执行 docker pull ubuntu 命令一样。
Docker会创建一个容器,就像手动执行 docker container create命令一样。
Docker为这个容器分配了一个可读写的文件系统,作为这个容器的最终层。这允许一个运行的容器在它的本地文件系统中创建和修改文件或文件夹。
Docker创建了一个网络接口并将容器连接到默认网络,因为我们没有指定任何网络选项。这包括为容器指定一个ip地址。默认情况下,容器可以使用主机的网络连接来连接到外网。
Docker启动这个容器,并执行/bin/bash命令。由于容器以交互方式附加到了终端(因为使用了-i -t选项),所以我们可以从键盘输入命令并可以在终端看到输出结果。
当输入exit命令来终止/bin/bash命令时,容器将会停止,但不会被删除。我们可以再次启动它或删除它。
Docker是使用Go语言编写的,它使用了Linux内核的一些特性来实现它的功能。Docker使用了一种叫做名称空间(namespaces)的技术来实现容器之间的隔离,当运行一个容器时,Docker就会为这个容器创建一组的名称空间。
这些名称空间提供了一个隔离层,容器的每一层运行在独立的名称空间中,并且访问权限也仅限于那一层名称空间。