Azure Functions 自动订阅到 Event Grid





5.00/5 (3投票s)
Azure 函数自动订阅到 Event Grid。
引言
随着 Event Grid 的引入,出现了更快地分发事件的新可能性,消除了像 Service Bus 中那样轮询事件的需要。现在,当消息满足订阅过滤器时,Event Grid 会自行执行简单的基于 HTTP 的事件传递,从而可以降低延迟和成本。到目前为止,一切都运行得很好,但要将函数连接到 Event Grid 需要大量的手动工作,需要在 Azure 门户中进行多次点击。考虑到在几个不同的环境中存在许多函数,这可能导致部署过程中的许多错误。但即使在部署之后,谁来维护它呢?你需要进行多少次点击才能在你的订阅中引入更改?
背景
“让我们项目中使用 Event Grid”——我提议道。事情就是这样开始的。
回想一下我们当时已经存在的 Service Bus 订阅的解决方案——每一个都手动创建、手动维护,没有变更历史。对我来说,这是“不可行”的。我希望有一个尽可能接近消费者的消费者定义(订阅定义),并且能够清楚地了解订阅在我们的代码存储库中是如何随时间变化的。
这个想法背后很简单
- 将消费者函数创建为 Azure
FunctionApp
。 - 在同一个 Azure
FunctionApp
中创建消费者订阅定义。 - 在 CI/CD 过程中使用 PowerShell 脚本将函数连接到 Event Grid。
将会有许多带有 FunctionApp
的组件。每个组件都将被包含在一个资源组中。每个 FunctionApp
可以有许多函数,并且这些函数中的每一个都可以有许多订阅定义。
-- Resource Group
|-- Function App
|-- function
| -- subscription filter
| -- ...
| -- ...
| -- ...
Using the Code
我在我的 FunctionApp
2.x 项目中创建了两个函数。第一个是标准的 EventGridTrigger
,负责消费传入的事件。第二个函数则包含订阅的定义。
消费者函数
using Microsoft.Azure.EventGrid.Models;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace EventGridSubscription2xFunctionApp
{
public class MyFunction
{
[FunctionName("MyFunction")]
public static async
Task Run([EventGridTrigger] EventGridEvent eventGridEvent, ILogger log)
{
...
}
}
}
关于 FunctionApp 1.x 的说明
请注意,为了让它适用于 Azure Functions 1.x,您必须使用特定的 NuGet 包版本。
绑定也不同。
[FunctionName("MyFunction")]
public static async Task Run
([EventGridTrigger] JObject eventGridEvent, TraceWriter log)
{
...
}
订阅定义函数
订阅函数必须遵循命名约定,以便以后能够轻松地发现并连接到 EventGrid
组件。在我们的例子中,我们选择使用“_Subscription
”作为包含 EventGrid
订阅定义的函数名称的后缀。
[FunctionName("MyFunction_Subscription")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
ILogger log)
{
var subscription1 = Create("subscription-name-1",
"my-event-type-11", "my-event-type-12");
var subscription2 = Create("subscription-name-2",
"my-event-type-2");
var subscriptions = new[] { subscription1, subscription2 };
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonConvert.SerializeObject
(subscriptions, Formatting.Indented),
Encoding.UTF8, "application/json")
};
}
该函数将由 HTTP 请求触发,并返回 JSON 格式的订阅定义。由于一个函数可以由不同的订阅触发,因此将为每个订阅创建单独的定义。在上面的示例中,它们是 subscription1
和 subscription2
。
private static EventGridSubscription Create
(string subscriptionName, params string[] eventTypes)
{
var subscription = new EventGridSubscription
{
Name = subscriptionName,
Prefix = "prefix", // Subject Begins With
Postfix = "postfix", // Subject Ends With
EventTypes = eventTypes
};
return subscription;
}
包含订阅定义的响应模型如下。响应是 JSON 格式的。
public class EventGridSubscription
{
private readonly List<string> _eventTypes;
public EventGridSubscription()
{
_eventTypes = new List<string>();
}
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("eventTypes")]
public string[] EventTypes { get; set; }
[JsonProperty("subjectBeginsWith")]
public string Prefix { get; set; }
[JsonProperty("subjectEndsWith")]
public string Postfix { get; set; }
}
PowerShell
此解决方案的核心组件是 PowerShell 脚本,该脚本从“_Subscription
”函数获取订阅定义,并在 EventGrid
上挂载 WebHook
来创建或更新订阅。在我们当前的解决方案中,我们有许多带有 FunctionApp
的组件。每个组件都在一个单独的资源组中。因此,脚本需要组件资源组名称以及包含 EventGrid
的资源组作为输入参数。
param (
[string][Parameter(Mandatory=$true)]$EventGridTopicName,
[string][Parameter(Mandatory=$true)]$EventGridTopicResourceGroupName,
[string][Parameter(Mandatory=$true)]$FunctionAppsResourceGroupName
)
逻辑很简单
- 查找所有
FunctionApp
- 查找所有带有“
_Subscription
”后缀的函数 - 获取函数密钥和订阅定义
- 在
EventGrid
上注册函数
现在一步一步来。
获取资源组中的所有 FunctionApp
只需获取 Kind
为“functionapp
”的所有资源。
{
[CmdletBinding()]
param
(
[string] [Parameter(Mandatory=$true)] $ResourceGroupName
)
$azureRmResourceParams = @{
ResourceGroupName = $ResourceGroupName
ResourceType = 'Microsoft.Web/sites'
ApiVersion = '2018-09-01'
}
$resources = Get-AzureRmResource @azureRmResourceParams
$functionApps = @()
foreach ($resource in $resources) {
if($resource.Kind -eq "functionapp"){
$name = $resource.Name
$functionApps += $name
}
}
return $functionApps
}
然后我们遍历 FunctionApp
并获取所有带有“_Subscription
”前缀的函数。只有这些函数会被处理。
$azureRmResourceParams = @{
ResourceGroupName = $ResourceGroupName
ResourceType = 'Microsoft.Web/sites/functions'
ResourceName = $FunctionApp
ApiVersion = '2015-08-01'
}
$functions = Get-AzureRmResource @azureRmResourceParams
foreach ($function in $functions) {
$functionName = $function.ResourceName
if ($functionName.endswith("_Subscription") )
{
...
}
}
这是核心逻辑,获取函数的主密钥,获取订阅,最后将它们作为元数据分组。
if($masterKey -eq $null){
$functionAppMasterKeyParams = @{
ResourceGroup = $ResourceGroupName
FunctionApp = $FunctionApp
}
$masterKey = Get-FunctionAppMasterKey @functionAppMasterKeyParams
}
## get subscription parameters
$subscriptionFunctionName = $functionName -replace "$FunctionApp/", ""
$subscriptionApiUrl =
'http://'+$FunctionApp+'.azurewebsites.net/api/'+$subscriptionFunctionName +
'?code='+$masterKey
$subscriptionResult = Invoke-WebRequest $subscriptionApiUrl |
ConvertFrom-Json
## get function endpoint
$functionAppName = $subscriptionFunctionName -replace "_Subscription", ""
$eventGridApiUrl = 'http://'+$FunctionApp+'.azurewebsites.net/
admin/host/systemkeys/
eventgrid_extension?code='+$masterKey
$sysKeyResult = Invoke-WebRequest $eventGridApiUrl |
select -Expand Content | ConvertFrom-Json | select value
$sysKey = $sysKeyResult.value
$eventGridSubscriptionEndpoint = 'https://'+$FunctionApp+
'.azurewebsites.net/runtime/webhooks/
eventgrid?functionName='+$functionAppName+'&code='+$sysKey
foreach($result in $subscriptionResult)
{
$subscription = @{
name = $result.name
endpoint = $eventGridSubscriptionEndpoint
eventTypes = $result.eventTypes
subjectEndsWith = $result.subjectEndsWith
subjectBeginsWith = $result.subjectBeginsWith
}
$subscription
}
关于 FunctionApp 1.x 与 2.x 的说明
FunctionApp
1.x 和 2.x 的 API 存在差异。与所述情况相关的差异如下。
SystemKey
API
1.x 'https://'+$FunctionApp+'.azurewebsites.net/admin/host/systemkeys/
eventgridextensionconfig_extension?code='+$MasterKey
2.x 'http://'+$FunctionApp+'.azurewebsites.net/admin/host/systemkeys/
eventgrid_extension?code='+$masterKey
Azure 函数 WebHook
用于 EventGrid
URL
1.x 'https://'+$FunctionApp+'.azurewebsites.net/admin/extensions/
EventGridExtensionConfig?functionName='+$functionAppName+'&code='+$sysKey
2.x 'https://'+$FunctionApp+'.azurewebsites.net/runtime/webhooks/
eventgrid?functionName='+$functionAppName+'&code='+$sysKey
关于 FunctionApp 2.x 的说明
2.x 版本中 FunctionApp
的密钥必须存储在存储文件中,而不是 blob 中。由于 blob 是默认值,因此应显式设置。PowerShell 脚本需要访问 FunctionApp
密钥。这可以通过 Azure 门户手动设置,或在 ARM 模板中自动化。我强烈建议您不要手动进行任何操作。
在 Azure DevOps 中整合
Azure DevOps 的标准发布管道可用于部署 FunctionApp
和 PowerShell 脚本。只需部署 FunctionApp
,然后运行 Azure PowerShell 任务,它将完成神奇的工作——将 EventGrid
连接到函数。
关注点
ARM 模板方法
还有其他方法可以做到这一点。您可以使用 此 ARM 模板。但是…… ARM 模板的流程是这样的:
- 部署
FunctionApp
- 使用 PowerShell 获取
FunctionApp
的SystemKey
- 使用
SystemKey
部署 ARM
这是一个先有鸡还是先有蛋的问题。在部署 FunctionApp
之前,您需要 SystemKey
。您无法在 FunctionApp
部署期间将其提取并传递给同一个 ARM 中的 EventGrid
模板。第二个缺点(在我看来)是订阅定义保存在 ARM 模板中。我希望将过滤器定义保留在靠近消费者代码的地方。
历史
- 2018 年 11 月 19 日:初始版本
- 2018 年 11 月 20 日:添加了 EventGridSubscription.zip