apisix~集成服務發現註冊中心

摘要

當業務量發生變化時,需要對上游服務進行擴縮容,或者因服務器硬件故障需要更換服務器。如果網關是通過配置來維護上游服務信息,在微服務架構模式下,其帶來的維護成本可想而知。再者因不能及時更新這些信息,也會對業務帶來一定的影響,還有人爲誤操作帶來的影響也不可忽視,所以網關非常必要通過服務註冊中心動態獲取最新的服務實例信息。架構圖如下所示:

  1. 服務啓動時將自身的一些信息,比如服務名、IP、端口等信息上報到註冊中心;各個服務與註冊中心使用一定機制(例如心跳)通信,如果註冊中心與服務長時間無法通信,就會註銷該實例;當服務下線時,會刪除註冊中心的實例信息;
  2. 網關會準實時地從註冊中心獲取服務實例信息;
  3. 當用戶通過網關請求服務時,網關從註冊中心獲取的實例列表中選擇一個進行代理;

常見的註冊中心:Eureka, Etcd, Consul, Nacos, Zookeeper 等

如何擴展註冊中心?

基本步驟

APISIX 要擴展註冊中心其實是件非常容易的事情,其基本步驟如下:

  1. 在 apisix/discovery/ 目錄中添加註冊中心客戶端的實現;
  2. 實現用於初始化的 _M.init_worker() 函數以及用於獲取服務實例節點列表的 _M.nodes(service_name) 函數;
  3. 將註冊中心數據轉換爲 APISIX 格式的數據;

集成nacos

https://www.bookstack.cn/read/apisix-2.13-zh/275fbdd662ec08a9.md

discovery:
  nacos:
    host:
      - "http://192.168.xx.xx:8848"
    prefix: "/nacos/v1"
    fetch_interval: 1
    weight: 1
    timeout:
      connect: 2000
      send: 2000
      read: 5000

集成kubernetes

https://www.bookstack.cn/read/apisix-2.13-zh/a586728927b44a68.md
https://apisix.apache.org/zh/docs/apisix/next/discovery/kubernetes/

discovery:
  kubernetes:
    service:
      schema: https
      host: 192.168.xx.xx
      port: "6443"  #這裏有一個比較坑的地方,port 必須是字符串,否則會導致 APISIX 啓動報錯:
    client:
      token: QUtUOH...
    match:
     - default
     - ^my-[a-z]+$

獲取serviceaccount賬號的token的方法

apisix的k8s服務發現配置

1 爲了讓 APISIX 能查詢和監聽 Kubernetes 的 Endpoints 資源變動,我們需要創建一個 ServiceAccount:

kind: ServiceAccount
apiVersion: v1
metadata:
  name: apisix-test
  namespace: default

2 以及一個具有集羣級查詢和監聽 Endpoints 資源權限的 ClusterRole:

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: apisix-test
rules:
- apiGroups: [ "" ]
  resources: [ endpoints ]
  verbs: [ get,list,watch ]

3 再將這個 ServiceAccount 和 ClusterRole 關聯起來:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: apisix-test
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: apisix-test
subjects:
  - kind: ServiceAccount
    name: apisix-test
    namespace: default

4 然後我們需要獲取這個 ServiceAccount 的 token 值,如果 Kubernetes 是 v1.24 之前的版本,可以通過下面的方法獲取 token 值:

$ kubectl get secrets | grep apisix-test
$ kubectl get secret apisix-test-token-89hcm -o jsonpath={.data.token} | base64 -d

5 Kubernetes 從 v1.24 版本開始,不能再通過 kubectl get secret 獲取 token 了,需要手動建立secret來生成token:

# 集羣執行這個yaml  
apiVersion: v1
kind: Secret
metadata:
  name: apisix-test-kubeapi
  namespace: default
  annotations:
    kubernetes.io/service-account.name: "apisix-test"
type: kubernetes.io/service-account-token
data:

# 然後執行
kubectl get secret apisix-test-kubeapi -n default -o jsonpath={".data.token"} | base64 -d 

6 我們在 APISIX 的配置文件 config.yaml 中添加如下內容( 將上面生成的 token 填寫到 token 字段 ):

discovery:
  kubernetes:
    service:
      schema: https
      host: 127.0.0.1
      port: "6443"
    client:
      token: ...

上面服務發現,我選擇kubernetes時,需要注意服務名的書寫規範(坑比較多),而我直接在節點節,使用<服務名>.<命令空間>,這種方式反而是支持的,配置如下

clusterrole相關操作

kubectl describe  serviceaccount  <sa-name>
kubectl describe clusterrole <clusterrole-name>
kubectl describe clusterrolebinding <clusterrolebinding-name>
kubectl get rolebindings,clusterrolebindings --all-namespaces | grep apisix-test  

添加上游服務

多k8s集羣時,service_name的組成,需要加個id,例如[id]/[namespace]/[name]:[portName]

說明: service_name 必須滿足格式:[namespace]/[name]:[portName]

  • namespace: Endpoints 所在的命名空間
  • name: Endpoints 的資源名
  • portName: Endpoints 定義包含的 ports.name 值,如果 Endpoints 沒有定義 ports.name,請依次使用 targetPort, port 代替。設置了 ports.name 的情況下,不能使用後兩者
  • 例如,下面的portName,可以通過kubectl describe endpoints 來獲取,本例中portName就是http-test1

apisix解析kubenetes後的返回值: 以如下 Endpoints 爲例:

apiVersion: v1
kind: Endpoints
metadata:
  name: plat-dev
  namespace: default
subsets:
  - addresses:
      - ip: "10.5.10.109"
      - ip: "10.5.10.110"
    ports:
      - port: 3306
        name: port

這裏有一個比較坑的地方,port 必須是字符串,否則會導致 APISIX 啓動報錯 invalid discovery kubernetes configuration: object matches none of the required

問題與解決

  • k8s token錯誤
2024/05/17 03:01:07 [error] 54#54: *114 [lua] informer_factory.lua:295: list failed, kind: Endpoints, reason: Unauthorized, message : {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}

檢查token值

  • k8s serviceaccount賬號無權限
2024/05/17 03:01:07 [error] 54#54: *114 [lua] informer_factory.lua:295: list failed, kind: Endpoints, reason: Forbidden, message : {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Forbidden","code":403}

檢查serviceaccount綁定的clusterrole是否有endpoint權限

  • 上游服務未找到
2024/05/17 03:05:22 [error] 52#52: *623 [lua] init.lua:400: nodes(): get unexpected upstream service_name: cms.default.svc.cluster.local, client: 192.168.60.136, server: _, request: "GET /k8s/products HTTP/1.1", host: "test-apisix.pkulaw.com"

2024/05/17 03:05:22 [error] 52#52: *623 [lua] init.lua:545: handle_upstream(): failed to set upstream: no valid upstream node: nil, client: 192.168.60.136, server: _, request: "GET /k8s/products HTTP/1.1", host: "test-apisix.pkulaw.com"

因爲apisix中獲取到的是k8s服務的endpoints地址【由service指向的pod的真實地址】,這個地址對k8s集羣外是不公開的,如圖

大功告成!

參考:https://www.aneasystone.com/archives/2023/03/apisix-service-discovery.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章