+86 13541016684Mon. - Fri. 10:00-22:00

开发/测试体验-在AWS上运行Docker

开发/测试体验-在AWS上运行Docker

假如你被指派为一个用以监测运动健身的移动应用建立REST API,同时要求在笔记本上的开发环境完成第一个端点的编码。在运行了所有单元测试并成功通过后,你将代码放入Git,并且通知QA工程师程序已经等待检 测,而且一切运行良好。然而,当QA工程师认真地将最新版本代码部署到测试环境后却发现,这个新开发的REST端点往往连前几分钟的测试都通不过。

为什么会发生这样的情况?你明明已经完整的运行了单元测试,而代码传递给QA工程师之前又没发生任何问题。在与QA工程师一起奋斗数个小时后,你发现测试环境使用了一个过时版本的第三方库,而正是这个原因导致了你的REST端点崩溃。

在 软件开发过程中,这个问题并不稀奇,轻视开发、测试、演示、生产环境的区别很容易会造成应用程序崩溃,而传统的类似改变管理过程的处理方法已经适应不了当 下应用程序快速的建立和部署周期。取而代之,我们需要的是将开发环境与测试环境无缝对接,消除人工干涉和容易出错的资源供应及配置。

长期以 来,AWS为开发者提供了自动化建立可靠及有效的开发环境。类似Amazon EC2和AWS CloudFormation等服务都允许开发者们像代码一样的管理基础设施。通过CloudFormation服务,AWS资源可以使用JSON做预分 配。CloudFormation模板可以在应用程序代码中正确的进行描述,通过EC2的自动化能力,用户可以快速和可靠地建立及结束某个环境。正是基于 这个原因,AWS非常适合开发和测试工作。

类似Docker等容器技术,将资源配置声明理念进行的更深一步。类似 CloudFormation提供给EC2实例的功能,Docker为容器建立提供了一个非常实用的声明语法。同时,Docker容器并不依赖任何虚拟化 平台,或者一个专用的操作系统。容器的运行仅仅需要一个Linux内核,这就意味着它几乎可以运行在任何环境之下——不管是笔记本或者是EC2实例。

Docker容器的架构如下图所示:
5437aa037eba7

图1

Docker 容器使用了一个被称为libcontainer的执行环境,它为不同的Linux内核隔离特性提供了一个接口,类似命名空间及控制组。这种架构允许多个容 器在共享同一个Linux内核的情况下完全隔离地运行。鉴于Docker容器并不需要一个专用的操作系统,因此它比虚拟机更加的便捷和轻量。

Docker平台架构由下图一系列组件组成:
5437aa96bcfa6

图2

Docker客户端并不与运行的容器直接通信,取而代之,它通过TCP sockets或者REST与Docker守护进程通信,而守护进程将与主机上的容器直接通信。同时,Docker客户端并不需要与守护进程安装在同一台主机上。

在使用Docker时有3个理念必须理解:镜像(image)、注册表(registry)和容器(container)。

镜像,用于建立容器组件,它是个只读模板,使用它可以发布一个以上的容器实例。理论上说,它非常类似于AMI。

注 册表用于储存镜像,既可以在本地,也可以在远程。当我们发布一个容器时,Docker首先会在本地注册表上搜索镜像。如果在本地注册表上没有发现,它随后 会搜索远程公用的注册表,也就是DockerHub。如果在DockerHub发现所需镜像,Docker会将它下载到本地注册表,并使用它来发布所需容 器。DockerHub非常类似于GitHub,我们可以使用它来建立公用或私有镜像资源。鉴于这个属性,有效及安全的镜像发布将非常便捷。

可以这么说,容器运行在一个镜像的实例上,Docker使用容器来执行和运行被打包在镜像中的软件。

你也可以为一个正在运行的容器建立一个Docker镜像,类似为一个EC2实例建立AMI。举个例子,用户可以发布一个容器,并使用类似APT或者YUM的包管理器安装大量的软件,然后将更新提交到一个新的Docker镜像。

但 是这里还存在更有效和灵活的途径来建立镜像,那就是使用DockerFile,它允许声明式的镜像定义。DockerFile语法由一系列的命令组成,我 们可以用之安装和配置镜像中包括的各种组件。写一个DockerFile就像茶余饭后使用UserData配置一个EC2实例那么简单。类似一个 CloudFormation模板,DockerFile可以使用一个版系统进行跟踪和发布,你可以将DockerFile比作一个镜像的建立文件。

那么在运动健身移动应用的打造中,Docker又会起到什么样的作用?应用程序架构由下图中的组件构成:

5437ab1199e57

图3

首 先,我们需要建立一个Docker镜像用于发布运行中REST端点的容器。我们可以使用它在笔记本上测试我们的代码,而QA工程师则可以使用它在EC2实 例上对应用程序进行测试。REST端点使用Ruby和Sinatra框架建立,因此它们需要被封装到容器中。我们将使用Amazon DynamoDB作为后端,因此,为了保证应用程序在AWS内外都可以使用,Docker镜像同样需要封装DynamoDB数据库。这样一 来,DockerFile的代码可能如下所示:

FROM ubuntu:14.04
MAINTAINER Nate Slater <slatern@amazon.com>
RUN apt-get update && apt-get install -y curl wget default-jre git
RUN adduser --home /home/sinatra --disabled-password --gecos '' sinatra
RUN adduser sinatra sudo
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER sinatra
RUN curl -sSL https://get.rvm.io | bash-s stable
RUN /bin/bash -l -c "source /home/sinatra/.rvm/scripts/rvm"
RUN /bin/bash -l -c "rvm install 2.1.2"
RUN /bin/bash -l -c "gem install sinatra"
RUN /bin/bash -l -c "gem install thin"
RUN /bin/bash -l -c "gem install aws-sdk"
RUN wget -O /home/sinatra/dynamodb_local.tar.gz https://s3-us-west
-2.amazonaws.com/dynamodb-local/dynamodb_local_2013-12-12.tar.gz
RUN tar -C /home/sinatra -xvzf /home/sinatra/dynamodb_local.tar.gz

DockerFile 的内容必须要是自声明的,RUN关键字用以执行命令。默认情况下,命令执行在超级用户权限下。鉴于需要使用RVM来安装Ruby,我们需要使用USER关 键字来转换到Sinatra用户权限,因此Ruby相关文件会安装到用户目录下。从USER命令生效起,随后的RUN命令都是使用Sinatra用户权限 来执行。这同样意味着,当容器发布后,它也是以Sinatra用户权限来执行命令的。

Docker守护进程负责管理镜像与运行容器,而Docker客户端通常被用以将命令发送到守护进程。因此在使用上文DockerFile建立镜像时,我们需要执行这个客户端命令:

$ docker build --tag=”aws_activate/sinatra:v1" .

在 docker.io网站上,我们可以发现完整的Docker客户端命令说明文档。下面,我们着重看一下我们建立镜像所使用的命令。Tag选项用于在镜像上 建立识别符,其典型值是owner/repository:version。这样一来,我们可以轻易的识别镜像中所包含的内容,并且可以从注册表中轻易的 发现这个镜像的所有权。

在执行build命令后,我们可以在DockerFile中使用声明来拥有一个配置好的镜像。DockerFile如下:

$ docker imagesREPOSITORY   TAG            IMAGE ID       CREATED                     
VIRTUAL SIZE
aws_activate/sinatra        v1             84b6d4a5a22b   
36 hours ago                942.2 MB
ubuntu                      14.04          96864a7d2df3    
6 days ago                  205.1 MB

毫无疑问,我们可以看到Docker建立好了我们所需的镜像,并给它分配tag中指定的所有权,同时还会拥有一个唯一的镜像ID。现在,我们就可以通过新建立好的镜像来发布容器:

$ docker run -it aws_activate/sinatra:v1 /bin/bash

运 行这个命令后,容器将成功发布,同时我们将进入Bash shell。在Bash shell中,我们可以像与Linux服务器一样与容器交互。鉴于我们建立的是一个Web应用程序,我们会从Git repository中克隆最新版本到容器,用以运行我们的单元测试,并做好给QA传送的准备。当代码被克隆到容器之后,并且做好了被测试的准备,我们会 将运行容器中所做的更新克隆到一个新的镜像。为了完成这个步骤,我们需要确定容器的ID:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b9d03d60ba89 aws_activate/sinatra:v1 "/bin/bash" 11 minutes ago Up
11 minutes nostalgic_davinci

下一步,我们运行提交命令:

$ docker commit -m “ready for testing” b9d03d60ba89 
aws_activate/sinatra:v1.1

现在我们在本地注册表中会拥有一个新的容器:

$ docker imagesREPOSITORY  TAG               IMAGE ID CREATED                    
VIRTUAL SIZE
aws_activate/sinatra       v1.1           40355be9eb8f 
21 hours ago               947.5 MB
aws_activate/sinatra       v1             84b6d4a5a22b 
3 days ago                 942.2 MB
ubuntu                     14.04          96864a7d2df3 
8 days ago                 205.1 MB

Version 1.1版本镜像拥有服务我们REST端点所需的Sinatra应用程序。我们可以使用以下命令来运行Web应用程序:

$ docker run -d -w /home/sinatra -p 10001:4567 
aws_activate/sinatra:v1.1 ./run_app.sh

上面命令告诉Docker需要做以下的工作:

1. 从镜像aws_activate/sinatra:v1.1建立一个容器

2. (-d)表示以分离的形式运行容器

3. 将工作路径设置为/home/sinatra (-w)

4. 映射容器端口到主机端口4567——10001

5. 在容器中执行一个叫做run_app.sh的shell script

这个shell script会在容器中启动DynamoDB,并且在4567下使用Thin网络服务器的模式发布Sinatra应用程序。现在,如果我们在运行这个Docker容器的笔记本浏览器中指向http://localhost:10001/activity/1,我们将看到以下结果:

{"activity_id":"1",
"user_id":" db430d35-92a0-49d6-ba79-0f37ea1b35f7",
"type":"meal",
"calories":100,
"date":"2014-09-26 15:33:58 +0000"}

我们的端点看起来运行良好——活动记录从本地DynamoDB中取出,并从Sinatra应用程序代码中以JSON的格式返回。

如果想让这个容器可以给QA工程师做进一步测试,我们可以将之推送给DockerHub这个公用的注册表。类似GitHub,DockerHub提供了公用和私有两个选项,可以满足这个容器不面向所有人的需求。

QA 工程师将在EC2中运行这个实例,这就意味着我们将需要一个配置了Docker守护进程和客户端软件的EC2实例。假设需要使用 CloudFormation启动一个EC2实例和CloudFormation表,我们可以借助CloudFormation AWS::EC2::Instance类型的UserData属性,使用Docker软件安装程序中的引导程序。CloudFormation中规定 EC2实例的JSON文件可能拥有类似如下代码:

"DockerInstance": {
     "Type": "AWS::EC2::Instance",
     "Properties": {
          "InstanceType": "t2.micro", 
          "ImageId": {"Fn::FindInMap" : ["RegionMap",{"Ref" :
          "AWS::Region"}, "64"]}, 
"KeyName": {"Ref": "KeyName"}, 
"SubnetId": {"Ref": "SubnetId"}, 
"SecurityGroupIds": [{"Ref": "SecurityGroupId"}],  
"Tags": [{"Key": "Name", "Value": "DockerHost"}], 
"UserData": {"Fn::Base64":
"#include https://get.docker.io" }
}}

这样一来,如果QA工程师登入CloudFormation堆栈建立的EC2实例,镜像可以使用如下命令从远程的DockerHub注册表中取出:

$ docker pull aws_activate/sinatra:v1.1

这里从镜像中启动容器的命令和上文没太大的区别,有一个区别是环境变量会使用“-e”选项来设置,而Sinatra应用程序则会被配置为“test”环境。这个配置将使用区域端点(regional endpoint)来连接DynamoDB,而不是本地端点:

$ docker run -d -w /home/sinatra –e “RACK_ENV=test” -p 10001:4567
 aws_activate/sinatra:v1.1 ./run_app.sh 

到这里,QA工程师就可以通过HTTP在公共DNS(名称是EC2实例,端口号是10001)下访问REST端点。当然,前提你还需要 设置一个安全组规则,并允许10001端口访问。如果发现任何bug,运行的容器可以提交到一个新的镜像,指定一个合适的版本号,并将之提交到注册表。容 器的状态会被完整的保存,因此软件工程师可以便捷的复制QA中发现的问题,检查日志文件并且做常规的排错。

我们希望通过本文让用户对 Docker有一个很好的认识,同时也认识到AWS和Docker的完美兼容。Docker的可移植性让它非常适合开发和测试,因为我们可以在多个团队中 非常便捷的共享容器。EC2和CloudFormation完美的支撑了容器在AWS中的运行,但是AWS的便利绝不止于此。AWS ElasticBeanstalk,允许开发者将整个应用程序堆栈部署到Docker容器。经常关注本网站,你将看到更多关于AWS中运行Docker的 博客。