概要 †Azure Monitor の統合アラート エクスペリエンスには、以前は Log Analytics と Application Insights によって管理されていたアラートも含まれるようになった。 目次 †
どのようなログを検知するかを決める。 †Azure Monitor では、設定したクエリに合致するレコードの件数や、数値の平均値などが一定数以上の時に通知を行う事ができる。
上記の前提で、今回は異常チェック用として以下のクエリを使用する。 traces
| where (message has_cs "[ERROR] Result:" or message has_cs "http: panic serving")
and cloud_RoleName == "関数アプリ名(全て小文字)"
and timestamp > ago (1h)
※ Kustoクエリについては Kusto クエリ言語 を参照。 サンプルアプリ †ストレージコンテナ1にアップロードされたCSVファイルをストレージコンテナ2にそのまま出力するシンプルな関数アプリ。
{
"version": "2.0",
"httpWorker": {
"description": {
"defaultExecutablePath": "server.exe"
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
}
}
{
"bindings" : [ {
"type" : "blobTrigger",
"direction" : "in",
"name" : "blobData",
"path" : "ストレージコンテナ名1/{name}",
"dataType" : "binary",
"connection" : "AzureWebJobsStorage"
},
{
"type" : "blob",
"direction" : "out",
"name" : "$return",
"path" : "ストレージコンテナ名2/{name}",
"dataType" : "binary",
"connection" : "AzureWebJobsStorage"
} ]
}
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
)
type ReturnValue struct {
Data string
}
type InvokeResponse struct {
Outputs map[string]interface{}
Logs []string
ReturnValue interface{}
}
type InvokeRequest struct {
Data map[string]interface{}
Metadata map[string]interface{}
}
func printDebug(format string, params ...interface{}){
log.SetOutput(os.Stdout)
msg := fmt.Sprintf(format, params...)
log.Printf("[DEBUG] %s\n", msg)
}
func printInfo(format string, params ...interface{}){
log.SetOutput(os.Stdout)
msg := fmt.Sprintf(format, params...)
log.Printf("[INFO] %s\n", msg)
}
func printError(format string, params ...interface{}){
log.SetOutput(os.Stderr)
msg := fmt.Sprintf(format, params...)
log.Printf("[ERROR] %s\n", msg)
log.SetOutput(os.Stdout)
}
func init(){
log.SetOutput(os.Stdout)
log.SetFlags(0)
}
func blobTriggerHandler(w http.ResponseWriter, r *http.Request) {
printInfo("START blobTriggerHandler")
fileUri := ""
defer func(){
err := recover()
if fileUri == "" {
fileUri = "unknown"
}
if err != nil {
printError("Result: Failure %s", fileUri)
panic("blobTriggerHandler Error!\n");
} else {
printInfo("Result: Success %s", fileUri)
}
}()
logs := make([]string, 0)
//------------------------------------------------
// データ取得
//------------------------------------------------
var invokeReq InvokeRequest
d := json.NewDecoder(r.Body)
decodeErr := d.Decode(&invokeReq)
if decodeErr != nil {
http.Error(w, decodeErr.Error(), http.StatusBadRequest)
return
}
fileUri = invokeReq.Metadata["Uri"].(string)
filename := invokeReq.Metadata["name"].(string)
sysInfo := fmt.Sprintf("%v", invokeReq.Metadata["sys"])
fileData, _ := base64.StdEncoding.DecodeString(invokeReq.Data["blobData"].(string))
printDebug("filename : %s", filename)
printDebug("fileUri : %s", fileUri)
printDebug("sysinfo : %s", sysInfo)
//------------------------------------------------
// CSV文字列をパースする
//------------------------------------------------
rows := parseCsv(string(fileData), fileUri)
printDebug("csv data: %v", rows)
//------------------------------------------------
// レスポンス生成
//------------------------------------------------
returnValue := fileData
invokeResponse := InvokeResponse{Logs: logs, ReturnValue: string(returnValue)}
js, err := json.Marshal(invokeResponse)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(js)
printInfo("END blobTriggerHandler")
}
/**
* CSV文字列のパース.
*/
func parseCsv(csvText string, fileUri string) ([]map[string]string) {
procIndex := -1
defer func(){
err := recover()
if err != nil {
printError("error: file: %s, line: %d, %v", fileUri, procIndex, err)
panic("parseCsv Error!\n");
}
}()
lines := strings.Split(csvText, "\n")
var columns []string
rows := make([]map[string]string, 0)
for i, line := range lines {
procIndex = i
if i == 0 {
columns = strings.Split(line, ",")
} else {
values := strings.Split(line, ",")
row := make(map[string]string, len(values))
for j, val := range values {
// ヘッダの列数より多い時はわざとコケるようにしておく
colname := columns[j]
row[colname] = val
}
rows = append(rows, row)
}
}
return rows
}
/**
* メイン.
*/
func main() {
httpInvokerPort, exists := os.LookupEnv("FUNCTIONS_HTTPWORKER_PORT")
if exists {
log.Println("FUNCTIONS_HTTPWORKER_PORT: " + httpInvokerPort)
}
mux := http.NewServeMux()
mux.HandleFunc("/MyBlobTrigger", blobTriggerHandler)
log.Println("Go server Listening...on httpInvokerPort:", httpInvokerPort)
log.Fatal(http.ListenAndServe(":"+httpInvokerPort, mux))
}
#!/bin/bash
# 全てのリソース名に付与する接頭文字 (Storageアカウント名などは世界でユニークな必要があるので他ユーザと被らないような名前を付ける)
PREFIX=XXXXXXX
subscriptionId=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
region=japanwest
insightsRegion=japaneast # 2020/7時点では Appplication Insights で西日本(japanwest)は使用できない
resourceGroup=${PREFIX}ResourceGroup
storageAccountName=${PREFIX}straccount
storageSku=Standard_LRS
storageContainerIn=${PREFIX}-strcontainer-i
storageContainerOut=${PREFIX}-strcontainer-o
funcAppName=${PREFIX}FuncApp
funcVersion=2
funcPlanName=${PREFIX}premiumplan
funcPlanSku=EP1
insightsName=${PREFIX}Insights
insightsDays=30
# リソースグループの作成
echo az group create
az group create \
--name $resourceGroup \
--location $region
# ストレージアカウントの作成
echo az storage account create
az storage account create \
--name $storageAccountName \
--location $region \
--resource-group $resourceGroup \
--sku $storageSku
# Storageコンテナの作成
echo "az storage container create(in)"
az storage container create \
--name $storageContainerIn \
--resource-group $resourceGroup \
--account-name $storageAccountName
echo "az storage container create(out)"
az storage container create \
--name $storageContainerOut \
--resource-group $resourceGroup \
--account-name $storageAccountName
# Application Insights 拡張が利用できない場合は追加インストール
x=`az monitor app-insights --help 2>&1`
if [ "$?" != "0" ]; then
az extension add -n application-insights
fi
# Application Insights コンポーネント作成
if [ "$subscriptionId" != "" ]; then
echo az monitor app-insights component create
az monitor app-insights component create \
--app $insightsName \
--location $insightsRegion \
--resource-group $resourceGroup \
--query-access Enabled \
--retention-time $insightsDays \
--subscription $subscriptionId
fi
# 関数アプリのプラン作成(プレミアムプラン)
echo az functionapp plan create
az functionapp plan create \
--name $funcPlanName \
--resource-group $resourceGroup \
--location $region \
--sku $funcPlanSku
# 関数アプリの作成
echo az functionapp create
az functionapp create \
--name $funcAppName \
--storage-account $storageAccountName \
--resource-group $resourceGroup \
--functions-version $funcVersion \
--plan $funcPlanName \
--app-insights $insightsName
#!/bin/bash
PREFIX=XXXXXXX
resourceGroup=${PREFIX}ResourceGroup
funcAppName=${PREFIX}FuncApp
rm -rf functions.zip
exefile=`cat functions/host.json | grep defaultExecutablePath | awk '{print $2}' | sed 's/"//g'`
cd functions
rm -rf $exefile
GOOS=windows GOARCH=amd64 go build -o $exefile
zip -r ../functions.zip *
cd ../
az functionapp deployment source config-zip -g $resourceGroup -n $funcAppName --src functions.zip
rm -rf functions.zip
リソース作成 ./create_resources.sh 関数アプリのデプロイ ./deploy_funcapp.sh アラートの作成 †Azureポータルで "モニター" を検索し、選択。 アラートから [新しいアラートルール] を選択。 関数アプリに関連付けている Application Insight を選択し、条件名のリンクをクリック。 custom log search で下図のように入力/選択。 [アクショングループの選択] を押下。 リソースグループ、アクショングループ名などを入力。(まだ [確認及び作成] は押さない) [通知]タブに切り替えて、通知の種類、名前を入力。(電子メール/プッシュ通知... を選択する) 通知先のメールアドレスを入力し [OK] アクションタブでは他の関数の起動等を設定できるが、今回は何も指定せずに [確認及び作成]。 メールの件名などを入力し [アラートルールの作成] を押下。 エラー通知の例 †上記で設定したアラートに合致するログが見つかった時には、下図のようなメールが送信されてくる。
メール本文の [View 10 Result(s)] ボタンを押下すると Azure ポータルの Insight のログ検索画面が開き、結果が表示される。
料金 †https://azure.microsoft.com/ja-jp/pricing/details/monitor/ には下表の通り記載されている。(2020/8月現在) アラート †
通知 †
※SMS と音声通話は省略 |