雪球的Docker實踐
一、背景
雪球(http://xueqiu.com)是一家涉足證券行業的互聯網金融公司,成立于2010年,去年獲得 C 輪融資。雪球的產品形態包括社區、行情、組合、交易等,覆蓋滬深港美市場的各個品種。
與傳統社交網絡的單一好友關系不同,雪球在用戶、股票基金及衍生品、組合三個維度上都進行深度的相互連接。同時雪球的用戶活躍度和在線時長極高,以致我們在進行技術方案選型和評估的時候必須提出更高的要求。
目前雪球的 DAU 為1M,帶寬為1.5G,物理機數量為200+,云虛擬機數量約50個。雪球采用了 Docker 容器作為線上服務的一個基本運行單元,雪球的容器數量近1k。本文從創業公司的視角介紹雪球是如何引入 Docker 容器,并且逐步填坑和推廣上線,然后實現跨機房部署的能力,并基于 Docker 實現發布流程及周邊生態的自動化。
二、雪球對容器的選型
我們的服務面臨的操作系統環境大致有五種:物理機、自建虛擬機、云虛擬機、LXC 容器、Docker 容器。
我們主要的考慮的選型指標包括“成本”、“性能”、“穩定性”三個硬性基礎指標,以及“資源隔離能力”、“標準化能力”、“伸縮能力”這幾個附加指標。這五類操作系統環境在雪球的實踐如下:
雪球的 SRE 團隊借助 Docker 對整個公司的服務進行了統一的標準化工作,在上半年已經把開發測試、預發布、灰度、生產環境的所有無狀態服務都遷移到了 Docker 容器中。
三、上 Docker、遷業務、填坑
雪球借助 Docker 對服務進行的標準化和遷移工作,主要是順著 Docker 的 Build -> Ship -> Run 這三個流程進行的。同時在遷移過程中填了許多坑,算是摸著石頭過河的階段。
1) 構建
我們首先設計了自己的 Docker Image 層級體系。
在 Base 這一層,我們從 ubuntu-upstart 鏡像開始制作 base 鏡像。這里做了動靜分離,靜態的部分(static.sh 和對應的 static 文件)和動態的部分 ( runtime.sh ) 都會被添加進 base 鏡像,靜態部分會在構建 base 鏡像過程中被執行,而動態部分會在啟動 project 鏡像的過程中被執行。其中靜態部分我們更推薦靈活的使用 shell 腳本來完成多個基礎配置,而不是寫冗長的 Dockerfile。下圖是一個很好的例子
基于 base 鏡像,我們又添加進入了 JDK、NodeJS 等運行環境,并在JDK的基礎上進一步構建了 Resin 鏡像。在上述鏡像中再添加一層業務執行代碼,則構成了業務鏡像。不同的業務鏡像是每個業務運行的最小單元。
在雪球我們大力的推進了服務化,去狀態。這樣的一個業務鏡像就具備了遷移部署、動態伸縮的能力。
需要提醒的是 docker build 鏡像的過程中會遇到臨時容器的一些問題,主要涉及訪問外網和dameon能力。
2) 分發
分發的過程痛點不多,主要是 Registry 的一些與刪除相關的 Bug、Push 鏡像的性能,以及高可用問題,而本質上高可用是存儲的高可用。在雪球我們使用了硬件存儲,接下來計劃借助 Ceph 等分布式文件系統去解決。
3) 運行
Docker 運行的部分可以探討的內容較多,這里分為網絡模型、使用方式、運維生態圈三部分來介紹。
a. 網絡模型
絕大多數對 Docker 的網絡使用模型可以匯總為三類:
- Bridge 模式(NAT)
- Bridge 模式(去 NAT)
- 端口映射
Docker 默認的橋接是用的第一種 NAT 方式,也即是把命名空間中的 veth 網卡綁定到自己的網橋 docker0。然后主機使用iptables來配置 NAT,并使用 DHCP 服務器 dnsmasq 來分配 IP 地址。在雪球我們對 Bridge 模式去掉了 NAT,也即把宿主機的 IP 從物理網卡上移除,直接配置到網橋上去,并且使用靜態的 IP 分配策略。這樣的好處是 Docker 的 IP 可以直接暴露到交換機上。而端口映射方案不容易做服務發現,雪球并沒有使用。
其中去 NAT 的 Bridge 模式需要在宿主機上禁用 iptables 和 ip_forward,以及禁用相關的內核模塊,以避免網絡流量毛刺風暴問題。
b. 使用方式
我們在 docker run 的時候通過 –mac-address 傳入了靜態 MAC 地址,并通過前述 runtime.sh 在運行時修改了 docker 的 eth0 IP。其中計算 MAC 地址的算法是:
IP="aa.bb.cc.dd" MAC_TXT=`echo "$IP" | awk -F'.' '{print "0x02 0x42 "$1" "$2" "$3" "$4}'` MAC=`printf "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" $MAC_TXT`
同時我們按照雪球統一的運維規范在運行時修改了容器 Hostname、Hosts、DNS 配置。
在交互上,我們在容器中提供了 sshd,以方便業務同學直接 ssh 方式進入容器進行交互。Docker 原生不提供 /sbin/init 來啟動 sshd 這類后臺進程的,一個變通的辦法是使用帶 upstart 的根操作系統鏡像,并將 /sbin/init 以 entrypoint 參數啟動,作為 PID=1 的進程,并且嚴禁各種其他 CMD 參數。這樣其他進程就可以成為 /sbin/init 的子進程并作為后臺服務跑起來。
c. 運維生態圈
首先我們對容器做了資源限制。一個容器默認分配的是 4 core 8 G 標準。CPU 上,我們對 share 這種相對配額方式和 cpuset 這種靜態綁定方式都不滿意,而使用了 period + quota 兩個參數做動態絕對配額。在內存中我們禁用了 swap,原因是當一個服務 OOM 的時候,我們希望服務會 Fast Fail 并被監控系統捕捉到,而不是使用 swap 硬撐。死了比慢要好,這也是我們大力推進服務化和去狀態的原因。
在日志方面,我們以 rw 方式映射了物理機上的一個與 Docker IP 對應的目錄到容器的 /persist 目錄,并把 /persist/logs 目錄軟連接為業務的相對 logs 目錄。這樣對業務同學而言,直接輸出日志到相對路徑即可,并不需要考慮持久化的事宜。這樣做也有助于去掉 docker 的狀態,讓數據和服務分離。日志收集我們使用 logback appender 方式直接輸出。對于少數需要 tail -F 收集的,則在物理機上實現。
在監控方面,分成兩部分,對于 CPU、Mem、Network、BlkIO 以及進程存活和TCP連接,我們把宿主機的 cgroup 目錄以及一個統一管理的監控腳本映射到容器內部,這個腳本定期采集所需數據,主動上報到監控服務器端;對于業務自身的 QPS、Latency 等數據,我們在業務中內嵌相關的 metrics 庫來推送。不在宿主機上使用 docker exec API 采集的原因是性能太差。
四、混合云
通過采購硬件實現彈性擴容的,都是耍流氓。雪球活躍度與證券市場的熱度大體成正相關關系,當行情好的時候,業務部門對硬件資源的需求增長是極其陡峭的。無法容忍硬件采購的長周期后,我們開始探索私有云+公有云混合部署的架構。
可以對本地機房和遠端云機房的流量請求模型歸類如下:
我們把服務棧分為接入層、服務層、數據層三層。其中第一個階段只針對接入層做代理回源,目的可以是借助公有云全球部署的能力實現全球就近接入。第 二個階段,我們開始給遠端的接入層鋪設當地的服務層。演進到第三個階段,遠端的服務層開始希望直接請求本地數據。這里比較有趣的一點是,如果遠端服務是只 讀邏輯,那么我們只需要把數據做單向同步即可。如果要考慮雙向同步,也即演進到第四個階段,也即我們所說的雙活。其中不同機房之間的流量切換可以使用 DNS 做負載均衡,在雪球我們開發了一套 HTTP DNS 較完美的解決此問題。
目前雪球的混合云架構演進到第三個階段,也即在公有云上部署了一定量的只讀服務,獲得一定程度的彈性能力。
在公有云上部署 Docker 最大的難題是不同虛擬機上的 Docker 之間的網絡互通問題。我們與合作廠商進行了一些探索,采用了如下的 Bridge (NAT) 方案。
其基本思路與 Flannel 一致。首先虛擬機要部署在同一個 VPC 子網中,然后在虛擬機上開啟 iptables 和 ip_forward 轉發,并給每個虛擬機創建獨立的網橋。網橋的網段是獨立的 C 段。最后在 VPC 的虛擬路由器上設置對應的目標路由。
這個方案的缺點是數據包經過內核轉發有一定的性能損失,同時在網絡配置和網段管理上都有不小的成本。慶幸的是越來越多的云服務商都在將 Docker 的網絡模型進行產品化。
五、發布系統 Rolling
當使用 Docker 對服務進行標準化后,我們認為有必要充分發揮 Docker 裝箱模型的優勢來實現對業務的快速發布能力,同時希望有一個平臺能夠屏蔽掉本地機房與遠端公有云機房的部署差異,進而獲得跨混合云調度的能力。于是我們開 發了一套發布系統平臺,命名為 Rolling,意喻業務系統如滾雪球般不斷向前。
在此之前,雪球有一套使用開源軟件 Capistrano 構建的基于 ssh 分發的部署工具,Rolling 平臺與其對比如下:
Rolling 的上下游系統如下圖所示:
下面截取了幾張 Rolling 平臺在部署過程中的幾個關鍵步驟截圖:
上圖顯示了 Rolling 在部署時的第一個步驟:鏡像選擇。根據設計,在開發同學將代碼提交后到 Gitlab 后,Gitlab 中的 hook 就能直接觸發 Rolling 進行代碼編譯和鏡像構建。我們的設計思路是,每一次提交對應一次鏡像構建。這樣實際上鏡像就代表代碼,非常方便以最小粒度進行迭代升級以及回滾。
上圖顯示了 Rolling 在部署時的第三個步驟:資源選擇。目前雪球仍然是靠人力進行調度配置,接下來會使用自動化的調度工具進行資源配置,而 Rolling 已經賦予我們這種可能性。在真正開始部署之后,還有一鍵暫停、強制回滾、灰度發布的功能。
上圖顯示了某業務在使用 Rolling 部署后的運行狀態。
Rolling 平臺的帶來的質變意義有兩方面:
其一從運維同學的角度,Rolling 使得我們對服務的調度能力從靜態躍遷為動態。而配合以大力推進的服務去狀態化,Rolling 完全可以發展成為公司內部私有 PaaS 云平臺的一款基石產品。
其二從業務同學的角度,其上線時不再是申請幾臺機器,而是申請多少計算和存儲資源。理想情況下業務同學甚至可以評估出自己每個 QPS 耗費多少 CPU 和內存,然后 Rolling 平臺能夠借助調度層計算出匹配的 Docker 容器的數量,進而進行調度和部署。也即從物理機(或虛擬機)的概念回歸到計算和存儲資源本身。
六、接下來的工作
一方面,雪球會和合作廠商把公有云部署 Docker 的網絡模型做到更好的產品化,更大程度的屏蔽底層異構差別。另一方面,我們考慮從資源層引入相對成熟的開源基礎設施,進而為調度層提供自動化的決策依據。