DockOne微信分享(一九三):容器化在一下科技的落地實踐
【編者的話】隨著公司業務的不斷髮展和微服務化的改造,專案越來越多,每天的釋出也越來越多, 傳統運維體系效率低下,難以滿足業務的需求。近年來Docker發展非常迅速,構建一次,到處執行的理念也給運維帶來了一絲曙光,Kubernetes平臺的出現更是加速了容器化在企業落地的進度。本文主要分享了Kubernetes在一下科技落地的相關實踐。
容器化的背景介紹
隨著公司業務規模的不斷變大,為了能夠快速迭代,公司的後端架構也逐漸地從傳統架構模型轉向了微服務架構。公司主要開發語言是Java、Golang、PHP,大部分的服務都執行在公有云的虛擬主機上。
在沒有容器化之前,我們遇到的一些主要問題:
流程繁瑣
後端架構轉成微服務以後服務的數量變得更多,每個服務都需要開通虛擬機器,配置監控,配置Jenkins,配置ELK,配置白名單等。不同的開發語言有些配置還不同,這些瑣碎的事情消耗了運維的很多精力。
沒有穩定的測試環境
由於測試環境之間也需要互相呼叫,經常聯調,由於一些歷史原因,不是所有的服務都有穩定的測試環境,這給測試人員帶來了不少的麻煩,經常會用線上的部分機器進行除錯,存在不少的潛在風險。
資源利用率低
為了方便管理,每個服務都是分配的單獨的伺服器進行部署,由於產品使用者的使用習慣,公司大部分的服務的資源使用都有高峰低估的特點,為了保障服務的資源充足,我們核心的服務高峰的時候也會控制資源使用率,所以大部分服務在平時的資源使用率都是很低的,造成了資源的大量浪費。
擴容/縮容不及時
業務高峰期的時候經常需要擴容機器,擴容需要開發申請,運維審批,部署服務,測試確認等很多環節,一個服務的擴容最快也得需要半個小時才能完成,半個小時對於很對關鍵服務來說其實是很長的,是無法忍受的。
部署系統的不足
後端系統在向微服務演進的過程中,不同的後端小團隊出現了程式語言,框架等的多元化,之前存在的部署系統不能充分滿足多語言多框架的部署需求(之前大部分是PHP)。
整體架構
為了解決上面提到的問題,同時也看到很多大公司已經在生產環境採用了Kubernetes,我們也把我們的服務逐漸遷移到Kubernetes容器叢集上。
各個模組的落地細節
容器的接入
這一部分主要跟大家分享的是我們怎樣把執行在傳統虛擬機器/物理機上的服務逐漸接入到Kubernetes上的。
在進行容器化改造之前,我們的運維平臺已經具有了程式碼釋出的功能,我們為不同程式語言的專案制定了部署規範,比如部署路徑,日誌路徑,啟停方式,回撥指令碼等。每一個服務要接入部署系統都需要在平臺上提工單申請,工單資訊大致如下:
通過上面的工單,足夠可以收集運維關心的服務資訊,這些資訊都會記錄到我們的運維平臺上。容器叢集搭建完成以後,開發人員只需要為該專案申請容器部署即可,構建的過程可以複用之前的,不用做任何改動,只需要在構建完成以後製作相應的映象推送到內部的Docker倉庫中,後面會詳細說這些,下面是開發申請專案接入Kubernetes的主要選項(有分頁不便截圖):
- 專案名稱
- 環境選擇,線上/測試
- 容器副本數量
- HPA擴容範圍,比如3-10
- 資源配置(request),比如1核1G
- 叢集內訪問/負載均衡,如果連線該專案的服務都已經部署到了容器中,那麼該服務選擇叢集內訪問的方式就行,也就是採用clusterIP的方式,如果該服務是對公網的,或者訪問該服務的客戶端部署在傳統VM/物理機上,這時候就需要一個負載均衡了。這裡的負載均衡是公有云提供的LoadBalancer。
開發人員提交工單,運維人員處理完工單以後這個專案基本上就可以通過部署系統部署到容器了。運維平臺通過呼叫Kubernetes叢集的介面來建立相應的Service,Deployment,HPA等。當然這只是大概的流程,具體實施過程中還需要做好開發人員的容器知識的培訓和編寫相關基礎文件。
容器CICD
這裡簡單說一下上圖的具體步驟:
1、推送程式碼,程式碼倉庫使用的是內部搭建的GitLab倉庫。
2、運維平臺是以專案為中心而設計開發的,上面說了每個專案要想接入部署系統都會提交發布申請工單,提交完以後專案和該專案的程式碼倉庫的繫結關係就會儲存到平臺上,然後開發就能從平臺上提交容器的釋出工單,如下圖是開發者提交發布申請的表單。
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; seds/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#
4、Jenkins配置了GitLab的互信金鑰,可以拉取所有專案的程式碼。
5、平臺觸發Jenkins構建任務以後,就會把該次構建的容器image推送到內部的Hub中。
6、測試的Kubernetes叢集開發和測試可以隨意釋出, 線上的是需要測試人員確認通過以後才可以上線的, 這裡有一個點是線上環境上線的映象是已經上線到測試環境的相同映象.
7、上線到Kubernetes,開發人員從運維平臺上點選按鈕觸發,運維平臺呼叫Kubernetes的介面通過替換容器的image地址來實現釋出/回滾。
容器的CICD大致就是這樣的流程。
容器的管理
這裡主要跟大家分享的是我們的運維平臺上提供的容器相關的功能。我們的運維平臺是以專案為中心來設計的,所有的其他資源都會跟專案繫結,比如伺服器,資料庫,容器資源,負責人等。此外平臺還提供了許可權的管理,不同的人員對於專案有不同的許可權,基本上每個開發對於專案的常規操作我們都會整合到平臺上。容器這塊我們沒有使用官方的Dashboard,也是基於API來整合到運維平臺的。
- 編輯配置,這個功能主要是給運維人員開放的,方便給專案調整引數。
- 基本資訊,包括容器資訊,服務資訊,HPA資訊等,方便開發檢視。
- 監控資訊,這些資訊也是通過直接使用的Prometheus和Heapster的介面來獲取的。
- Web終端,這裡也是通過呼叫Kubernetes相關的介面並寫了一個Socket/">WebSocket服務來實現的,方便開發人員緊急處理問題。
日誌系統
在沒有采用容器之前,我們已經用ELK來解決日誌的問題了,我們主要有2種使用方式:
- 當服務的日誌量不是很大的時候我們採用的是程式直接以json的形式寫日誌到Redis的list中,然後我們使用Logstash傳送到ES中,然後從Kibana上就可以查看了。
- 當服務的日誌量較大的時候,我們採用寫檔案的方式,然後採用Filebeat傳送到Kafka叢集,然後採用Logstash傳送到ES叢集。
採用Kubernetes容器以後,由於磁碟限制這塊不是很完善,我們要求所有的服務都採用輸出到Redis的方式或者標準輸出的方式來打日誌,這樣本地磁碟也就不用考慮限制了。這裡有一點要說明一下:
- 由於所有服務都直接採用Redis的方式後,Redis很容易成為瓶頸,如果Redis掛了很可能會影響線上業務,基於這一點,我們讓公司的Golang工程師幫忙開發了一個Redis -> Kafka的一個代理服務,這樣的話業務不用修改任何程式碼,還是用Redis的方式輸出日誌,只不過後面其實是輸出到了Kafka叢集了,代理服務是無狀態的,可以無限擴容,這樣效能問題也就解決了。
監控系統
在接入容器之前我們已經採用了Prometheus來監控我們的伺服器和業務,Kubernetes對Prometheus的支援很完美,所以我們這塊能很快適應,業務程式碼也不用怎麼修改,這裡有幾個點:
- 我們在Kubernetes上部署了Prometheus來專門監控所有業務的Pod狀態,比如Pod的HPA使用率,Pod的不可用數量等。
- 由於業務監控需要採集的資料巨大,多個服務如果公用一個Prometheus抓取端的話會有效能問題,我們採用了為每一個服務啟動一個Prometheus抓取端的辦法來解決這個問題,每個服務單獨的Prometheus抓取端通過配置檔案中的正則匹配來控制只抓取該服務的資料。
- 看板採用的是Grafna。服務註冊到運維平臺的時候也會自動通過Grafna的介面建立相應的看板專案。
- 報警傳送的話我們自己有封裝的釘釘/企業微信/郵件/簡訊等功能的介面。
服務發現
公司用到服務發現的主要是Java的Spring專案,所以採用的是Eureka,我們之前在沒有采用容器的時候就已經有了一套Eureka註冊中心,由於Pod的網路和虛擬機器的網路是直接互通的,服務遷移到容器中以後我們依然採用的之前的一套註冊中心。
配置中心
服務遷移到容器的時候我們就要求開發在開發程式碼的時候生產和測試是同一個jar包,根據啟動引數的不同來選擇環境,這樣的話就可以實現構建一次,生產/測試到處運行了。配置中心這塊我們採用的是Apollo,這塊也是通過呼叫Apollo本身的API來整合到運維平臺的,做到了從平臺上修改/編輯配置等。
踩過的坑
initialDelaySeconds引數導致Pod迴圈重啟問題
這個引數是Kubernetes配置服務的健康檢查的時候使用的,我們知道,Kubernetes的健康檢查有2種,一種是存活檢查,當這個檢查失敗的時候,Kubernetes會嘗試重啟該Pod,另一種是就緒檢查,當這個檢查失敗的時候,Kubernetes會把該Pod從Service上摘下來,還有一個點是很重要的,比如我在Kubernetes上部署了一個Java專案,這個專案啟動時間大概是30秒,那麼我就需要配置上面的這個引數,Pod啟動的時候讓健康檢查的探針30秒以後再執行檢查,實際場景中,一個普通的Spring Boot專案啟動要花費40秒左右(limit為4核),所以這個值對於Java來說至少需要配置成60才可以,不然經常會出現新部署的Pod一直重啟。
使用PreStop Hook保證服務安全退出
在實際生產環境中使用Spring框架,由於服務更新過程中,服務容器被直接終止,由於Eureka Server有快取,部分請求仍然被分發到終止的容器,雖然有重試機制,但是高併發情況下也經常會遇到介面呼叫超時/失敗的情況,為了減少這些錯誤,可以在容器退出前主動從Eureka上登出這個節點,等待2倍左右的Eureka檢測時間(2*5),這需要開發提供介面並將呼叫介面的指令碼新增到PreStop中,參考:curl ofollow,noindex" target="_blank">http://127.0.0.1/debug/stop/eurekaClient;sleep 10
健康檢查超時時間相關配置
實際工作中遇到過一個服務不管怎麼配置,總是會莫名奇怪地出現重啟的現象,看日誌也沒有看出來,後來問題找到了,是健康檢查配置的問題,最開始使用Kubernetes的時候我們給每個服務配置健康檢查的超時時間是1秒,失敗1次就算失敗;後來跟業務溝通了一下,他們的服務本來就是響應慢,好吧,我們只好修改了一下超時時間,調大了一點,失敗次數也改成了3次,連續3次失敗才算失敗。
Java獲取宿主機CPU/記憶體的問題
由於JVM獲取的是宿主機系統的引數,所以導致預設情況下JVM獲取的GC執行緒數和分配的HeapSize不是我們想要的,我們是通過調整JVM啟動引數來解決這些問題的,參考:java -server -XX:ParallelGCThreads=4 -Xmx2g -Xms2g -jar service.jar,當然也可以通過其他的方式來修改。
結束語
以上是容器化在一下科技的落地實踐,以及在容器化過程中碰到的一些問題和解決方案,希望這次分享能對大家有所幫助,我們生產環境的容器化還處於開始階段,還在逐漸推進中,以後可能會遇到更多的問題,希望能跟大家相互學習,共同進步。
Q&A
Q:PreStop Hook的參考地址能給個外網地址看嘛?
A:這個看官方的文件就行吧,我這個場景裡只是用了一個curl,讓開發提供一個介面就行。具體PreStop Hook官方文件上有詳細的舉例。
Q:Apollo配置中心,配置怎麼落到服務裡的,或者容器裡?
A:我們這邊大部分是Java專案,使用的官方提供的SDK,具體可以看下Apollo的使用文件。
Q:日誌怎麼採集和展示?用什麼方案?
A:ELK,採集日誌主要是程式直接輸出到Redis,這點有些不一樣。
Q:CD的配置是怎麼管理的?
A:相關的上線配置都是存在運維平臺上,服務的配置使用的Apollo配置中心。
Q:Kubernetes的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:Prometeus也是Kubernetes管理嗎,配置檔案他的配置檔案怎麼管理的?
A:這塊我們寫了一個簡單的服務部署到了Kubernetes的Master節點上,當一個服務接入Kubernetes上以後,運維平臺會去掉這個服務的介面,就會建立一個Prometheus Server專門抓取該服務的監控資料,通過Prometheus的配置可以做到只匹配該服務,我們這邊是每個服務配置一個單獨的Prometheus Server抓取端。
以上內容根據2018年11月20日晚微信群分享內容整理。分享人 吳飛群,一下科技運維工程師 。DockOne每週都會組織定向的技術分享,歡迎感興趣的同學加微信:liyingjiesd,進群參與,您有想聽的話題或者想分享的話題都可以給我們留言。