12 分鐘從頭搭建一個完整的Rails應用
在 Docker 和 Ansible 的技術社區內存在著很多好玩的東西,我希望在你閱讀完這篇文章后也能像我們一樣熱愛它們。當然,你也會收獲一些實踐知識,那就是如何通過部署 Ansible 和 Docker 來為 Rails 應用搭建一個完整的服務器環境。
也許有人會問:你怎么不去用 Heroku?首先,我可以在任何供應商提供的主機上運行 Docker 和 Ansible;其次,相比于方便性,我更偏向于喜歡靈活性。我可以在這種組合中運行任何程序,而不僅僅是 web 應用。最后,我骨子里是一個工匠,我非常了解如何把零件拼湊在一起工作。Heroku 的基礎模塊是 Linux Container,而 Docker 表現出來的多功能性也是基于這種技術。事實上,Docker 的其中一個座右銘是:容器化是新虛擬化技術。
為什么使用 Ansible?
我重度使用 Chef 已經有4年了(LCTT:Chef 是與 puppet 類似的配置管理工具),基礎設施即代碼的觀念讓我覺得非常無聊。我花費大量時間來管理代碼,而不是管理基礎設施本身。不論多小的改變,都需要相當大的努力來實現它。使用 Ansible,你可以一手掌握擁有可描述性數據的基礎架構,另一只手掌握不同組件之間的交互作用。這種更簡單的操作模式讓我把精力集中在如何將我的技術設施私有化,提高了我的工作效率。與 Unix 的模式一樣,Ansible 提供大量功能簡單的模塊,我們可以組合這些模塊,達到不同的工作要求。
除了 Python 和 SSH,Ansible 不再依賴其他軟件,在它的遠端主機上不需要部署代理,也不會留下任何運行痕跡。更厲害的是,它提供一套內建的、可擴展的模塊庫文件,通過它你可以控制所有的一切:包管理器、云服務供應商、數據庫等等等等。
為什么要使用 Docker?
Docker 的定位是:提供最可靠、最方便的方式來部署服務。這些服務可以是 mysqld,可以是 redis,可以是 Rails 應用。先聊聊 git 吧,它的快照功能讓它可以以最有效的方式發布代碼,Docker 的處理方法與它類似。它保證應用可以無視主機環境,隨心所欲地跑起來。
一種最普遍的誤解是人們總是把 Docker 容器看成是一個虛擬機,當然,我表示理解你們的誤解。Docker 滿足單一功能原則,在一個容器里面只跑一個進程,所以一次修改只會影響一個進程,而這些進程可以被重用。這種模型參考了 Unix 的哲學思想,當前還處于試驗階段,并且正變得越來越穩定。
設置選項
不需要離開終端,我就可以使用 Ansible 來在這些云平臺中生成實例:Amazon Web Services,Linode,Rackspace 以及 DigitalOcean。如果想要更詳細的信息,我于1分25秒內在位于阿姆斯特丹的2號數據中心上創建了一個 2GB 的 DigitalOcean 虛擬機。另外的1分50秒用于系統配置,包括設置 Docker 和其他個人選項。當我完成這些基本設定后,就可以部署我的應用了。值得一提的是這個過程中我沒有配置任何數據庫或程序開發語言,Docker 已經幫我把應用所需要的事情都安排好了。
Ansible 通過 SSH 為遠端主機發送命令。我保存在本地 ssh 代理上面的 SSH 密鑰會通過 Ansible 提供的 SSH 會話分享到遠端主機。當我把應用代碼從遠端 clone 下來,或者上傳到遠端時,我就不再需要提供 git 所需的證書了,我的 ssh 代理會幫我通過 git 主機的身份驗證程序的。
Docker 和應用的依賴性
我發現有一點挺有意思的:大部分開發者非常了解他們的應用需要什么版本的編程語言,這些語言依賴關系有多種形式:Python 的包、Ruby 的打包系統 gems、node.js 的模塊等等,但與數據庫或消息隊列這種重要的概念相比起來,這些語言就處于很隨便的境地了——隨便給我個編程語言環境,我都能把數據庫和消息隊列系統跑起來。我認為這是 DevOps 運動(它旨在促進開發與運維團隊的和諧相處)的動機之一,開發者負責搭建應用所需要的環境。Docker 使這個任務變得簡單明了直截了當,它為現有環境加了實用的一層配置。
我的應用依賴于 MySQL 5.5和 Redis 2.8,依賴關系放在“.dockercontainerdependencies”文件里面:
- gerhard/mysql:5.5
- gerhard/redis:2.8
Ansible 會查看這個文件,并且通知 Docker 加載正確的鏡像,然后在容器中啟動。它還會把這些服務容器鏈接到應用容器。如果你想知道 Docker 容器的鏈接功能是怎么工作的,可以參考Docker 0.6.5 發布通知.
我的應用包括一個 Dockerfile,它詳細指定了 Ruby Docker 鏡像的信息,這里面的步驟能夠保證把正確的 Ruby 版本加載到鏡像中。
- FROM howareyou/ruby:2.0.0-p353
- ADD ./ /terrabox
- RUN \
- . /.profile ;\
- rm -fr /terrabox/.git ;\
- cd /terrabox ;\
- bundle install --local ;\
- echo '. /.profile && cd /terrabox && RAILS_ENV=test bundle exec rake db:create db:migrate && bundle exec rspec' > /test-terrabox ;\
- echo '. /.profile && cd /terrabox && export RAILS_ENV=production && rake db:create db:migrate && bundle exec unicorn -c config/unicorn.rails.conf.rb' > /run-terrabox ;\
- # END RUN
- ENTRYPOINT ["/bin/bash"]
- CMD ["/run-terrabox"]
- EXPOSE 3000
第一步是復制應用的所有代碼到 Docker 鏡像,加載上一個鏡像的全局環境變量。這個例子中的 Ruby Docker 鏡像會加載 PATH 配置,這個配置能確保鏡像加載正確的 Ruby 版本。
接下來,刪除 git 歷史,Docker 容器不需要它們。我安裝了所有 Ruby 的 gems,創建一個名為“/test-terrabox”的命令,這個命令會被名為“test-only”的容器執行。這個步驟的目的是能正確解決應用和它的依賴關系,讓 Docker 容器正確鏈接起來,保證在真正的應用容器啟動前能通過所有測試項目。
CMD 這個步驟是在新的 web 應用容器啟動后執行的。在測試環節結束后馬上就執行/run-terrabox命令進行編譯。
最后,Dockerfile 為應用指定了一個端口號,將容器內部端口號為3000的端口映射到主機(運行著 Docker 的機器)的一個隨機分配的端口上。當 Docker 容器里面的應用需要響應來自外界的請求時,這個端口可用于反向代理或負載均衡。
Docker 容器內運行 Rails 應用
沒有本地 Docker 鏡像,從零開始部署一個中級規模的 Rails 應用大概需要100個 gems,進行100次整體測試,在使用2個核心實例和2GB內存的情況下,這些操作需要花費8分16秒。裝上 Ruby、MySQL 和 Redis Docker 鏡像后,部署應用花費了4分45秒。另外,如果從一個已存在的主應用鏡像編譯出一個新的 Docker 應用鏡像出來,只需花費2分23秒。綜上所述,部署一套新的 Rails 應用,解決其所有依賴關系(包括 MySQL 和 Redis),只需花我2分鐘多一點的時間就夠了。
需要指出的一點是,我的應用上運行著一套完全測試套件,跑完測試需要花費額外1分鐘時間。盡管是無意的,Docker 可以變成一套簡單的持續集成環境,當測試失敗后,Docker 會把“test-only”這個容器保留下來,用于分析出錯原因。我可以在1分鐘之內和我的客戶一起驗證新代碼,保證不同版本的應用之間是完全隔離的,同操作系統也是隔離的。傳統虛擬機啟動系統時需要花費好幾分鐘,Docker 容器只花幾秒。另外,一旦一個 Dockedr 鏡像編譯出來,并且針對我的某個版本的應用的測試都被通過,我就可以把這個鏡像提交到一個私有的 Docker Registry 上,可以被其他 Docker 主機下載下來并啟動一個新的 Docker 容器,而這不過需要幾秒鐘時間。
總結
Ansible 讓我重新看到管理基礎設施的樂趣。Docker 讓我有充分的信心能穩定處理應用部署過程中最重要的步驟——交付環節。雙劍合璧,威力無窮。
從無到有搭建一個完整的 Rails 應用可以在12分鐘內完成,這種速度放在任何場合都是令人印象深刻的。能獲得一個免費的持續集成環境,可以查看不同版本的應用之間的區別,不會影響到同主機上已經在運行的應用,這些功能強大到難以置信,讓我感到很興奮。在文章的最后,我只希望你能感受到我的興奮!
我在2014年1月倫敦 Docker 會議上講過這個主題,已經分享到 Speakerdeck了。
如果想獲得更多的關于 Ansible 和 Docker 的內容,請訂閱 changlog 周報,它會在每周六推送一周最有價值的關于這兩個主題的新聞鏈接。
如果你想為我們的 Changlog 寫一篇文章,請使用 Draft repo,他們會幫到你的。
下次見,Gerhard。