本文旨在简单解析 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 中相关的文件和接口为:
环境就搭建完成了,那 Sentinel 是怎么读取流控规则和控制流量的呢?
简单分析
在 Sentinel 有一个处理configmap流控规则的类:EnvoyRlsRuleManager
,它设置了一个监听器,当 etcd 数据发生变化时,就是触发更新本地保存的流控规则。
上面是处理规则更新的逻辑,那初始化呢,初始化类为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接口的实现类:
在 Sentnel 中,grpc服务器是如何启动的呢?
有一个类 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 方法。