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