Kubernetes 在知名互联网公司的(dotnet)落地实践
容器化背景
本來生活網(benlai.com)是一家生鮮電商平臺,公司很早就停止了燒錢模式,開始追求盈利。既然要把利潤最大化,那就要開源節流,作為技術可以在省錢的方面想想辦法。我們的生產環境是由 IDC 機房的 100 多臺物理機所組成,占用率高達 95%,閑置資源比較多,于是我們考慮借助 k8s 來重構我們的基礎設施,提高我們資源的利用率。
?
容器化項目團隊最初加上我就只有三個人,同時我們還有各自的工作任務要做,留給容器化的時間較少,因此我們要考慮如何快速的搭建容器平臺,避免走全部自研這條路,這對我們來說是個巨大的挑戰。在經歷了一年的容器化之旅后,分享下我們這一年所踩過的坑和獲得的經驗。
面臨的問題
在搭建 k8s 集群前,有很多問題擺在我們面前:
l??人手不足,時間也不充裕,不能有太多自研的需求
l??我們目前的發布是由測試人員完成的,不可能要求他們去寫一個 yaml 或執行 kubectl 做發布,這個學習成本太高也容易出錯,因此我們必須構建一個用戶體驗良好的可視化平臺給發布人員使用
l??我們有大量的 .NET 項目,而 .NET 環境又依賴 Windows
l??ConfigMap/Secret 不支持版本控制,同時用來存業務配置也不是很方便
l??k8s 集群和外部的通信如何打通
容器平臺
作為小團隊去構建一個容器平臺,自研的工作量太大了。前期我們調研過很多可視化平臺,比如 k8sdashboard 和 rancher 等等,但是要操作這些平臺得需要專業的 k8s 運維知識,這對我們的測試人員不太友好。后來我們嘗試了 KubeSphere(kubesphere.io)平臺,各方面都比較符合我們的需求,于是決定使用該平臺作為我們容器平臺的基礎,在這之上構建我們自己的發布流程。感興趣的朋友可以去了解下這個平臺。
.NET 項目
我們的項目有 Java 也有 .NET 的,.NET 項目占了 80% 以上。要支持.NET 意味著要支持 Windows。在我們去年開始啟動項目的時候,k8s 剛升級到 1.14 版本,是支持Windows 節點的第一個版本,同時功能也比較弱。經過實驗,我們成功對 .NET Framework 的程序進行了容器化,在不改代碼的前提下運行在了 Windows 服務器上,并通過 k8s 進行管理。不過我們也遇到了一些比較難處理的問題,使用下來的總結如下:
l??Kubernetes 版本必須是 1.14 版本或以上
l??大多數 Linux 容器若不做處理會自動調度到 Windows 節點上
l??Windows 基礎鏡像體積普遍比較大
l??必須使用 Windows Server 2019 及以上版本
l??k8s 關鍵性組件以 Windows 服務形式運行,而非 Pod
l??存儲和網絡的支持有局限性
l??部署步驟復雜,易出錯
我們調研了一段時間后決定放棄使用 Linux 和Windows 的混合集群,因為這些問題會帶來巨大的運維成本,而且也無法避免 Windows 的版權費。
?
我們也考慮過把這些項目轉換成 Java,但其中包含大量的業務邏輯代碼,把這些重構為 Java 會消耗巨大的研發和測試的人力成本,顯然這對我們來說也是不現實的。那么有沒有一種方案是改動很少的代碼,卻又能支持 Linux 的呢?答案很明顯了,就是把 .NET 轉 .NET Core。我們采用了這種方案,并且大多數情況能很順利的轉換一個項目而不需要修改一行業務邏輯代碼。
?
當然這個方案也有它的局限性,比如遇到如下情況就需要修改代碼無法直接轉換:
l??項目里使用了依賴 Windows API 的代碼
l??引用的第三方庫無 .NET Core 版本
l??WCF 和 Web Forms 的代碼
這些修改的成本是可控的,也是我們可以接受的。到目前為止我們已經成功轉換了許多 .NET 項目,并且已運行在 k8s 生產環境之上。
集群暴露
由于我們是基于物理機部署也就是裸金屬(Bare Metal)環境,所以無論基于什么平臺搭建,最終還是要考慮如何暴露 k8s 集群這個問題。
暴露方式
l??LoadBalancer, 是 Kubernetes 官方推薦的暴露方式,很可惜官方支持的方式都需要部署在云上。我們公司全部是裸機環境部署,無法使用云方案。
l??NodePort,端口范圍一般是 30000 以上,每個端口只能對應一種服務。如果應用越來越多,那端口可能就不夠用了。它最大的問題是如果你暴露某一個節點給外部訪問,那么這個節點會成為單點。如果你要做高可用,這幾個節點都暴露出去,前面一樣也要加一個負載均衡,這樣事情就復雜了。
l??Ingress,可以解決 NodePort 端口復用的問題,它一般工作在7層上可以復用 80 和 443 端口。使用 Ingress 的前提是必須要有 Ingress Controller 配合,而 Ingress Controller 同樣會出現你需要暴露端口并公開的問題。這時候如果你用 HostNetwork 或 HostPort 把端口暴露在當前的節點上,就存在單點問題;如果你是暴露多個節點的話,同樣需要在前面再加一個LB。
l??HostNetwork/HostPort,這是更暴力的方式,直接把 Pod 的端口綁定到宿主機的端口上。這時候端口沖突會是一個很大的問題,同時單點問題依舊存在。
裸金屬方案
我們分別試用了兩套方案 MetalLB(metallb.universe.tf)和 Porter(github.com/kubesphere/porter),這兩個都是以 LoadBalancer 方式暴露集群的。我們測試下來都能滿足需求。Porter 是 KubeSphere 的子項目,和 KubeSphere 平臺兼容性更好,但是目前? Porter 沒有如何部署高可用的文檔,我在這里簡單分享下:
前置條件
l??首先你的路由器,必須是三層交換機,需要支持 BGP 協議。現在大多數路由設備都會支持這個協議,所以這個條件一般都能滿足
l??其次集群節點上不能有建立 BGP 通信的其他服務。舉例來說,當使用 Calico 時,開啟了BGP模式。它的 BGP 端口運行在每個集群節點,Calico 和 Porter 同時啟用BGP 協議,會有部分節點同時運行兩個組件與交換機建立 BGP 協議造成沖突。而 KubeSphere 默認安裝的 Calico 是 ipip 模式的,所以我們沒有遇到沖突問題
l??最后一定要有網絡運維人員支持,配合你完成路由器配置以及了解整個網絡拓撲結構。了解網絡拓撲結構是非常重要的,否則會遇到很多問題
配置和部署
架構和邏輯
Porter有兩個插件:Porter-Manager 和 Porter-Agent
Porter-Manager 是使用Deployment 部署到 Master 節點上的,但默認只部署1個副本,它負責同步 BGP 路由到物理交換機(單向 BGP 路由,只需將 kubernetes 私有路由發布給交換機即可,無需學習交換機內物理網絡路由)。還有一個組件,Porter-Agent,它以 DaemonSet 的形式在所有節點都部署一個副本,功能是維護引流規則。
高可用架構
部署好后,你可能會有疑問:
l??單個路由器掛了怎么辦
l??單個 porter-manager 掛了怎么辦
l??porter-manager 和路由器網絡斷了怎么辦
l??EIP 下一跳地址所在的節點掛了怎么辦
l??某個 EIP 流量突然飆升,一個節點扛不住怎么辦
一般路由器或交換機都會準備兩臺做 VSU (Virtual Switching Unit) 實現高可用,這個是網絡運維擅長的,這里不細講了。主要說下其他幾點怎么解決
l??確保一個 EIP 有多條 BGP 路由規則,交換機和 Manager 是多對多部署的
l??確保交換機開啟等價路由(ECMP)
l??要做好故障演練和壓力測試
MetalLB 的高可用部署也是類似思路,雖然架構稍有不同,比如它和路由器進行 BGP 通信的組件是 Speaker,對應 Porter 的 Manager;它與Porter 的區別在于高可用目前做的比較好;但是 Local 引流規劃不如 Porter,EIP 的下一跳節點必須是 BGP 對等體(鄰居)。
配置中心
Kubernetes 的 ConfigMap 和 Secret 在一定程度上解決了配置的問題,我們可以很輕松的使用它們進行更改配置而不需要重新生成鏡像,但我們在使用的過程中還是會遇到一些痛點:
1.?????不支持版本控制
Deployment 和 StatefulSet 都有版本控制,我們可以使用 rollout 把應用回滾到老的版本,但是 ConfigMap/Secret 卻沒有版本控制,這會產生一個問題就是當我們的Deployment ?要進行回滾時,使用的 ConfigMap 還是最新的,我們必須把 ConfigMap 改回上一個 Deployment 版本所使用的 ConfigMap 內容,再進行回滾,但是這樣的操作是很危險的,假設改完 ConfigMap 后有個 Pod 出了問題自動重啟了,又或者 ConfigMap 以文件形式掛載到 Pod 中,都會造成程序讀取到了錯誤的配置。一個折中的做法是每個版本都關聯一個單獨的 ConfigMap。
2.?????不太適合管理業務配置
一個應用有時候會需要加很多業務配置,在維護大量業務配置時,我們可能需要搜索關鍵字、查看某個 key 的備注、對 value 的格式進行校驗、批量更新配置等等,但是如果使用了ConfigMap ,我們就不得不再做一些工具來滿足這些需求,而這些需求對于配置的維護效率是有非常大的影響。
3.?????熱更新
我們知道如果 ConfigMap 是以文件形式進行掛載,那么當修改了 ConfigMap 的值后,過一會所有的 Pod 里相應的文件都會變更,可是如果是以環境變量的方式掛載卻不會更新。為了讓我們的程序支持熱更新,我們需要把 ConfigMap 都掛載成文件,而當配置有很多時麻煩就來了,如果 Key 和文件是一對一掛載,那么 Pod 里會有很多小文件;如果所有 Key 都放到一個文件里,那么維護配置就成了噩夢。
4.?????配置大小限制
ConfigMap 本身沒有大小限制,但是 etcd 有,默認情況下一個 ConfigMap 限制為 1MB,我們估算了下,有個別應用的配置加起來真有可能突破這個限制,為了繞過這個大小限制,目前也只有切割成多個 ConfigMap 的方法了。
?
為了解決這些痛點,我們綜合考慮了很多方案,最終決定還是使用一套開源的配置中心作為配置的源,先通過Jenkins 把配置的源轉換成 ConfigMap,以文件形式掛載到 Pod 中進行發布,這樣以上的問題都可以迎刃而解。
?
我們選擇了攜程的 Apollo(github.com/ctripcorp/apollo)作為配置中心 ,其在用戶體驗方面還是比較出色的,能滿足我們日常維護配置的需求。
微服務
在遷移微服務到 k8s 集群的時候基本都會遇到一個問題,服務注冊的時候會注冊成 Pod IP,在集群內的話沒有問題,在集群外的服務可就訪問不到了。
?
我們首先考慮了是否要將集群外部與 Pod IP 打通,因為這樣不需要修改任何代碼就能很平滑的把服務遷移過來,但弊端是這個一旦放開,未來是很難收回來的,并且集群內部的 IP 全部可訪問的話,等于破壞了 k8s 的網絡設計,思考再三覺得這不是一個好的方法。
?
我們最后選擇了結合集群暴露的方式,把一個微服務對應的 Service 設置成 LoadBalancer,這樣得到的一個 EIP 作為服務注冊后的 IP,手動注冊的服務只需要加上這個 IP 即可,如果是自動注冊的話就需要修改相關的代碼。
?
這個方案有個小小的問題,因為一個微服務會有多個 Pod,而在遷移的灰度過程中,外部集群也有這個微服務的實例在跑,這時候服務調用的權重會不均衡,因為集群內的微服務只有一個 IP,通常會被看作是一個實例。因此如果你的業務對負載均衡比較敏感,那你需要修改這里的邏輯。
調用鏈監控
我們一直使用的是點評的 CAT(github.com/dianping/cat)作為我們的調用鏈監控,但是要把 CAT 部署到 k8s 上比較困難,官方也無相關文檔支持。總結部署 CAT 的難點主要有以下幾個:
l??CAT 每個實例都是有狀態的,并且根據功能不同,相應的配置也會不同,因此每個實例的配置是需要不一樣的,不能簡單的掛載 ConfigMap 來解決
l??CAT 每個實例需要綁定一個 IP 給客戶端使用,不能使用 Service 做負載均衡。
l??CAT 每個實例必須事先知道所有實例的 IP 地址
l??CAT 每個實例會在代碼層面獲取自己的 IP,這時候獲取到的是可變的 Pod IP,與綁定的 IP 不一致,這就會導致集群內部通信出問題
為了把 CAT 部署成一個 StatefulSet 并且支持擴容,我們參考了 Kafka 的 Helm 部署方式,做了以下的工作:
l??我們為每個 Pod 創建了一個 Service,并且啟用 LoadBalancer 模式綁定了 IP,使每個 Pod 都會有一個獨立的對外 IP 地址,稱它為 EIP;
l??我們把所有實例的信息都保存在配置中心,并且為每個實例生成了不同的配置,比如有些實例是跑 Job,有些實例是跑監控的;
l??我們會預先規劃好 EIP,并把這些 EIP 列表通過動態生成配置的方式傳給每個實例;
l??最后我們給每個實例里塞了一個特殊的文件,這個文件里存的是當前這個實例所綁定的 EIP 地址。接著我們修改了 CAT 的代碼,把所有獲取本地 IP 地址的代碼改成了讀取這個特定文件里的 IP 地址,以此欺騙每個實例認為這個 EIP 就是它自己的本地 IP。
擴容很簡單,只需要在配置中心里添加一條實例信息,重新部署即可。
CI/CD
由于 KubeSphere 平臺集成了 Jenkins,因此我們基于 Jenkins 做了很多 CI/CD 的工作。
發布流程
起初我們為每個應用都寫了一個 Jenkinsfile,里面的邏輯有拉取代碼、編譯應用、上傳鏡像到倉庫和發布到 k8s 集群等。接著我為了結合現有的發布流程,通過 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 的內核版本升級到最新以后就再也沒發生過宕機了。所以說內核的版本與 k8s 和 Docker 的穩定性是有很大的關系。同樣把 k8s 和 Docker 升級到一個穩定的版本也是很有必要的。
未來展望
我們目前對未來的規劃是這樣的:
l??支持多集群部署
l??支持容器調試
l??微服務與 istio 的結合
l??應用管理界面
?
編后:每一家公司都有自己經歷的階段,如何可拆解、因地制宜解決問題就考驗選擇。首先是活在當下,同時還要考慮一下未來的一段時期。采用開源軟件可以大大享受社區紅利,但在做整合的過程中不得不面對各自適配、取舍。采用更主流的、統一的技術棧可能會在簡約方面獲益。為作者的探索點贊!
另外,面對這個case,是?.NET Core救了?.NET?項目呢, 還是初期采用?.NET的坑呢,已經說不清楚了。當當、京東、蘑菇街都經歷了.NET或者php轉java.....
k8s?彌補了.NET 在互聯網的弱勢 ,如果沒有k8s,.net core也救不了.net,用了k8s, 容器化方面.NET Core的優勢要比Java大得多
總結
以上是生活随笔為你收集整理的Kubernetes 在知名互联网公司的(dotnet)落地实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于TensorFlow开发者证书,你想
- 下一篇: Dapr微服务应用开发系列0:概述