Sentinel结合Envoy流控样例分析

470 阅读4分钟

本文旨在简单解析 Sentinel 结合 Envoy 实现流控的样例。

在 Sentinel 中有一个模块 sentinel-cluster-server-envoy-rls

该模块提供了Envoy rate limiting gRPC service实现 Sentinel token server

Token Server 已经支持了Envoy RLS v2 and v3 API,从Envoy 1.18.0版本,v2 API 已经不支持移除了

Sentinel Envoy RLS - Kubernetes sample

下面这个例子将会描述如何介结合k8s集群的envoy使用Sentinel RLS token server

1) 构建Docker镜像

在Sentinel的sentinel-cluster-server-envoy-rls目录下构造Docker镜像

docker build -t "sentinel/sentinel-envoy-rls-server:latest" -f ./Dockerfile .

Dockerfile内容为:

FROM java:8-jre

ENV SENTINEL_HOME /sentinel

COPY target/sentinel-envoy-rls-token-server.jar $SENTINEL_HOME/

WORKDIR $SENTINEL_HOME

ENTRYPOINT ["sh", "-c"]
CMD ["java -Dcsp.sentinel.log.dir=/sentinel/logs/ ${JAVA_OPTS} -jar sentinel-envoy-rls-token-server.jar"]

2)部署Sentinel RLS token server 本步骤我们将在k8s集群中部署Sentinel RLS token server

我们提供了一个部署文件模版 sentinel-rls.yml

apiVersion: v1
kind: ConfigMap
metadata:
  name: sentinel-rule-cm
data: # 通过configmap设置一个简单的流控规则
  rule-yaml: |- 
    domain: foo #仅对domain foo生效
    descriptors:
      - resources:
        - key: "destination_cluster"
          value: "service_httpbin" #处理目标集群为 service_httpbin
        count: 1 # 最大QPS为1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sentinel-rls-server
  labels:
    app: sentinel
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sentinel
  template:
    metadata:
      labels:
        app: sentinel
    spec:
      containers:
        - name: sentinelserver
          # You could replace the image with your own image here
          image: "registry.cn-hangzhou.aliyuncs.com/sentinel-docker-repo/sentinel-envoy-rls-server:latest" #该镜像实际就是上述第一步构造的镜像
          imagePullPolicy: Always
          ports:
            - containerPort: 10245
            - containerPort: 8719
          volumeMounts: # 设置ConfigMap存储卷
            - name: sentinel-rule-config
              mountPath: /tmp/sentinel
          env:
            - name: SENTINEL_RLS_RULE_FILE_PATH # 设置容器环境变量,该变量为流控规则文件位置
              value: "/tmp/sentinel/rule.yaml"
      volumes: 
        - name: sentinel-rule-config
          configMap: # 存储卷插件类型
            name: sentinel-rule-cm
            items: # 要暴露的键值数据
              - key: rule-yaml # 这个就是上述configmap中data字段中的数据
                path: rule.yaml # 对应的键在挂载点目录中映射的文件名称,该例中在容器中的位置为/tmp/sentinel/rule.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: sentinel-rls-service # 定义处理流控规则的服务,实际 rate limit cluster处理规则就是它处理
  labels:
    name: sentinel-rls-service
spec:
  type: ClusterIP
  ports:
    - port: 8719
      targetPort: 8719
      name: sentinel-command
    - port: 10245
      targetPort: 10245
      name: sentinel-grpc
  selector:
    app: sentinel

3) 部署 Envoy 实例

我们在K8s集群中部署 Envoy 实例,如果你的集群中已经存在了 Envoy 实例,你就可以配置rate limit cluster的地址sentinel-rls-service和端口port 10245,实际者两个值就是上述Service中的值。

配置 Envoy 的yaml文件太长了,我只从中截取关键部分,具体内容可以到 Sentinel 中去查询,地址: github.com/alibaba/Sen…

http_filters:
						.......
                - name: envoy.filters.http.ratelimit
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
                    domain: foo # 和上述 sentinel-rls.yml 中configmap中定义的一致
                    request_type: external
                    failure_mode_deny: false
                    stage: 0
                    rate_limit_service:
                      grpc_service:
                        envoy_grpc:
                          cluster_name: rate_limit_cluster # 处理流控的grpc服务
                        timeout: 2s
                      transport_api_version: V3
                - name: envoy.filters.http.router
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
      clusters:
      - name: service_httpbin
        connect_timeout: 0.5s
        type: LOGICAL_DNS
        # Comment out the following line to test on v6 networks
        dns_lookup_family: V4_ONLY
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: service_httpbin
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: httpbin.org
                    port_value: 80
      - name: rate_limit_cluster
        type: STRICT_DNS
        connect_timeout: 10s
        lb_policy: ROUND_ROBIN
        http2_protocol_options: {}
        load_assignment:
          cluster_name: rate_limit_cluster
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: sentinel-rls-service # 上述处理流控grpc接口的实际服务,就是 sentinel-rls.yml 中定义的服务
                    port_value: 10245

Envoy中提供了一个 rate limite service 服务,实现流控。但在实际中,我们可能需要根据不同场景设置一定的流控规则,rate limite service提供了一个grpc接口 RateLimitService.ShouldRateLimit,我们可以在该接口中实现自己想要的流控处理逻辑。流控介绍文档:www.envoyproxy.io/docs/envoy/…

在 Sentinel 中相关的文件和接口为:

ratelimit.png

环境就搭建完成了,那 Sentinel 是怎么读取流控规则和控制流量的呢?

简单分析

在 Sentinel 有一个处理configmap流控规则的类:EnvoyRlsRuleManager,它设置了一个监听器,当 etcd 数据发生变化时,就是触发更新本地保存的流控规则。

listener.png

上面是处理规则更新的逻辑,那初始化呢,初始化类为EnvoyRlsRuleDataSourceService

我截取了部分该类的代码:

public class EnvoyRlsRuleDataSourceService {

    private final Yaml yaml;
    private ReadableDataSource<String, List<EnvoyRlsRule>> ds;

    .........
    public synchronized void init() throws Exception {
       
        ..........
        this.ds = new FileRefreshableDataSource<>(configPath, s -> Arrays.asList(yaml.loadAs(s, EnvoyRlsRule.class)));
        EnvoyRlsRuleManager.register2Property(ds.getProperty());
    }

    .......

    private String getRuleConfigPath() {
        return Optional.ofNullable(System.getenv(SentinelEnvoyRlsConstants.RULE_FILE_PATH_ENV_KEY))
            .orElse(SentinelConfig.getConfig(SentinelEnvoyRlsConstants.RULE_FILE_PATH_PROPERTY_KEY));
    }
}

在上述 getRuleConfigPath方法中,实际就是读取 sentinel-rls.yml 文件中配置的环境变量 SENTINEL_RLS_RULE_FILE_PATH的值。

处理流控grpc接口的实现类:

shouldRateLimit.png

在 Sentnel 中,grpc服务器是如何启动的呢?

grpc.png

有一个类 SentinelEnvoyRlsServer,在māin方法中,进行了初始化configmap配置文件和grpc服务器。

public class SentinelRlsGrpcServer {

    private final Server server;

    public SentinelRlsGrpcServer(int port) {
        ServerBuilder<?> builder = ServerBuilder.forPort(port)
            .addService(new com.alibaba.csp.sentinel.cluster.server.envoy.rls.service.v3.SentinelEnvoyRlsServiceImpl())
            .addService(new SentinelEnvoyRlsServiceImpl());
        # 定义grpc处理哪些服务
        server = builder.build();
    }
  .........
}

那这个main方法又是什么时候执行的呢?

Sentinel在 pom.xml 文件中,有这么一段配置:

<transformers>
     <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
       <mainClass>
 com.alibaba.csp.sentinel.cluster.server.envoy.rls.SentinelEnvoyRlsServer
       </mainClass>
    </transformer>
</transformers>

这段配置使用了 Maven Shade 插件中的 ManifestResourceTransformer 转换器,将 com.alibaba.csp.sentinel.cluster.server.envoy.rls.SentinelEnvoyRlsServer 类指定为 JAR 文件的入口类。这样,在运行 JAR 文件时,就会自动执行该类中的 main 方法。