公司介紹
炫一下(北京)科技有限公司2011年8月成立於北京,是移動短視訊娛樂分享應用和移動視訊技術服務提供商。旗下擁有“秒拍”、“小咖秀”、“一直播”三款移動視訊產品 [1-2] 。 2016年11月21日,旗下擁有秒拍、小咖秀、一直播的一下科技宣佈完成5億美元E輪融資,該輪融資也創下了國內移動視訊行業的單輪融資金額最高紀錄。 [3] 根據一下科技公佈的資料,截至2016年11月21日,秒拍與小咖秀的日播放量峰值已突破25億次,日均上傳量突破150萬條,日均覆蓋使用者超過 7000萬 [2] 。
分享嘉賓
一下科技的吳飛群,感謝大佬的文件支援。
容器化的背景介紹
隨著公司業務規模的不斷變大, 為了能夠快速迭代, 公司的後端架構也逐漸地從傳統架構模型轉向了微服務架構. 公司主要開發語言是Java, Golang, PHP.大部分的服務都執行在公有云的虛擬主機上
暴露問題
流程繁瑣
- 後端架構轉成微服務以後服務的數量變得更多, 每個服務都需要開通虛擬機器, 配置監控, 配置jenkins, 配置ELK, 配置白名單等.
- 不同的開發語言有些配置還不同,有一定的瓶頸
- 很多公司甚至沒有專業運維
沒有穩定的測試環境.
由於測試環境之間也需要互相呼叫, 經常聯調, 由於一些歷史原因, 不是所有的服務都有穩定的測試環境, 這給測試人員帶來了不少的麻煩, 經常會用線上的部分機器進行除錯, 存在不少的潛在風險.
資源利用率低
為了方便管理, 每個服務都是分配的單獨的伺服器進行部署, 由於產品使用者的使用習慣, 公司大部分的服務的資源使用都有高峰低估的特點, 為了保障服務的資源充足, 我們核心的服務高峰的時候也會控制資源使用率, 所以大部分服務在平時的資源使用率都是很低的, 造成了資源的大量浪費.
擴容/縮容不及時
業務高峰期的時候經常需要擴容機器, 擴容需要開發申請, 運維審批, 部署服務, 測試確認等很多環節, 一個服務的擴容最快也得需要半個小時才能完成, 半個小時對於很對關鍵服務來說其實是很長的, 是無法忍受的.
部署系統的不足
後端系統在向微服務演進的過程中, 不同的後端小團隊出現了程式語言, 框架等的多元化, 之前存在的部署系統不能充分滿足多語言多框架的部署需求.(之前大部分是PHP)
整體架構
各個模組的落地細節
容器的接入
這一部分主要跟大家分享的是我們怎樣把執行在傳統虛擬機器/物理機上的服務逐漸接入到k8s上的.
在進行容器化改造之前, 我們的運維平臺已經具有了程式碼釋出的功能, 我們為不同程式語言的專案制定了部署規範, 比如部署路徑, 日誌路徑, 啟停方式, 回撥指令碼等. 每一個服務要接入部署系統都需要在平臺上提工單申請,工單資訊大致如下:
通過上面的工單, 足夠可以收集運維關心的服務資訊, 這些資訊都會記錄到我們的運維平臺上.容器叢集搭建完成以後, 開發人員只需要為該專案申請容器部署即可, 構建的過程可以複用之前的, 不用做任何改動, 只需要在構建完成以後製作相應的映象推送到內部的docker倉庫中。
叢集內訪問/負載均衡, 如果連線該專案的服務都已經部署到了容器中, 那麼該服務選擇叢集內訪問的方式就行, 也就是採用clusterIP的方式, 如果該服務是對公網的, 或者訪問該服務的客戶端部署在傳統VM/物理機上, 這時候就需要一個負載均衡了.這裡的負載均衡是公有云提供的LoadBalancer. 開發人員提交工單, 運維人員處理完工單以後這個專案基本上就可以通過部署系統部署到容器了.運維平臺通過呼叫K8s叢集的介面來建立相應的service, deployment, HPA等. 當然這只是大概的流程, 具體實施過程中還需要做好開發人員的容器知識的培訓和編寫相關基礎文件.
-
推送程式碼, 程式碼倉庫使用的是內部搭建的gitlab倉庫
-
運維平臺是以專案為中心而設計開發的, 上面說了每個專案要想接入部署系統都會提交發布申請工單,提交完以後專案和該專案的程式碼倉庫的繫結關係就會儲存到平臺上,然後開發就能從平臺上提交容器的釋出工單, 如下圖是開發者提交發布申請的表單: 3.我們的專案構建採用的是Jenkins, 跟大部分使用Jenkins的方式不太一樣, 我們通過Jenkins的API來觸發構建, Jenkins對於開發人員是不可見的. 我們會在Jenkins上提前建立不同程式語言的任務模板, 開發提交發布工單以後, 運維平臺會通過呼叫Jenkins的API複製模板建立一個和該專案在運維平臺上名字一致的Job, 並配置好相關的引數資訊, 比如git地址, 分支/tag名稱, 構建的shell等, 然後觸發構建任務. 這裡有2個點需要給大家說明一下, 一個就是我們之前對服務的標準化以後, 開發人員不需要編寫Dockerfile, 而是採用通用的dockerfile, 替換一下相關的變數即可, 這些工作在構建的時候自動觸發; 二是我們採用gitlab上的tag來作為每次部署的版本號.我們沒有采用commitID是因為它可讀性較差, 開發每次提交發布以後, 我們平臺會用時間戳+使用者ID去gitlab上建立tag, 同時這個tag也會作為該服務映象的一部分.
構建shell樣例
mvn clean;
mvn package -Dmaven.test.skip;
sed s/service-name.jar/${JOB_NAME}/g /template/Dockerfile > Dockerfile
cp target/*.jar ${JOB_NAME}
docker build -t inner.hub.com/${JOB_NAME}:#tag# .
docker push inner.hub.com/${JOB_NAME}:#tag#
複製程式碼
-
Jenkins配置了gitlab的互信金鑰, 可以拉取所有專案的程式碼.
-
平臺觸發Jenkins構建任務以後, 就會把該次構建的容器image推送到內部的hub中.
-
測試的k8s叢集開發和測試可以隨意釋出, 線上的是需要測試人員確認通過以後才可以上線的, 這裡有一個點是線上環境上線的映象是已經上線到測試環境的相同映象.
-
上線到k8s, 開發人員從運維平臺上點選按鈕觸發, 運維平臺呼叫k8s的介面通過替換容器的image地址來實現釋出/回滾.
容器的管理
運維平臺上提供的容器相關的功能. 我們的運維平臺是以專案為中心來設計的, 所有的其他資源都會跟專案繫結, 比如伺服器, 資料庫, 容器資源, 負責人等. 此外平臺還提供了許可權的管理, 不同的人員對於專案有不同的許可權, 基本上每個開發對於專案的常規操作我們都會整合到平臺上. 容器這塊我們沒有使用官方的dashboard, 也是基於API來整合到運維平臺的.
- 編輯配置 這個功能主要是給運維人員開放的, 方便給專案調整引數.
- 基本資訊 包括容器資訊, 服務資訊, HPA資訊等, 方便開發檢視
- 監控資訊 這些資訊也是通過直接使用的prometheus和heapster的介面來獲取的.
- web終端 這裡也是通過呼叫k8s相關的介面並寫了一個websocket服務來實現的.方便開發人員緊急處理問題.
日誌系統
在沒有采用容器之前, 我們已經用ELK來解決日誌的問題了, 我們主要有2種使用方式:
當服務的日誌量不是很大的時候我們採用的是程式直接以json的形式寫日誌到redis的list中, 然後我們使用logstash傳送到ES中, 然後從kibana上就可以檢視了. 當服務的日誌量較大的時候, 我們採用寫檔案的方式, 然後採用filebeat傳送到kafka叢集, 然後採用logstash傳送到ES叢集
採用k8s容器以後, 由於磁碟限制這塊不是很完善, 我們要求所有的服務都採用輸出到redis的方式或者標準輸出的方式來打日誌, 這樣本地磁碟也就不用考慮限制了.這裡有一點要說明一下:
由於所有服務都直接採用redis的方式後, redis很容易成為瓶頸, 如果redis掛了很可能會影響線上業務, 基於這一點, 我們讓公司的Golang工程師幫忙開發了一個redis -> kafka的一個代理服務, 這樣的話業務不用修改任何程式碼, 還是用redis的方式輸出日誌, 只不過後面其實是輸出到了kafka叢集了, 代理服務是無狀態的, 可以無限擴容, 這樣效能問題也就解決了.
監控系統
在接入容器之前我們已經採用了prometheus來監控我們的伺服器和業務, k8s對prometheus的支援很完美, 所以我們這塊能很快適應, 業務程式碼也不用怎麼修改. 這裡有幾個點:
我們在k8s上部署了prometheus來專門監控所有業務的Pod狀態, 比如Pod的HPA使用率, Pod的不可用數量等.
由於業務監控需要採集的資料巨大, 多個服務如果公用一個prometheus抓取端的話會有效能問題, 我們採用了為每一個服務啟動一個prometheus抓取端的辦法來解決這個問題,每個服務單獨的prometheus抓取端通過配置檔案中的正則匹配來控制只抓取該服務的資料.
看板採用的是grafna.服務註冊到運維平臺的時候也會自動通過grafna的介面建立相應的看板專案.
報警傳送的話我們自己有封裝的釘釘/企業微信/郵件/簡訊等功能的介面.
spring boot1.5 專案接入prometheus相關例項參考 github.com/hellorocky/…
服務發現
公司用到服務發現的主要是Java的spring專案, 所以採用的是Eureka, 我們之前在沒有采用容器的時候就已經有了一套eureka註冊中心, 由於pod的網路和虛擬機器的網路是直接互通的, 服務遷移到容器中以後我們依然採用的之前的一套註冊中心.
配置中心
服務遷移到容器的時候我們就要求開發在開發程式碼的時候生產和測試是同一個jar包, 根據啟動引數的不同來選擇環境, 這樣的話就可以實現構建一次, 生產/測試到處執行了. 配置中心這塊我們採用的是apollo, 這塊也是通過呼叫apollo本身的API來整合到運維平臺的, 做到了從平臺上修改/編輯配置等.
踩過的坑 && 總結經驗
initialDelaySeconds引數導致pod迴圈重啟問題
這個引數是k8s配置服務的健康檢查的時候使用的, 我們知道, k8s的健康檢查有2種, 一種是存活檢查, 當這個檢查失敗的時候, k8s會嘗試重啟該pod, 另一種是就緒檢查,當這個檢查失敗的時候, k8s會把該pod從service上摘下來, 還有一個點是很重要的, 比如我在k8s上部署了一個Java專案, 這個專案啟動時間大概是30秒, 那麼我就需要配置上面的這個引數,pod啟動的時候讓健康檢查的探針30秒以後再執行檢查,實際場景中, 一個普通的springboot專案啟動要花費40秒左右(limit為4核), 所以這個值對於Java來說至少需要配置成60才可以, 不然經常會出現新部署的pod一直重啟.
使用prestop hook保證服務安全退出
在實際生產環境中使用spring框架,由於服務更新過程中,服務容器被直接終止,由於eureka server有快取, 部分請求仍然被分發到終止的容器,雖然有重試機制, 但是高併發情況下也經常會遇到介面呼叫超時/失敗的情況,為了減少這些錯誤, 可以在容器退出前主動從eureka上登出這個節點, 等待2倍左右的eureka檢測時間(2*5), 這需要開發提供介面並將呼叫介面的指令碼新增到prestop中, 參考: curl http://127.0.0.1/debug/stop/eurekaClient;sleep 10
健康檢查超時時間相關配置
實際工作中遇到過一個服務不管怎麼配置, 總是會莫名奇怪地出現重啟的現象, 看日誌也沒有看出不來, 後來問題找到了, 是健康檢查配置的問題, 最開始使用k8s的時候我們給每個服務配置健康檢查的超時時間是1秒, 失敗1次就算失敗; 後來跟業務溝通了一下, 他們的服務本來就是響應慢, 好吧, 我們只好修改了一下超時時間, 調大了一點, 失敗次數也改成了3次. 連續3次失敗才算失敗.
Java獲取宿主機CPU/記憶體的問題
由於JVM預設情況下獲取的是宿主機系統的引數,所以導致JVM獲取的GC執行緒數和分配的heapsize不是我們想要的, 我們是通過調整JVM啟動引數來解決這些問題的, 參考: java -server -XX:ParallelGCThreads=4 -Xmx2g -Xms2g -jar service.jar, 當然也可以通過其他的方式來修改.
QA
Q: prestop hook的參考地址能給個外網地址看嘛? A: 這個看官方的文件就行吧, 我這個場景裡只是用了一個curl, 讓開發提供一個介面就行. 具體prestop hook官方文件上有詳細的舉例.
Q: apollo配置中心,配置怎麼落到服務裡的,或者容器裡? A: 我們這邊大部分是Java專案, 使用的官方提供的SDK, 具體你可以看下apollo的使用文件.
Q: 日誌怎麼採集和展示?用什麼方案? A: ELK, 採集日誌主要是程式直接輸出到redis, 這點有些不一樣.
Q: CD的配置是怎麼管理的? A: 相關的上線配置都是存在運維平臺上, 服務的配置使用的apollo配置中心.
Q: ELK是部署成sidecar模式整合採集器還是日誌輸出到外部儲存?日誌怎麼收集怎麼清洗的? A: 上面說了, 程式直接輸出到redis. 然後直接儲存到了ES中.
Q: redis和mysql之類的元件也都完成了容器化嗎?做的是叢集嗎? 怎麼樣的形式? A: 沒有, 我們暫時推廣的是無狀態的介面, 有狀態的暫時沒有進行容器化.
Q: 關於war tomcat執行方式,如何指定不同環境的配置檔案能否像 jar在啟動的時候指定環境配置檔案,在tomcat裡的catalina.sh裡指定配置檔案。 A: 這個不是很清楚, 公司的服務都是springboot的, 都是jar包這種形式的了.
Q: k8s的hpa元件是原生的嗎,只根據cpu記憶體來進行伸縮,有沒有出現過什麼問題 A: 是原生的, 出現過的問題是, 之前都只採用了一個緯度的擴容(CPU), 後來發現該pod經常OOM, 後來加上了記憶體的擴容, Java服務例外.
Q: Prometheus 資料怎麼儲存的,每個例項都存在本地目錄嗎? A: 我們有專門的Node節點來跑prometheus pod通過node label排程, 採用的本地SSD磁碟, 每個服務一個目錄, 大概這個樣子.
Q: 還有就是還有就是日誌部分 現在redis是瓶頸嗎 redis也是叢集? A: 分享的時候提到了, redis是瓶頸, 後來公司golang工程師搞了一個reids--> kafka的代理服務, 採用的redis協議, 但是直接寫入到了kafka, 解決了效能問題.
Q: 網路元件用的什麼,容器和虛機是大二層嗎? A: 容器和虛機是互通的,網路元件是用的Flannel, 這塊主要是公有云提供商負責維護的, 我們其實這塊接觸的不多. 效能的話這塊我也會答不了.
Q: 請問FileBeat是直接在容器裡獲取文字的嗎? A: 容器中我們沒有用到filebeat.
Q: pod有配request 和 limit 嗎,配了的話怎麼和jvm 引數打通?謝謝 A: 配置了, 這塊暫時都是顯性指定引數, 沒有做到自動感知, 也就沒有打通.
Q: 請問叢集的配額限制是從什麼角度做的 ,是pod層面還是namespace層面 A: 目前只配置了pod的request和limit, 基於namespace的配額暫時沒有配置, 目前沒有遇到相關的問題.
Q: prometeus也是k8s管理嗎,配置檔案他的配置檔案怎麼管理的 A: 這塊我們寫了一個簡單的服務部署到了k8s的master節點上, 當一個服務接入k8s上以後, 運維平臺會去掉這個服務的介面, 就會建立一個prometheus server專門抓取該服務的監控資料.通過prometheus的配置可以做到只匹配該服務, 我們這邊是每個服務配置一個單獨的prometheus server抓取端.