1. 程式人生 > >Kubernetes 在知乎上的應用

Kubernetes 在知乎上的應用

依賴 load pic 接口 被占用 做的 定時 詳細信息 相對

從 Mesos 到 Kubernetes

之前的調度框架是基於 Mesos 自研的。采用的語言是 Python。運行了大概兩年多的時間了,也一直比較穩定。但隨著業務的增長,現有的框架的問題逐漸暴露。

  1. 調度速度遇到瓶頸,影響大業務的部署速度。
  2. 不能很好的支持有狀態服務。

解決上述問題的方案有兩個,一個是對現有系統進行改進重構,另一個是遷移到 Kubernetes。我們最終選擇遷移到 Kubernetes,主要基於以下考慮。

  1. Kubernetes 的架構設計簡單明了,容器管理的抽像做的很好,重易進行復用和二次開發,沒有必要造重復的輪子。比較典型的像Pod、Mesos 也已經引進了類似概念。
  2. Kubernetes 已經逐漸成為業界主流。社區很活躍,新的特性不斷地被添加進來,這導致 Kubernetes 變的越來越重,但基本的架構和核心功能是一直比較穩定的。
  3. 相對於 Mesos 來講,基於 Kubernetes 的開發成本是要低一些的,尤其是在熟悉之後。便於 k8s 的推廣使用。除了主要的業務運行平臺 bay,我們的負載均衡平臺、Kafka 平臺以及定時任務平臺全部都是基本 Kubernetes 的。

整體架構

技術分享

資源層

這一層主要是集群資源,主要包括內存、CPU、存儲、網絡等。主要運行的組件有 Docker daemon、kubelet、cAdvisor、CNI 網絡插件等,主要為上層提供資源。

控制層(Kubernetes master)

控制層主要包括 Kubernetes 的 master 組件,Scheduler、Controller、API Server。提供對 Kubernetes 集群資源的控制。

接入層(Watch Service、API)

這一層包含的東西比較多,主要包含各個平臺用於接入 Kubernetes 集群所開發的組件。主要包含以下組件:

  1. 容器平臺 bay 。
    一是用來向部署系統提供容器/容器組的創建、更新、刪除、擴容等接口,是對 Kubernetes 原生 API 的一層封裝,主要是為了與部署系統對接。二是用來註冊服務發現、業務監控報警等所需要的配置。
  2. 負載均衡平臺(Load Balance)。
    我們的負載均衡平臺采用的主要是 Haproxy。也是跑在容器裏的。由於負載均衡系統的特殊性,並沒有跑在容器平臺 bay 上面,而是單獨開發了一個平臺對 HAProxy 進行管理。可以方便的創建和管理 HAProxy 集群,並在業務流量發生變化的時候進行自動或者手動擴容。
  3. Kafka 平臺(Kafka)。 主要提供在 Kubernetes 上創建管理 Kafka 集群的服務。我們在 Kubernetes 之上開發了一套基於本地磁盤的調度框架,可以使 pod 根據集群中的本地磁盤信息進行調度。沒有使用共享存儲系統主要是為了性能考慮。最新的 Kubernetes 好像也加入了類似本地磁盤的調度特性,不過在我們開發的時候是還沒有支持的。
  4. 定時任務平臺。
    這個平臺是在 Kubernetes 支持 Cron job 之前開發的。也是最早接入 Kubernetes 的服務。

管理層(Castle Black;Monitor;Auto Scale)

主要是根據接入層提供的一些配置或者信息來完成特定的功能。

  1. Castle Black,這個服務是一個比較關鍵的服務。這個服務通過 Kubernetes 的 watch API,及時的同步業務容器的信息,根據容器信息進行服務註冊和反註冊。我們主要是註冊 Consul 和 DNS。Kafka 平臺和負載均衡平臺也依賴於這個服務進行服務註冊。同時對外提供查詢接口,可以查詢業務的實時容器信息。
  2. Monitor,這個主要是業務容器的監控,主要包含該業務總容器數、不正常容器數以及註冊信息是否一致等信息,CPU 和內存等資源的監控我們采用 cAdvisor 和我們內部的監控系統來實現。
  3. Auto Scale,我們沒有使用 Kubernetes 本身的自動擴容機制,而是單獨進行了開發,主要是為了支持更加靈活的擴容策略。

配置層(etcd)

應用層的組件所需要的配置信息全部由接入層的服務寫入到 etcd 中。應用層組件通過 watch etcd 來及時獲取配置的更新。

下面這張圖說明了在我們的容器平臺上,上面描述的一些組件是如何結合在一起,使業務可以對外提供服務的。通過 bay 平臺向 Kubernetes APIServer發送請求,創建 deployment,pod 創建成功並且健康檢查通過後,Castle Black watch 到 pod 信息,將 IP,port 等信息註冊到 Consul 上,HAProxy watch 對應的 Consul key,將 pod 加入其後端列表,並對外提供服務。

技術分享

監控與報警

cAdvisor

我們的監控指標的收集主要是采用 CAvisor。沒有采用 Heapster 的主要原因有以下幾點:

  1. 針對 CAvisor 我們進行了二次開發,與內部指標系統結合的也比較好,應用的時間也較長。
  2. Heapster 采用 pull 模型,雖然是並行 pull,但在集群規模較大的情況下,有成為性能瓶頸的可能,而且目前無法進行橫向擴展。
  3. Heapster 中默認提供的很多聚合指標我們是不需要的。也沒有維護兩個監控系統的必要。

內部指標與報警系統

指標和報警都是用的我們內部比較成熟的系統。

日誌收集

Logspout Kafka ES/HDFS,日誌收集我們使用的也是 ELK,但跟通常的 ELK 有所不同。我們這裏的 L 用的是 Logspout,一個主要用於收集容器日誌的開源軟件。我們對其進行了二次開發,使之可以支持動態 topic 收集。我們通過環境變量的形式把 topic 註入到容器中。logspout 會自動發現這個容器並提取出 topic,將該容器的日誌發送到 Kafka 對應的 topic 上。因此我們每個業務日誌都有自己的 topic,而不是打到一個大的 topic 裏面。日誌打到 Kafka 裏之後,會有相應的 consumer 消費日誌,落地 ES 和 HDFS。ES 主要用來作日誌查詢,HDFS 主要用來做日誌備份。

整個日誌收集流程如下圖所求:

技術分享

網絡方案

CNI Bridge host-local,網絡部分我們做的比較簡單。首先我們的每個主機都給分配了一個 C 段的 IP 池,這個地址段裏的每個 IP 都是可以跨主機路由的。IP 地址從 X.X.X.2 到 X.X.X.255,容器可以使用的地址是 X.X.X.3 到 X.X.X.224,這個 IP 數量是足夠的。然後在主機上創建一個該地址段的 Linux Bridge。容器的 IP 就從 X.X.X.3 到 X.X.X.224 這個地址空間內分配,容器的 veth pair 的一段掛在 Linux Bridge 上,通過 Linux Brigde 進行跨主機通信。性能方面基本沒有損耗。

具體的實現我們采用了 Bridge 和 host-local 這兩個 CNI 插件,Bridge 主要用來掛載/卸載容器的 veth pair 到 Linux Bridge 上,host-local 主要利用本地的配置來給容器分配 IP。

上述流程如下圖所示:

技術分享

IP 池的分配由我們的雲服務商提供,我們不需要管具體的 IP 池的分配與路由配置。

上面主要介紹了知乎在容器和 Kubernetes 應用的一些現狀,在這個過程中我們也踩了不少坑,在這裏與大家分享一下。

etcdv3 版本問題

Kubernetes 的較新版本默認使用的存儲後端是 etcd3。etcd 選用的版本不對,是會有坑的。etcd 3.10 之前的版本,V3 的 delete api 默認是不會返回被刪除的 value 的。導致 Kubernetes API server 會收不到 delete event。被占用的資源會得不到釋放。最終導致資源耗盡。scheduler 無法再調度任何任務。詳細信息可以看這個 issue(https://github.com/coreos/etcd/issues/4620)。

Pod Eviction

這個是 Kubernetes 的一個特性,如果由於網絡或者機器原因,node 離線了,變為 unready 狀態。Kubernetes 的 node controller 會將該 node 上的 pod 刪除,稱作 pod eviction。這個特性應該說是合理的,但在大概是 1.5 版本之前,當集群中所有的 node 都變為 unready 狀態的時候,所有 node 上的 pod 都會被刪除。這個其實是不合理的,因為出現這種情況大概率是 API Server 的機器網絡出了問題,所以這個時候不應該把所有 node 上的 pod 全部刪除。最新的版本將這個特性進行了改進,集群中 ready 的 node 達到一定數量的情況下,才對 not ready 的 node 進行 pod eviction。這個就比較合理了。另外提醒大家一定要做好 API Server 的高可用。

CNI 插件 Docker daemon 重啟 IP 泄露

在使用 CNI 網絡插件的時候,如果 Docker daemon 發生了重啟,會重新分配新的 IP,但舊的 IP 不會被釋放,會導致 IP 地址的泄漏。由於時間和精力問題,我們采取了比較 tricky 的方式,在 Docker dameon 啟動之前,我們會默認把本機的 IP 全部釋放掉。這個是通過 Supervisor 的啟動腳本來實現的。希望後續 Kubernetes 社區可以從根本上解決這個問題。

Docker bug

Docker 使用過程中也遇到了一些 bug。比如 docker ps 會卡住, 使用 portmapping 會遇到端口泄漏的問題等。我們內部自己維護了一個分支,修復了類似的問題。Docker daemon 是基礎,它的穩定性一定要有保證,整個系統的穩定性才有保證。

Rate Limit

Kubernetes 的 Controller manager、Scheduler、以及 API Server 都是有默認的 rate limit 的,在集群規模較大的時候,默認的 rate limit 肯定是不夠用的,需要自己進行調整。

https://www.kubernetes.org.cn/2508.html

Kubernetes 在知乎上的應用