容器化已經成爲一種趨勢,它能夠解決不少運維中的痛點,好比效率、成本、穩定性等問題,而接入容器的過程當中每每也會碰到不少問題和不便。在有贊最開始作容器化是爲了快速交付開發測試環境,在容器化的過程當中,咱們碰到過容器技術、運維體系適配、用戶使用習慣改變等各類問題,本文主要介紹有贊容器化過程當中碰到的問題以及採起的方案。php
在有贊同時會有不少個項目、平常在並行開發,環境的搶佔問題嚴重影響了開發、測試和上線的效率,咱們須要給每一個項目提供一套開發聯調(daily)、測試環境(qa),而且隨着項目、平常的生命週期項目環境也會隨着建立和銷燬,咱們最先的容器化需求就是怎麼解決環境快速交付的問題。前端
[有贊環境]
上面是有贊大體的研發流程,在標準流程中咱們有四套穩定環境,分別是 Daily 環境、Qa 環境、預發環境和測試環境。咱們的開發、測試、聯調工做通常並不會直接在穩定環境中進行,而是會拉一套獨立的項目環境出來,隨着代碼通過開發、測試、預發驗收最終發佈到生產環境後再同步回 Daily/Qa 的穩定環境中。java
[項目環境]node
咱們提供了一套以最小的資源投入知足最大項目並行度的環境交付方案,在 Daily/Qa 穩定環境的基礎上,隔離出N個項目環境,在項目環境裏只須要建立該項目所涉及應用的計算資源,其它缺失的服務調用由穩定環境提供,在項目環境裏,咱們大量使用了容器技術。python
[持續交付]git
後面咱們又在項目環境快速交付的解決方案的基礎上實現了持續交付流水線,目前已經有超過 600 套項目/持續交付環境,加上 Daily/Qa 穩定環境,涉及計算實例四五千個,這些計算實例不管是 cpu 仍是內存使用率都是很是低的,容器化能夠很是好的解決環境交付的效率問題,以及提升資源使用率來節省成本的投入。github
咱們的容器化方案基於 kubernetes(1.7.10)和 docker(1.12.6)、docker(1.13.1),下面介紹一下咱們在各個方面遇到的問題以及解決方案。docker
有贊後端主要是 java 應用,採用定製的 dubbo 服務化方案,過程當中沒法作到整個單元全量容器化,和原有集羣在網絡路由上互通也就成了剛需,因爲咱們沒法解決公有云上 overlay 網絡和公有云網絡的互通問題,因此一開始咱們放棄了 overlay 網絡方案,採用了託管網絡下的 macvlan 方案,這樣既解決了網絡互通的問題也不存在網絡性能問題,可是也就享受不到公有云彈性資源的優點了。隨着有贊多雲架構的發展以及愈來愈多的雲廠商支持容器 overlay 網絡和 vpc 網絡打通,彈性資源的問題才獲得了緩解。後端
容器的隔離主要利用內核的 namespace 和 cgroup 技術,在進程、cpu、內存、IO等資源隔離限制上有比較好的表現,但其餘方面和虛擬機相比存在着不少的不足,咱們在使用過程當中碰到最多的問題是容器裏看到的 cpu 數和內存大小不許確,由於/proc文件系統沒法隔離,致使容器裏的進程"看到"的是物理機的 cpu 數以及內存大小。api
咱們的 java 應用會根據服務器的內存大小來決定 jvm 參數應該怎麼配置,咱們是採用 lxcfs 方案來規避的。
由於咱們有超賣的需求以及 kubernetes 默認也是採用 cpu share 來作 cpu 限制,雖然咱們使用了 lxcfs,CPU 數仍是不許的。jvm 以及不少 Java sdk 都會根據系統的 CPU 數來決定建立多少線程,致使 java 應用在線程數和內存使用上都比虛擬機多的多,嚴重影響運行,其餘類型的應用也有相似的問題。
咱們會根據容器的規格內置一個環境變量 NUM_CPUS,而後好比 nodejs 應用就會按照這個變量來建立它的 worker 進程數。在解決 java 類應用的問題時,咱們索性經過 LD_PRELOAD 將 JVM_ActiveProcessorCount 函數覆蓋掉,讓它直接返回 NUM_CPUS 的值[1]。
在容器化以前,有讚的應用已經所有接入到發佈系統,在發佈系統裏已經標準化了應用的打包、發佈流程,因此在應用接入方面成本仍是比較小的,業務方無需提供 Dockerfile。
容器鏡像咱們分了三層,依次爲 stack 層(os),runtime 層(語言環境),應用層(業務代碼和一些輔助agent),應用以及輔助 agent 由 runit 來啓動。因爲咱們的配置尚未徹底分離,在應用層目前仍是每一個環境獨立打包,鏡像裏除了業務代碼以外,咱們還會根據業務的語言類型放一些輔助的 agent。咱們一開始也想將各類 agent 拆成多個鏡像,而後每一個 pod 運行多個容器,後來由於解決不了 pod 裏容器的啓動順序(服務啓動有依賴)問題,就把全部服務都扔到一個容器裏去運行了。
咱們的容器鏡像集成過程也是經過 kubernetes 來調度的(會調度到指定的打包節點上),在發佈任務發起時,管控系統會在集羣中建立一個打包的 pod,打包程序會根據應用類型等參數編譯代碼、安裝依賴,而且生成 Dockerifile,而後在這個 pod 中使用 docker in docker 的方式來集成容器鏡像並推送到倉庫。
爲了加速應用的打包速度,咱們用 pvc 緩存了 python 的 virtualenv,nodejs 的 node_modules,java 的 maven 包等文件。另外就是 docker 早的版本里,Dockerfile ADD 指令是不支持指定文件屬主和分組的,這樣會帶來一個問題就是須要指定文件屬主時(咱們的應用是以 app 帳號運行的)須要多運行一次 RUN chown,這樣鏡像也就多了一層數據,因此咱們打包節點的 docker 版本採用了官方比較新的 ce 版本,由於新版本支持 ADD --chown 特性。
有讚的應用內部調用有比較完善的服務化和 service mesh 方案,集羣內的訪問不用過多考慮,負載均衡只須要考慮用戶和系統訪問的 http 流量,在容器化以前咱們已經自研了一套統一接入系統,因此在容器化負載均衡上咱們並無完整按照 ingress 的機制來實現 controller,ingress 的資源配置是配在統一接入裏的,配置裏面轉發的 upstream 會和 kubernetes 裏的 service 關聯,咱們只是作了一個 sync 程序 watch kube-api,感知 service 的變化來實時更新統一接入系統中 upstream 的服務器列表信息。
在容器化接入過程當中開發會反饋是控制檯比較難用,雖然咱們優化了屢次,和 iterm2 等的體驗仍是有所不足,最終咱們仍是放開了項目/持續交付環境這種須要頻繁登錄調試的 ssh 登錄權限。
另一個比較嚴重的問題是,當一個應用啓動後健康檢查有問題會致使 pod 一直在從新調度,而在開發過程當中開發確定是但願看到失敗現場的,咱們提供了調試發佈模式,讓容器不作健康檢查。
有贊有專門的日誌系統,咱們內部叫天網,大部分日誌以及業務監控數據都是經過 sdk 直接打到天網裏去了,因此容器的標準輸出日誌僅僅做爲一種輔助排查問題的手段。咱們容器的日誌收集採用的是 fluentd,通過 fluentd 處理後按照天網約定的日誌格式打到 kafka,最終由天網處理進入 es 作存儲。
咱們涉及到灰度發佈的流量主要包含三部分:
首先,咱們在入口的統一接入上統一打上灰度須要用的各類維度的標籤(好比用戶、店鋪等),而後須要對統一接入、http client 以及 dubbo client 作改造,目的是讓這些標籤可以在整個調用鏈上透傳。咱們在作容器灰度發佈時,會發一個灰度的 deployment,而後在統一接入以及灰度配置中心配置灰度規則,整個鏈路上的調用方都會感知這些灰度規則來實現灰度發佈。
通過以前項目/持續交付的上線和迭代,大部分應用自己已經具有了容器化的條件。不過對於上線來講,須要整個運維體系來適配容器化,好比監控、發佈、日誌等等。目前咱們生產環境容器化準備基本完成,生產網已經上了部分前端 nodejs 應用,其餘應用也在陸續推進中,但願之後能夠分享更多生產環境中的容器化經驗。
以上是有贊在容器化上的應用,以及在容器化過程當中碰到的一些問題和解決方案,咱們生產環境的容器化還處於開始階段,後面還會碰到各類個樣的問題,但願可以和你們互相學習,後面可以有更多的經驗分享給你們。
[1] https://github.com/fabianenar...