C# 開源一個基於 yarp 的 API 閘道器 Demo,支援繫結 Kubernetes Service

痴者工良發表於2022-01-10

關於 Neting

剛開始的時候是打算使用微軟官方的 Yarp 庫,實現一個 API 閘道器,後面發現坑比較多,弄起來比較麻煩,就放棄了。目前寫完了檢視 Kubernetes Service 資訊、建立 Route 和 Cluster 和繫結 Kubernetes Service。簡單來說,就是完成了基礎部分,配置路由和後端服務繫結,如果想實現動態路由和直接轉發等功能,只需要按照官方的文件,增加中介軟體即可。

原本打算使用 .NET 6 的 AOT(一共40MB) ,但是打包執行會容易出現一些依賴問題和環境問題,因此放棄了,依然採用 Runtime + 應用 的方式部署,進行總共 120 MB 左右。

後端專案地址:https://github.com/whuanle/neting

前端專案地址:https://github.com/whuanle/neting-web

體驗地址:http://neting.whuanle.cn:30080/

大概介面是這樣的:

Route:即來源入口,支援 http/https 和 gRPC,Route 是設計進入叢集前的訪問者流量如何繫結後端服務,可以配置訪問者 URL 的區配規則;

Cluster:後端服務,一個 Cluster 可以繫結多個型別的後端服務,主要實現一個域名不同的字尾訪問多個微服務,或者同一個例項負載均衡等;

Service:檢視 Kubernetes Service 的一些網路資訊;

Neting 目前只實現了簡單的配置,僅供讀者瞭解 Yarp 以及入門 Kubernetes API 開發、監控叢集資源等。讀者也可以從中瞭解 etcd 的使用,如何設計一個 Kubernetes Controller 應用。

基礎功能已經做了,讀者可根據需求,自行增加中介軟體即可。

下面介紹如何部署 Neting 專案,需要讀者提前建立好叢集。

談談對 Yarp 的看法。

首先,Yarp 的倉庫地址是 https://github.com/microsoft/reverse-proxy

倉庫在微軟的官方賬號下,從瞭解到的資訊來看,Yarp 的出現主要是解決 Microsoft 內部的需求,也有人說是為了給 Azure 用。倉庫程式碼目標也是主要解決微軟內部需求,很多 API 對外部開發者也不是很友好,實際開發起來有一定難度,當然可定製性也比較多,主要從 ASP.NET Core 的中介軟體下手,做各方面的擴充套件。

另外這個專案不是成熟專案,Yarp 只是一個庫,它不是一個完整應用,很多地方還沒有確定其穩定性,也沒有效能測試報告等,也沒有基於此的成熟的應用出現。API 上和其他方面,開發難度還是比較複雜的,包括文件很多地方也沒有說清楚。

部署 etcd

Neting 的主要設計,是 etcd 作為資料儲存後端,當使用者建立反向代理或者其他型別的規則時,etcd Watch 自動通知 Neing 例項,重新整理這些配置到記憶體中,交由 Yarp 使用(當然也可以不放到記憶體,在需要使用的時候,自動從 etcd 中取)。

Neting 使用 etcd 作為儲存後端,etcd 支援叢集,但是這裡為了方便使用單個 etcd 例項。因為 etcd 是有狀態應用,因此需要繫結一個儲存卷。另外 etcd 也需要建立一個 service,以便在叢集中訪問例項。

etcd 叢集(這裡是單例項叢集) ->  Service(名稱為neting-svc)
     ↓
PersistentVolumeClaim
     ↓
PersistentVolume

你可以在專案的 yaml 下面,找到 etcd.yaml 和 etcds.yaml 、neting-etcd.yaml 三個檔案,檢視如何部署 etcd 叢集。

為了方便,建立的儲存卷是本地卷,不能跨節點共享資料。當然你也可以通過修改 neting-etcd.yaml 檔案,使用其他型別的卷。

  hostPath:
      # 宿主上目錄位置,需要先提前建立
    path: /data/etcd
      # 此欄位為可選
    type: Directory

將 neting-etcd.yaml 檔案上傳到叢集,然後建立 etcd 例項。

root@master:~/neting# kubectl apply -f neting-etcd.yaml 
service/neting-etcd created
statefulset.apps/neting-etcd created
persistentvolume/neting-etcd created

建立 etcd 叢集的時候,會建立多個相關的資源。

root@master:~/neting# kubectl get statefulset
NAME          READY   AGE
neting-etcd   1/1     36s

root@master:~/neting# kubectl get pv
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS
neting-etcd   1Gi        RWO            Recycle          Available

root@master:~/neting# kubectl get svc
NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                               AGE
neting-etcd    ClusterIP   10.96.206.255    <none>        2379/TCP,2380/TCP                     54s

其實還包括 PersistentVolumeClaim,可以執行 kubectl get pvc 檢視。

建立 secret

secret 的主要作用是給 Neting 提供使用者登入的賬號密碼,即前面提到的 admin/admin123,這個配置寫在了 secret.yaml。

secret.yaml 的全部內容如下:

apiVersion: v1
kind: Secret
metadata:
  name: neting-basic-auth
type: Opaque
stringData:
# Neting 的登入賬號密碼
  NETING_USER: admin
  NETING_PASSWORD: admin123
  NETING_TOKENKEY: dzbfhisj@3411DDFF5%$%^&&

這個配置很簡單,其中 NETING_TOKENKEY 表示簽名 token 的金鑰,Neting 使用 Jwt Token 做使用者憑證,在頒發憑證給使用者時,需要加密使用者資訊簽名。

上傳 secret.yaml 到叢集中,然後建立它。Secret 中的資訊最終會生成 base64,Kubernetes 的 etcd 中(注,不是前面自行建立的 etcd)。secret 中的資訊,最終會以環境變數的形式出現在 Neting Pod 中。

root@master:~/neting# kubectl apply -f secret.yaml 
secret/neting-basic-auth created

root@master:~/neting# kubectl get secret
NAME                      TYPE                                  DATA   AGE
neting-basic-auth         Opaque                                3      10s
...
data:
  NETING_PASSWORD: YWRtaW4xMjM=
  NETING_TOKENKEY: ZHpiZmhpc2pAMzQxMURERkY1JSQlXiYm
  NETING_USER: YWRtaW4=
kind: Secret

部署 Neting

Neting 的依賴關係如下:

Neting -> 啟動(Secret)
 ↓
Service - etcd
 ↓
etcd 例項

Neting 是由 ASP.NET Core API + React/Ant Design 編寫的 Web 專案,為了結構簡單,Neting 在 wwwroot 目錄託管了前端靜態檔案,以便在同一個埠下訪問,並且減少跨域、繫結 IP 等事情。

Neting 已被上傳到阿里雲映象倉庫中,docker pull 地址 : registry.cn-hangzhou.aliyuncs.com/whuanle/neting:review1.0

Neting 是需要在 Pod 中連線到 Kubernetes API Server 的,因此需要配置 ServiceAccount 或者直接使用 Kubeconfig。遺憾的是,C# 的 KubernetesClient 對 ServiceAccount 支援並不好,因此只能使用 Kubeconfig,當然直接使用 Kubeconfig 可能會帶來一些安全問題,好在這是 Demo,Neting 只會使用 獲取 Servive 和 Endpoint 部分的資訊,不會對叢集進行修改、刪除等操作,因此如果需要更高安全級別的操作,可嘗試自行解決 Kubenetes - C# 的 ServiceAccount 問題。

Kubernetes 和 etcd 的 C# SDK 體驗不佳,讀者搞雲原生中介軟體的時候,還是用 Go 搞比較好,C# 適合寫業務。

將你的 Kubernetes 管理配置檔案複製到 /root/.kube/config 中。注意,這一步一定要在會被排程 Pod 執行的節點上處理,因為這個配置檔案不能跨節點使用。

cp -f /etc/kubernetes/admin.conf /root/.kube/config

然後啟動 Neting:

kubectl apply -f neting.yaml

接著,為 Neting 建立 Service,以便在外網訪問。

root@master:~/neting# kubectl apply -f neting-svc.yaml 
service/neting created

root@master:~/neting# kubectl get svc -o wide
neting         NodePort    10.102.240.255   <none>        80:30080/TCP                          11s   app=neting
neting-etcd    ClusterIP   10.96.206.255    <none>        2379/TCP,2380/TCP                     31m   app=neting-etcd

部署完成後,可以通過節點的 IP 和 30080 埠,訪問到。

接著隨便點選一個選單,便會要求登入。

賬號密碼分別是 adminadmin123

登入後,憑證會儲存到你的瀏覽器中,有效期為 7 天。

點選 Service 可以看到叢集中的 Service 的資訊。

使用

接著我們來建立 Yarp 反向代理 配置。

Yarp 的反向代理物件繫結分為兩個部分,Route 和 Cluster。

Cluster 即是服務後端例項,如你有一個應用部署了 N 個例項,每個例項都有一個 IP,那麼 Cluster 需要記錄你這些例項的 IP,以便在訪問時,通過負載均衡演算法選擇其中一個訪問。YARP 帶有內建的負載平衡演算法,但也為任何自定義負載平衡方法提供了可擴充套件性。這裡就不展開來講。

讀者可以參考 https://microsoft.github.io/reverse-proxy/articles/load-balancing.html

我的 Kubernetes 中,有測試 Ingress 時留下來的兩個應用, web1 和 web2,這裡可以使用一下。

接著,筆者到域名管理處,解析了一個域名繫結應用。

接著建立 Route。

是可以基於 Yarp 專案設計一個 API 閘道器,或者代替 Kubernetes 的 Ingress ,實現流量入口,API 閘道器 + 負載均衡。

說不定你還可以編寫類似 Dapr 的服務網格功能,使用邊車模式為叢集中的應用提供非侵入式流量代理服務。

介紹一下專案

Neting 就是後端專案,NetingCrdBuilder 跟當前專案無關,是筆者本來打算做類似 Dapr,建立自定義資源以及以及 Kubernetes Operater 用的,不想寫了,就不寫了。Yarp.ReverseProxy 是 Yarp 基礎庫,為了發表除錯和檢視原始碼,不通過 Nuget 引用,而是抽取原始碼,通過程式碼直接引用。

後端專案大部分都寫了註釋,這裡就不再多說了。

如果想在本地測試和開發,可以先把前後端專案拉下來。

本地開發,你需要在後端專案的 appsettings.json 或 appsettings.Development.json 檔案修改配置。

其中 admin.conf 是 Kubernetes API Server 連線的驗證配置檔案,通過配置檔案與 Kuberntes 連線的時候才能通過授權訪問資源。程式碼在 KubernetesExtensions 中,你也可以通過 Kubernetes proxy 等方式訪問 Kubernetes 進行開發。

然後需要在前端的 Constants.js 檔案中,配置你本地的後端地址。

export const Options = {
    // host: "http://127.0.0.1:80"  // 開發配置
    host: ""                        // 部署配置
}

如果前後端都在同一個埠下,則 host:"" 即可。

相關文章