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