在AWS上運(yùn)行Docker:一個更好的開發(fā)&測試體驗
假如你正在為一個跟蹤健康和健身的移動應(yīng)用編寫REST API。一開始你在你的筆記本上的開發(fā)環(huán)境中編寫代碼,在運(yùn)行了所有單元測試并成功通過后,你將所有代碼提交到Git,并且通知QA工程師測試。然而,當(dāng)QA工程師認(rèn)真地將***版本代碼部署到測試環(huán)境后卻發(fā)現(xiàn),這個新開發(fā)的REST程序往往連前幾分鐘的測試都通不過。
為什么會發(fā)生這樣的情況?你明明已經(jīng)完整的運(yùn)行了單元測試,而代碼移交給QA工程師之前又沒發(fā)生任何問題。在與QA工程師一起奮斗數(shù)個小時后,你發(fā)現(xiàn)測試環(huán)境使用了一個過時版本的第三方庫,而正是這個原因?qū)е铝四愕腞EST程序無法正常運(yùn)行。
在軟件開發(fā)過程中,這個問題并不稀奇,開發(fā)、測試、演示、生產(chǎn)環(huán)境中的微小的區(qū)別都會引起各種各樣的問題,而之前的處理流程已經(jīng)適應(yīng)不了現(xiàn)在應(yīng)用程序快速的構(gòu)建和部署流程。我們需要的是將開發(fā)環(huán)境與測試環(huán)境無縫對接,減少人工干涉以及配置。
AWS為開發(fā)者提供了自動化構(gòu)建可靠及高效的開發(fā)環(huán)境。類似Amazon EC2和AWS CloudFormation等服務(wù)都允許開發(fā)者們通過代碼的方式管理基礎(chǔ)設(shè)施。通過CloudFormation服務(wù),AWS資源可以使用JSON做預(yù)分配。CloudFormation模板可以在應(yīng)用程序代碼中正確的進(jìn)行描述,通過EC2的自動化能力,用戶可以快速和可靠地新建及結(jié)束某個環(huán)境。正是基于這個原因,AWS非常適合開發(fā)和測試工作。
類似Docker之類的容器技術(shù)讓資源配置聲明的理念成為現(xiàn)實。類似CloudFormation提供給EC2實例的功能,Docker為容器建立提供了一個非常實用的聲明語法。同時,Docker容器并不依賴任何虛擬化平臺,或者一個專用的操作系統(tǒng)。容器的運(yùn)行僅僅需要一個Linux內(nèi)核,這就意味著它幾乎可以運(yùn)行在任何環(huán)境之下——不管是筆記本或者是EC2實例。
Docker容器的架構(gòu)如下圖所示:
Docker容器使用了一個被稱為Libcontainer的執(zhí)行環(huán)境,它為不同的Linux內(nèi)核隔離特性提供了一個接口,類似命名空間及控制組。這種架構(gòu)允許多個容器在共享同一個Linux內(nèi)核的情況下完全隔離地運(yùn)行。鑒于Docker容器并不需要一個專用的操作系統(tǒng),因此它比虛擬機(jī)更加的便捷和輕量。
Docker平臺架構(gòu)由下圖一系列組件組成:
Docker客戶端并不與運(yùn)行的容器直接通信,取而代之,它通過TCP Sockets或REST與Docker守護(hù)進(jìn)程通信,而守護(hù)進(jìn)程將與主機(jī)上的容器直接通信。同時,Docker客戶端并不需要與守護(hù)進(jìn)程安裝在同一臺主機(jī)上。
在使用Docker時有3個理念必須理解:鏡像(image)、注冊表(registry)和容器(container)。
鏡像,用于建立容器組件,它是個只讀模板,使用它可以發(fā)布一個以上的容器實例。理論上說,它非常類似于AMI。
Registry用于儲存鏡像,既可以在本地,也可以在遠(yuǎn)程。當(dāng)我們發(fā)布一個容器時,Docker首先會在本地Registry上搜索鏡像。如果在本地Registry上沒有發(fā)現(xiàn),它隨后會搜索遠(yuǎn)程公用的Registry,也就是DockerHub。如果在DockerHub發(fā)現(xiàn)所需鏡像,Docker會將它下載到本地注冊表,并使用它來發(fā)布所需容器。DockerHub非常類似于GitHub,我們可以使用它來建立公用或私有鏡像資源。鑒于這個屬性,有效及安全的鏡像發(fā)布將非常便捷。
可以這么說,容器運(yùn)行在一個鏡像的實例上,Docker使用容器來執(zhí)行和運(yùn)行打包在鏡像中的軟件。
你也可以為一個正在運(yùn)行的容器建立一個Docker鏡像,類似為一個EC2實例建立AMI。舉個例子,用戶可以發(fā)布一個容器,并使用類似APT或者YUM的包管理器安裝大量的軟件,然后將更新提交到一個新的Docker鏡像。
但是這里還存在更有效和靈活的途徑來建立鏡像,那就是使用Dockerfile,它允許聲明式的鏡像定義。Dockerfile語法由一系列的命令組成,我們可以用之安裝和配置鏡像中包括的各種組件。寫一個Dockerfile就像茶余飯后使用UserData配置一個EC2實例那么簡單。類似一個CloudFormation模板,Dockerfile可以使用一個版本控制系統(tǒng)進(jìn)行跟蹤和發(fā)布,你可以將Dockerfile比作一個鏡像的建立文件。
那么在運(yùn)動健身移動應(yīng)用的打造中,Docker又會起到什么樣的作用?應(yīng)用程序架構(gòu)由下圖中的組件構(gòu)成:
#p#
首先,我們需要建立一個Docker鏡像,用于發(fā)布運(yùn)行中REST程序的容器。我們可以在筆記本上測試我們的代碼,而QA工程師則可以使用這個鏡像在EC2實例上對應(yīng)用程序進(jìn)行測試。REST程序使用Ruby和Sinatra框架編寫,因此它們需要被封裝到容器中。我們將使用Amazon DynamoDB作為后端,因此,為了保證應(yīng)用程序在AWS內(nèi)外都可以使用,Docker鏡像同樣需要封裝DynamoDB數(shù)據(jù)庫。這樣一來,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
DockefFile的內(nèi)容不再解釋了,RUN關(guān)鍵字用以執(zhí)行命令。默認(rèn)情況下,命令執(zhí)行在超級用戶權(quán)限下。鑒于需要使用RVM來安裝Ruby,我們需要使用USER關(guān)鍵字來轉(zhuǎn)換到Sinatra用戶權(quán)限,因此Ruby相關(guān)文件會安裝到用戶目錄下。從USER命令生效起,隨后的RUN命令都是使用Sinatra用戶權(quán)限來執(zhí)行。這同樣意味著,當(dāng)容器發(fā)布后,它也是以Sinatra用戶權(quán)限來執(zhí)行命令的。
Docker守護(hù)進(jìn)程負(fù)責(zé)管理鏡像與運(yùn)行容器,而Docker客戶端通常被用以將命令發(fā)送到守護(hù)進(jìn)程。因此在使用上文Dockerfile建立鏡像時,我們需要執(zhí)行這個客戶端命令:
- $ docker build --tag=”aws_activate/sinatra:v1" .
在docker.io網(wǎng)站上,我們可以發(fā)現(xiàn)完整的Docker客戶端命令說明文檔。下面,我們著重看一下建立鏡像所使用的命令。Tag選項用于在鏡像上建立識別符,其典型值是owner/repository:version。這樣一來,我們可以輕易的識別鏡像中所包含的內(nèi)容,并且可以從注冊表中輕易的發(fā)現(xiàn)這個鏡像的所有權(quán)。
在執(zhí)行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中指定的所有權(quán),同時還會擁有一個唯一的鏡像ID。現(xiàn)在,我們就可以通過新建立好的鏡像來發(fā)布容器:
- $ docker run -it aws_activate/sinatra:v1 /bin/bash
運(yùn)行這個命令后,容器將成功發(fā)布,同時我們將進(jìn)入Bash shell。在Bash shell中,我們可以像與Linux服務(wù)器一樣與容器交互。鑒于我們建立的是一個Web應(yīng)用程序,我們會從Git repository中克隆***版本到容器,用以運(yùn)行我們的單元測試,并做好給QA傳送的準(zhǔn)備。當(dāng)代碼被克隆到容器之后,并且做好了被測試的準(zhǔn)備,我們會將運(yùn)行容器中所做的更新克隆到一個新的鏡像。為了完成這個步驟,我們需要確定容器的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
下一步,我們運(yùn)行提交命令:
- $ docker commit -m “ready for testing” b9d03d60ba89
- aws_activate/sinatra:v1.1
現(xiàn)在我們在本地注冊表中會擁有一個新的容器:
- $ 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版本鏡像擁有服務(wù)我們REST端點(diǎn)所需的Sinatra應(yīng)用程序。我們可以使用以下命令來運(yùn)行Web應(yīng)用程序:
- $ docker run -d -w /home/sinatra -p 10001:4567
- aws_activate/sinatra:v1.1 ./run_app.sh
上面命令告訴Docker需要做以下的工作:
- 從鏡像aws_activate/sinatra:v1.1建立一個容器
- (-d)表示以分離的形式運(yùn)行容器
- 將工作路徑設(shè)置為/home/sinatra (-w)
- 映射容器端口到主機(jī)端口4567——10001
- 在容器中執(zhí)行一個叫做run_app.sh的shell script
這個shell script會在容器中啟動DynamoDB,并且在4567下使用Thin網(wǎng)絡(luò)服務(wù)器的模式發(fā)布Sinatra應(yīng)用程序。現(xiàn)在,如果我們在運(yùn)行這個Docker容器的筆記本瀏覽器中指向http://localhost:10001/activity/1,我們將看到以下結(jié)果:
- {"activity_id":"1",
- "user_id":" db430d35-92a0-49d6-ba79-0f37ea1b35f7",
- "type":"meal",
- "calories":100,
- "date":"2014-09-26 15:33:58 +0000"}
我們的程序看起來運(yùn)行良好——活動記錄從本地DynamoDB中取出,并從Sinatra應(yīng)用程序代碼中以JSON的格式返回。
如果想讓這個容器可以給QA工程師做進(jìn)一步測試,我們可以將之推送給DockerHub這個公用的注冊表。類似GitHub,DockerHub提供了公用和私有兩個選項,可以滿足這個容器不面向所有人的需求。
QA工程師將在EC2中運(yùn)行這個實例,這就意味著我們將需要一個配置了Docker守護(hù)進(jìn)程和客戶端軟件的EC2實例。假設(shè)需要使用CloudFormation啟動一個EC2實例和CloudFormation表,我們可以借助CloudFormation AWS::EC2::Instance類型的UserData屬性,使用Docker軟件安裝程序中的引導(dǎo)程序。CloudFormation中規(guī)定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實例,鏡像可以使用如下命令從遠(yuǎn)程的DockerHub注冊表中取出:
- $ docker pull aws_activate/sinatra:v1.1
這里從鏡像中啟動容器的命令和上文沒太大的區(qū)別,有一個區(qū)別是環(huán)境變量會使用“-e”選項來設(shè)置,而Sinatra應(yīng)用程序則會被配置為“test”環(huán)境。這個配置將使用區(qū)域端點(diǎn)(regional endpoint)來連接DynamoDB,而不是本地端點(diǎn):
- $ 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端點(diǎn)。當(dāng)然,前提你還需要設(shè)置一個安全組規(guī)則,并允許10001端口訪問。如果發(fā)現(xiàn)任何bug,運(yùn)行的容器可以提交到一個新的鏡像,指定一個合適的版本號,并將之提交到注冊表。容器的狀態(tài)會被完整的保存,因此軟件工程師可以便捷的復(fù)制QA中發(fā)現(xiàn)的問題,檢查日志文件并且做常規(guī)的排錯。
我們希望通過本文讓用戶對Docker有一個很好的認(rèn)識,同時也認(rèn)識到AWS和Docker的***兼容。Docker的可移植性讓它非常適合開發(fā)和測試,因為我們可以在多個團(tuán)隊中非常便捷的共享容器。EC2和CloudFormation***的支撐了容器在AWS中的運(yùn)行,但是AWS的便利絕不止于此。AWS ElasticBeanstalk,允許開發(fā)者將整個應(yīng)用程序堆棧部署到Docker容器。經(jīng)常關(guān)注本網(wǎng)站,你將看到更多關(guān)于AWS中運(yùn)行Docker的博客。