概要
云应用程序通常都需要使用前端网关,为用户、设备或其他应用程序提供同一个入口点。 在 Service Fabric 中,网关可以是任意无状态服务(如 ASP.NET Core 应用程序) 。
本文介绍了如何将Ocelot用作 Service Fabric 应用程序的网关。Ocelot直接与 Service Fabric 集成,以便可以使用一组丰富的路由规则向后端 Service Fabric 服务发布 API。
架构
常见 Service Fabric 体系结构使用单页 Web 应用程序,向公开 HTTP API 的后端服务发出 HTTP 调用请求。
随着应用程序越来越复杂,必须向大量后端服务发布API的网关亦是如此。Ocelot旨在通过路由规则、访问控制、速率限制、监视、事件日志记录和响应缓存来处理复杂 API,最大限度地减少用户需要执行的操作。 Ocelot支持 Service Fabric 服务发现、分区解析和副本选择,从而智能地将请求直接路由到 Service Fabric 中的后端服务,用户无需编写自己的无状态 API 网关。
应用程序方案
Service Fabric 中的服务可以是无状态服务,也可以是有状态服务,可采用以下三种方案之一进行分区:单独分区、Int64 范围分区和已命名分区。 必须确定特定服务实例的具体分区,才能解析服务终结点。解析服务终结点时,必须指定服务实例名称(例如,fabric:/myapp/myservice)以及服务的具体分区,但单独分区情况除外。
Ocelot可与无状态服务、有状态服务和任何分区方案的任意组合配合使用。
https://ocelot.readthedocs.io/en/latest/features/servicefabric.html
如果您正在使用无状态/Guest服务,则ocelot将能够通过命名服务进行代理而无需其他任何操作。但是,如果您正在使用有状态服务/ actor服务,则必须使用客户端请求发送PartitionKind和PartitionKey查询字符串值。
以下示例展示如何设置一个ReRoute以便在在Service Fabric中工作。 最重要的是ServiceName,它由Service Fabric应用程序名称和特定服务名称组成的。 我们还需要将UseServiceDiscovery设置为true,并在GlobalConfiguration中设置ServiceDiscoveryProvider。 这里的例子显示了一个典型的配置。 它假定Service Fabric在本地主机上运行,并且命名服务位于19081端口上。
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/values",
"UpstreamPathTemplate": "/servicea/api/values",
"UpstreamHttpMethod": [ "Get"],
"DownstreamScheme": "http",
"ServiceName": "NanoFabric_ServiceFabric/ServiceA",
"UseServiceDiscovery": true,
"AuthenticationOptions": {
"AuthenticationProviderKey": "apikey",
"AllowedScopes": []
},
"AddHeadersToRequest": {
"claims_City": "Claims[City] > value > |",
"claims_State": "Claims[State] > value > |"
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10,
"TimeoutValue": 5000
}
},
{
"DownstreamPathTemplate": "/{route}",
"UpstreamPathTemplate": "/serviceoauth/{route}",
"UpstreamHttpMethod": [ "Get", "Options", "Post" ],
"DownstreamScheme": "http",
"ServiceName": "NanoFabric_ServiceFabric/ServiceOAuth",
"UseServiceDiscovery": true
}
],
"GlobalConfiguration": {
"RequestIdKey": "OcRequestId",
"AdministrationPath": "/administration",
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 19081,
"Type": "ServiceFabric"
}
}
}
原理就是借助 Service Fabric 中内置的反向代理,Service Fabric 群集中运行的微服务可以发现包含 http 终结点的其他服务,并与之通信。
微服务通信模型
Service Fabric 中的微服务在群集中的部分节点上运行,可以出于各种原因在这些节点之间迁移。 因此,微服务的终结点可能会动态变化。 若要发现群集中的其他服务并与之通信,微服务必须完成以下步骤:
l 通过命名服务解析服务位置。
l 连接到服务。
l 在实现服务解析以及在发生连接故障时应用的重试策略的循环中,包装上述步骤
使用反向代理通信
反向代理是在每个节点上运行的服务,用于代表客户端服务处理终结点解析、自动重试及其他连接故障。 可以将反向代理配置为,一边处理客户端服务的请求,一边应用各种策略。 借助反向代理,客户端服务可以使用任意客户端 HTTP 通信库,无需服务中有特殊的解析和重试逻辑。
反向代理在本地节点上公开一个或多个终结点,以供客户端服务用来向其他服务发送请求。
反向代理使用特定的统一资源标识符 (URI) 格式来识别传入请求应该转发到的服务分区:
http(s)://<Cluster FQDN | internal IP>:Port/<ServiceInstanceName>/<Suffix path>?PartitionKey=<key>&PartitionKind=<partitionkind>&ListenerName=<listenerName>&TargetReplicaSelector=<targetReplicaSelector>&Timeout=<timeout_in_seconds>
l http(s): 可以将反向代理配置为接受 HTTP 或 HTTPS 流量。 对于 HTTPS 转发,在设置反向代理侦听 HTTPS 后,请参阅使用反向代理连接到安全服务。
l 群集的完全限定域名 (FQDN) | 内部 IP: 对于外部客户端,可以配置反向代理,以便可以通过群集域(例如 mycluster.eastus.cloudapp.azure.com)访问反向代理。 默认情况下,反向代理在每个节点上运行。 对于内部流量,可在本地主机或任意内部节点 IP(例如 10.0.0.1)*问反向代理。
l Port:为反向代理指定的端口,例如 19081。
l ServiceInstanceName: 在不使用“fabric:/”方案的情况下尝试访问的已部署服务实例的完全限定名称。 例如,若要访问 fabric:/myapp/myservice/ 服务,可以使用 myapp/myservice。
l 服务实例名称要区分大小写。 若 URL 中的服务实例名称大小写不同,则会导致请求失败,并显示 404(未找到)。
l 后缀路径: 要连接到的服务的实际 URL 路径,例如 myapi/values/add/3。
l PartitionKey: 对于分区服务,这是针对要访问的分区计算出的分区键。 请注意,这不是分区 ID GUID。 对于使用单独分区方案的服务,此参数不是必需的。
l PartitionKind: 服务分区方案。 该方案可以是“Int64Range”或“Named”。 对于使用单独分区方案的服务,此参数不是必需的。
l ListenerName 服务中的终结点采用以下形式:{"Endpoints":{"Listener1":"Endpoint1","Listener2":"Endpoint2" ...}}。 当服务公开了多个终结点时,此参数标识应将客户端请求转发到的终结点。 如果服务只有一个侦听器,则可以省略此项。
l TargetReplicaSelector 这指定应当如何选择目标副本或实例。
l 当目标服务为有状态服务时,TargetReplicaSelector 可以是下列其中一项:“PrimaryReplica”、“RandomSecondaryReplica”或“RandomReplica”。 如果未指定此参数,默认值为“PrimaryReplica”。
l 当目标服务为无状态服务时,反向代理将选择服务分区的一个随机实例来将实例转发到其中。
l Timeout: 此参数指定反向代理针对服务创建的 HTTP 请求(代表客户端请求)的超时。 默认值为 60 秒。 这是一个可选参数
Ocelot充当微服务和外部客户端之间的网络边界,可以进行网络地址转换并将外部请求转发到内部的 IP:端口终结点。 要允许外部客户端直接访问微服务的终结点,必须先将Ocelot配置为将流量转发到群集中服务使用的每个端口。 另外,大多数微服务(尤其是有状态微服务)并不驻留在群集的所有节点上。 这些微服务在故障转移时可在节点之间移动。 在这种情况下,负载均衡器无法有效确定要将流量转发到的副本的目标节点位置。
可以在Ocelot中直接配置反向代理的端口,而无需配置单个服务的端口。 这种配置可让群集外部的客户端使用反向代理访问群集内部的服务,无需经过额外的配置。
通过Ocelot可从群集外部访问群集中公开 HTTP 终结点的所有微服务。 这意味着微服务设计为内部的可能会被确定的恶意用户发现。这潜在地提供可被利用的严重漏洞;例如:
恶意用户可以通过反复调用没有足够强化的攻击面的内部服务来发起拒绝服务攻击。
恶意用户可能会将格式错误的数据包传送到内部服务,从而导致意外行为。
设计为内部的服务可能会返回不应公开给群集外部的服务的私有或敏感信息,从而将此敏感信息泄露给恶意用户。在网关上开启身份认证、流控等措施来解决安全问题。
反向代理是一种可选的 Azure Service Fabric 服务,有助于在 Service Fabric 群集中运行的微服务发现包含 http 终结点的其他服务,并与之通信,在创建新的 Service Fabric 群集时,Azure 门户提供了一个启用反向代理的选项。 无法通过门户升级现有群集来使用反向代理。
我们的示例项目
我们的示例项目代码放在 https://github.com/geffzhang/NanoFabric-ServiceFabric ,解决方案中包含了一个后端服务ServiceA,是个无状态的服务,一个Ocelot 网关和一个Identity Server 4的认证服务,在网关上集成了IdentityServer4 的认证服务 ,由网关负责认证,认证完成将Claims 转换为HttpHeader 中转发到下游服务。
服务实例A是一个无状态的服务
我们将其配置为运行2个实例。在Application Parameters中,我将* _InstanceCount参数值设置为2:
让Service Fabric选择端口,我们将从端点中删除该Port
属性:
当开发机器上的无法实现在同一端口上运行多个实例,如果填写了Port 属性,_InstanceCount只能保持为1. 让端口保持动态,我们可以在本地实现服务的伸缩。
部署自己的网关
部署自己的网关这听起来像是需要做很多工作,实际上非常简单。我们需要与反向代理相同的行为,只需要更多的控制。在我们这个开源的开发的世界,这个问题已经解决了,我们有开源的API网关Ocelot http://threemammals.com/ocelot ,而且做得非常好,可以完美的和Service Fabric 一起工作。
我们将添加一个新的空aspnet core无状态服务
让我们配置我们的端点。您需要知道我们的网关在哪里,所以我们给它一个特定的端口。在ServiceManifest中,设置端点的端口:
<Endpoint Protocol="http" Name="ServiceEndpoint" Type="Input" Port="8492" />
网关是系统的入口点,必须保持可用状态。我们在每个节点上部署它。修改ApplicationParameters中添加的参数NanoFabricGateway_InstanceCount。确保生产部署的值为-1。
<Parameter Name="NanoFabricGateway_InstanceCount" Value="-1"/>
请注意,如果部署到本地群集,则无法在同一端口上运行多个服务实例。对于本地开发群集,要么将其保留为1,要么让端口为动态。
代码实现上并没有什么特殊的地方。
配置Azure负载均衡器
当然,没有人想通过端口访问您的网站8492。接下来,我们需要设置负载均衡器以指向我们新部署的网关。在部署在azure上的新集群(可以参考这篇文章使用Powershell https://noelbundick.com/posts/service-fabric-cluster-quickstart/ ),现有AppPortLBRule1端口80。编辑它,并将后端端口更改为指向网关端口:8492在我的情况下。同时请注意,Load Balancer定义了一个Health Probe。如果健康检测未成功,则负载均衡器将假定后端服务池不健康,并且不会重定向您的请求。
参考文章:
https://docs.microsoft.com/zh-cn/azure/service-fabric/service-fabric-api-management-overview
https://ocelot.readthedocs.io/en/latest/features/servicefabric.html