这一篇仍是承接以前的思路,在上一篇文章中,咱们分享了一个思路,能够把storage account的访问日志,经过PowerShell脚本下载下来,而后自动上传到Log Analytics,这样就能够直接在LA中对日志进行分析和查询了,这种方法虽然简单方便,但终究算不上是自动化的方式,扩展一下思路以后,咱们不妨尝试着用eventgrid结合function的方式,把这套方法转换成一套自动化的方案
json
首先先把EventGrid和Function是什么普及下api
EventGrid服务器
经过 Azure 事件网格,可以使用基于事件的体系结构轻松生成应用程序。 首先,选择要订阅的 Azure 资源,而后提供要向其发送事件的事件处理程序或 WebHook 终结点。 事件网格包含来自 Azure 服务对事件的内置支持,如存储 blob 和资源组。 事件网格还使用自定义主题支持本身的事件。微信
简单来讲就像是个触发器同样,能够触发各类事件,而后作出针对性的响应,听起来和logic apps里的触发器有点像,但eventgrid只是个单纯的事件中转工具,下游的处理方式彻底由其余产品完成,如下这张图也能够看出来eventgrid所处的位置,相似一个消息队列同样用来作事件的传输app
https://docs.microsoft.com/en-us/azure/event-grid/?WT.mc_id=AZ-MVP-5001235 ide
Function工具
Azure Functions 是一种无服务器解决方案,可使用户减小代码编写、减小须要维护的基础结构并节省成本。 无需担忧部署和维护服务器,云基础结构提供保持应用程序运行所需的全部最新资源。post
Function其实比较好理解,如今各个云基本上都有相似的产品,横向对比的话就是AWS的lambda,一个彻底托管的代码运行平台ui
https://docs.microsoft.com/zh-cn/azure/azure-functions/functions-overview?WT.mc_id=AZ-MVP-5001235 this
结合上边的图,其实就能看出来咱们的思路,eventgird内置了blob的触发器,也就是说当新的blob出现时,就会自动触发eventgrid,而下游的处理程序,咱们就能够结合function来作,代码实际上是现成的,就用以前的就能够,只不过须要稍加改动而已,整体的工做量很小
固然这里其实有个隐藏的问题,由于storage account的log是存储在$logs容器里,而这个容器在Azure后台是不会触发eventgrid的,这就有点坑了,咱们采用的方法是能够建立个function,而后用azcopy按期的把log sync到另一个container便可,一行代码就搞定,很简单,因此暂时先不写出来了
实现步骤
下边就来看具体的实现步骤了,首先先建立好function app,function app和function的关系很简单,function app至关因而运行function的平台,代码是跑在这个平台上的,function app中能够包含不少个function
建立Function App
function app建立过程很是简单,咱们选择runtime是PowerShell Core便可
建立Function
Function app建立好以后,就能够在里边建立function了,azure其实内置了evenr grid trigger的function,建立时直接选择便可
建立Event Grid Subscription
function准备好了,接下来就能够准备eventgrid了,能够直接在function里建立event grid subscription
建立event grid subscription时,能够选择的type有不少,这里注意要选择storage类型的
配置Storage account的event type
而后注意须要配置一下filter,由于咱们要把路径限制到某个特定的范围,而不是全部blob都会触发事件
触发器也准备完成了,接下来就能够准备在function里处理的代码了
编写Code
由于以前的代码是循环处理的,而eventgrid其实是一条条推送过来的,因此这里的逻辑须要进行些许调整
Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource) { $xHeaders = "x-ms-date:" + $date $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource $bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash) $keyBytes = [Convert]::FromBase64String($sharedKey) $sha256 = New-Object System.Security.Cryptography.HMACSHA256 $sha256.Key = $keyBytes $calculatedHash = $sha256.ComputeHash($bytesToHash) $encodedHash = [Convert]::ToBase64String($calculatedHash) $authorization = 'SharedKey {0}:{1}' -f $customerId,$encodedHash return $authorization } Function Post-LogAnalyticsData($customerId, $sharedKey, $body, $logType) { $method = "POST" $contentType = "application/json" $resource = "/api/logs" $rfc1123date = [DateTime]::UtcNow.ToString("r") $contentLength = $body.Length $signature = Build-Signature ` -customerId $customerId ` -sharedKey $sharedKey ` -date $rfc1123date ` -contentLength $contentLength ` -method $method ` -contentType $contentType ` -resource $resource $uri = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01" $headers = @{ "Authorization" = $signature; "Log-Type" = $logType; "x-ms-date" = $rfc1123date; "time-generated-field" = $TimeStampField; } $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing return $response.StatusCode } Function ConvertSemicolonToURLEncoding([String] $InputText) { $ReturnText = "" $chars = $InputText.ToCharArray() $StartConvert = $false foreach($c in $chars) { if($c -eq '"') { $StartConvert = ! $StartConvert } if($StartConvert -eq $true -and $c -eq ';') { $ReturnText += "%3B" } else { $ReturnText += $c } } return $ReturnText } Function FormalizeJsonValue($Text) { $Text1 = "" if($Text.IndexOf("`"") -eq 0) { $Text1=$Text } else {$Text1="`"" + $Text+ "`""} if($Text1.IndexOf("%3B") -ge 0) { $ReturnText = $Text1.Replace("%3B", ";") } else { $ReturnText = $Text1 } return $ReturnText } Function ConvertLogLineToJson([String] $logLine) { $logLineEncoded = ConvertSemicolonToURLEncoding($logLine) $elements = $logLineEncoded.split(';') $FormattedElements = New-Object System.Collections.ArrayList foreach($element in $elements) { $NewText = FormalizeJsonValue($element) $FormattedElements.Add($NewText) > null } $Columns = ( "version-number", "request-start-time", "operation-type", "request-status", "http-status-code", "end-to-end-latency-in-ms", "server-latency-in-ms", "authentication-type", "requester-account-name", "owner-account-name", "service-type", "request-url", "requested-object-key", "request-id-header", "operation-count", "requester-ip-address", "request-version-header", "request-header-size", "request-packet-size", "response-header-size", "response-packet-size", "request-content-length", "request-md5", "server-md5", "etag-identifier", "last-modified-time", "conditions-used", "user-agent-header", "referrer-header", "client-request-id" ) $logJson = "[{"; For($i = 0;$i -lt $Columns.Length;$i++) { $logJson += "`"" + $Columns[$i] + "`":" + $FormattedElements[$i] if($i -lt $Columns.Length - 1) { $logJson += "," } } $logJson += "}]"; return $logJson } $storageAccount = Get-AzStorageAccount -ResourceGroupName $ResourceGroup -Name $StorageAccountName -ErrorAction SilentlyContinue if($null -eq $storageAccount) { throw "The storage account specified does not exist in this subscription." } $storageContext = $storageAccount.Context $token = $Null $maxReturn = 5000 $successPost = 0 $failedPost = 0 $subject=$eventGridEvent.subject.ToString() $BlobArray=$subject.Split('/') $container=$BlobArray[$BlobArray.indexof('containers')+1] $BlobIndex=$subject.indexof('blobs/')+6 $Blob=$subject.substring($BlobIndex,$subject.length - $BlobIndex) Write-Output("> Downloading blob: {0}" -f $blob) $filename = ".\log.txt" Get-AzStorageBlobContent -Context $storageContext -Container $container -Blob $blob -Destination $filename -Force > Null Write-Output("> Posting logs to log analytic workspace: {0}" -f $blob) $lines = Get-Content $filename foreach($line in $lines) { $json = ConvertLogLineToJson($line) $response = Post-LogAnalyticsData -customerId $customerId -sharedKey $sharedKey -body ([System.Text.Encoding]::UTF8.GetBytes($json)) -logType $logType if($response -eq "200") { $successPost++ } else { $failedPost++ Write-Output "> Failed to post one log to Log Analytics workspace" } } remove-item $filename -Force Write-Output "> Log lines posted to Log Analytics workspace: success = $successPost, failure = $failedPost"
最后还有一个步骤是须要给function app受权来访问storage,这步就不详细讲了,也能够用storage account key来作,固然这种方法其实不是很推荐
最后能够在function里的监控里看到完成的效果
总结
整体来讲,其实和单纯经过PowerShell脚本的方式相比变化并不大,可是由于增长了eventgrid和function,让整个方案变得更加灵活,相似的思路还能够扩展到不少其余任务上,以更cloud native的方式来看待和处理问题