搏风雨 2018-05-23
Docker是一个基于容器的虚拟化平台,不同之处在于,你必须创建全新的机器以将它们彼此隔离并确保其独立性,因此Docker将允许你创建仅包含你的应用的容器。以容器形式打包,这些应用程序可以轻松部署在任何运行Docker的主机上,每个容器仍然完全独立!
Docker平台由两部分组成:在后台运行并负责管理容器的Docker后台程序以及允许你通过命令行工具与后台进程交互的Docker客户端。
让我们从最普遍的问题开始。你的团队为客户开发了一款产品,并对其进行了测试,现在是向客户交付内置解决方案的时候了。你如何以最短的时间和最少的资源做到这一点?通常,你会准备许多不同的配置文件和脚本,并编写安装说明,然后花费更多时间解决用户错误或环境兼容性问题。假设你曾经做过一次,如果你需要多次安装产品会怎么样?你拥有数百或数千名客户,而不是一个客户,并且必须重复所有安装和配置步骤。手动完成此操作需要花费太多时间,而且费用高且容易出错。如果你需要将产品更新到更新版本,则会变得更加困难。
另一个问题-可重用性。假设你有一个制作浏览器游戏的部门。假设你的部门开发了几款游戏,它们都使用相同的技术堆栈。为了发布新游戏,你必须重新配置与其他配置几乎相同的新服务器。
像其他任何问题一样,我们可以将多个解决方案应用于我们的问题:
安装脚本
第一种方法是编写一个脚本,它将安装你需要的所有内容并在所有相关服务器上运行它。脚本可以是简单的“sh”文件,也可以是复杂的文件,如SFX模块。这种方法的缺点是脆弱和不稳定。如果脚本写得不好,迟早会在某个时候失败。在失败之后,客户环境实际上会变得“漏洞百出”,并且仅仅“回滚”脚本所能执行的动作并不那么容易。
云服务
第二种方法是使用云服务。你可以在虚拟服务器上手动安装所需的所有内容,然后制作镜像。之后,你可以根据需要多次克隆它。也有一些缺点。首先,你依赖于云服务,客户可能不同意你所做的服务器选择。其次,受限于云的速度。因为与专用服务器相比,云提供的虚拟服务器的性能可能无法让你满意。
虚拟机
第三种方法是关于虚拟机。仍然有一些缺点。由于磁盘空间或网络流量受限,下载虚拟机镜像并不总是很方便,虚拟机映像可能非常大。因此,虚机镜像内的任何更改都需要再次下载整个镜像。另外,并非所有虚拟机都支持内存或CPU共享。
Docker是开创性的解决方案。以下是Docker容器和简单服务器之间最重要的区别:
无状态
启动后,容器配置不能也不必更改。所有的前期工作都必须在创建镜像的阶段(也称为编译时间)或在容器开始阶段完成。不同类型的配置,端口,公共文件夹和环境变量:在容器启动时必须知道所有这些。当然,Docker允许你随意使用它的内存和文件系统,但是在启动过程中接触你可以接触的东西,这并不是好的方法。
纯粹
容器不知道主机系统的任何信息,不能干扰其他容器,要么进入其他文件系统,要么发送信号给其他的进程。当然,Docker允许容器相互通信,但只能严格使用已声明的方法。你还可以运行赋予一些超级权限容器,例如,访问真实网络或物理设备。
Lazy
当你启动容器时,它不会复制创建它的镜像文件系统。该容器仅在镜像顶部创建一个空文件系统 - 一个图层。该镜像由重叠图层列表组成。这就是为什么容器的起动速度不到一秒钟的原因。
声明
所有容器属性都以声明形式存储。创建镜像的步骤也被描述为严格分离的步骤。网络设置,文件系统的内容,内存容量,公共端口等都在Dockerfile中或在启动时使用静态键的帮助下设置。
功能
容器只做一件事,但做得很好。假设容器只能在一个只在应用程序中执行一个功能的进程中生存。由于容器没有内核启动分区,一个初始化进程,甚至通常只有一个pseudo-root用户,所以它不是一个完整的操作系统。这种专业化使得容器实现的功能可预测和可扩展。
严格
默认情况下,Docker容器禁止访问网络(也可以禁用)。但是,如果有必要,可以违反任何这些规则。
与此同时,Docker允许我们设计更灵活的测试架构,让每个容器都包含一个应用程序(数据库,语言,组件)的模块。要测试新版本的模块,只需要更换相应的容器。
使用Docker,可以从容器构建应用程序,每个层的组件与其他组件隔离。这是微服务架构的概念。
在我们公司,几个月前开始使用Docker的理念。一位同事就如何帮助我们以及将如何使用它进行了调查。一开始,只使用Docker进行一些自动化测试,但是在看到它的强大功能之后,我们决定进一步开发。
无论在谈论自动化测试还是关于集成测试,我们都面临同样的问题:并行测试和孤立环境。每个测试用例都需要一个干净的数据库才能执行。数据库加载过程大约需要1分30秒。要在每次自动化测试开始时执行此过程,需要几天才能正确测试产品。我们试图导出/导入数据库转储,但仍然不够快。
使用Docker,只在镜像编译期间加载数据库,只有一次。这意味着该镜像已经包含一个干净的数据库,并且从该镜像启动的每个容器都为准备测试提供了一个干净的环境。
另一点涉及所有测试常见的资源。一方面,我们希望隔离被测系统(SUT);另一方面,我们也想分享SUT之间的一些产品。我们想分享所有必需的依赖关系。例如,我们不希望每张图片中都有所有库文件的副本,因为这样我们就浪费了大量的内存,更不用说性能了。为了解决这个问题,我们配置了一个Maven本地存储库,以便在安装了Docker的主机系统和容器之间共享。在Docker镜像中,我们只保留了一些项目需要测试的指令。所有通用库都在所有容器之间共享。
除了自动化测试外,我们还对产品进行一些集成测试。这些测试还需要一个单独的数据库,以便加载和测试。
我们通过使用Jenkins来确保我们项目的持续集成。到目前为止,我们有一台虚拟机作为Jenkins主设备,两台虚拟机作为从设备。不久之前,我们决定从Jenkins中删除所有执行,并让其仅处理Jenkins配置。因此,只有2个slave可以实际做某件事。但是,除了自动化和集成测试之外,我们在Jenkins还有其他一些工作,这也需要一个执行环境。为此,我们配置了一个从服务器来运行集成测试,另一个服务器执行不需要数据库的作业。所以,通过这种设置(图1),我们一次只能执行一次集成测试。
图1:以前的Jenkins设置
在某个特定时间,测试执行花费了太多时间,因为一切都按顺序执行。当所有slave的执行都活跃起来时,意味着他们每个人都在编译某个项目,至少有一份工作会因为缺乏资源而挂起。
我们在Linux机器上配置了Docker引擎,并且为我们的测试构建了Dockerfiles,并开始从Jenkins触发它们。我们已经建立了一个独立的环境来执行集成测试,但是,结果我们正面临一些其他问题。每次执行测试后,我们需要进行一些清理并导出日志。我们可以使用一些Linux脚本来做到这一点,但这会给我们带来很多麻烦。
我们已经决定改变我们的方式,以便为我们提供更多的性能和更少的麻烦。我们已经将Jenkins从Windows移到了Linux,现在我们在Docker中运行了Jenkins(如图2所示)。Docker社区非常新颖,在Docker Hub上,你可以找到很多Jenkins镜像。通过一些额外的配置,你可以构建一个严谨的CI环境。
我们在那里发现了一个Jenkins的镜像。当你使用这个镜像启动一个容器时,你可以为这个容器设置一个角色,成为一个Jenkins master或Jenkins slave。
图2:新的Jenkins设置
一个Docker容器用作Jenkins master,它存储所有配置——作业,插件,构建历史等,并关注工作区,日志并执行所有必要的清理。另外四个容器作为slave。你注意到了吗?4个slave相比以前的2个,所可扩展性是我们使用Docker的另一个原因。每个slave都有自己独立的环境,这意味着它自己的数据库。不久之后,它就像拥有4台虚拟机一样,但要好得多。现在我们可以同时执行至少3次集成测试。
我们在存储库中找到的Docker镜像只安装了Jenkins。但是,为了确保我们项目的CI,我们需要在从属容器内安装一些其他工具。为此,我们扩展了Jenkins镜像,并且安装了一些特定于Linux的工具,Java,ActiveMQ,PostgreSQL,我们配置了数据库角色和模式,Maven,Node.JS,我们创建了一些默认目录将在以后的项目中使用。
所有这些工具都只安装在用于启动从服务器的镜像上。对于master,我们不需要他们,因为它不会执行任何工作。
镜像如下:
有两种方法可以使用这些镜像启动容器。我们可以逐个启动容器,或者使用Docker Compose,一个用于定义和运行多容器Docker应用程序的工具。即使使用命令行,添加一个更多的从属容器也是非常容易的,更不用说Docker Compose了。docker-compose.yml包含以下内容:
现在我们已经为第一个容器运行定义了一切。我们对所有其他容器都有类似的配置,只是名称不同。使用命令docker-compose up,我们可以启动所有的容器,可以用类似的方式禁止它们:docker-compose down。很高兴有Docker Compose命令,它必须从docker-compose.yml文件所在的相同位置执行。
当然,我们已经注意到在主容器被删除的情况下可能遇到麻烦。会发生什么,我们会失去我们所有的CI系统吗?不,有备份计划,我们将所有配置存储在Docker卷内,如图。卷是一个或多个容器内的特殊目录,旨在为持久或共享数据提供功能。所以我们坚持让Jenkins回到Docker主机。如果我们删除了与Jenkins相关的所有容器,我们不会丢失任何东西,仍然拥有启动另一个Jenkins实例的所有必要数据。
使用Docker卷来保存容器中的数据