假如你正在為一個跟蹤健康和健身的移動應用編寫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容器的架構如下圖所示:
Docker平臺架構由下圖一系列組件組成: Docker客戶端并不與運行的容器直接通信,取而代之,它通過TCP Sockets或REST與Docker守護進程通信,而守護進程將與主機上的容器直接通信。同時,Docker客戶端并不需要與守護進程安裝在同一臺主機上。
在使用Docker時有3個理念必須理解:鏡像(image)、注冊表(registry)和容器(container)。
鏡像,用于建立容器組件,它是個只讀模板,使用它可以發布一個以上的容器實例。理論上說,它非常類似于AMI。
Registry用于儲存鏡像,既可以在本地,也可以在遠程。當我們發布一個容器時,Docker首先會在本地Registry上搜索鏡像。如果在本地Registry上沒有發現,它隨后會搜索遠程公用的Registry,也就是DockerHub。如果在DockerHub發現所需鏡像,Docker會將它下載到本地注冊表,并使用它來發布所需容器。DockerHub非常類似于GitHub,我們可以使用它來建立公用或私有鏡像資源。鑒于這個屬性,有效及安全的鏡像發布將非常便捷。
可以這么說,容器運行在一個鏡像的實例上,Docker使用容器來執行和運行打包在鏡像中的軟件。
你也可以為一個正在運行的容器建立一個Docker鏡像,類似為一個EC2實例建立AMI。舉個例子,用戶可以發布一個容器,并使用類似APT或者YUM的包管理器安裝大量的軟件,然后將更新提交到一個新的Docker鏡像。
但是這里還存在更有效和靈活的途徑來建立鏡像,那就是使用Dockerfile,它允許聲明式的鏡像定義。Dockerfile語法由一系列的命令組成,我們可以用之安裝和配置鏡像中包括的各種組件。寫一個Dockerfile就像茶余飯后使用UserData配置一個EC2實例那么簡單。類似一個CloudFormation模板,Dockerfile可以使用一個版本控制系統進行跟蹤和發布,你可以將Dockerfile比作一個鏡像的建立文件。
那么在運動健身移動應用的打造中,Docker又會起到什么樣的作用?應用程序架構由下圖中的組件構成:
首先,我們需要建立一個Docker鏡像,用于發布運行中REST程序的容器。我們可以在筆記本上測試我們的代碼,而QA工程師則可以使用這個鏡像在EC2實例上對應用程序進行測試。REST程序使用Ruby和Sinatra框架編寫,因此它們需要被封裝到容器中。我們將使用Amazon DynamoDB作為后端,因此,為了保證應用程序在AWS內外都可以使用,Docker鏡像同樣需要封裝DynamoDB數據庫。這樣一來,Dockerfile的代碼可能如下所示:
DockefFile的內容不再解釋了,RUN關鍵字用以執行命令。默認情況下,命令執行在超級用戶權限下。鑒于需要使用RVM來安裝Ruby,我們需要使用USER關鍵字來轉換到Sinatra用戶權限,因此Ruby相關文件會安裝到用戶目錄下。從USER命令生效起,隨后的RUN命令都是使用Sinatra用戶權限來執行。這同樣意味著,當容器發布后,它也是以Sinatra用戶權限來執行命令的。
Docker守護進程負責管理鏡像與運行容器,而Docker客戶端通常被用以將命令發送到守護進程。因此在使用上文Dockerfile建立鏡像時,我們需要執行這個客戶端命令:
在docker.io網站上,我們可以發現完整的Docker客戶端命令說明文檔。下面,我們著重看一下建立鏡像所使用的命令。Tag選項用于在鏡像上建立識別符,其典型值是owner/repository:version。這樣一來,我們可以輕易的識別鏡像中所包含的內容,并且可以從注冊表中輕易的發現這個鏡像的所有權。
在執行build命令后,我們可以在Dockerfile中使用聲明來擁有一個配置好的鏡像。Dockerfile如下:
運行這個命令后,容器將成功發布,同時我們將進入Bash shell。在Bash shell中,我們可以像與Linux服務器一樣與容器交互。鑒于我們建立的是一個Web應用程序,我們會從Git repository中克隆最新版本到容器,用以運行我們的單元測試,并做好給QA傳送的準備。當代碼被克隆到容器之后,并且做好了被測試的準備,我們會將運行容器中所做的更新克隆到一個新的鏡像。為了完成這個步驟,我們需要確定容器的ID:
下一步,我們運行提交命令:
現在我們在本地注冊表中會擁有一個新的容器: Version 1.1版本鏡像擁有服務我們REST端點所需的Sinatra應用程序。我們可以使用以下命令來運行Web應用程序:
上面命令告訴Docker需要做以下的工作:
從鏡像aws_activate/sinatra:v1.1建立一個容器
(-d)表示以分離的形式運行容器
將工作路徑設置為/home/sinatra (-w)
映射容器端口到主機端口4567——10001
在容器中執行一個叫做run_app.sh的shell script
這個shell script會在容器中啟動DynamoDB,并且在4567下使用Thin網絡服務器的模式發布Sinatra應用程序。現在,如果我們在運行這個Docker容器的筆記本瀏覽器中指向http://localhost:10001/activity/1,我們將看到以下結果:
我們的程序看起來運行良好——活動記錄從本地DynamoDB中取出,并從Sinatra應用程序代碼中以JSON的格式返回。
如果想讓這個容器可以給QA工程師做進一步測試,我們可以將之推送給DockerHub這個公用的注冊表。類似GitHub,DockerHub提供了公用和私有兩個選項,可以滿足這個容器不面向所有人的需求。
QA工程師將在EC2中運行這個實例,這就意味著我們將需要一個配置了Docker守護進程和客戶端軟件的EC2實例。假設需要使用CloudFormation啟動一個EC2實例和CloudFormation表,我們可以借助CloudFormation AWS::EC2::Instance類型的UserData屬性,使用Docker軟件安裝程序中的引導程序。CloudFormation中規定EC2實例的JSON文件可能擁有類似如下代碼:
這里從鏡像中啟動容器的命令和上文沒太大的區別,有一個區別是環境變量會使用“-e”選項來設置,而Sinatra應用程序則會被配置為“test”環境。這個配置將使用區域端點(regional endpoint)來連接DynamoDB,而不是本地端點:
到這里,QA工程師就可以通過HTTP在公共DNS(名稱是EC2實例,端口號是10001)下訪問REST端點。當然,前提你還需要設置一個安全組規則,并允許10001端口訪問。如果發現任何bug,運行的容器可以提交到一個新的鏡像,指定一個合適的版本號,并將之提交到注冊表。容器的狀態會被完整的保存,因此軟件工程師可以便捷的復制QA中發現的問題,檢查日志文件并且做常規的排錯。我們希望通過本文讓用戶對Docker有一個很好的認識,同時也認識到AWS和Docker的完美兼容。Docker的可移植性讓它非常適合開發和測試,因為我們可以在多個團隊中非常便捷的共享容器。EC2和CloudFormation完美的支撐了容器在AWS中的運行,但是AWS的便利絕不止于此。AWS ElasticBeanstalk,允許開發者將整個應用程序堆棧部署到Docker容器。經常關注本網站,你將看到更多關于AWS中運行Docker的博客。