當(dāng)云原生網(wǎng)關(guān)遇上圖數(shù)據(jù)庫谊迄,NebulaGraph 的 APISIX 最佳實踐

image

本文介紹了利用開源 API 網(wǎng)關(guān) APISIX 加速 NebulaGraph 多個場景的落地最佳實踐:負(fù)載均衡窑多、暴露接口結(jié)構(gòu)與 TLS Termination吉懊。

API 網(wǎng)關(guān)介紹

什么是 API 網(wǎng)關(guān)

API 網(wǎng)關(guān)是位于客戶端和服務(wù)器之間的“中間人”庐橙,用于管理、監(jiān)控和保護 API借嗽。它可以在 API 之前執(zhí)行一些操作态鳖,例如:身份驗證、授權(quán)恶导、緩存浆竭、日志記錄、審計、流量控制邦泄、安全删窒、防火墻、壓縮顺囊、解壓縮肌索、加密、解密等特碳。

API 網(wǎng)關(guān)可以工作在 TCP/IP 4 層和 OSI 7 層诚亚。跑在 7 層的 API 網(wǎng)關(guān)可以使用多種協(xié)議,例如:HTTP午乓、HTTPS站宗、WebSocket、gRPC益愈、MQTT 等梢灭。在這些應(yīng)用層協(xié)議中做一些操作,比如蒸其,請求的重寫敏释、轉(zhuǎn)發(fā)、合并摸袁、重試颂暇、緩存、限流但惶、熔斷、降級湿蛔、鑒權(quán)膀曾、監(jiān)控、日志阳啥、審計等等添谊。

這里舉例一下借助 API 網(wǎng)關(guān)可以做的具體的事:

  • 在網(wǎng)關(guān)層增加認(rèn)證層,比如:JWT 認(rèn)證察迟、OAuth2 認(rèn)證斩狱、OpenID 認(rèn)證等等,這樣不需要在每個服務(wù)中都做具體的認(rèn)證集成工作扎瓶,進而節(jié)省許多開發(fā)成本所踊。
  • 借助網(wǎng)關(guān)給跳板機 SSH 流量增加無需客戶端修改的復(fù)雜認(rèn)證,比如:跳轉(zhuǎn)任何客戶端的 SSH 登錄概荷,給出一個網(wǎng)址或者輸入框秕岛,引導(dǎo)登陸者通過網(wǎng)頁的 SSO 認(rèn)證(包含多因素認(rèn)證),再通過網(wǎng)關(guān)轉(zhuǎn)發(fā)到 SSH 服務(wù)。
  • 甚至在網(wǎng)關(guān)層做 Serverless 數(shù)據(jù)庫继薛!TiDB 社區(qū)的同學(xué)們就在做這個事兒修壕,他們從普通的 MySQL 客戶端的登錄請求中解析能推斷出轉(zhuǎn)到需要的 TiDB 示例的信息,并且在需要 cold start 喚醒實例的時候把連接保持住遏考,可以參考這篇文章:TiDB Gateway慈鸠。
  • 如果你特別慘在維護屎山項目,不得不針對舊版本的應(yīng)用程序?qū)π掳姹镜姆?wù)端進行兼容灌具,這時候 API 網(wǎng)關(guān)也可以通過一些請求重寫青团,把舊版本的請求轉(zhuǎn)換成新版本的請求。

只要腦洞大稽亏,理論上 API 網(wǎng)關(guān)可以做很多事壶冒。但顯然不是所有的事情都是適合在這一層去做的,通常那些比較通用的事情才適合在這一層去做截歉,上面我只是給出一些典型和極端的具體例子胖腾。

Apache APISIX

API 網(wǎng)關(guān)是從 LB、Reverse Proxy 項目演進過來的瘪松。隨著云原生的興起咸作,API 網(wǎng)關(guān)也逐漸成為了云原生的一部分,流行的開源網(wǎng)關(guān)有:

而且其中很多都是基于 Nginx/OpenResty 的下游項目宵睦。這里就以 Apache APISIX 為例记罚,介紹一下 NebulaGraph 借助 API 網(wǎng)關(guān)的幾個實踐。

NebulaGraph 介紹

NebulaGraph 是一個開源的分布式圖數(shù)據(jù)庫壳嚎,它的特點是:

  • 高性能:可達到每秒百萬級的讀寫桐智,具有極高的擴展性,在千億點烟馅、萬億邊的數(shù)據(jù)規(guī)模下支持毫秒級的查詢说庭。
  • 易擴展:分布式的架構(gòu)可在多臺機器上擴展。每臺機器上可以運行多個服務(wù)進程郑趁,它的查詢層是無狀態(tài)的計算存儲分離架構(gòu)刊驴,可以容易地引入不同配置、不同類型的計算層寡润,實現(xiàn)同一集群上 TP捆憎、AP、圖計算等不同負(fù)載的混合查詢梭纹。
  • 易使用:類 SQL 的原生查詢語言躲惰,易于學(xué)習(xí)和使用,同時支持 openCypher变抽。
  • 豐富生態(tài):NebulaGraph 的生態(tài)系統(tǒng)正在不斷壯大礁扮,目前已經(jīng)有了多個客戶端知举,包括 Java、Python太伊、Go雇锡、C++、JavaScript僚焦、Spark锰提、Flink 等,同時也有了多個可視化工具芳悲,包括 NebulaGraph Studio立肘、NebulaGraph Dashboard、NebulaGraph Explorer 等名扛。

本文討論的問題

本文給出了基于 NebulaGraph 集群應(yīng)用中涉及到 API 網(wǎng)關(guān)的幾個場景谅年。

  • 查詢接口的負(fù)載均衡
  • 底層存儲接口的暴露
  • 傳輸層的加密

查詢接口負(fù)載均衡

首先是圖數(shù)據(jù)庫查詢接口 graphd 的負(fù)載均衡與高可用的問題。

NebulaGraph 內(nèi)核由三種服務(wù)組成:graphd肮韧、metad 和 storaged:

image

所以融蹂,在默認(rèn)情況下,集群只會暴露 graphd 的接口弄企,提供給客戶端連接超燃,執(zhí)行 nGQL 的查詢。其中拘领,graphd 是無狀態(tài)的意乓,這意味著可以在多個 graphd 之間做負(fù)載均衡。這里约素,我們有兩種方法:基于客戶端的(Client-Side LB)與基于代理的届良。

客戶端的負(fù)載均衡

客戶端的負(fù)載均衡,就是在客戶端圣猎,也就是應(yīng)用程序中伙窃,實現(xiàn)負(fù)載均衡的邏輯。NebulaGraph 的各個語言的客戶端里邊已經(jīng)內(nèi)置了輪詢(Round-Robin)負(fù)載均衡样漆,我們只需要在客戶端配置多個 graphd 的地址就可以了。比如晦闰,我們在創(chuàng)建連接池的時候放祟,指定了兩個不同的 graphd 的地址(對應(yīng)不同進程實例),下面以 Python 代碼為例:

from nebula3.gclient.net import ConnectionPool
from nebula3.Config import Config

config = Config()
config.max_connection_pool_size = 10
connection_pool = ConnectionPool()
connection_pool.init([('127.0.0.1', 9669), ('127.0.0.1', 49433)], config)

在取得連接的時候呻右,就會從連接池中隨機取得一個連接:

In [10]: connection0 = connection_pool.get_connection()

In [11]: connection1 = connection_pool.get_connection()

# 這兩個連接的 graphd 地址是不同的
In [12]: connection0._port, connection1._port
Out[12]: (9669, 49433)

這種客戶端負(fù)載均衡的問題在于配置跪妥、實現(xiàn)細(xì)節(jié)與應(yīng)用代碼耦合在一起,如果需要修改負(fù)載均衡的策略声滥,就要修改應(yīng)用代碼眉撵,這樣就會增加應(yīng)用的復(fù)雜度侦香。

代理的負(fù)載均衡

基于代理的負(fù)載均衡,就是在應(yīng)用程序之前纽疟,增加一個代理層罐韩,來實現(xiàn)負(fù)載均衡的邏輯。這樣污朽,應(yīng)用程序就不需要關(guān)心負(fù)載均衡的問題了散吵。在 K8s 里的話,我們可以使用 K8s 的 Service 來實現(xiàn)這個代理層蟆肆。

這是一個在 Minikube 中為 NebulaGraph 集群中 graphd 創(chuàng)建的 Service:

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Service metadata:
  labels:
    app.kubernetes.io/cluster: nebula
    app.kubernetes.io/component: graphd     app.kubernetes.io/managed-by: nebula-operator
    app.kubernetes.io/name: nebula-graph
  name: nebula-graphd-svc-nodeport
  namespace: default
spec:
  externalTrafficPolicy: Local
  ports:
  - name: thrift
    port: 9669
    protocol: TCP
    targetPort: 9669
    nodePort: 30000
  - name: http
    port: 19669
    protocol: TCP
    targetPort: 19669
    nodePort: 30001
  selector:
    app.kubernetes.io/cluster: nebula
    app.kubernetes.io/component: graphd     app.kubernetes.io/managed-by: nebula-operator
    app.kubernetes.io/name: nebula-graph
  type: NodePort
EOF

創(chuàng)建后矾睦,我們就可以通過它暴露的單獨端口來訪問 NebulaGraph 集群中的 graphd 了:

In [13]: connection_pool = ConnectionPool()
    ...: connection_pool.init([('192.168.49.2', 9669)], config)
Out[13]: True

In [14]: connection0 = connection_pool.get_connection()

In [15]: connection1 = connection_pool.get_connection()

In [16]: connection0._ip, connection1._ip
Out[16]: ('192.168.49.2', '192.168.49.2')

可以看到,在連接層面上來看炎功,客戶端只知道代理的地址枚冗,而不知道 NebulaGraph 集群中的 graphd 的地址,這樣就實現(xiàn)了客戶端與 NebulaGraph 集群中的 graphd 的解耦蛇损。

然而赁温,當(dāng)我們在 Connection 之上創(chuàng)建 Session 的時候,就能看到實際上客戶端的不同請求是落在了不同的 graphd 上的:

In [17]: session = connection_pool.get_session('root', 'nebula')

In [18]: session._session_id
Out[18]: 1668670607568178

In [19]: session1 = connection_pool.get_session('root', 'nebula')

In [20]: session1._session_id
Out[20]: 1668670625563307

# 得到每一個 session 的 ID

In [21]: session.execute("SHOW SESSIONS")

# 它們分別對應(yīng)了兩個不同的 graphd 實例

Out[21]: ResultSet(keys: ['SessionId', 'UserName', 'SpaceName', 'CreateTime', 'UpdateTime', 'GraphAddr', 'Timezone', 'ClientIp'], values: [1668670607568178, "root", "", utc datetime: 2022-11-17T07:36:47.568178, timezone_offset: 0, utc datetime: 2022-11-17T07:36:47.575303, timezone_offset: 0, "nebula-graphd-0.nebula-graphd-svc.default.svc.cluster.local:9669", 0, "172.17.0.1"],[1668670625563307, "root", "", utc datetime: 2022-11-17T07:37:05.563307, timezone_offset: 0, utc datetime: 2022-11-17T07:37:03.638910, timezone_offset: 0, "nebula-graphd-1.nebula-graphd-svc.default.svc.cluster.local:9669", 0, "172.17.0.1"])

底層存儲接口的暴露

在 NebulaGraph 中州藕,可以通過 StorageClient 來訪問底層的存儲接口束世,這個接口可以用來做一些分析型、數(shù)據(jù)全掃描計算的工作床玻。

然而毁涉,存儲層的分布式服務(wù)實例不像 graphd 那樣,它們是有狀態(tài)的锈死。這其實與 K8s 或者 Docker Compose 的部署模型是相違背的贫堰。如果訪問的應(yīng)用 storaged 客戶端在集群外部,我們需要在 NebulaGraph 集群中的每一個存儲實例上都部署一個代理 Service待牵。這非常不方便其屏,有時候還是一種浪費。

此外缨该,由于 NebulaGraph 內(nèi)部服務(wù)發(fā)現(xiàn)機制和 storaged 客戶端的實現(xiàn)機制決定偎行,每一個 storaged 服務(wù)實體都是由其內(nèi)部的 host:port 唯一確定和尋址的,這給我們中間的代理工作也帶來了一些麻煩贰拿。

總結(jié)來看蛤袒,我們的需求是:

  • 能夠從集群外部訪問 NebulaGraph 的存儲層每一個實例
  • 每一個實例的訪問地址(host:port)和內(nèi)部的地址是完全一致的

為了實現(xiàn)這個需求,我之前的做法是為每一個實例單獨部署一個 graphd 代理(消耗一個地址膨更,保證端口不變)妙真,再在外部手動搭一個 Nginx 作為代理,配合 DNS 把內(nèi)部的地址解析 Nginx 上荚守,然后通過域名找到上游(每一個單獨的 graphd 代理)珍德。本文的延伸閱讀 1练般、2 中給出了相關(guān)的實驗步驟。

最近锈候,我找到了一個相對優(yōu)雅的可維護的方式:

  1. 在 NebulaGraph 集群同一個命名空間下引入一個 APISIX 網(wǎng)關(guān)薄料;
  2. 利用 APISIX 中的 Nginx TCP 代理的封裝 stream-proxy 來暴露 storaged 的接口;
  3. 為了最終只利用一個集群的出口(Service晴及,我們利用其支持的 TLSv1.3 中的 extend host name 字段:SNI 來路由上游)都办,做到用不同域名的 TCP over TLS 指向后端的不同 storaged;
  4. 只需要 Storage 客戶端能支持 TLSv1.3(發(fā)送 SNI)虑稼,并且能解析所有 storaged 的地址到 APISIX 的 Service 上即可琳钉;

示例圖:

           ┌────────────────────────────────────────────────────────────────────────────────────┐
           │  K8s Cluster                                                                       │
           │                                                      ┌──────────────────────────┐  │
           │          ┌────────────────────────────────────┐      │ NebulaGraph Cluster      │  │
           │          │  APISIX API-GATEWAY                │      │       ┌──────────────┐   │  │
           │          │                                    │      │       │ storaged-0   │   │  │
           │          │                                    │ ┌────┼──────?│              │   │  │
           │          │                                    │ │    │       │              │   │  │
           │          │   ┌────────────────────────────┐   │ │    │       └──────────────┘   │  │
           │          │   │ stream-proxy               │   │ │    │                          │  │
  ┌─────┐  │ .─────.  │   │                ┌────┐      │   │ │    │       ┌──────────────┐   │  │
  │     │  │╱       ╲ │   │  - addr: 9559  │    │──────┼───┼─┘    │       │ storaged-1   │   │  │
━━┫ DNS ┣━━( Service )╋━━━╋?   tls: true   │    │      │   │ ┌────┼──────?│              │   │  │
  │     │  │`.     ,' │   │                │    │──────┼───┼─┘    │       │              │   │  │
  └─────┘  │  `───'   │   │                │    │      │   │      │       └──────────────┘   │  │
           │          │   │                │SNI │      │   │      │                          │  │
           │          │   │                │    │──────┼───┼─┐    │       ┌──────────────┐   │  │
           │          │   │                │    │      │   │ │    │       │ storaged-2   │   │  │
           │          │   │                │    │      │   │ └────┼──────?│              │   │  │
           │          │   │                │    │──────┼───┼─┐    │       │              │   │  │
           │          │   │                └────┘      │   │ │    │       └──────────────┘   │  │
           │          │   └────────────────────────────┘   │ │    │                          │  │
           │          │                                    │ │    │       ┌──────────────┐   │  │
           │          │                                    │ │    │       │ storaged-3   │   │  │
           │          │                                    │ └────┼──────?│              │   │  │
           │          │                                    │      │       │              │   │  │
           │          │                                    │      │       └──────────────┘   │  │
           │          └────────────────────────────────────┘      └──────────────────────────┘  │
           │                                                                                    │
           └────────────────────────────────────────────────────────────────────────────────────┘

這樣做的好處是:

  • 在 APISIX 中比較優(yōu)雅地維護代理的配置,并且可以用到 APISIX 現(xiàn)代化的流量管理能力蛛倦;
  • 不需要為每一個 storaged 單獨創(chuàng)建 Service歌懒,只需要一個 Service、集群地址就可以了溯壶;
  • 為流量增加了 TLSv1.3 的加密及皂,提高了安全性。同時且改,沒有給 NebulaGraph 集群內(nèi)部的南北流量帶來的性能損耗验烧;

在本文的結(jié)尾,給出了實驗過程又跛,包含了本文提到的所有要點和細(xì)節(jié)碍拆。

傳輸層的加密

我們在前一個問題中提及到了,在 APISIX 網(wǎng)關(guān)中 terminate TLSv1.3 的連接慨蓝,借助 SNI 信息路由 storaged 的方法感混。其實,單獨將 graphd 接口的 TLS 交給網(wǎng)關(guān)來做礼烈,好處也是非常明顯的:

  • 證書管理在統(tǒng)一的網(wǎng)關(guān)控制面做弧满,更加方便;
  • 證書運維無 NebulaGraph 集群配置侵入(NebulaGraph 原生支持 TLS 加密此熬,但是加密之后帶來了集群內(nèi)部通信的開銷庭呜,而且配置和集群其他層面配置在一起,證書更新涉及進程重啟犀忱,不夠靈活)募谎;

具體的方法在后邊實操中也是有體現(xiàn)的。

實操:利用 APISIX 的 stream-proxy 暴露 storaged 的接口

實驗環(huán)境:Minikube

本實驗在本地的 Minikube 上做峡碉。首先,啟動一個 Minikube驮审。因為 APISIX 內(nèi)部的 etcd 需要用到 storageclass鲫寄,我們帶上窮人版的 storageclass 插件吉执。同時,為了在 K8s 外部訪問 storaged 的時候用和內(nèi)部相同的域名和端口地来,將把 node-port 允許的端口擴充到小于 9779 的范圍戳玫。

    --addons="default-storageclass" \
    --extra-config=apiserver.service-node-port-range=1-65535

實驗環(huán)境:NebulaGraph on K8s

這里,我們使用 Nebula Operator 來部署 NebulaGraph 集群未斑,具體的部署方法可以參考 Nebula Operator 文檔:https://docs.nebula-graph.com.cn/3.3.0/nebula-operator/1.introduction-to-nebula-operator/咕宿。

咱們做實驗,就偷個懶蜡秽,用我寫的 Nebula-Operator-KinD 來一鍵部署:

curl -sL nebula-kind.siwei.io/install-on-K8s.sh | bash

實驗環(huán)境:APISIX on K8s

首先府阀,是安裝。在 Helm 參數(shù)中指定打開 stream-proxy 的開關(guān):

helm repo add apisix https://charts.apiseven.com
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install apisix apisix/apisix \
  --set gateway.type=NodePort \
  --set gateway.stream.enabled=true \
  --set ingress-controller.enabled=true

# dashboard 也裝上芽突,方便我們繞過 admin API call 做一些方便的操作试浙。
helm install apisix-dashboard apisix/apisix-dashboard

因為截止到現(xiàn)在,APISIX 的 Helm Chart 之中并沒有提供 stream-proxy TCP 的監(jiān)聽端口的 TLS 支持的配置格式寞蚌,見:https://github.com/apache/apisix-helm-chart/issues/348田巴。我們需要手動更改 APISIX 的 ConfigMap,把 stream-proxy 的 TLS 配置加上:

kubectl edit ConfigMap apisix

我們編輯把 stream_proxy.tcp 改寫成這樣:

     stream_proxy:                 # TCP/UDP proxy
       only: false
       tcp:                        # TCP proxy port list
         - addr: 9779
           tls: true
         - addr: 9559
           tls: true

這里我們需要重建 APISIX Pod挟秤,因為 APISIX 的 stream-proxy 的 TLS 配置是在啟動的時候加載的壹哺,所以我們需要重建 APISIX Pod:

kubectl delete $(kubectl get po -l "app.kubernetes.io/name=apisix" -o name)

開始實驗

這個實驗的目標(biāo)是把 NebulaGraph 的 storaged 的接口暴露出來,讓外部的客戶端可以訪問到艘刚,而暴露的方式如圖:

           ┌────────────────────────────────────────────────────────────────────────────────────┐
           │  K8s Cluster                                                                       │
           │                                                      ┌──────────────────────────┐  │
           │          ┌────────────────────────────────────┐      │ NebulaGraph Cluster      │  │
           │          │  APISIX API-GATEWAY                │      │       ┌──────────────┐   │  │
           │          │                                    │      │       │ storaged-0   │   │  │
           │          │                                    │ ┌────┼──────?│              │   │  │
           │          │                                    │ │    │       │              │   │  │
           │          │   ┌────────────────────────────┐   │ │    │       └──────────────┘   │  │
           │          │   │ stream-proxy               │   │ │    │                          │  │
  ┌─────┐  │ .─────.  │   │                ┌────┐      │   │ │    │       ┌──────────────┐   │  │
  │     │  │╱       ╲ │   │  - addr: 9559  │    │──────┼───┼─┘    │       │ storaged-1   │   │  │
━━┫ DNS ┣━━( Service )╋━━━╋?   tls: true   │    │      │   │ ┌────┼──────?│              │   │  │
  │     │  │`.     ,' │   │                │    │──────┼───┼─┘    │       │              │   │  │
  └─────┘  │  `───'   │   │                │    │      │   │      │       └──────────────┘   │  │
           │          │   │                │SNI │      │   │      │                          │  │
           │          │   │                │    │──────┼───┼─┐    │       ┌──────────────┐   │  │
           │          │   │                │    │      │   │ │    │       │ storaged-2   │   │  │
           │          │   │                │    │      │   │ └────┼──────?│              │   │  │
           │          │   │                │    │──────┼───┼─┐    │       │              │   │  │
           │          │   │                └────┘      │   │ │    │       └──────────────┘   │  │
           │          │   └────────────────────────────┘   │ │    │                          │  │
           │          │                                    │ │    │       ┌──────────────┐   │  │
           │          │                                    │ │    │       │ storaged-3   │   │  │
           │          │                                    │ └────┼──────?│              │   │  │
           │          │                                    │      │       │              │   │  │
           │          │                                    │      │       └──────────────┘   │  │
           │          └────────────────────────────────────┘      └──────────────────────────┘  │
           │                                                                                    │
           └────────────────────────────────────────────────────────────────────────────────────┘

我們已經(jīng)有了所有的框架管宵,我們要往里填箭頭和圓圈就行。

$ kubectl get po
NAME                                         READY   STATUS     RESTARTS      AGE
apisix-6d89854bc5-5m788                      1/1     Running    1 (31h ago)   2d4h
apisix-dashboard-b544bd766-nh79j             1/1     Running    8 (31h ago)   2d10h
apisix-etcd-0                                1/1     Running    2 (31h ago)   2d10h
apisix-etcd-1                                1/1     Running    2 (31h ago)   2d10h
apisix-etcd-2                                1/1     Running    2 (31h ago)   2d10h
nebula-graphd-0                              1/1     Running    2 (31h ago)   3d4h
nebula-metad-0                               1/1     Running    2 (31h ago)   3d4h
nebula-storaged-0                            1/1     Running    2 (31h ago)   3d4h
nebula-storaged-1                            1/1     Running    2 (31h ago)   3d4h
nebula-storaged-2                            1/1     Running    2 (31h ago)   3d4h

配置 APISIX 的 stream-proxy

參考 APISIX 文檔:https://apisix.apache.org/docs/apisix/stream-proxy/#accept-tls-over-tcp-connection昔脯。

我們用 APISIX 的 API 來配置 stream-proxy:


apisix_api_key="edd1c9f034335f136f87ad84b625c8f1"
apisix_pod=$(kubectl get po -l \
    "app.kubernetes.io/name=apisix" -o name)

kubectl exec -it $apisix_pod -- \
    curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 \
    -H "X-API-KEY: $apisix_api_key" -X PUT -d \
'{
    "sni": "nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local",
    "upstream": {
        "nodes": {
            "172.17.0.13:9779": 1
        },
        "type": "roundrobin"
    }
}'

kubectl exec -it $apisix_pod -- \
    curl http://127.0.0.1:9180/apisix/admin/stream_routes/2 \
    -H "X-API-KEY: $apisix_api_key" -X PUT -d \
'{
    "sni": "nebula-storaged-1.nebula-storaged-headless.default.svc.cluster.local",
    "upstream": {
        "nodes": {
            "172.17.0.18:9779": 1
        },
        "type": "roundrobin"
    }
}'

kubectl exec -it $apisix_pod -- \
    curl http://127.0.0.1:9180/apisix/admin/stream_routes/3 \
    -H "X-API-KEY: $apisix_api_key" -X PUT -d \
'{
    "sni": "nebula-storaged-2.nebula-storaged-headless.default.svc.cluster.local",
    "upstream": {
        "nodes": {
            "172.17.0.5:9779": 1
        },
        "type": "roundrobin"
    }
}'

這里需要注意啄糙,目前,APISIX 的 stream-proxy 上游節(jié)點不支持域名解析是受限于上游的 lua 庫云稚,詳見 issue:https://github.com/apache/apisix/issues/8334隧饼。理想情況下,這里應(yīng)該給出每一個 storaged 的 SNI 相同的地址作為 upstream.nodes静陈。像這樣:

kubectl exec -it $apisix_pod -- \
    curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 \
    -H "X-API-KEY: $apisix_api_key" -X PUT -d \
'{
    "sni": "nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local",
    "upstream": {
        "nodes": {
            "nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local": 1
        },
        "type": "roundrobin"
    }
}'

配置 APISIX 中 storaged 地址的 TLS 證書

在生產(chǎn)環(huán)境下燕雁,我們應(yīng)該以云原生的方式去管理自簽或者公共信任的證書。這里鲸拥,我們就手動利用 MKCert 工具來做這件事兒拐格。

安裝 MKCert:

# 首次運行,需要安裝 mkcert刑赶,并且生成根證書
# macOS 的話
brew install mkcert
# ubuntu 的話
apt-get install wget libnss3-tools
# 然后再去 https://github.com/FiloSottile/mkcert/releases/ 下載 mkcert

簽發(fā)證書:

mkcert '*.nebula-storaged-headless.default.svc.cluster.local'

利用 APISIX Dashboard 將證書導(dǎo)入到 APISIX 之中捏浊。單獨開一個終端,運行:

export POD_NAME=$(\
    kubectl get pods \
    -l "app.kubernetes.io/name=apisix-dashboard,app.kubernetes.io/instance=apisix-dashboard" \
    -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(\
    kubectl get pod $POD_NAME \
    -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
kubectl \
    port-forward $POD_NAME 8080:$CONTAINER_PORT --address='0.0.0.0'

瀏覽器訪問:http://10.1.1.168:8080/ssl/list撞叨,賬號密碼都是 admin金踪。點擊 Create 按鈕浊洞,將剛剛生成的證書導(dǎo)入到 APISIX 之中。

image

增加 APISIX 的 NodePort Service

創(chuàng)建一個 NodePort Service胡岔,用于暴露 APISIX 的 9779 端口法希。這樣,我們就可以通過外部的 IP 地址訪問到 APISIX 了靶瘸。

cat <<EOF | kubectl apply -f -
spec:
  selector:
    app.kubernetes.io/instance: apisix
    app.kubernetes.io/name: apisix
  ports:
    - protocol: TCP
      port: 9779
      targetPort: 9779
      name: thrift
      nodePort: 9779
  type: NodePort
EOF

因為前邊 Minikube 中我們配置了端口的范圍覆蓋到了 9779苫亦,所以我們可以看到,這個 NodePort Service 的端口在宿主機上也可以從 Minikube ip 的同一個端口訪問到:

$ minikube service apisix-svc
$ minikube service list
|------------------------|---------------------------------|-------------------|---------------------------|
|       NAMESPACE        |              NAME               |    TARGET PORT    |            URL            |
|------------------------|---------------------------------|-------------------|---------------------------|
...
| default                | apisix-svc                      | thrift/9779       | http://192.168.49.2:9779  |<---
...
|------------------------|---------------------------------|-------------------|---------------------------|

當(dāng)然怨咪,Minikube 假設(shè)我們的服務(wù)都是 HTTP 的屋剑,給出的 URL 是 HTTP:// 的。不用理會它惊暴,我們心里知道它是 TCP over TLS 就好了饼丘。

配置 K8s 外部 DNS

這里需要配置一個 DNS 服務(wù),讓我們可以通過 nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local 等三個域名通過 Minikube 的 NodePort Service 訪問到 NebulaGraph 的 storaged 服務(wù)肄鸽。

獲得 Minikube 的 IP 地址:

$ minikube ip
192.168.49.2

配置 /etc/hosts

192.168.49.2 nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local
192.168.49.2 nebula-storaged-1.nebula-storaged-headless.default.svc.cluster.local
192.168.49.2 nebula-storaged-2.nebula-storaged-headless.default.svc.cluster.local
192.168.49.2 nebula-metad-0.nebula-metad-headless.default.svc.cluster.local

驗證 NebulaGraph Storage Client 可以從所有的節(jié)點中獲取到數(shù)據(jù)

這里油啤,為了方便,我們用到 Python 的客戶端益咬。

由于在寫本文的時候,NebulaGraph Python 客戶端的 StorageClient 尚未支持 TLS幽告,對它支持的 PR 剛好是我為了本實驗寫的:https://github.com/vesoft-inc/nebula-python/pull/239

所以冗锁,這里從個人分支安裝這個客戶端:

git clone https://github.com/wey-gu/nebula-python.git
cd nebula-python
python3 -m pip install .
python3 -m pip install ipython
# 進入 ipython
ipython

我們在 iPython 中交互式驗證:

from nebula3.mclient import MetaCache, HostAddr
from nebula3.sclient.GraphStorageClient import GraphStorageClient
from nebula3.Config import SSL_config
import ssl
import os

meta_cache = MetaCache([('nebula-metad-0.nebula-metad-headless.default.svc.cluster.local', 9559)],
                       50000)

storage_addrs = [HostAddr(host='nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local', port=9779),
                 HostAddr(host='nebula-storaged-1.nebula-storaged-headless.default.svc.cluster.local', port=9779),
                 HostAddr(host='nebula-storaged-2.nebula-storaged-headless.default.svc.cluster.local', port=9779)]

# 自簽證書配置
current_dir = os.path.abspath(".")

ssl_config = SSL_config()
ssl_config.cert_reqs = ssl.CERT_OPTIONAL
ssl_config.cert_reqs = ssl.CERT_OPTIONAL
ssl_config.ca_certs = os.path.join(
    os.path.expanduser("~/.local/share/mkcert"), 'rootCA.pem'
)
ssl_config.keyfile = os.path.join(
    current_dir, 'nebula-storaged-headless.default.svc.cluster.local+1-key.pem'
)
ssl_config.certfile = os.path.join(
    current_dir, 'nebula-storaged-headless.default.svc.cluster.local+1.pem'
)

# 實例化 StorageClient

graph_storage_client = GraphStorageClient(meta_cache, storage_addrs, 5000, ssl_config)

# 驗證可以從所有的節(jié)點中獲取到數(shù)據(jù)
resp = graph_storage_client.scan_vertex(
        space_name='basketballplayer',
        tag_name='player')
while resp.has_next():
    result = resp.next()
    for vertex_data in result:
        print(vertex_data)

結(jié)果?:

("player112" :player{name: "Jonathon Simmons", age: 29})
("player117" :player{name: "Stephen Curry", age: 31})
("player119" :player{name: "Kevin Durant", age: 30})
("player134" :player{name: "Blake Griffin", age: 30})
("player141" :player{name: "Ray Allen", age: 43})
("player144" :player{name: "Shaquille O'Neal", age: 47})
("player149" :player{name: "Ben Simmons", age: 22})
("player100" :player{name: "Tim Duncan", age: 42})
("player101" :player{name: "Tony Parker", age: 36})
("player110" :player{name: "Cory Joseph", age: 27})
("player126" :player{name: "Kyrie Irving", age: 26})
("player131" :player{name: "Paul George", age: 28})
("player133" :player{name: "Yao Ming", age: 38})
("player140" :player{name: "Grant Hill", age: 46})
("player105" :player{name: "Danny Green", age: 31})
("player109" :player{name: "Tiago Splitter", age: 34})
("player111" :player{name: "David West", age: 38})
...

總結(jié)

  • NebulaGraph 查詢接口的負(fù)載均衡可以借助 K8s Service來做齐唆;
  • NebulaGraph 底層存儲接口的暴露在 K8s 中可以利用 APISIX Stream Proxy 和 SNI 來優(yōu)雅實現(xiàn);
  • 利用 API 網(wǎng)關(guān)對出口傳輸層的加密是一個很好的選擇冻河,相較于用 NebulaGraph 原生的 TLS 的方式箍邮。

一些坑

fbthrift Python 并不支持發(fā)送 extend host name(SNI):https://github.com/vesoft-inc/nebula-python/pull/238,寫了 PR 去做支持叨叙。這時候 APISIX 中的報錯是 failed to find SNI

2022/11/15 10:18:26 [error] 78#78: *1744270 stream [lua] init.lua:842: stream_ssl_phase(): failed to fetch ssl config: failed to find SNI: 
please check if the client requests via IP or uses an outdated protocol. If you need to report an issue, provide a packet capture file of the TLS handshake., context: 
ssl_certificate_by_lua*, client: 172.17.0.1, server: 0.0.0.0:9779

參考延伸閱讀的 3-6锭弊。

此外,我還發(fā)現(xiàn) APISIX stream 里邊不解析上游 node 域名擂错,我查了所有 DNS 都沒有問題味滞,去提了 issue 才知道是已知問題:https://github.com/apache/apisix/issues/8334,只好先手配 IP:Port 作罷。

2022/11/15 12:26:59 [error] 44#44: *9538531 stream [lua] resolver.lua:47: parse_domain(): failed to parse domain: nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local, error: failed to query the DNS server: dns client error: 101 empty record received while prereading client data, client: 172.17.0.1, server: 0.0.0.0:9779
2022/11/15 12:26:59 [error] 44#44: *9538531 stream [lua] upstream.lua:79: parse_domain_for_nodes(): dns resolver domain: nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local error: failed to query the DNS server: dns client error: 101 empty record received while prereading client data, client: 172.17.0.1, server: 0.0.0.0:9779
2022/11/15 12:26:59 [error] 44#44: *9538531 stream [lua] init.lua:965: stream_preread_phase(): failed to set upstream: no valid upstream node while prereading client data, client: 172.17.0.1, server: 0.0.0.0:9779

延伸閱讀

  1. https://gist.github.com/wey-gu/950e4f4c673badae375e59007d80d372
  2. https://gist.github.com/wey-gu/699b9a2ef5dff5f0fb5f288d692ddfd5
  3. https://docs.python.org/3/library/ssl.html#ssl.SSLContext.sslsocket_class
  4. https://github.com/apache/thrift/commit/937228e030569bf25ceb379c9491426709792701
  5. https://github.com/apache/thrift/pull/894
  6. https://github.com/apache/thrift/blob/e8353cb46e9f5e71f9b76f55d6bf59530b7f98ef/lib/py/src/transport/TSSLSocket.py#L184

謝謝你讀完本文 (///▽///)

要來近距離體驗一把圖數(shù)據(jù)庫嗎剑鞍?現(xiàn)在可以用用 NebulaGraph Cloud 來搭建自己的圖數(shù)據(jù)系統(tǒng)喲刹悴,快來節(jié)省大量的部署安裝時間來搞定業(yè)務(wù)吧~ NebulaGraph 阿里云計算巢現(xiàn) 30 天免費使用中,點擊鏈接來用用圖數(shù)據(jù)庫吧~

想看源碼的小伙伴可以前往 GitHub 閱讀攒暇、使用、(з)-☆ star 它 -> GitHub子房;和其他的 NebulaGraph 用戶一起交流圖數(shù)據(jù)庫技術(shù)和應(yīng)用技能形用,留下「你的名片」一起玩耍呢~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市证杭,隨后出現(xiàn)的幾起案子田度,更是在濱河造成了極大的恐慌,老刑警劉巖解愤,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镇饺,死亡現(xiàn)場離奇詭異,居然都是意外死亡送讲,警方通過查閱死者的電腦和手機奸笤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門监右,熙熙樓的掌柜王于貴愁眉苦臉地迎上來健盒,“玉大人扣癣,你說我怎么就攤上這事憨降⌒┚伲” “怎么了热押?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵萄凤,是天一觀的道長骄蝇。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么册招? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任是掰,我火速辦了婚禮辱匿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘絮短。我一直安慰自己丁频,他們只是感情好邑贴,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布胁勺。 她就那樣靜靜地躺著独旷,像睡著了一般嵌洼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上褐啡,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天备畦,我揣著相機與錄音懂盐,去河邊找鬼糕档。 笑死,一個胖子當(dāng)著我的面吹牛尿背,可吹牛的內(nèi)容都是我干的捶惜。 我是一名探鬼主播吱七,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼陪捷,長吁一口氣:“原來是場噩夢啊……” “哼市袖!你這毒婦竟也來了苍碟?” 一聲冷哼從身側(cè)響起撮执,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抒钱,失蹤者是張志新(化名)和其女友劉穎谋币,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體早芭,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡退个,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年语盈,在試婚紗的時候發(fā)現(xiàn)自己被綠了黎烈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片照棋。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡烈炭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趴捅,到底是詐尸還是另有隱情拱绑,我是刑警寧澤丽蝎,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布屠阻,位于F島的核電站,受9級特大地震影響吧恃,放射性物質(zhì)發(fā)生泄漏痕寓。R本人自食惡果不足惜蝇闭,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一丁眼、第九天 我趴在偏房一處隱蔽的房頂上張望苞七。 院中可真熱鬧,春花似錦卢厂、人聲如沸惠啄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粒氧。三九已至,卻和暖如春摘盆,著一層夾襖步出監(jiān)牢的瞬間孩擂,已是汗流浹背箱熬。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工坦弟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酿傍,地道東北人赤炒。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓亏较,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遵岩。 傳聞我的和親對象是個殘疾皇子尘执,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容