【編者的話】開源節流,是企業提高利潤的兩大方向;中臺戰略或基礎結構體系經常肩負了節流的重任。不管大小企業,容器化都被認爲能夠大幅度地提高效率,增長運維標準化和資源利用率。可是此類事情一旦作很差很容易形成花了大量成本而效果得不到承認的尷尬結果。本次分享從團隊的實際經驗出發,聊一下容器化生態體系落地中的一些事情。
監控
容器環境通常是提供一整套解決方案的,監控能夠分爲三種:指標監控、業務監控、調用鏈監控。docker
業務監控和調用鏈監控更多的取決於業務開發部門的選型,如skywalking等。數據庫
容器環境下,指標監控非Prometheus莫屬,經過Service Discovery機制中的Kubernetes plugin得到scrape路徑,以後的鏈路就比較通暢了。後端
使用Prometheus過程當中一個繞不開的問題是持久化存儲,WAL中保存的數據不宜過多,不然內存和加載速度都會產生很大問題,官方支持的remote read/write列表中,咱們考查了InfluxDB和TiDB這兩個,實踐中二者佔用的內存都很是大,建議在集羣外的物理機中進行部署,若是使用InfluxDB,若是集羣中Pod建立頻繁(例如使用了cronjob)可能會觸發key數量限制。
日誌
日誌分爲兩種:std系列日誌和文件日誌,它們的區別主要在於收集方式不一樣,通常來講,收集上來的日誌都會並進入ELK體系,後面的處理就都差很少了。api
std系列日誌因其屬於Linux模型,能夠統一從Docker數據目錄中予以收集,一種部署方式是使用DaemonSet部署Fluentd並掛載hostPath。緩存
文件形態的日誌略顯複雜,NFS/CephFS等分佈式存儲確定不適合存放日誌,咱們經過emptyDir形式實現目錄共享,而後新增filebeat sidecar對共享目錄中的日誌文件進行收集,入ELK體系。
如何與持續交付對接
這裏咱們關注持續交付部署部分的方案,Kubernetes的部署本質上就是不一樣類型的資源對象以yaml格式應用,在自研與使用開源方案之間,咱們選用了Helm做爲部署階段中,持續交付與Kubernetes的溝通橋樑。經過Helm咱們能夠把部署配置變成一個JSON對象,輔以標準化的部署模版,實現部署的標準化,同時自帶了資源狀態監測,應用管理等功能。安全
做爲一個toB性質的服務,咱們不該該只關注服務自己的可用性和性能,更應該從最終用戶體驗維度進行自查改進。例如Kubernetes官方的Benchmark工具中提到Pod平均啓動時間,可是對項目來講更加關注的是Pod平均ready時間,而探針的結果是受到項目依賴,數據庫等因素的影響的。對於特定項目,不少數值是穩定的,咱們能夠在報警系統中進行一些統計學方面的處理。
如何正確地添加Sidecar
剛剛的日誌章節,提到了使用Filebeat Sidecar來收集日誌,持續交付對接過程當中提到了使用模版來生成項目的yaml文件。這就意味着,日誌Sidecar容器必須在項目部署配置中予以體現,與項目進行耦合。這帶來了很大的複雜度,也令日誌系統的配置變動流程很是複雜。畢竟穩定的項目通常不會去更新部署配置,日誌系統要一直兼容老版本的規則文件。於是須要一種手段,把日誌配置和項目配置進行隔離。網絡
咱們找到的辦法是Kubernetes的動態准入控制(Mutating Admission Webhook)來實現sidecar injection。經過這一機制,全部的資源在操做(增刪改)同步到etcd前,都會請求Webhook,Webhook能夠經過或否決(allow/reject),也能夠響應一個JSON Patch,修改對象的部分資源。架構
事實上,經常會發現咱們定義的Pod中會被默認注入default service account,就是Kubernetes中內置Admission的做用產物,如今很是火的Istio,其劫持流量的原理爲修改每一個Pod的網絡規則,也是經過這種機制注入init-container,從而在Pod中修改iptables來實現。app
經過這一機制,還能夠針對諸如hostPort,hostPath,探針規範做出安全審計,能夠說提供了至關豐富的想象空間。風險點是Webhook必須穩定可靠,延時較長不是問題,1.14+提供了timeoutSeconds,但若是返回一個不能被apply的patch,會致使資源建立失敗。運維
在日誌應用場合,咱們註冊了Pod對象的Create動做,項目只須要經過annotation傳入幾個簡單配置,就能夠自動生成一個自定義的Filebeat Sidecar,很是乾淨和方便。
如何實現自定義PodIP
Kubernetes中每次Pod的建立都會分配一個新的IP,社區的目的是但願用戶使用Service+DNS的機制實現通訊,但實際上,在一些基礎組件的容器化過程當中,因爲軟件兼容性,咱們會但願某些業務容器的IP固化,不因重啓而變動。
這裏以Redis舉例要用到穩定的IP的場景:
在Redis集羣模式中,「cluster meet」命令只支持IP格式,不支持域名解析配置,社區中有人提出過這個issue結果被拒了。雖然說Redis集羣中任意一個節點的IP變動均可以在Redis集羣內自動識別(由於Instance ID不變),可是若是由於意外狀況致使全部Redis集羣節點同時發生重啓,集羣內節點兩兩沒法發現彼此,那就只能由運維人工介入,從新讓節點發現彼此,此外IP的變動也會致使有緩存的Redis客戶端產生錯誤。
在Kubernetes中,Service相關資源由kube-proxy負責,主要體如今iptables或IPVS規則中,而PodIP是由CNI負責分配,具體體如今eth-pair和路由表中。咱們選用了Calico做爲CNI插件,經過cni.projectcalico.org/ipAddrs這個annotation將預期的IP傳遞給Calico。
相對於對CNI進行二次開發自行實現IPAM來講,這種方法的開發成本較小。
在具體實現上:因爲Pod是經過上級對象資源的模版建立,沒法在模版中爲每一個Pod自定義annotation,因此咱們一樣經過動態准入機制實現,例如在sts資源中自定義一個annotation並傳遞一組IP,隨後劫持Pod的建立,根據序號依次爲Pod新增annotation,以激活Calico的指定PodIP功能。
這裏注意的一點是,咱們在實現IP固化功能後,一些微服務團隊也但願使用這個功能。他們想要解決的痛點是容器發版以後,註冊中心仍然保有舊的PodIP的問題。這裏不適合去作IP固化:
緣由一:Web項目大都使用deployment發佈,在rs和Pod階段,podName會添加隨機字符串,沒法甄別排序;事實上,咱們只對sts資源開放了固化IP的方案;
緣由二:微服務應用應當實現對SIGINT,SIGTERM等信號的監聽,在pod terminationGracePeriodSeconds中自行實現註冊中心的反註冊。
任務調度
咱們有一些祖傳的業務員仍然使用PHP,PHP在進程管理上比較欠缺,物理機環境下不少調度工做要藉助於cronjob來完成。咱們一些PHP項目一開始上容器的時候,採用的就是Kubernetes提供的cronjob機制,使用下來有這麼幾個問題:
Pod執行日誌經過ELK體系收集後展現不直觀;
更新代碼後Pod在節點的首次啓動會由於pull代碼而不許時
沒法手動執行啓動
間隔時間較短的cron大幅度提升了集羣Pod總數,增長管理節點的壓力。
最後咱們選擇使用開源的goCron方案,爲項目單獨部署任務專用deployment,經過gRPC的方式進行任務的啓停和日誌傳輸。
值得注意的是,在開源goCron方案中,由Server角色向Node角色發起請求,可是咱們不可能爲每個Node容器都配備Ingress或者NodePort暴露。
在有關二次開發中,咱們爲gRPC proto參數中新增了target字段。即Server角色中心化部署,每一個容器編排集羣部署一個Agent角色做爲中轉,最終經過SVC達到Node角色。
集羣事件監控
咱們排查問題的時候第一件事通常都是describe一下相關資源,而後查看event,可是事實上,event默認只能存在1小時;kube-apiserver中有一個參數定義了事件在etcd中的保留時間:event-ttl Amount of time to retain events. (default 1h0m0s)。
這個1h主要是考慮到大規模集羣中etcd的性能瓶頸;但即便是小集羣,這個值也不建議調整到24h以上。這意味着,若是半夜中集羣中發生事件,到了白天上班只能看到restart計數器+1或者對象存活時間清零,而找不到任何相關信息。
因此咱們通過二次開發,在全部集羣內部署了一個事件收集中間件,監聽全部ns中的ev,發送至ES,並進行一些簡單的聚合,以metrics的形式暴露給prom。這一工具深受運維團隊好評,而且逐漸成爲了集羣健康的重要晴雨表。
容器內時間模擬及系統參數模擬
容器化和虛擬化相比,最大的區別在於容器和物理機共享了內核,內核實現了進程調度、網絡、io,等等功能,並經過Namespace和CGroup實現隔離。可是在這些隔離中,時間、CPU、內存等信息不在隔離範圍內,從而帶來了問題。
首先咱們看一下CPU和內存,在容器中,若是咱們打印/proc/cpuinfo或是/proc/meminfo,取到的是物理機的核數和內存大小,但實際上容器必然是會有資源限制的,這會誤導容器環境中的進程,使得一些預期中的優化變成了負優化。如線程數、GC的默認設置。
針對此問題的解決方案有三個:
Java/Golang/Node啓動時手動參數傳入資源最大限制
Java 8u131+和Java 9+添加-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap;Java 8u191+和Java 10+默認開啓UseContainerSupport,無需操做;可是這些手段沒法修正進程內直接讀取/proc下或者調用top、free -m、uptime等命令輸出的內容
改寫相關內核參數,對任意程序都有效果
前兩種方案,侵入性較高,咱們選擇使用第三種方案,改寫相關內核參數,使用LXCFS實現,yaml中使用hostPath裝載。
關於LXCFS,這裏只提供一個關鍵詞,你們能夠去搜索相關信息。
與CPU/內存相相似的還有Uptime、diskStats、Swaps等信息,改寫後容器內top、free -m、uptime等命令都會顯示正確。
值得注意的是CPU的限制,容器中所謂的CPU限制,並非綁定獨佔核,而是限制使用時間。舉個例子:一臺4核的物理機,能並行4個線程;而一臺32核的宿主機上起一個限制爲4核的容器,它仍然能並行32個線程,只不過每一個核只能佔用1/8的時間片。
關於容器內時間的模擬,咱們使用了libfaketime,進程啓動時添加LD_PRELOAD和FAKETIME環境變量。
最後聊一下Kubernetes的基礎,etcd。當api-server不可用的時候,直接讀取etcd中的數據將成爲最後的救命稻草。然而etcd中存放的數據在某個版本以後已經變成了Protobuf編譯過的二進制數據。get出來以後肉眼沒法識別。
我平時會使用Auger這個開源項目,經過管道的形式將etcd中的內容還原成yaml文本。
我認知中的Kubernetes,它是一個容器編排體系,是一套雲原生的微服務架構。
Q&A
Q:落地過程必然涉及到以前開發、測試和運維流程的變動,組織和相關人員都會面臨調整,這部分工做貴公司是如何推動的,踩了哪些坑,如何解決的?
A:這個一言難盡啊,人的問題是最難解決的,能用技術解決的都不是問題,要是說回答的話,初期打通公司各個關節,讓大boss承認這件事,行政命令強推,很重要。否則作出來也沒人用,就是白忙活,在用戶中找小白鼠迭代,而不是本身弄個自覺得完美的推出去。
Q:Java容器瞬間拉起的過程,整個集羣都會被CPU用盡,如何解決Java CPU啓動時候CPU資源互爭的狀況?
A:這個問題咱們也遇到過,後來把內核升級到4.19後就再也不發生了,不少內存耗盡,CPU爆炸的問題咱們都經過內核升級解決了。
Q:ELK還有文件存儲平臺是搭建在Kubernetes系統內的嗎?另外貴公司管理Kubernetes服務是否選用了諸如Rancher之類的管理平臺?
A:ELK在集羣裏,開發測試環境用了Rancher,線上本身開發的管理平臺,畢竟Rancher是真的好用。
Q:日誌平臺怎麼解決無法像grep -C查找上下文,日誌平臺怎麼標準化日誌格式?
A:這個得看日誌平臺具體開發是怎麼實現的了,通常來講這不是問題
日誌格式的標準化,得和業務合做。事實上日誌平臺通常是中臺部門的單獨的系統,它要單獨開發。
Q:容器化落地怎麼協調開發的需求?好比開發學習成本,好比本地調試和現場保留復現問題,排查問題的方法方式對開發友好。
A:這仍是人的問題,不少業務開發不肯意學習,不接受新事物,一葉障目否認容器,這真的沒辦法。仍是從人身上尋求妥協吧。每一個人的精力都是有限的,這種事情陷進去很難拔出來;公開培訓,講座,駐場支持,培養業務部門懂的人。
Q:容器環境如何落地文件存儲,或者對象存儲,如何選擇後端的塊設備:
A:咱們選用的是CephFS和NFS。
Q:線上Kubernetes集羣採用什麼方式部署,二進制仍是kubeadm等,部署架構是怎麼樣的?
A:若是瞭解證書製做和Kubernetes各個組件的做用,建議從二進制文件入手,企業環境能夠本身寫Ansible等腳本。kubeadm維護通常不適用於線上環境。
Q:集羣網絡方案怎麼選擇的,對外提供服務如何作的?
A:Ingress Controller能夠用hostPort暴露,外層Nginx去upstream一下。
Q:若是服務發現用ZooKeeper,那Dubbo服務要實現灰度發佈如何實現?
A:目前咱們的灰度發佈都是基於Ingress方案,Nginx Ingress Controller容許直接使用Nginx規則配置,Traefik功能也足夠強大。因爲我主要的開發技術棧是Golang,不太瞭解Dubbo。
Q:長期存活的容器會形成容器日誌堆積,請問有沒有不重啓容器就能實現清理與歸檔的方案?
A:這個問題咱們有想過,可是沒有什麼方案。若是從源頭上規避,可讓業務日誌不要落地成文件,或者自行按日期流轉。
Q: 如何採集Kubernetes中的容器前臺日誌?Filebeat貌似只能採集文件吧:
A:前臺日誌也就是std日誌,分享中提到用Fluentd和hostPath採集物理機Docker目錄中的內容,能夠去了解一下Docker目錄結構。
Q:監控系統數據持久化方案是什麼?如何保證監控系統的數據正確性呢?
A:數據持久化咱們用了TiDB,外部用Zabbix作了一層冗餘。
Q:我是一名Java工程師,有7年經驗,想轉行到容器相關領域,請問成爲容器開發工程師須要哪些條件?
A:對Linux要很是瞭解,脫離JVM看一些系統方面的知識。此外容器的語言基本上都是Go,微服務那套和Java沒啥區別,熟悉Protobuf。
Q:std日誌是如何區分不一樣Pod的?
A:Fluentd有對應模塊,收集到以後直接就有hostName和NS。
Q:如何保證日誌Sidecar的存活與否不會影響到業務容器?
A:Sidecar和業務容器原本就是互相隔離的,如今1.10+的Kubernetes在Pod內只會共享網絡,不會默認共享pid了,應該不會有啥影響。
Q:event採集是如何實現的,是否能夠開源代碼?
A:我記得sentry就有一個插件能實現。公司代碼未得許可不會開源。
Q:Sidecar方式收集日誌會出現延時,特別是丟失問題,這個如何解決?
A:減小Filebeat的採集時間,這個我感受無解。或者在gracefultime上作文章,讓Filebeat多活一會。
文章轉載自dockerone社區:http://dockone.io/article/9887