apisix~集成服務發現注冊中心
集(ji)成服務發現注冊(ce)中心
1 摘要
當業務(wu)量發生變化時,需(xu)要對上(shang)游服(fu)務(wu)進行(xing)擴縮容,或者因服(fu)務(wu)器(qi)(qi)硬件(jian)故(gu)障需(xu)要更(geng)換服(fu)務(wu)器(qi)(qi)。如果(guo)網關是通(tong)過(guo)配置來(lai)(lai)(lai)維護上(shang)游服(fu)務(wu)信(xin)息(xi),在微服(fu)務(wu)架構模式下(xia)(xia),其帶來(lai)(lai)(lai)的維護成本可(ke)想而知。再者因不能及(ji)時更(geng)新(xin)這些信(xin)息(xi),也(ye)會對業務(wu)帶來(lai)(lai)(lai)一(yi)定的影響(xiang),還(huan)有人為誤操作帶來(lai)(lai)(lai)的影響(xiang)也(ye)不可(ke)忽視,所以(yi)網關非常必要通(tong)過(guo)服(fu)務(wu)注冊(ce)中(zhong)心動態獲(huo)取最新(xin)的服(fu)務(wu)實例(li)信(xin)息(xi)。架構圖如下(xia)(xia)所示(shi):

- 服務啟動時將自身的一些信息,比如服務名、IP、端口等信息上報到注冊中心;各個服務與注冊中心使用一定機制(例如心跳)通信,如果注冊中心與服務長時間無法通信,就會注銷該實例;當服務下線時,會刪除注冊中心的實例信息;
- 網關會準實時地從注冊中心獲取服務實例信息;
- 當用戶通過網關請求服務時,網關從注冊中心獲取的實例列表中選擇一個進行代理;
常見的注冊(ce)中心:Eureka, Etcd, Consul, Nacos, Zookeeper 等(deng)
2 如何擴展注冊中心?
基本步驟
APISIX 要擴展注(zhu)冊中心其(qi)實是(shi)件(jian)非(fei)常容易的事情,其(qi)基本步驟如下(xia):
- 在 apisix/discovery/ 目錄中添加注冊中心客戶端的實現;
- 實現用于初始化的 _M.init_worker() 函數以及用于獲取服務實例節點列表的 _M.nodes(service_name) 函數;
- 將注冊中心數據轉換為 APISIX 格式的數據;
3 集成nacos
discovery:
nacos:
host:
- "//192.168.xx.xx:8848"
prefix: "/nacos/v1"
fetch_interval: 1
weight: 1
timeout:
connect: 2000
send: 2000
read: 5000
4 集成kubernetes
4.1 單集群
discovery:
kubernetes:
service:
schema: https
host: 192.168.xx.xx
port: "6443" #這里有一個比較坑的地方,port 必須是字符串,否則會導致 APISIX 啟動報錯:
client:
token: xxx
match:
- default
- ^my-[a-z]+$
上游配置:服務名稱格式:{namespace}/{service-name}:http
4.2 多集群配置
discovery:
kubernetes:
- id: svt
client:
token: xxx
service:
host: 10.10.104.19
port: "6443"
schema: https
- id: pre
service:
schema: https
host: 10.10.85.93
port: "6443" #這里有一個比較坑的地方,port 必須是字符串,否則會導致 APISIX 啟動報錯:
client:
token: xxx
多集群時上游配置
上(shang)游配置:服務名稱格式:{集群id}/{namespace}/{service-name}:http

5 k8s中獲取serviceaccount賬號的token的方法
1 為了讓 APISIX 能查(cha)詢和監聽 Kubernetes 的 Endpoints 資(zi)源變動,我(wo)們需要創建一個 ServiceAccount:
kind: ServiceAccount
apiVersion: v1
metadata:
name: apisix-test
namespace: default
2 以(yi)及一個(ge)具有集群(qun)級查詢和監聽 Endpoints 資源權限的 ClusterRole:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: apisix-test
rules:
- apiGroups: [ "" ]
resources: [ endpoints ]
verbs: [ get,list,watch ]
3 再將這個 ServiceAccount 和(he) 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 然后(hou)我們需(xu)要獲取這(zhe)個 ServiceAccount 的 token 值,如果(guo) Kubernetes 是 v1.24 之前(qian)的版(ban)本,可以通過(guo)下(xia)面的方法獲取 token 值:
$ kubectl get secrets | grep apisix-test
$ kubectl get secret apisix-test-token-89hcm -o jsonpath={.data.token} | base64 -d
5 Kubernetes 從 v1.24 版本開(kai)始(shi),不能再通過 kubectl get secret 獲取 token 了,需要手動建立(li)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 的配(pei)置文件(jian) config.yaml 中添加如(ru)下內容(rong)( 將上面生(sheng)成(cheng)的 token 填(tian)寫到 token 字段 ):
discovery:
kubernetes:
service:
schema: https
host: 127.0.0.1
port: "6443"
client:
token: ...
上面服務發現,我選擇kubernetes時,需要注意服務名的書寫規范(坑比較多),而(er)我直接在(zai)節點節,使用<服務名>.<命令空間>,這種方式(shi)反而(er)是支持(chi)的,配置(zhi)如下

6 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
7 添加上游服務
多(duo)k8s集(ji)群時,service_name的(de)組(zu)成(cheng),需要(yao)加個(ge)id,例如[id]/[namespace]/[name]:[portName]
說明(ming): service_name 必(bi)須滿足(zu)格式:[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后的(de)返回值: 以如下 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
8 問題與解決
- 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綁(bang)定的clusterrole是(shi)否有(you)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中獲取(qu)到(dao)的是k8s服務的endpoints地(di)址【由(you)service指向的pod的真實地(di)址】,這個地(di)址對k8s集群外是不公(gong)開的,如圖

大功告成!
參考: