Apisix(三)集成k8s服务发现

1,785 阅读11分钟

一、前言

在使用apisix作为网关处理http请求的时候,常用到的几个概念:consumer,router,upstream,service,plugin。apisix通过对于consumer,router,upstream,service,plugin的配置最终可以实现,在apisix内部即使对于同一个URL可以按照用户不同,分配不同的URL访问控制策略和不同的后端处理服务。

对于url“/cmp/user/usercent”按照不同的用户A和B,配置当consumer A访问url的时候,限流是100次/s,后端共3个upstream提供服务,对于consumer B访问url的时候,限流是20次/s后端共2个upstream提供服务。

二、Apisix服务发现注册中心介绍

上述内容为apisix提供的基本的能力,通过对于router/service配置多个upstream来实现多个后端服务负载均衡处理,确保请求处理的可用性。然后在实际使用中upstream后端对应的服务配置,往往会因为请求波动,后端服务故障等原因进行相应调整。

在微服务架构下服务众多,如果全部靠手动来处理,那么管理和维护成本会非常高也非常繁琐。所以网关非常有必要可以通过注册中心来获取到最新的后端服务实例,这样网关就可以通过集成注册中心实现对于服务的动态管理。

Apisix作为云原生api网关也考虑到了这个场景,apisix支持通过多种形式实现动态服务发现。例如:DNS,consul,eureka,nacos,k8s,基于控制面的服务发现。

Apisix在apisix/discovery目录下实现了对于不同注册中心的实现,基本上所有的功能都是开箱即用的,只需要做简单配置即可,接下来重点看下如何结合k8s来实现服务发现。

三、Apisix + k8s服务发现

Apisix支持对于单个k8s集群以及多个k8s集群,分别对应待发现服务位于1个或者多个k8s集群中。这里主要介绍单个集群服务发现的配置。

多个集群配置和单个集群类似只是多了根据id来区分不同集群。

Apisix默认未开启服务发现功能,在使用服务发现能力的时候,需要手动添加服务发现的相关配置到apisix服务中,另外也需要在apisix中配置服务发现对应的路由信息。

Apisix配置文件服务发现配置

单集群服务发现全量配置

discovery:
  kubernetes:
    service:
      schema: https #default https
      host: ${KUBERNETES_SERVICE_HOST} #default ${KUBERNETES_SERVICE_HOST}
      port: ${KUBERNETES_SERVICE_PORT}  #default ${KUBERNETES_SERVICE_PORT}
    client:
      token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      #token: |-
       # eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEif
       # 6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEifeyJhbGciOiJSUzI1NiIsImtpZCI

    default_weight: 50 # weight assigned to each discovered endpoint. default 50, minimum 0
    namespace_selector:
      equal: default
    label_selector: |-
      first="a",second="b"
    shared_size: 1m #default 1m
参数作用和解释

标红的为使用服务发现(除了集群配置信息之外)重点关注的配置。

  • service.schema:表示apisix和k8s api server的通信协议默认是https。可选值在http和https之间,一般情况下都是https。
  • service.host:Kubernetes API Server的服务主机地址,默认使用环境变量${KUBERNETES_SERVICE_HOST},也可以根据集群具体情况选择填写对应ip或者集群内DNS serivce全路径。
  • service.port:设置与Kubernetes API Server通信的端口号,默认使用环境变量${KUBERNETES_SERVICE_PORT}。一般情况下都是443。
  • client.token_file:指定存放Kubernetes服务帐户令牌的文件路径,APISIX通过读取该文件中的令牌与API Server进行身份验证。如果文件不存在或者token无效,APIServer会拒绝apisix请求。
  • client.token:和token_file作用一样,两者选一种使用即可,
  • default_weight:为Kubernetes中发现的每个endpoint设置默认权重,默认值为50,最小值为0。权重会影响到负载均衡时的选择,权重配置越大分到的流量越多。

对于default_weight有一些个人理解(以下内容没有在官网找到具体解释,为个人根据配置分析得来,未能经过实际验证)

  1. 对于单个集群不管weight配置的是什么值,因为后端服务endpoints列表中的实例获取到的权重都是配置的值,也就是说单个集群配置的endpoints都是相同的权重
  2. 对于配置多个集群,便可以通过不同的weight来将流量进行按照权重划分,权重越高的集群对应的endpoints会分到更多的流量。
  • namespace_selector: 根据namespace选择器进行服务发现选择,可以根据equal,not_equal,match,not_match进行配置。

    • equal: 服务发现只选择namespace等于配置的命名空间的endpoint
    • not_equal: 服务发现只选择namespace不等于配置的命名空间的endpoint
    • match:服务发现选择namespace匹配以下命名空间的endpoint
    •     match:
            - default
            - ^my-[a-z]+$
      
    • not_match:服务发现选择namespace不匹配以下命名空间的endpoint
    •     not_match:
            - default
            - ^my-[a-z]+$
      
  • label_selector: 通过标签选择器进行后端endpoint选择。可以理解为是更精细化的一个管理,一般情况下通过NS选择器就能确定出当前NS下的对应服务。如果同一个服务需要进行更细致区分,例如:根据版本信息的灰度或者流量控制,可以通过标签选择器进行处理。

  • shared_size:设置APISIX用于存储服务发现结果的共享内存大小,默认为1MB。这个内存区域用于保存服务发现的结果,以便快速查找和更新。如果空间被用完或者空间不够,可能导致服务发现的实例信息来不及更新或者丢失的情况,因此需要合理设置和管理。

对于shard_size可以在开启prometheus插件之后,结合一些监控指标来进行判断。prometheus会提供

  • apisix_shared_dict_capacity_bytes
  • apisix_shared_dict_free_space_bytes

指标来监控apisix中共享内存的情况,以上两个参数表示apisix分配给共享字典的内容总量和剩余内容大小。在通过name标签查看服务发现的内容使用情况,可以来间接了解shared_size的使用情况。

  • apisix_shared_dict_capacity_bytes{name="discovery"}
  • apisix_shared_dict_free_space_bytes{name="discovery"}

多集群服务发现全量配置

参数基本上和单集群保持一致,在单集群的基础上通过添加id字段区分不同的集群。

discovery:
  kubernetes:
  - id: release  # a custom name refer to the cluster, pattern ^[a-z0-9]{1,8}
    service:
      schema: https #default https
      host: "1.cluster.com"
      port: "6443"
    client:
      token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      #token: |-
       # eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEif
       # 6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEifeyJhbGciOiJSUzI1NiIsImtpZCI
    default_weight: 50 
    namespace_selector:
      equal: default
    label_selector: |-
      first="a",second="b"
    shared_size: 1m #default 1m

Apisix路由配置

在配置好apisix服务发现的相关配置以后,我们接下来只要在apisix中将路由和k8s集群的service关联起来即可。同样在Apisix中针对单集群和多集群在router的定义有稍微的差异。

使用服务发现来配置upstream信息的时候,upstream配置属性按照以下进行调整。

  "upstream": {
    "type": "roundrobin",
    "hash_on": "vars",
    "scheme": "http",
    "discovery_type": "kubernetes",
    "pass_host": "pass",
    "service_name": "apisix/cmp:http"
  }

这里特别对于service_name进行说明,service_name表示upstream对应的集群后端服务,

单集群模式下router配置规则

service_name 必须满足格式:[namespace]/[name]:[portName]

  • namespace: Endpoints 所在的命名空间
  • name: Endpoints 的资源名
  • portName: Endpoints 定义包含的 ports.name 值,如果 Endpoints 没有定义 ports.name,请依次使用 targetPort, port 代替。设置了 ports.name 的情况下,不能使用后两者。

多集群模式下router配置规则

service_name 必须满足格式:[id]/[namespace]/[name]:[portName]

  • id: Kubernetes 服务发现配置中定义的集群 id 值
  • namespace: Endpoints 所在的命名空间
  • name: Endpoints 的资源名
  • portName: Endpoints 定义包含的 ports.name 值,如果 Endpoints 没有定义 ports.name,请依次使用 targetPort, port 代替。设置了 ports.name 的情况下,不能使用后两者。

因此上述配置中"service_name": "apisix/cmp:http"表示:在单集群模式下apisix命名空间名为cmp的k8s endpoint会作为upstream的后端服务。并且该cmp endpoint的集群中ports.name = http。以下是集群中的cmp endpoint配置。

  Downloads k get endpoints cmp -oyaml
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    endpoints.kubernetes.io/last-change-trigger-time: "2024-03-18T03:45:58Z"
  creationTimestamp: "2024-03-12T03:31:38Z"
  name: cmp
  namespace: apisix
  resourceVersion: "28252131"
  uid: 54068254-ac2d-4dcb-bc0a-2bb7c1fa3cf4
subsets:
- addresses:
  - ip: 10.244.4.239
    nodeName: node-new5
    targetRef:
      kind: Pod
      name: cmp-5bf8949475-fng5m
      namespace: apisix
      uid: 82bbf482-3b1b-49eb-a3bb-2127846ccdb5
  - ip: 10.244.7.174
    nodeName: node-new3
    targetRef:
      kind: Pod
      name: cmp-5bf8949475-dvjd4
      namespace: apisix
      uid: 547b1077-1855-4073-b3b5-8ee222ec6558
  ports:
  - name: http
    port: 8012
    protocol: TCP

在完成以上apisix的服务发现配置和路由配置信息后,便可在apisix集群k8s集群服务发现能力自动将请求路由到对应的集群的后端服务。

四、Apisix + k8s服务发现示例

接下来以SPS CMP服务为例,通过Apisix集成k8s服务发现能力,我们直接通过访问apisix gateway请求,apisix自动路由请求到k8s集群中的后端服务。

k8s apisix configmap添加服务发现配置

    discovery:                      # Service Discovery
     kubernetes:                     # Kubernetes service discovery
       service:
         schema: https                     # apiserver schema, options [http, https], default https
         host: "kubernetes.default.svc.cluster.local" #${KUBERNETES_SERVICE_HOST}  # apiserver host, options [ipv4, ipv6, domain, environment variable], default ${KUBERNETES_SERVICE_HOST}
         port: "443" #${KUBERNETES_SERVICE_PORT}  # apiserver port, options [port number, environment variable], default ${KUBERNETES_SERVICE_PORT}
       client:
         # serviceaccount token or path of serviceaccount token_file
         token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
       namespace_selector:
         match: 
         - apisix
       # label_selector: |-
       #   app=cmp,
       # shared_size: 1m #default 1m

apisix中添加CMP服务对应的路由信息

这里结合proxy-rewrite插件对于url进行正则表达式全局匹配。将所有cmp开头的url,路由到service_name='apisix/cmp:http'对应到集群服务。

curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
    "uri": "/cmp/*",
    "name": "cmp",
    "plugins": {
       "proxy-rewrite" : {
          "regex_uri": ["^/cmp/(.*)", "/${1}"]
        }
     },
     "upstream": {
        "service_name": "apisix/cmp:http",
        "type": "roundrobin",
        "discovery_type": "kubernetes"
      }
}'

集群中cmp endpoint

  Downloads k get endpoints cmp -oyaml
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    endpoints.kubernetes.io/last-change-trigger-time: "2024-03-18T03:45:58Z"
  creationTimestamp: "2024-03-12T03:31:38Z"
  name: cmp
  namespace: apisix
  resourceVersion: "28252131"
  uid: 54068254-ac2d-4dcb-bc0a-2bb7c1fa3cf4
subsets:
- addresses:
  - ip: 10.244.4.239
    nodeName: node-new5
    targetRef:
      kind: Pod
      name: cmp-5bf8949475-fng5m
      namespace: apisix
      uid: 82bbf482-3b1b-49eb-a3bb-2127846ccdb5
  - ip: 10.244.7.174
    nodeName: node-new3
    targetRef:
      kind: Pod
      name: cmp-5bf8949475-dvjd4
      namespace: apisix
      uid: 547b1077-1855-4073-b3b5-8ee222ec6558
  ports:
  - name: http
    port: 8012
    protocol: TCP

通过apisix访问cmp服务

通过访问cmp/ams/amcenter接口可以看到返回值已经将cmp中接口信息返回。

  Downloads curl -i http://192.168.101.23:30290/cmp/ams/amcenter
HTTP/1.1 401
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: client-uuid=128a2beb-e9bc-4054-9ea1-9778963cd818; Max-Age=315360000; Expires=Fri, 14-Apr-2034 09:26:09 GMT; Path=/; HttpOnly
Date: Tue, 16 Apr 2024 09:26:09 GMT
Server: APISIX/3.8.0

{"timestamp":"2024-04-16T17:26:09.846+08:00","status":401,"path":"/ams/amcenter","method":"GET","ms":3,"error":"login.required","message":"用户未登录"}%

五、总结

  1. 到这里我们了解到apisix可以继承k8s服务发现能力,从而将路由匹配到的url自动转到对应的k8s集群的后端服务进行处理,在实际使用中可以节省很多不必要的配置和处理。
  2. 同时apisix也支持多集群以及其他服务发现组件,可以根据实际需要来选择合适的组件和方案。
  3. 本文偏向方案探索实际使用之前,需要将discovery下的各个参数进行详细了解,以及各个参数配置不同值对于实际使用的影响进行评估,从而在生产中做到最优。