百分點大規模Kubernetes集群實踐

去年8月底,百分點與雲知聲聯合發布了Google開源的集群管理系統Kubernetes的「發行版」——Sextant。在百分點大規模Kubernetes集群經過四五個月的應用實踐後,到目前為止,集群上已經承載了百分點推薦系統的大部分業務組件和部分的運維組件。那麼,在使用過程中會遇到哪些問題?如何解決?本篇將詳盡總結百分點在實踐中的經驗教訓,期望能夠更多地回饋社區。

從0到1

在傳統的集群管理方法下,百分點服務器利用率長期處於20%以下。通常為了完成某個業務目標,團隊會申請各自的服務器,然後工程師使用跳板機登陸到這些服務器上完成程序的部署。

這樣的弊端是:首先,這些服務器上的空閒資源並不會貢獻出來為其他團隊所使用;其次,這些服務器在解決業務高峰問題之後,負載下降,而這時團隊並不希望服務器被回收,因為不知道如何備份服務器之上的數據。

這樣,集群服務器利用率逐步降低,整體集群的維護和管理也變得異常困難,在百分點AI技術運用增多的趨勢下,常遇到計算資源不足而導致業務進展緩慢的情況。

如何解決呢?

我們做了很多嘗試,最終決定選擇CoreOS、Kubernetes(以下簡稱K8s)、Ceph相結合的技術方案。

對於Kubernetes在生產環境中的應用,百分點是比較早的一批實踐者,從開始關注Kubernetes1.0,到將1.2版本實際部署到我們的生產環境中,圍繞Kubernetes做了很多周邊工作,使Kubernetes能夠更好地服務於業務場景。

限於篇幅原因,這裡不展開介紹Kubernetes的基本原理和概念了,感興趣的讀者可以在我們的githubsextant項目中,找到相當豐富的文檔。

為了達成這樣的目標,我們要求開發者使用Docker將自己的應用程序完成封裝,逐步將手動的集群部署,切換到使用K8S的集群容器編排之上。如下圖所示,需要在團隊項目的gitlab repo中增加相應的Dockerfile和編排文件內容,CI環境會自動將應用完成編譯->單元測試->docker鏡像打包->鏡像提交的工作。封裝在容器中的應用使用Ceph存儲數據,並通過ingress LoadBalancer對外部提供服務。

對於Kubernetes在生產環境上的部署,我們希望最大限度的簡化K8S集群維護人員的工作。比如擴容一台機器、下架維修一台機器等,只需要維護人員直接完成開機/關機的操作即可。由於K8S的調度編排,可以屏蔽這類操作對應用的影響。

三步完成集群安裝


首先要解決的問題,就是如何能夠高效的、自動化的進行組件的部署以減少手動部署可能帶來的問題。

百分點、雲知聲在百度科學家王益帶領下,合作開發了基於PXE自動化安裝CoreOS+Kubernetes集群的開源Sextant項目,只需要簡單的三個步驟即可完成集群的安裝:規劃集群->啟動bootstrapper->節點開機自動安裝。

  • PXE採用PXE的技術,從網路安裝CoreOS的操作系統,完成節點操作系統的初始化。

  • cluster-desc.yaml是集群的描述文件,諸如操作系統類型、Flanneld網路模式、Kubernetes版本等都會配置在這個文件中。對於每個待安裝的節點,需要根據MAC地址配置相應的角色,例如Kube-master,flanneld-master等。

  • Bootstrapper是Sextant項目的核心服務,載入cluster-desc.yaml配置文件,並提供web服務,根據節點的MAC地址生成相應的cloud-config.yaml文件,從而安裝並啟動kubernetes相關組件。

操作順序:

  • Step 0集群規劃 
    規劃集群,將集群信息描述為cluster-desc.yaml配置文件,例如操作系統的類型、etcd節點的數量、flanneld協議類型,哪些節點作為master等等。

  • Step 1編譯、運行bootstrapper 
    通過上一步驟的cluster-desc.yaml文件,編譯bootstrapper的docker image並啟動,bootstrapper會提供PXE、DHCP、DNS以及Docker Registry等服務。

  • Step 2安裝kubernetes節點 
    將服務器接入集群,開機並從網路引導安裝,即可自動完成CoreOS以及K8s組件的安裝過程。

下一步就是應用遷移,在收獲中也充滿了痛。

收獲和痛



至此,我們自認為可以開始像習大大元旦助詞說的那樣「擼起袖子」,進行逐步的遷移工作了。但經過簡單的性能測試後,結果並不理想。運行在K8S之上的服務響應延遲,導致出現至少20%的性能損耗。在踩坑之後,我們對K8S的網路有了更加深入的理解。

一、打造kubernetes高性能網路

熟悉Docker的讀者可能會了解到,Docker容器有三種網路模式,但為了達到容器內的網路環境隔離,通常會選擇使用NAT方式完成容器內的網路包轉換和轉PO。這樣,在同一台主機上啟動的容器或不同主機啟動的容器之間,除了配置NAT端口的4層地址可訪問外,都不可以直接互相訪問。

Kubernetes的解決思路和Bridged模式的虛擬機群很像——用一個通用的IP地址分配服務,為運行在各個host上的container統一分配IP地址。這樣運行在不同host上的containers之間通信,直接使用對方的container IP地址就可以了,而不需要考慮host IP。這實際上把Docker模式中的host IP和containerIP這兩層IP地址變成了一層。

在Kubernetes的文檔裡闡述了一個叫Pod的概念,並且解釋一個Pod裡可以運行一個或者多個Docker containers。實際上,一個Pod就是一個Docker container。所謂在Pod裡運行的多個containers,實際上是啟動的時候加了–net=container:參數的containers,它們不會得到自己的IP地址,而是和pod container共享IP地址。這樣一來,一個pod裡的containers之間通信的時候可以用localhost地址,而跨越pod的通信用pod IP。 看上去Kubernetes的做法裡相對於Docker的做法,多了一層Pod的概念。

但是實際上每個container里約定俗成地只運行一個服務進程,所以還是三層概念:

  • 節點(node)

  • Pod

  • Container

Kubernetes做到這種網路結構有多種方法,如overlay networking、BGP和其他SDN技術,常見的做到包括:Flannel、 Calico、 L2 networking、OpenVSwitch等,我們對常用並活躍的項目進行了針對性評測,結論如下:

可以看到,在L2模式下,針對高性能web應用場景可以達到最佳性能。當然如果有專用的SDN交換機設備,也可以大大提高SDN的網路性能。但綜合成本、方案複雜程度、方案後續可擴展性考慮,最終選擇flannel host-gw模式。這個模式,要求所有host保持穩定的2層網路連接,然後通過Linux路由表,將Docker bridge的包完成3層轉PO。

另外一點,在評測過程中發現,linux加在netfilter和iptables相關內核模塊之後,平均網路延遲會下降10%左右。但就目前狀態來說,使用iptables NAT作為Kubernetes的service負載均衡仍然是性能最高、最簡單的方式。後續如果可以使用更優秀的方法提供service負載均衡,可以去掉iptables以降低容器之間的網路延遲。

在這種網路部署下,使用普通的千兆網卡和千兆交換機,即可以較低成本來搭建大規模的集群,並獲得相對可觀的性能。在針對網路延遲有更高要求的服務,比如Redis等,則考慮直接物理部署作為折中方案。

二、打造高可用的前端負載均衡器

從上面的介紹可以看出,Kubernetes service主要仍是針對數據中心內部互相訪問,若要方便地提供HTTP web服務的創建,則需要引入Ingress的概念。

眾所周知的是,Kubernetes中的Service可以將一組pod提供的服務暴露出來供外部使用,並默認使用iptables的方式提供負載均衡的能力。Service通過使用iptables,在每個主機上根據Kubernetes service定義,自動同步NAT表,將請求均衡的轉PO到後端pod上,並在pod故障時自動更新NAT表。相對於使用userspace方式直接轉PO流量有更高的效率。常用的Service有ClusterIP、Loadbalancer以及NodePort方式。

  • ClusterIP是通過每個節點的kuber-proxy進程修改本地的iptables,使用DNAT的方式將ClusterIP轉換為實際的endpoint地址。

  • NodePort是為了Kubernetes集群外部的應用方便訪問kubernetes的服務而提供的一種方案,它會在每個機器上。

  • 由於NAT性能的問題,NodePort會帶來一定的性能損失,在一些場景下,我們也會選用Loadbalancer作為k8s集群外部應用訪問K8s集群內部應用的統一入口。百分點採用的Loadbalancer負載均衡器是基於haproxy,通過watcher Kubernetes-apiserver中service以及endpoint信息,動態修改haproxy轉PO規則來做到的。

從上面的介紹可以看出,Kubernetes service仍是主要針對數據中心內部互相訪問,若要方便地提供HTTP web服務的創建,則需要引入Ingress的概念。

1.Ingress
對於對外提供服務的web應用來說,需要提供7層反向代理的機制,使得公網的流量可以轉入集群之中。百分點採用的是Nginx,通過WatcherKubernetes中Ingress資源信息,動態修改對應的service匹配endpoint的地址,使得整個配置流程只需通過kubctl提交一個配置即可。Ingress作為數據中心web請求的入口,將流量引入到集群內部,完成處理後經由Ingress返回外部請求者。這樣一來,任何一個部署在kubernetes上的web應用,都可以簡單的通過提交一個Ingress資源,完成web請求對外的開通。

2.Ingress HA
Ingress的機器是整個集群的入口,如果其中一台機器出現故障,帶來的影響將會是致命的。我們也曾考慮過使用F5等技術做前端的高可用,但最後基於成本和可維護性考慮,最終使用Keepalived+vip的方案。 

3.Ingress優化

  • 性能優化 
    由於NginxIngress Controller要監聽物理機上的80端口,我們最初的做法是給他配置了hosrtport,但當大量業務上線時,我們發現QPS超過500/s就會出現無法轉PO數據包的情況。經過排查發現,系統軟中斷占用的CPU特別高,hostport會使用iptables進行數據包的轉PO,後來將Ingress Controller修改為hostnetwork模式,直接使用Docker的host模式,性能得到提升,QPS可以達到5k以上。

  • Nginx配置優化 
    Nginx IngressController大致的工作流程是先通過監聽Service、Ingress等資源的變化然後根據Service、Ingress的信息以及nginx.temple文件,將每個service對應的endpoint填入模板中生成最終的Nginx配置。但是很多情況下模板中默認的配置參數並不滿足我們的需求,這時需要通過kubernetes中ConfigMap機制基於Nginx Ingress Controller使用我們定制化的模板。

  • 日志回滾 
    默認情況下Docker會將日志記錄在系統的/var/lib/docker/container/xxxx下面的文件裡,但是前端日志量是非常大的,很容易就會將系統盤寫滿,通過配置ConfigMap的方式,可以將日志目錄改到主機上,通過配置logrotate服務可以做到日志的定時回滾、壓縮等操作。

  • 服務應急 
    當線上服務出現不可用的情況時,我們會準備一套應急的服務作為備用,一但服務出現問題,我們可以將流量切換到應急的服務上去。在k8s上,這一系列操作變得更加簡單,這需再準備一套ingress規則,將生產環境的Servuce改為應急的Service,切換的時候通過kubectl replace -f xxx.yaml 將相應的Ingress替換,即可做到服務的無感知切換。

三、打造一體化Kubernetes集群服務

作為一個集群化的操作系統,基礎服務必不可少,開發者通常需要經常查看服務的日志,查看監控數據,查看運行狀態等。我們為Kubernetes集群配置了很多基礎類服務,使集群使用起來更加高效。

1.日志管理
當我們的應用運行在集群操作系統上時,如何高效地查看、分析服務日志是一個必須要解決的問題。百分點採用了Fluented+Elasticsearch+Kibana的方案,整個套件也都是運行在Kubernetes集群上的。

  • Fluentd 
    Fluented是使用Kubernetes中Daemonset的機制,使得fluented啟動在每一個節點上,並自動采集Docker Container的日志到ES集群中。

  • Elasticsearch 
    ES自帶的Discovery機制並不能在kubernetes中完美的運行,這裡使用kubernetes的插件,使其他通過Service的方式使master節點能夠自動發現client和data節點的endpoint地址,組成集群。ES中數據節點的存儲是放在Ceph集群中的,保證了數據可靠性。

  • Kibana 
    Kibana中能夠根據用戶自定義篩選、聚合,方便用戶查詢使用。

2 . 系統監控
通過heapster+collectd+influxdb+grafana解決多源數據采集、監控數據存儲、查詢展現的問題。

  • heapster負責從cAdvisor中采集宿主機以及container中的監控數據並寫入influxdb中。 

  • collectd負責采集類似nginx組件的監控數據寫入influxdb中。

  • influxdb負責按時間序列存儲監控數據,並且支持類SQL的語法來訪問數據 。

  • grafana提供了WebUI,通過用戶自定義的查詢規則,生成SQL語句來查詢influxdb中的數據,並最終以圖表的形式展現給用戶。

3.統一Dashboard 
為了簡化kubernetes集群的使用,百分點的技術團隊開發了Sirius系統,使用戶使用起來更加的方便,並且此項目也在Github上進行了開源。

4.持續化集成
Docker是我們落地Devops理念的核心技術,從開發人員寫下第一行代碼開始,我們構建了這樣一條持續集成的流水線。

我們選擇了Gitlab作為代碼倉庫,當開發人員向Gitlab提交代碼,Jenkins會監聽每個tag,每次push事件,有選擇的自動構建為docker image,並推向Docker Registry,存儲在我們的Docker倉庫中。隨後,Jenkins會將新版本的鏡像推到集成測試的Kubernetes集群中,完成一次構建、測試、預上線的流程。待測試通過後,再發布到生產環境。

5.持續化部署
在逐步將線上應用遷移到kubernetes集群過程中,當然也遇到不少問題,每個應用在提交之後需要經過多次修改和更新才可以正式上線,為了方便更新、並盡量減少人為操作的失誤,我們使用「編排文件版本管理+kubernetes deployment」完成持續化部署。

  • 什麼是Deployment? 
    Deployment描述了待部署Pod的狀態,只需要定義我們期望的一組Pod的狀態,kube-controller會幫助我們在集群上維持這一狀態,並且可以很方便地在上面做roll-out和roll-back。

  • 如何更新Deployment? 
    直接使用kubectl editdeployment/{your deployment}即可對相應deployment進行修改。並且可以指定最大不可用的Pod個數來控制滾動更新的進度。每次執行edit命令之後,就會觸發deployment的rolling update,應用會在後台完成逐個平滑升級。

  • 持續部署 
    在每個應用的代碼倉庫中,會增加一個.kube的目錄,下面存放本應用的yaml編排文件,每次部署升級都直接使用對應版本的編排文件即可完成部署。

四、打造統一持久化存儲平台

Kubernetes在運行時是基於容器技術的,這就意味著容器的停止會銷毀容器中的數據。若應用要使用持久化的存儲,如果直接掛在容器所在主機的磁盤目錄,這個編排系統會顯得十分混亂。雖然Kubernetes提供了諸如hostPath機制,除非應用和主機具有非常明確的綁定關係,否則不推薦使用。這樣,我們需要一個通過網路可訪問的存儲池,作為統一的集群存儲平台。選型的問題這裡不詳細展開,我們最中使用ceph作為Kubernetes的後端存儲。

在部署時,考慮到kubernetes編排的網路請求可能和ceph的數據存儲請求搶占網卡帶寬而導致整體集群癱瘓,預先將kubernetes訪問ceph集群的網路使用單獨的兩個網口做bond0之後連接ceph集群的交換機。同時為了防止多個容器突發性的高IOPS對ceph集群的訪問,我們正在開發storage-iops的qos限制功能。

雖然ceph提供了3種存儲訪問的方式,我們還是選用了相對穩定的rbd,沒有使用ceph filesystem模式。在rbd模式下,首先要保證內核已經加在了rbd.ko內核模塊,和ceph-common包。這一步,我們在前面提到的sextant自動安裝系統中已經完成打包。

接下來在使用rbd作為pod存儲時可以參考示例:

{
     "apiVersion": "v1beta3",
     "id": "rbdpd2",
     "kind": "Pod",
     "metadata": {
         "name": "rbd2"
     },
     "spec": {
         "containers": [
             {
                 "name":  "rbd-rw",
                 "image":  "kubernetes/pause",
                 "volumeMounts": [
                     {
                          "mountPath": "/mnt/rbd",
                          "name": "rbdpd"
                     }
                 ]             
                  }         ]
,         "volumes": [             {                 "name":  "rbdpd",                 "rbd": {                      "monitors": [                                                           "192.168.0.1:6789"                                   ],                      "pool": "rbd",                      "image": "foo",                      "user": "admin",                      "secretRef": {                                                     "name": "ceph-secret"                                           },                      "fsType": "ext4",                      "readOnly": true                 }            
                   }         ]    
     }
}

目前,我們已經使用kubernetes+ceph rbd部署使用了MySQL、MongoDB、Redis、InfluxDB、ElasticSearch等服務和應用。

百分點實踐總結


到目前為止,百分點的Kubernetes集群上承載了推薦系統的大部分業務組件和部分的運維組件。Kubernetes相對來說還比較新,集群上也需要更多的工具才能讓用戶應用起來更加的方便。非常感謝Kubernetes社區、王益以及雲知聲公司的技術團隊,百分點會持續專注在Kubernetes的建設,期望能夠更多地回饋社區。