對PHP項目在Docker上運行的一些探索

Docker出現後,容器技術在互聯網領域獲得了空前的普及,不管是大公司仍是屌絲創業公司的碼農基本上都會在各類技術社區或者各類演講會議上了解到過相關技術,咱們做爲一家屌絲創業公司也不例外,去年對Docker作了一番瞭解,並在年前測試了一些方案,今天在這裏總結一下遇到的各類坑以及踩坑過程當中的一些思考,但願能對了解過Docker而且躍躍欲試的同窗有點啓發,固然更歡迎在這方面有豐富經驗的同窗給一些建議或者指點。html

這篇文章不會說起Docker的理論知識或者基本概念相關的內容,若是想了解Docker的基本使用,Docker的官方文檔是個不錯的選擇,若是想了解基本的理論,能夠參考下coolshell博客上的幾篇文章:Docker基礎技術:Linux Namespace(上)Docker基礎技術:Linux Namespace(下)Docker基礎技術:Linux CGroupDocker基礎技術:AUFSDocker基礎技術:DeviceMapperlaravel

1、項目環境

咱們項目用的標準的PHP技術棧:PHP-FPM + Nginx,運行在阿里雲的ECS上,數據庫也是阿里雲的服務。git

2、緣起

對於一個普通的屌絲創業公司的屌絲項目來講,理論上來講是不必用太複雜的技術的,對新技術的剋制也是碼農的一個職業操守。然而我之因此在項目上作這個嘗試也並不是是想嘗試新技術,而是源自於項目上線以來遇到的各類問題,好比前面文章裏有提到過的laravel的性能問題,而HHVM或者PHP7這些新技術都大大提到了性能,一會兒所有切換風險太大,因此能夠用容器來運行某幾個服務測試一下;再好比以前有出現過一個BUG,某個接口出現問題佔用內存過高致使整個系統響應超時;再好比,看了各類技術大會上別人分享的經驗確實想本身試一下,哈哈。docker

3、牛刀小試

根據Docker的理念,每一個容器都是一個獨立的服務,服務之間經過接口相互協做。咱們採用的方案是PHP-FPM和Nginx分別跑在不一樣的容器中,PHP-FPM容器暴露端口給Nginx容器。shell

容器之間的網絡通訊最初用的是Docker自帶的link,link雖然很簡單,可是不太好用,link是經過在容器啓動時修改/etc/hosts文件來實現的,當時用的時候遇到的一個問題是被link的容器重啓後,這個容器IP變了,可是link的容器裏並無更新(1.10版本對link機制作了修改,不知道是否解決了這個問題,還沒具體看),還有一個問題就是當同一個服務須要開多個容器時就搞不定了,固然若是是用集羣的方式來跑就更搞不定了,因此link只能在本地開發時玩玩,在生產環境沒啥用。最後這部分用的方案是用consul來作服務發現,關於consul就不在這裏展開介紹了,有興趣的同窗能夠看下這篇文章:Service Discovery for Docker Containers using Consul and Registrator數據庫

而後代碼是放在host上,在容器啓動時掛載一下代碼的目錄。根據接口使用的場景將接口分到了幾個不一樣的容器裏面去跑,對資源隔離作了下壓力測試,符合預期,很滿意!而後選了兩個容器跑HHVM測試了下,確實性能提升了不少。整個結構以下圖所示:
PHP+Nginx Dockersegmentfault

這樣能夠正常使用了,而後就是日誌的處理,最開始掛載了一個host上的目錄用來存放日誌,可是這樣也不太符合Docker的理念,因而就又弄了個logstash容器,將各類容器的日誌寫到logstash容器裏面去。服務器

而後就是對容器的監控,咱們用了oneapm提供的服務,還不錯,也有其餘一些開源工具的選擇,可是用oneapm的服務更方便一些。網絡

一個完整的技術棧就這樣搞定了,跑了幾天也沒出現什麼問題。做爲一個有追求的碼農,固然是要有更高的要求的,後來想到這樣一個問題:Docker的理念是容器便是一個完整的服務,可是咱們的用法是把在容器內把PHP-FPM跑起來了,代碼並不在裏面,而是經過目錄掛載的方式來實現的,這樣就須要在host上面部署好代碼,沒有完美的實現容器即服務的理念。好,咱們追求下完美!架構

4、完美之路

其實把代碼放進容器並不僅是爲了去追求理念上的完美,還有另一個用意,那就是基於Docker的CI。基本思路是每次發佈都會build一個新版本的鏡像,build過程當中須要把代碼更新到最新版本,而後安裝依賴的第三方庫以及其餘一些業務相關的預處理,build完成後再push到registry,而後在各個節點pull下來,重啓容器。整個流程以下圖所示:
基於Docker的持續集成

怎麼樣?這下完美了吧!若是現實也是那麼完美就行了,可是恰恰現實老是會有各類各樣的坑等着你!

首先,在沒有用這個機制以前,發佈一次代碼也就幾秒鐘的事情,merge下分支,而後各個節點pull下代碼就能夠了,可是用了Docker後,merge分支後就須要build一下鏡像,build後再push到registry,而後再到各個節點把鏡像pull下來,少說也是要花分鐘級別的。這時候有的同窗可能會說Docker鏡像的原理不是layer機制麼,對,確實是layer,每次build只須要更新須要更新的layer就能夠,可是仍是沒辦法作到像git那樣只更新那幾個文件,至少要更新整個項目的代碼目錄。

其次,以前只須要更新一下代碼就能夠了,如今卻須要把容器重啓一下!原本重啓容器是很快的,可是在容器裏PHP-FPM跑起來後卻很慢,我猜想是在執行docker stop時會發一個SIGTERM的信號到容器裏的進程,PHP-FPM在處理SIGTERM信號時會作一些清理工做。一個節點至少有幾十個容器,每一個容器都重啓一下很是耗時!

這樣原本很簡單的一個發佈,用了這個「完美」的機制後卻須要好幾分鐘。雖然整個流程徹底自動化,可是確實不必爲了完美的理念作出這樣一個妥協。

此外還遇到了另一個很是奇葩的問題:升級到1.10後,容器啓動後常常會遇到端口不通的狀況,這時候若是進入到容器裏發起一次網絡請求,好比ping如下某個IP,端口就會恢復正常。用各類工具調試了好久依然沒找出來問題在哪,屌絲創業公司也沒精力研究這麼高深的問題,只好暫時擱置了。

掙扎了好久,最後又回到了老路子。

5、面對現實

雖然沒能理念上完美的方案,可是也要讓整套架構更方便的擴展,好比在流量高峯期快速上架節點。得益於雲計算近幾年的發展,這個問題也比較容易解決。前面提到代碼是放在host的一個目錄,那麼基本上每一個host只須要三個條件就可以上線:

  1. 項目代碼目錄

  2. 安裝Docker

  3. 配置好salt-minion(用來更新配置文件)

只須要在購買一臺ECS後,作完上面的操做,生成一個系統鏡像,須要上線新節點時用這個系統鏡像來直接配置服務器便可。節點上線後,啓動各個容器,經過前面提到的用consul實現的服務發現機制,自動加入集羣。

6、繼續探索

後面打算用Docker Swarm來簡單作下集羣管理,將來對調度需求比較大時可能會嘗試下Mesos或者Kubernetes。不過關於容器的生態鏈都發展的很是快,可能等須要的時候又有更好的方案了吧。

簡單總結下整個過程的感悟吧,從架構上來講,永遠都沒有完美的架構,只有最適合本身的架構。從碼農角度來講,在作技術選型時最重要的是權衡當前項目需求、團隊對各類技術瞭解的程度以及該技術選型帶來的複雜度。切勿看了幾個大牛的技術架構分享就在本身項目上躍躍欲試,最後弄的連本身都搞不定還給別人留下一個爛攤子收拾。最後,在項目迭代過程當中保持項目的簡單的能力永遠是衡量一個碼農技術的水平的重要標準。

相關文章
相關標籤/搜索