乐趣区

关于apisix:当云原生网关遇上图数据库NebulaGraph-的-APISIX-最佳实践

本文介绍了利用开源 API 网关 APISIX 减速 NebulaGraph 多个场景的落地最佳实际:负载平衡、裸露接口构造与 TLS Termination。

API 网关介绍

什么是 API 网关

API 网关是位于客户端和服务器之间的“中间人”,用于治理、监控和爱护 API。它能够在 API 之前执行一些操作,例如:身份验证、受权、缓存、日志记录、审计、流量管制、平安、防火墙、压缩、解压缩、加密、解密等。

API 网关能够工作在 TCP/IP 4 层和 OSI 7 层。跑在 7 层的 API 网关能够应用多种协定,例如:HTTP、HTTPS、WebSocket、gRPC、MQTT 等。在这些应用层协定中做一些操作,比方,申请的重写、转发、合并、重试、缓存、限流、熔断、降级、鉴权、监控、日志、审计等等。

这里举例一下借助 API 网关能够做的具体的事:

  • 在网关层减少认证层,比方:JWT 认证、OAuth2 认证、OpenID 认证等等,这样不须要在每个服务中都做具体的认证集成工作,进而节俭许多开发成本。
  • 借助网关给跳板机 SSH 流量减少无需客户端批改的简单认证,比方:跳转任何客户端的 SSH 登录,给出一个网址或者输入框,疏导登陆者通过网页的 SSO 认证(蕴含多因素认证),再通过网关转发到 SSH 服务。
  • 甚至在网关层做 Serverless 数据库!TiDB 社区的同学们就在做这个事儿,他们从一般的 MySQL 客户端的登录申请中解析能推断出转到须要的 TiDB 示例的信息,并且在须要 cold start 唤醒实例的时候把连贯放弃住,能够参考这篇文章:TiDB Gateway。
  • 如果你特地惨在保护屎山我的项目,不得不针对旧版本的应用程序对新版本的服务端进行兼容,这时候 API 网关也能够通过一些申请重写,把旧版本的申请转换成新版本的申请。

只有脑洞大,实践上 API 网关能够做很多事。但显然不是所有的事件都是适宜在这一层去做的,通常那些比拟通用的事件才适宜在这一层去做,下面我只是给出一些典型和极其的具体例子。

Apache APISIX

API 网关是从 LB、Reverse Proxy 我的项目演进过去的。随着云原生的衰亡,API 网关也逐步成为了云原生的一部分,风行的开源网关有:

  • Nginx
  • Apache APISIX
  • Kong
  • Lura
  • OpenResty
  • Tyk
  • Traefik
  • Istio
  • Envoy

而且其中很多都是基于 Nginx/OpenResty 的上游我的项目。这里就以 Apache APISIX 为例,介绍一下 NebulaGraph 借助 API 网关的几个实际。

NebulaGraph 介绍

NebulaGraph 是一个开源的分布式图数据库,它的特点是:

  • 高性能:可达到每秒百万级的读写,具备极高的扩展性,在千亿点、万亿边的数据规模下反对毫秒级的查问。
  • 易扩大:分布式的架构可在多台机器上扩大。每台机器上能够运行多个服务过程,它的查问层是无状态的计算存储拆散架构,能够容易地引入不同配置、不同类型的计算层,实现同一集群上 TP、AP、图计算等不同负载的混合查问。
  • 易使用:类 SQL 的原生查询语言,易于学习和应用,同时反对 openCypher。
  • 丰盛生态:NebulaGraph 的生态系统正在一直壮大,目前曾经有了多个客户端,包含 Java、Python、Go、C++、JavaScript、Spark、Flink 等,同时也有了多个可视化工具,包含 NebulaGraph Studio、NebulaGraph Dashboard、NebulaGraph Explorer 等。

本文探讨的问题

本文给出了基于 NebulaGraph 集群利用中波及到 API 网关的几个场景。

  • 查问接口的负载平衡
  • 底层存储接口的裸露
  • 传输层的加密

查问接口负载平衡

首先是图数据库查问接口 graphd 的负载平衡与高可用的问题。

NebulaGraph 内核由三种服务组成:graphd、metad 和 storaged:

所以,在默认状况下,集群只会裸露 graphd 的接口,提供给客户端连贯,执行 nGQL 的查问。其中,graphd 是无状态的,这意味着能够在多个 graphd 之间做负载平衡。这里,咱们有两种办法:基于客户端的(Client-Side LB)与基于代理的。

客户端的负载平衡

客户端的负载平衡,就是在客户端,也就是应用程序中,实现负载平衡的逻辑。NebulaGraph 的各个语言的客户端里边曾经内置了轮询(Round-Robin)负载平衡,咱们只须要在客户端配置多个 graphd 的地址就能够了。比方,咱们在创立连接池的时候,指定了两个不同的 graphd 的地址(对应不同过程实例),上面以 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)

这种客户端负载平衡的问题在于配置、实现细节与利用代码耦合在一起,如果须要批改负载平衡的策略,就要批改利用代码,这样就会减少利用的复杂度。

代理的负载平衡

基于代理的负载平衡,就是在应用程序之前,减少一个代理层,来实现负载平衡的逻辑。这样,应用程序就不须要关怀负载平衡的问题了。在 K8s 里的话,咱们能够应用 K8s 的 Service 来实现这个代理层。

这是一个在 Minikube 中为 NebulaGraph 集群中 graphd 创立的 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

创立后,咱们就能够通过它裸露的独自端口来拜访 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 的地址,这样就实现了 客户端与 NebulaGraph 集群中的 graphd 的解耦

然而,当咱们在 Connection 之上创立 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")

# 它们别离对应了两个不同的 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 来拜访底层的存储接口,这个接口能够用来做一些剖析型、数据全扫描计算的工作。

然而,存储层的分布式服务实例不像 graphd 那样,它们是有状态的。这其实与 K8s 或者 Docker Compose 的部署模型是相违反的。如果拜访的利用 storaged 客户端在集群内部,咱们须要在 NebulaGraph 集群中的每一个存储实例上都部署一个代理 Service。这十分不不便,有时候还是一种节约。

此外,因为 NebulaGraph 外部服务发现机制和 storaged 客户端的实现机制决定,每一个 storaged 服务实体都是由其外部的 host:port 惟一确定和寻址的,这给咱们两头的代理工作也带来了一些麻烦。

总结来看,咱们的需要是:

  • 可能从集群内部拜访 NebulaGraph 的存储层每一个实例
  • 每一个实例的拜访地址(host:port)和外部的地址是完全一致的

为了实现这个需要,我之前的做法是为每一个实例独自部署一个 graphd 代理(耗费一个地址,保障端口不变),再在内部手动搭一个 Nginx 作为代理,配合 DNS 把外部的地址解析 Nginx 上,而后通过域名找到上游(每一个独自的 graphd 代理)。本文的延长浏览 1、2 中给出了相干的试验步骤。

最近,我找到了一个绝对优雅的可保护的形式:

  1. 在 NebulaGraph 集群同一个命名空间下引入一个 APISIX 网关;
  2. 利用 APISIX 中的 Nginx TCP 代理的封装 stream-proxy 来裸露 storaged 的接口;
  3. 为了最终只利用一个集群的进口(Service,咱们利用其反对的 TLSv1.3 中的 extend host name 字段:SNI 来路由上游),做到用不同域名的 TCP over TLS 指向后端的不同 storaged;
  4. 只须要 Storage 客户端能反对 TLSv1.3(发送 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 中比拟优雅地保护代理的配置,并且能够用到 APISIX 现代化的流量治理能力;
  • 不须要为每一个 storaged 独自创立 Service,只须要一个 Service、集群地址就能够了;
  • 为流量减少了 TLSv1.3 的加密,进步了安全性。同时,没有给 NebulaGraph 集群外部的南北流量带来的性能损耗;

在本文的结尾,给出了试验过程,蕴含了本文提到的所有要点和细节。

传输层的加密

咱们在前一个问题中提及到了,在 APISIX 网关中 terminate TLSv1.3 的连贯,借助 SNI 信息路由 storaged 的办法。其实,独自将 graphd 接口的 TLS 交给网关来做,益处也是非常明显的:

  • 证书治理在对立的网关管制面做,更加不便;
  • 证书运维无 NebulaGraph 集群配置侵入(NebulaGraph 原生反对 TLS 加密,然而加密之后带来了集群外部通信的开销,而且配置和集群其余层面配置在一起,证书更新波及过程重启,不够灵便);

具体的办法在后边实操中也是有体现的。

实操:利用 APISIX 的 stream-proxy 裸露 storaged 的接口

试验环境:Minikube

本试验在本地的 Minikube 上做。首先,启动一个 Minikube。因为 APISIX 外部的 etcd 须要用到 storageclass,咱们带上富人版的 storageclass 插件。同时,为了在 K8s 内部拜访 storaged 的时候用和外部雷同的域名和端口,将把 node-port 容许的端口裁减到小于 9779 的范畴。

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

试验环境: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

试验环境:APISIX on K8s

首先,是装置。在 Helm 参数中指定关上 stream-proxy 的开关:

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

因为截止到当初,APISIX 的 Helm Chart 之中并没有提供 stream-proxy TCP 的监听端口的 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)

开始试验

这个试验的指标是把 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   │   │  │
           │          │                                    │ └────┼──────▶│              │   │  │
           │          │                                    │      │       │              │   │  │
           │          │                                    │      │       └──────────────┘   │  │
           │          └────────────────────────────────────┘      └──────────────────────────┘  │
           │                                                                                    │
           └────────────────────────────────────────────────────────────────────────────────────┘

咱们曾经有了所有的框架,咱们要往里填箭头和圆圈就行。

$ 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 上游节点不反对域名解析是受限于上游的 lua 库,详见 issue:https://github.com/apache/apisix/issues/8334。现实状况下,这里应该给出每一个 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 证书

在生产环境下,咱们应该以云原生的形式去治理自签或者公共信赖的证书。这里,咱们就手动利用 MKCert 工具来做这件事儿。

装置 MKCert:

# 首次运行,须要装置 mkcert,并且生成根证书
# macOS 的话
brew install mkcert
# ubuntu 的话
apt-get install wget libnss3-tools
# 而后再去 https://github.com/FiloSottile/mkcert/releases/ 下载 mkcert

签发证书:

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

利用 APISIX Dashboard 将证书导入到 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 按钮,将刚刚生成的证书导入到 APISIX 之中。

减少 APISIX 的 NodePort Service

创立一个 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  |<---
...
|------------------------|---------------------------------|-------------------|---------------------------|

当然,Minikube 假如咱们的服务都是 HTTP 的,给出的 URL 是 HTTP:// 的。不必理睬它,咱们心里晓得它是 TCP over TLS 就好了。

配置 K8s 内部 DNS

这里须要配置一个 DNS 服务,让咱们能够通过 nebula-storaged-0.nebula-storaged-headless.default.svc.cluster.local 等三个域名通过 Minikube 的 NodePort Service 拜访到 NebulaGraph 的 storaged 服务。

取得 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 能够从所有的节点中获取到数据

这里,为了不便,咱们用到 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)

# 验证能够从所有的节点中获取到数据
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)

后果✅:

("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})
...

总结

  • NebulaGraph 查问接口的负载平衡能够借助 K8s Service 来做;
  • NebulaGraph 底层存储接口的裸露在 K8s 中能够利用 APISIX Stream Proxy 和 SNI 来优雅实现;
  • 利用 API 网关对进口传输层的加密是一个很好的抉择,相较于用 NebulaGraph 原生的 TLS 的形式。

一些坑

fbthrift Python 并不反对发送 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。

此外,我还发现 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

谢谢你读完本文 (///▽///)

要来近距离体验一把图数据库吗?当初能够用用 NebulaGraph Cloud 来搭建本人的图数据系统哟,快来节俭大量的部署安装时间来搞定业务吧~ NebulaGraph 阿里云计算巢现 30 天收费应用中,点击链接来用用图数据库吧~

想看源码的小伙伴能够返回 GitHub 浏览、应用、(^з^)-☆ star 它 -> GitHub;和其余的 NebulaGraph 用户一起交换图数据库技术和利用技能,留下「你的名片」一起游玩呢~

退出移动版