喜馬拉雅FM測試環境Docker化實踐

Docker 編程語言 Tomcat Linux 零空科技 2017-05-27

【摘要】隨著容器技術的流行,作為線上應用Docker的鋪墊,喜馬拉雅FM從16年開始推進測試環境Docker化。本次分享將重點為大家介紹我們在Docker化的過程中如何進行技術選型、環境搭建,特別是實踐中碰到的一些問題及其解決方案。

簡介

為什麼要Docker化?

  1. 標準化

  • 配置標準化,以部署Tomcat為例,實際物理環境中,通常一臺物理機部署多個Tomcat,這就存在Tomcat的端口及目錄管理問題。 理想狀態下:一個項目一個主機Tomcat,Tomcat永遠位於/usr/local/tomcat(或其它你喜歡的位置)下,對外端口是8080,debug端口是8000。

  • 部署標準化,現在雲平臺越來越流行,同時,也不會立即丟棄物理環境,因此必然存在著同時向雲環境和物理環境部署的問題。這就需要一系列工具,能夠屏蔽物理環境和雲環境的差異,Docker在其中扮演重要角色。

  • API化,通過API接口操作項目的部署(CPU、內存分配、機器分配、實例數管理等),而不是原來物理機環境的的手工命令行操作。

  • 自動化,調度系統可以根據API進行一些策略性的反應,比如自動擴容縮容。

  • 上述工作,原有的技術手段不是不可以做,可是太麻煩,可用性和擴展性都不夠好。

    幾個小目標

    1. 業務之間不互相干擾

    • 一個項目/war一虛擬機/容器

    • Ip-pert-task

  • 容器之間、容器與物理機之間可以互通

  • 容器編排:健康檢查、容器調度等

  • 使用方式:通過yaml/json來描述任務,通過API部署

  • 喜馬拉雅FM測試環境Docker化實踐

    總結一下,基於n臺物理機搭建容器環境,整個工作的主線:一個項目一個主機 ==> 物理機資源不夠 ==> 虛擬化 ==> 輕量級虛擬化 ==> Docker ==> 針對Docker容器帶來的網絡、存儲等問題 ==> 集群編排 ==> 對CI/CD的影響。

    網絡

    虛擬化網絡的兩種思路:

    Overlay

    • 隧道,通常用到一個專門的解封包工具

    • 路由,每個物理機充作一個路由器,對外,將容器數據路由到目標機器上;對內,將收到的數據路由到目標容器中。

    通常需要額外的維護物理機以及物理機上容器IP(段)的映射關係。

    Underlay

    不準確的說,容器的網卡暴露在物理網絡中,直接收發,通常由外部設備(交換機)負責網絡的連通性。

    經過對比,我們採用了MacVLAN,主要是因為:

    1. 簡單

    2. 效率高

    3. 我們就是想將容器“當成”虛擬機用,容器之間互通就行,不需要支持複雜的網絡伸縮、隔離、安全等策略。

    關於MacVLAN,這涉及到LAN ==> VLAN => MacVLAN的發展過程,請讀者自行了解。網絡部分參見《Docker MacVLAN實踐》。

    IP分配問題

    對於物理機、KVM等虛擬機來說,其生命週期很長,IP一經分配便幾乎不變,因此通常由人工通過命令或Web界面手動分配。而對於Docker容器來說,尤其是測試環境,容器的創建和銷燬非常頻繁,這就涉及到頻繁的IP分配和釋放。因此,IP分配必須是自動的,並且有一個IP資源池來管理IP。

    在Docker網絡中,CNM(Container Network Management)模塊通過IPAM(IP address management)driver管理IP地址的分配。我們基於TalkingData/Shrike改寫了自己的IPAM插件,fix了在多實例部署模式(一個Docker host部署一個IPAM,以防止單實例模式出現問題時,整個系統不可用的問題)下的重複存取問題。

    編排

    Docker解決了單機的虛擬化,但當一個新部署任務到達,由集群中的哪一個Docker執行呢?因此,Docker之外,需要一個編排工具,實現集群的資源管理和任務調度。

    喜馬拉雅FM測試環境Docker化實踐

    這些工具均採用Master/Slave架構,假設我們將物理機分為Master和Slave,這些工具在Slave上運行一個Agent(任務執行和數據上報),在Master上運行一個Manager(任務分發和數據彙總)。從功能上說,任務分發和容器資源彙總,這些工具基本都可以滿足要求。就我的理解,其實這些工具的根本區別就是:發展歷程的不同。

    1. 從一個Docker/容器化調度工具, 擴展成一個分佈式集群管理工具

    2. 從一個分佈式資源管理工具 ,增加支持Docker的Feature

    其中的不同,請大家自己體會一下。

    到目前為止,根據我們測試環境的實踐,發現我司有以下特點:

    1. 對編排的需求很弱,基本都是單個微服務項目的部署。微服務項目的協同、服務發現等由公司的服務治理框架負責。

    2. 基礎服務,比如MySQL、Hadoop等暫不上Docker環境

    3. 需要查詢編排工具的API接口,同時有一個良好的Web界面,對編排工具的數據彙總、資源管理能力有一定要求。

    因此,最終我們決定使用Marathon + Mesos 方案。當然,後面在實踐的過程中,因為網絡和編排工具的選擇,ip變化的問題給我們帶來很大的困擾,甚至專門開發了幾個小工具,參見下文。

    image的組織

    Docker的厲害之處,不在於發明了一系列新技術,而在於整合了一系列老技術,比如AUFS、LXC等,在Docker之前,我司運維也經常使用Cgroups來限制一些c項目進程使用的資源。阿里、騰訊等大廠在Cgroups、Namespace等基礎上搞一套自己的容器工具,現在也廣為人知。甚至在《盡在雙11:阿里巴巴技術演進與超越》關於Docker部分中提到,對於阿里,使用Docker初衷是Docker鏡像化,也就是其帶來的應用環境標準化,而不是容器化。

    Docker鏡像的實踐主要涉及到以下問題:

    1. 搭建私有image repository

    2. 對layer進行組織

    3. 鏡像的分發較慢

    • 預分發,但這不解決根本,只適用部分場景

    • 對layer進行壓縮,京東目前採用該方案

  • 鏡像化帶來的容器重啟問題。因為鏡像是一體的,哪怕只有一點更改,鏡像的發佈都必須銷燬之前的容器,然後按照新鏡像創建新容器。耗時是一方面,對以下場景也很不友好:

    對於具體的場景,可以有具體的辦法規避。對於通用的解決方案,阿里通過改寫Docker,對鏡像支持HotFix標識,deploy這類鏡像,不再創建新容器,而是更新容器。

    • 只是更新一個文件,項目、容器均不需要重啟

    • 因為加載緩存等原因,項目、容器啟動比較耗時

    我們要對鏡像的layer進行組織,以最大化的複用layer。

    喜馬拉雅FM測試環境Docker化實踐

    因為我們還只是在測試環境使用,鏡像較慢的矛盾還不是太突出,這方面並沒有做什麼工作。

    寫到這裡,我們可以看到一個技術之外的技術問題。阿里對於docker image feature的改造。

    1. 可以減少容器的重啟次數,進而減少IP的分配和釋放。容器生命週期的延長,給用戶的感覺是更像一個虛擬機。減少IP變化對其它組件的影響,一些組件不再必要。

    2. 影響到容器的編排策略。即deploy新的任務不再是選擇一個機器運行容器,而是找到原來的容器應用變更。這大概是阿里採用Docker Swarm編排工具並改造Docker Swarm的部分原因。畢竟Docker Swarm起點就是一個Docker編排工具,跟Docker更親近,也更容易改造。

    3. 我們在Docker化的過程中,對Docker的各種特性一則認為天經地義,二則逆來順受。出現問題,要麼想辦法規避,要麼在外圍造個輪子去解決(還是規避)。這讓我想到了最近在看的《大明王朝1566》,皇帝要大興土木,嚴黨要貪汙,胡宗憲左支右絀,勉力維持。海瑞認為問題的根兒在皇帝,直接上了《治安疏》。兩者都算不上什麼錯,胡宗憲在他的位置,重要的是保住總督的位置,這樣才能打倭寇。作為一個名義上的嚴黨分子,這樣的話也不能他來說。我們在技術的選擇上,也經常碰到這樣的問題,各種妥協。但越早的認識到各種方案的缺陷,才會避免陷入為了方案而方案,做到預判,嗅到風向變化,隨機應變。 ### CI

      本質上Jenkins如何跟Marathon結合的問題,現成的方案很多,本文不再贅述。

    關鍵是提供幾套不同的模板,以方便不同業務的童鞋使用。

    容器變化帶來的問題

    使用Docker後,容器在物理機之間自由漂移,物理機的角色弱化成了:單純的提供計算資源。但帶來的問題是,影響了許多系統的正常運行。

    IP變化

    許多系統的正常運行依賴IP,IP不穩定帶來一系列的問題。而解決IP的變化問題主要有以下方案

    1. 新增組件屏蔽IP變化

    2. 提供DNS服務(有緩存和多實例問題)

    3. 不要改變IP

    • 既然重啟後,IP會改變,就減少容器重啟

    • 服務與IP綁定(這個方案非常不優雅)

    對於Web服務,IP的變化導致要經常更改Nginx配置,為此,我們專門改寫了一個Nginx插件來解決這個問題。參見一個大牛的工具weibocom/nginx-upsync-module,我為大牛的工具新增了zk支持,參見qiankunli/nginx-upsync-module-zk。

    對於RPC服務,我司有自己獨立開發的服務治理系統,實現服務註冊和發現。但該系統有審核機制,系統認為服務部署在新的機器上時(通過IP識別新機器)應先審核才能對外使用。我們和開發同學協調,在服務上線時,進行額外處理來屏蔽掉這個問題。遺憾的是,對於跨語言調用,因為rpc客戶端不通用,仍有很多不便。

    文件存儲

    有許多項目會將業務數據存儲在文件中,這就意味著項目deploy進而容器重啟之後,要能找到並訪問這些文件。在Docker環境下主要有以下兩種方案:

    1. Docker volumn + cluster fs

    2. Docker volume plugin

    我們當下主要採用第一種,將cluster fs mount到每臺Docker host的特定目錄(例如/data),打通container /data ==> docker host /data == cluster fs /data,任意容器即可共享訪問/data目錄下的數據。

    日誌採集與查看

    為了將日誌持久化存儲,我們將容器的日誌目錄映射到了物理機上。but,一個項目的日誌分散在多個物理機中。

    我司原有日誌採集報警系統,負責日誌採集、彙總、報警。因此容器化後,日誌的採集和報警並不會有什麼影響。但該系統只採集錯誤日誌,導致開發人員要查看日誌以調試程序時,比較麻煩。最初,我們提供了一個Web Console來訪問容器,操作步驟為:login ==> find container ==> input console ==> op。但很多童鞋依然認為過於繁瑣,並且Web Console的性能也不理想。而直接為每個容器配置ssh server,又會對safe shutdown等產生不良影響。因此

    1. 登陸測試環境,90%是為了查看日誌

    2. 和開發約定項目的日誌目錄,並將其映射到物理機下

    3. 間接配置ssh。每個物理機啟動一個固定ip的ssh container,並映射日誌目錄

    4. 使用go語言實現了一個essh工具,essh -i marathon_app_name即可訪問對應的ssh container實例並查看日誌。

    當然,日誌的問題,也可以通過elk解決。

    部署有狀態服務

    其它問題

    Mesos + Marathon + Docker的文章很多,其實這才是本文的重點。

    1. Base image的影響

      1. 時區、Tomcat PermGensize、編碼等參數值的修正

      2. base image為了儘可能精簡,使用了alpine。其一些文件的缺失,導致一些java代碼無法執行。比如,當去掉/etc/hosts中ip和容器主機名的映射後,加上/etc/sysconfig/network的缺失,導致Java代碼InetAddress.getLocalHost()執行失敗。參見《Java InetAddress.getLocalHost() 在linux裡實現》

    2. Safe shutdown,部分服務退出時要保存狀態數據

    3. 支持sshd(已解決,但對解決其他問題是個有益的參考),以方便大家查看日誌文件(Web Console對查看大量日誌時,還是不太好用)

      1. 使用supervisord(管理SSH和Tomcat),需要通過supervisord傳導SIGTERM信號給Tomcat,以確保Tomcat可以safeshutdown。該方法比較low,經常發生supervisord讓Tomcat退出,但自己不退出的情況。

      2. 每臺機器啟動跟一個專門的容器,映射一些必要的volume,以供大家查看日誌等文件

    4. Marathon多機房主備問題

    5. 容器的漂移對日誌採集、分析系統的影響

    6. 對容器提供DNS服務,以使其可以正確解析外部服務的hostname

    7. 如何更好的推廣與應用的問題(這是個大問題,包括分享PPT的寫作思路、Jenkins模板的創建等,不比解決技術難題耗費的精力少)

    todo

    1. 日誌採集,簡化日誌搜索

    2. 一個集中式的DC。當下,項目部署的各個階段分散在不同的組件中。呈現出來的使用方式,不是面向用戶的。

    • Jenkins負責代碼的編譯和Marathon job的觸發

    • Marathon負責任務調度、銷燬和回滾等

    • Portainer負責容器數據的界面化以及Web Console

    這樣帶來的問題是:

    1. 對於運維人員人說,一些操作不能固化下來,比如回滾等,手工操作易出錯。

    2. 對於用戶來說,容易想當然的通過portainer進行增刪改容器的操作,進而引起系統的不一致。

    3. 因為是現成系統,很難加入我們自己的邏輯,這使得配置上經常出現一些語義衝突的情況。

    相關推薦

    推薦中...