Kubernetes 在原本生活網的落地實踐

【編者的話】原本生活網(benlai.com)是一家生鮮電商平臺,提供蔬菜、水果、海鮮等優質生鮮果蔬食材食品網購服務。當今容器技術被普遍關注,原本生活網在經歷了一年的技術革命後,基本完成了容器化所需的基礎設施建設。本文介紹原本生活網在 Kubernetes 落地過程當中的實踐和經驗。git

容器化背景

原本生活網是一家生鮮電商平臺,公司很早就中止了燒錢模式,開始追求盈利。既然要把利潤最大化,那就要開源節流,做爲技術能夠在省錢的方面想一想辦法。咱們的生產環境是由 IDC 機房的 100 多臺物理機所組成,佔用率高達 95%,閒置資源比較多,因而咱們考慮藉助 Kubernetes 來重構咱們的基礎設施,提升咱們資源的利用率。github

容器化項目團隊最初加上我就只有三我的,同時咱們還有各自的工做任務要作,留給容器化的時間較少,所以咱們要考慮如何快速的搭建容器平臺,避免走所有自研這條路,這對咱們來講是個巨大的挑戰。在經歷了一年的容器化之旅後,分享下咱們這一年所踩過的坑和得到的經驗。docker

面臨的問題

在搭建 Kubernetes 集羣前,有不少問題擺在咱們面前:數據庫

  • 人手不足,時間也不充裕,不能有太多自研的需求
  • 咱們目前的發佈是由測試人員完成的,不可能要求他們去寫一個 yaml 或執行 kubectl 作發佈,這個學習成本過高也容易出錯,所以咱們必須構建一個用戶體驗良好的可視化平臺給發佈人員使用
  • 咱們有大量的 .NET 項目,而 .NET 環境又依賴 Windows
  • ConfigMap/Secret 不支持版本控制,同時用來存業務配置也不是很方便
  • Kubernetes 集羣和外部的通訊如何打通

容器平臺

做爲小團隊去構建一個容器平臺,自研的工做量太大了。前期咱們調研過不少可視化平臺,好比 Kubernetes Dashboard 和 Rancher 等等,可是要操做這些平臺得須要專業的 Kubernetes 運維知識,這對咱們的測試人員不太友好。後來咱們嘗試了 KubeSphere(kubesphere.io) 平臺,各方面都比較符合咱們的需求,因而決定使用該平臺做爲咱們容器平臺的基礎,在這之上構建咱們本身的發佈流程。服務器

.NET 項目

咱們的項目有 Java 也有 .NET 的,.NET 項目佔了 80% 以上。要支持 .NET 意味着要支持 Windows。在咱們去年開始啓動項目的時候,Kubernetes 剛升級到 1.14 版本,是支持 Windows 節點的第一個版本,同時功能也比較弱。通過實驗,咱們成功對 .NET Framework 的程序進行了容器化,在不改代碼的前提下運行在了 Windows 服務器上,並經過 Kubernetes 進行管理。不過咱們也遇到了一些比較難處理的問題,使用下來的總結以下:網絡

  • Kubernetes 版本必須是 1.14 版本或以上
  • 大多數 Linux 容器若不作處理會自動調度到 Windows 節點上
  • Windows 基礎鏡像體積廣泛比較大
  • 必須使用 Windows Server 2019 及以上版本
  • Kubernetes 關鍵性組件以 Windows 服務形式運行,而非 Pod
  • 存儲和網絡的支持有侷限性
  • 部署步驟複雜,易出錯

咱們調研了一段時間後決定放棄使用 Linux 和 Windows 的混合集羣,由於這些問題會帶來巨大的運維成本,並且也沒法避免 Windows 的版權費。架構

咱們也考慮過把這些項目轉換成 Java,但其中包含大量的業務邏輯代碼,把這些重構爲 Java 會消耗巨大的研發和測試的人力成本,顯然這對咱們來講也是不現實的。那麼有沒有一種方案是改動不多的代碼,卻又能支持 Linux 的呢?答案很明顯了,就是把 .NET 轉 .NET Core。咱們採用了這種方案,而且大多數狀況能很順利的轉換一個項目而不須要修改一行業務邏輯代碼。負載均衡

固然這個方案也有它的侷限性,好比遇到以下狀況就須要修改代碼沒法直接轉換:運維

  • 項目裏使用了依賴 Windows API 的代碼
  • 引用的第三方庫無 .NET Core 版本
  • WCF 和 Web Forms 的代碼

這些修改的成本是可控的,也是咱們能夠接受的。到目前爲止咱們已經成功轉換了許多 .NET 項目,而且已運行在 Kubernetes 生產環境之上。微服務

集羣暴露

因爲咱們是基於物理機部署也就是裸金屬(Bare Metal)環境,因此不管基於什麼平臺搭建,最終仍是要考慮如何暴露 Kubernetes 集羣這個問題。

暴露方式

  • LoadBalancer,是 Kubernetes 官方推薦的暴露方式,很惋惜官方支持的方式都須要部署在雲上。咱們公司所有是裸機環境部署,沒法使用雲方案。
  • NodePort,端口範圍通常是 30000 以上,每一個端口只能對應一種服務。若是應用愈來愈多,那端口可能就不夠用了。它最大的問題是若是你暴露某一個節點給外部訪問,那麼這個節點會成爲單點。若是你要作高可用,這幾個節點都暴露出去,前面同樣也要加一個負載均衡,這樣事情就複雜了。
  • Ingress,能夠解決 NodePort 端口複用的問題,它通常工做在 7 層上能夠複用 80 和 443 端口。使用 Ingress 的前提是必需要有 Ingress Controller 配合,而 Ingress Controller 一樣會出現你須要暴露端口並公開的問題。這時候若是你用 HostNetwork 或 HostPort 把端口暴露在當前的節點上,就存在單點問題;若是你是暴露多個節點的話,一樣須要在前面再加一個LB。
  • HostNetwork/HostPort,這是更暴力的方式,直接把 Pod 的端口綁定到宿主機的端口上。這時候端口衝突會是一個很大的問題,同時單點問題依舊存在。

裸金屬方案

咱們分別試用了兩套方案 MetalLB(metallb.universe.tf)和 Porter(github.com/kubesphere/porter),這兩個都是以 LoadBalancer 方式暴露集羣的。咱們測試下來都能知足需求。Porter 是 KubeSphere 的子項目,和 KubeSphere 平臺兼容性更好,可是目前 Porter 沒有如何部署高可用的文檔,我在這裏簡單分享下:

前置條件

  • 首先你的路由器,必須是三層交換機,須要支持 BGP 協議。如今大多數路由設備都會支持這個協議,因此這個條件通常都能知足
  • 其次集羣節點上不能有創建 BGP 通訊的其餘服務。舉例來講,當使用 Calico 時,開啓了BGP模式。它的 BGP 端口運行在每一個集羣節點,Calico 和 Porter 同時啓用 BGP 協議,會有部分節點同時運行兩個組件與交換機創建 BGP 協議形成衝突。而 KubeSphere 默認安裝的 Calico 是 IPIP 模式的,因此咱們沒有遇到衝突問題
  • 最後必定要有網絡運維人員支持,配合你完成路由器配置以及瞭解整個網絡拓撲結構。瞭解網絡拓撲結構是很是重要的,不然會遇到不少問題

配置和部署


架構和邏輯
Porter有兩個插件:Porter-Manager 和 Porter-Agent。


Porter-Manager 是使用 Deployment 部署到 Master 節點上的,但默認只部署1個副本,它負責同步 BGP 路由到物理交換機(單向 BGP 路由,只需將 Kubernetes 私有路由發佈給交換機便可,無需學習交換機內物理網絡路由)。還有一個組件,Porter-Agent,它以 DaemonSet 的形式在全部節點都部署一個副本,功能是維護引流規則。


高可用架構

部署好後,你可能會有疑問:

  • 單個路由器掛了怎麼辦
  • 單個 porter-manager 掛了怎麼辦
  • porter-manager 和路由器網絡斷了怎麼辦
  • EIP 下一跳地址所在的節點掛了怎麼辦
  • 某個 EIP 流量忽然飆升,一個節點扛不住怎麼辦

通常路由器或交換機都會準備兩臺作 VSU(Virtual Switching Unit)實現高可用,這個是網絡運維擅長的,這裏不細講了。主要說下其餘幾點怎麼解決:

  • 確保一個 EIP 有多條 BGP 路由規則,交換機和 Manager 是多對多部署的
  • 確保交換機開啓等價路由(ECMP)
  • 要作好故障演練和壓力測試

MetalLB 的高可用部署也是相似思路,雖然架構稍有不一樣,好比它和路由器進行 BGP 通訊的組件是 Speaker,對應 Porter 的 Manager;它與 Porter 的區別在於高可用目前作的比較好;可是 Local 引流規劃不如 Porter,EIP 的下一跳節點必須是 BGP 對等體(鄰居)。

配置中心

Kubernetes 的 ConfigMap 和 Secret 在必定程度上解決了配置的問題,咱們能夠很輕鬆的使用它們進行更改配置而不須要從新生成鏡像,但咱們在使用的過程當中仍是會遇到一些痛點:

  • 不支持版本控制,Deployment 和 StatefulSet 都有版本控制,咱們可使用 rollout 把應用回滾到老的版本,可是 ConfigMap/Secret 卻沒有版本控制,這會產生一個問題就是當咱們的 Deployment 要進行回滾時,使用的 ConfigMap 仍是最新的,咱們必須把 ConfigMap 改回上一個 Deployment 版本所使用的 ConfigMap 內容,再進行回滾,可是這樣的操做是很危險的,假設改完 ConfigMap 後有個 Pod 出了問題自動重啓了,又或者 ConfigMap 以文件形式掛載到 Pod 中,都會形成程序讀取到了錯誤的配置。一個折中的作法是每一個版本都關聯一個單獨的 ConfigMap。
  • 不太適合管理業務配置,一個應用有時候會須要加不少業務配置,在維護大量業務配置時,咱們可能須要搜索關鍵字、查看某個 key 的備註、對 value 的格式進行校驗、批量更新配置等等,可是若是使用了 ConfigMap ,咱們就不得再也不作一些工具來知足這些需求,而這些需求對於配置的維護效率是有很是大的影響。
  • 熱更新,咱們知道若是 ConfigMap 是以文件形式進行掛載,那麼當修改了 ConfigMap 的值後,過一會全部的 Pod 裏相應的文件都會變動,但是若是是以環境變量的方式掛載卻不會更新。爲了讓咱們的程序支持熱更新,咱們須要把 ConfigMap 都掛載成文件,而當配置有不少時麻煩就來了,若是 Key 和文件是一對一掛載,那麼 Pod 裏會有不少小文件;若是全部 Key 都放到一個文件裏,那麼維護配置就成了噩夢。
  • 配置大小限制,ConfigMap 自己沒有大小限制,可是 etcd 有,默認狀況下一個 ConfigMap 限制爲 1MB,咱們估算了下,有個別應用的配置加起來真有可能突破這個限制,爲了繞過這個大小限制,目前也只有切割成多個 ConfigMap 的方法了。

爲了解決這些痛點,咱們綜合考慮了不少方案,最終決定仍是使用一套開源的配置中心做爲配置的源,先經過 Jenkins 把配置的源轉換成 ConfigMap,以文件形式掛載到 Pod 中進行發佈,這樣以上的問題均可以迎刃而解。

咱們選擇了攜程的 Apollo(github.com/ctripcorp/apollo) 做爲配置中心,其在用戶體驗方面仍是比較出色的,能知足咱們平常維護配置的需求。

微服務

在遷移微服務到 Kubernetes 集羣的時候基本都會遇到一個問題,服務註冊的時候會註冊成 Pod IP,在集羣內的話沒有問題,在集羣外的服務可就訪問不到了。

咱們首先考慮了是否要將集羣外部與 Pod IP 打通,由於這樣不須要修改任何代碼就能很平滑的把服務遷移過來,但弊端是這個一旦放開,將來是很難收回來的,而且集羣內部的 IP 所有可訪問的話,等於破壞了 Kubernetes 的網絡設計,思考再三以爲這不是一個好的方法。

咱們最後選擇告終合集羣暴露的方式,把一個微服務對應的 Service 設置成 LoadBalancer,這樣獲得的一個 EIP 做爲服務註冊後的 IP,手動註冊的服務只須要加上這個 IP 便可,若是是自動註冊的話就須要修改相關的代碼。

這個方案有個小小的問題,由於一個微服務會有多個 Pod,而在遷移的灰度過程當中,外部集羣也有這個微服務的實例在跑,這時候服務調用的權重會不均衡,由於集羣內的微服務只有一個 IP,一般會被看做是一個實例。所以若是你的業務對負載均衡比較敏感,那你須要修改這裏的邏輯。

調用鏈監控

咱們一直使用的是點評的 CAT(github.com/dianping/cat) 做爲咱們的調用鏈監控,可是要把 CAT 部署到 Kubernetes 上比較困難,官方也無相關文檔支持。總結部署 CAT 的難點主要有如下幾個:

  • CAT 每一個實例都是有狀態的,而且根據功能不一樣,相應的配置也會不一樣,所以每一個實例的配置是須要不同的,不能簡單的掛載 ConfigMap 來解決
  • CAT 每一個實例須要綁定一個 IP 給客戶端使用,不能使用 Service 作負載均衡
  • CAT 每一個實例必須事先知道全部實例的 IP 地址
  • CAT 每一個實例會在代碼層面獲取本身的 IP,這時候獲取到的是可變的 Pod IP,與綁定的 IP 不一致,這就會致使集羣內部通訊出問題

爲了把 CAT 部署成一個 StatefulSet 而且支持擴容,咱們參考了 Kafka 的 Helm 部署方式,作了如下的工做:

  • 咱們爲每一個 Pod 建立了一個 Service,而且啓用 LoadBalancer 模式綁定了 IP,使每一個 Pod 都會有一個獨立的對外 IP 地址,稱它爲 EIP;
  • 咱們把全部實例的信息都保存在配置中心,而且爲每一個實例生成了不一樣的配置,好比有些實例是跑 Job,有些實例是跑監控的;
  • 咱們會預先規劃好 EIP,並把這些 EIP 列表經過動態生成配置的方式傳給每一個實例;
  • 最後咱們給每一個實例裏塞了一個特殊的文件,這個文件裏存的是當前這個實例所綁定的 EIP 地址。接着咱們修改了 CAT 的代碼,把全部獲取本地 IP 地址的代碼改爲了讀取這個特定文件裏的 IP 地址,以此欺騙每一個實例認爲這個 EIP 就是它本身的本地 IP。

擴容很簡單,只須要在配置中內心添加一條實例信息,從新部署便可。

CI/CD

因爲 KubeSphere 平臺集成了 Jenkins ,所以咱們基於 Jenkins 作了不少 CI/CD 的工做。

發佈流程

起初咱們爲每一個應用都寫了一個 Jenkinsfile,裏面的邏輯有拉取代碼、編譯應用、上傳鏡像到倉庫和發佈到 Kubernetes 集羣等。接着我爲告終合現有的發佈流程,經過 Jenkins 的動態參數實現了徹底發佈、製做鏡像、發佈配置、上線應用、回滾應用這樣五種流程。

處理配置

因爲前面提到了 ConfigMap 不支持版本控制,所以配置中心拉取配置生成 ConfigMap 的事情就由 Jenkins 來實現了。咱們會在 ConfigMap 名稱後加上當前應用的版本號,將該版本的 ConfigMap 關聯到 Deployment 中。這樣在執行回滾應用時 ConfigMap 也能夠一塊兒回滾。同時 ConfigMap 的清理工做也能夠在這裏完成。

複用代碼

隨着應用的增多,Jenkinsfile 也愈來愈多,若是要修改一個部署邏輯將會修改所有的 Jenkinsfile,這顯然是不可接受的,因而咱們開始優化 Jenkinsfile。

首先咱們爲不一樣類型的應用建立了不一樣的 yaml 模板,並用模板變量替換了裏面的參數。接着咱們使用了 Jenkins Shared Library 來編寫通用的 CI/CD 邏輯,而 Jenkinsfile 裏只須要填寫須要執行什麼邏輯和相應的參數便可。這樣當一個邏輯須要變動時,咱們直接修改通用庫裏的代碼就所有生效了。

數據落地

隨着愈來愈多的應用接入到容器發佈中,不可避免的要對這些應用的發佈及部署上線的發佈效率、失敗率、發佈次數等指標進行分析;其次咱們當前的流程雖然實現了 CI/CD 的流程代碼複用,可是不少參數仍是要去改對應應用的 Jenkinsfile 進行調整,這也很不方便。因而咱們決定將全部應用的基本信息、發佈信息、版本信息、編譯信息等數據存放在指定的數據庫中,而後提供相關的 API,Jenkinsfile 能夠直接調用對應的發佈接口獲取應用的相關發佈信息等;這樣後期無論是要對這些發佈數據分析也好,仍是要查看或者改變應用的基本信息、發佈信息、編譯信息等均可以遊刃有餘;甚至咱們還能夠依據這些接口打造咱們本身的應用管理界面,實現從研發到構建到上線的一體化操做。

集羣穩定性

咱們在構建咱們的測試環境的時候,因爲服務器資源比較匱乏,咱們使用了線上過保的機器做爲咱們的測試環境節點。在很長一段時間裏,服務器不停的宕機,起初咱們覺得是硬件老化引發的,由於在主機告警屏幕看到了硬件出錯信息。直到後來咱們生產環境很新的服務器也出現了頻繁宕機的問題,咱們就開始重視了起來,而且嘗試去分析了緣由。

後來咱們把 CentOS 7 的內核版本升級到最新之後就再也沒發生過宕機了。因此說內核的版本與 Kubernetes 和 Docker 的穩定性是有很大的關係。一樣把 Kubernetes 和 Docker 升級到一個穩定的版本也是頗有必要的。

將來展望

咱們目前對將來的規劃是這樣的:

  • 支持多集羣部署
  • 支持容器調試
  • 微服務與 Istio 的結合
  • 應用管理界面

Q&A

Q:Kubernetes 在生產上部署,推薦二進制仍是 kubeadm 安裝?kubeadm 安裝除了提升運維難度,在生產上還有什麼弊端?
A:咱們使用了 kubeadm 部署 Kubernetes;建議選大家運維團隊較熟悉的那種方式部署。

Q:用 Prometheus Operator 監控 Kubernetes 的時候,有點不明白爲啥默認的閾值要那麼配置,好比說 API server,我怎麼去定義個人閾值,只要報警了,我就知道 API server 有問題了?
A:若要對 API server 的高可用進行監控,可先對 ready/desired 數量進行比對,其次能夠在集羣外部對 API server 進行訪問監控。

Q:咱們這用的是 Dubbo,Pod 更新的時候,好比說已經進來的流量,我如何去優雅處理,我這 Pod 號更新的時候,有依賴這個服務的應用就會報 Dubbo 超時了。
A:這個咱們也在優化中,目前的方案是在進程收到 SIGTERM 信號後,先禁止全部新的請求(可使 readinessProbe 失敗),而後等待全部請求處理完畢,根據業務特性設置等待時間,默承認覺得 30 秒,超時後自動強制中止。

Q:Jar 包啓動時加 JVM 限制嗎?仍是隻作 request 和 limit 限制?
A:個人理解是 request 和 limit 只是對整個容器的資源進行控制; 而 JVM 的相關參數是對容器內部的應用作限制,這二者並不衝突,能夠同時使用,也能夠單獨使用 request 和 limit;只不過 JVM 的限制上限會受到 limit 的制約。

Q:.NET Core應用部署在 Kubernetes 中相比 Java 操做複雜嗎?想了解下具體如何從 NET 轉到 .NET Core。
A:實際在 Kubernetes 內部署 .NET Core 和部署 Java 都同樣,選擇好對應的 .NET Core 基礎鏡像版本;而後以該版本的爲基礎製做應用的鏡像後部署到 Kubernetes 便可,只不過在選擇 .NET Core 的基礎鏡像時,我建議直接選擇 SDK 正常版本。咱們試過 runtime、sdk-alpine 等版本,雖然這些版本佔用空閒小,可是你不知道它裏面會少哪些基礎庫的東西,咱們在這個上面踩了不少坑。如今選擇的基礎鏡像是:mcr.microsoft.com/dotnet/core/sdk:3.1。轉換過程根據程序的複雜度決定,有些應用沒修改業務邏輯,而有些改的很厲害。

Q:鏡像 tag 和代碼版本是怎樣的對應關係?
A:咱們的鏡像 tag = 源碼的分支: [develop|master] + 日期 + Jenkins 的編譯任務序號,如:master-202004-27這樣。當這個 tag 的鏡像在線下都測試完畢時準備上線了,那麼就以這個鏡像的 tag 做爲應用代碼的 tag 編號,這樣就可以經過鏡像的 tag 追溯到應用代碼的 tag 版本。

Q:CentOS 7的系統版本和內核版本號,能說明下麼?
A:內核版本:3.10.0-1062.12.1.el7.x86_64,這個對應的是 CentOS 7.7,具體可參見 https://access.redhat.com/articles/3078。

Q:原本生活的日誌和監控方案是怎樣設計的?
A:因爲 KubeSphere 的日誌在老版本有延時,所以咱們線上是採集到 Kafka 而後經過現有的 Kibana 進行查詢,也就是 ELK,監控是基於 KubeSphere 自帶的 Prometheus,沒作太多修改。

Q:大家對容器監控是採用哪一種方式,另外應用的監控是如何作的,主要是性能這部分,好比阿里雲的 ARMS,目前在看這個。
A:監控的部分咱們還在優化中,主要思路是對 ready/desired 數量進行比對;應用性能方面的監控主要依靠 CAT 來體現。

Q:大家 yaml 模版複用是怎麼使用的,Helm 有用到嗎?
A:咱們把 yaml 文件內一些應用相關的數據抽取成變量,使之成爲應用 yaml 文件的基礎模板;而後在 Pipeline 構建時經過接口獲取到對應應用的相關參數,將這些參數結合 yaml 文件模板自動填充後生成對應應用的 yaml 文件;而後進行部署操做。Helm 沒有在應用部署中使用,但中間件有。

Q:Pod綁定了 SVC,使用 LoadBalancer 的 IP 自動註冊註冊中心,這塊如何實現的?
A:咱們的微服務是手動註冊 IP,若是自動的話須要與 Pipeline 結合,EIP 是能夠預先分配的。

文章轉載自dockerone社區:http://dockone.io/article/9983

分享人陳杰,原本生活網的架構負責人

相關文章
相關標籤/搜索