Azure GetAlert, czyli jak się zorientować, że źle się dzieje

Azure GetAlert, czyli jak zostać poinformowanym, że źle się dzieje

Mariusz Budzyn 20 listopada 2017

Dziś o tym, jak w FinAi monitorujemy nasze programy w chmurze i w jaki sposób rozbudowaliśmy powiadomienia.

poprzednim wpisie przedstawiłem Wam pokrótce, czym jest usługa Microsoft Application Insights oraz jak można ją wdrożyć przy minimalnym wysiłku. Dziś będzie mniej teorii, a więcej praktyki w postaci kodu. Pokażę Wam, jak w FinAi za pomocą tej usługi monitorujemy nasze programy w chmurze oraz jak rozbudowaliśmy powiadomienia.

ASP.NET web app Dashboard

Dla aplikacji ASP.NET, przy wykorzystaniu domyślnej konfiguracji Application Insights, dostajemy 90% najważniejszych informacji z pudełka, bez żadnego nakładu pracy. Możemy sprawdzić, ile mamy aktualnie serwerów i włączyć dla nich live stream do śledzenia aktualnego obciążenia maszyn oraz wielkości ruchu, który w tym momencie obsługujemy. Wszystko jest prezentowane w czasie rzeczywistym i w uproszczonej postaci. Idealne dla adminów! :)

 

W przystępnej formie zaprezentowana jest także ogólna liczba żądań, liczba żądań zakończona porażką, średni czas ładowania strony na urządzenia oraz średni czas generowania odpowiedzi po stronie backendu. Najciekawszą opcją (z punktu widzenia tego artykułu) jest liczba aktywnych alertów.

 

Alerty Application Insights

Domyślnie włączony jest jeden alert: anomalie. Muszę przyznać, że jest to całkiem inteligentny mechanizm wychwytujący niepożądane sytuacje, w których wzrasta: liczba żądań zakończonych porażką, długość odpowiedzi zależności (np. SQL), itp. Dodatkowo alert dostarcza informację który z atrybutów dodatkowych powtarza się najczęściej.

Możemy, oczywiście, dodawać własne alerty (Azure portal -> Application Insights -> [resource] -> Alerts -> Add alert). Przy Tworzeniu nowego alertu podajemy resource, nazwę, opis, metrykę i jej warunek progowy na włączający alert, a także informację, po jakim czasie od przekroczenia progu alert ma się wygasić. Do tego oznaczamy, czy wysłać maila do osób które mają rolę owner, contribiutor lub reader na komponencie Application Insights. Dodatkowo możemy podać adresy email „z palca” oraz wskazać URL, który ma być wywołany.

Ta ostatnia opcja daje najwięcej możliwości. W FinAi do komunikacji wewnątrzfirmowej wykorzystujemy komunikator Microsoft Teams – taki Slack, tylko od firmy z Redmond i z możliwością integracji z niemal każdym produktem tej firmy.

W zespole programistów dodaliśmy kanał Alerty a do niego łącznik webhook, przekopiowaliśmy URL do textboxa w portalu Azure i... klops :) Nie działało! Wynika to z niezgodności danych pomiędzy tym, co wypycha Azure w alercie, a tym, czego oczekuje Microsoft Teams. Ten problemik można jednak szybko rozwiązać pisząc prosty konwerter – wykorzystaliśmy do tego Azure Functions! Najprostsze i najszybsze rozwiązanie, do tego tanie: ok. 15 tysięcy wywołań kosztuje nas zawrotne 10 eurocentów miesięcznie :)

 

Application Insights -> azure functions -> Microsoft Teams

Sama funkcja ma dość prostą postać:

1.	public static async Task<HttpResponseMessage> Run(AppInsightData req, TraceWriter log)
2.	{
3.	    log.Info("Starting to handle alert from Application Insights");
4.	 
5.	    var teamsData = new TeamsWebhookData()
6.	    {
7.	        ...
8.	    };
9.	 
10.	    var data = await Task.Run(() => JsonConvert.SerializeObject(teamsData));
11.	    var httpContent = new StringContent(data, System.Text.Encoding.UTF8, "application/json");
12.	       
13.	    using (var client = new HttpClient())
14.	    {
15.	        var response = await client.PostAsync(
16.	                new Uri(@"https://outlook.office.com/webhook/..."),
17.	                httpContent
18.	            );
19.	 
20.	        log.Info(await response.Content.ReadAsStringAsync());
21.	 
22.	        if (response != null)
23.	            return response;
24.	    }
25.	}

Przychodzące dane zostaną zdeserializowane do obiektu, której postać przedstawiona jest na stronie Microsoftu. W FinAi przygotowaliśmy taki kawałek kodu:

1.	public enum AppInsightDataStatus
2.	{
3.	    Unknown,
4.	    Activated,
5.	    Resolved
6.	}
7.	/// <summary>
8.	/// https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/insights-webhooks-alerts
9.	/// </summary>
10.	public class AppInsightData
11.	{
12.	    private AppInsightDataStatus _status = AppInsightDataStatus.Unknown;
13.	    public string status
14.	    {
15.	        get
16.	        {
17.	            return _status.ToString();
18.	        }
19.	        set
20.	        {
21.	            foreach(var e in Enum.GetValues(typeof(AppInsightDataStatus)).Cast<AppInsightDataStatus>())
22.	                if(e.ToString() == value)
23.	                {
24.	                    _status = e;
25.	                    break;
26.	                }
27.	        }
28.	    }
29.	    public AppInsightDataContext context { get; set; }
30.	    public Hashtable properties { get; set; }
31.	}
32.	 
33.	public struct AppInsightDataContext
34.	{
35.	    public string timestamp { get; set; }
36.	    public string id { get; set; }
37.	    public string name { get; set; }
38.	    public string description { get; set; }
39.	    public string conditionType { get; set; }
40.	    public AppInsightDataContextCondition condition { get; set; }
41.	    public string subscriptionId { get; set; }
42.	    public string resourceGroupName { get; set; }
43.	    public string resourceName { get; set; }
44.	    public string resourceType { get; set; }
45.	    public string resourceId { get; set; }
46.	    public string resourceRegion { get; set; }
47.	    public string portalLink { get; set; }
48.	}
49.	 
50.	public struct AppInsightDataContextCondition
51.	{
52.	    public string metricName { get; set; }
53.	    public string metricUnit { get; set; }
54.	    public string metricValue { get; set; }
55.	    public string threshold { get; set; }
56.	    public string windowSize { get; set; }
57.	    public string timeAggregation { get; set; }
58.	    public string @operator { get; set; }
59.	}

Zainteresowanych tym, co dokładnie można przesłać do Microsoft Teams, odsyłam na stronę Microsoftu.

W danych wejściowych dostajemy wszystko to, co zdefiniowaliśmy w alercie (nazwę, opis, metrykę i warunek progowy metryki włączający alert) plus specyficzne elementy dla samego portalu Azure (resource group/name/type/id/region, identyfikator subskrypcji, link do portalu) oraz wartość metryki w momencie generowania alertu. Wykorzystując te dane można uzyskać podstawowy alert:

Wszystko fajnie, ale... pierwsze pytanie, jakie się nasuwa dla tego przykładu, brzmi: jakie żądania? Można taką wiadomość w prosty sposób rozbudować o np. 5 ostatnich żądań zakończonych porażką – w tym celu wykorzystaliśmy API do Application Insights.

1.	public static async Task<dynamic> GetData (string apiKey, string appId, string query)
2.	{
3.	        using (var client = new HttpClient())
4.	        {
5.	                client.DefaultRequestHeaders.Accept.Clear();
6.	                client.DefaultRequestHeaders.Add("x-api-key", apiKey);
7.	                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
8.	 
9.	                var response = await client.GetAsync(
10.	                                new Uri($"https://api.applicationinsights.io/beta/apps/{appId}/query?query={System.Uri.EscapeDataString(query)}"),
11.	                                HttpCompletionOption.ResponseContentRead
12.	                        );
13.	 
14.	                if (response != null)
15.	                        switch(response.StatusCode)
16.	                        {
17.	                                case HttpStatusCode.OK: //200
18.	                                        var converter = new ExpandoObjectConverter();
19.	                                        return JsonConvert.DeserializeObject<ExpandoObject>(await response.Content.ReadAsStringAsync(), converter);
20.	 
21.	                                default:
22.	                                        return null;
23.	                        }
24.	                return null;
25.	        }
26.	}

Taki alert generalnie by wystarczył, ale tu wychodzi jedna z większych wad Application Insights: opóźnienie. Alert wie, że przyszła nowa telemetria, ale API jeszcze jej nie zwraca. Opóźnienie jest spore – aż do 5 minut! Stąd dodaliśmy dodatkowy przycisk bezpośrednio do portalu z analityką:

Po pierwszym tygodniu od uruchomienia powiadomień z alertami, gdy zachwyt już opadł, słyszałem tylko: „a nie dałoby się zrobić tak, żeby w portalu już było wpisane zapytanie?”. Microsoft milczy w kwestii możliwości budowania linków z konkretnym zapytaniem, ale jak przyjrzymy się portalowi to istnieje przycisk Share a Link to Query, który robi dokładnie to, czego potrzebujemy! Link, który dostajemy do schowka działa po wysłaniu koledze – otwiera się portal z zapytaniem z momentu generowania linku. Oczywiście można w ten sposób wysłać dowolne zapytanie! Analizując linki do różnych zapytań zauważyłem, że zmienia się tylko parametr przekazywany jako q:

https://analytics.applicationinsights.io/subscriptions/[subscription-id]/resourcegroups/[resource-group-name]/components/[resource-name]?q=H4sIAAAAAAAAA0utSE4tKMnMzyvmAgDQFU1%2FCwAAAA%3D%3D&apptype=web&timespan=PT24H

Na pierwszy rzut oka ten ciąg nie ma nic wspólnego z naszym zapytaniem... ale w rzeczywistości nim jest! Wystarczy wcisnąć F12 i możemy prześledzić, co robi JavaScript po kliknięciu w przycisk. Jest to zmiana zapytania do Base64 i kompresja. Odtworzenie tego po stronie backendu – zwłaszcza w drugą stronę - jest bajecznie proste:

1.	public static string Encode(string query)
2.	{
3.	        var decodedBytes = Encoding.UTF8.GetBytes(query);
4.	        byte[] codedBytes;
5.	 
6.	        using (var memory = new MemoryStream())
7.	        {
8.	                using (var stream = new GZipStream(memory, CompressionMode.Compress, false))
9.	                {
10.	                        stream.Write(decodedBytes, 0, decodedBytes.Length);
11.	                }
12.	                codedBytes= memory.ToArray();
13.	        }
14.	 
15.	        var coded = Convert.ToBase64String(codedBytes);
16.	        var escaped = System.Uri.EscapeDataString(coded);
17.	        return escaped;
18.	}

Tym sposobem link do portalu z analityką ma już uzupełnione zapytanie. Respekt na open space gwarantowany! :)

Własne alerty

Gdy już rozkochacie cały zespół w alertach, to zaczną pojawiać się pytania: czy można tworzyć nowe metryki – specyficzne dla technicznego lub biznesowego profilu Waszej aplikacji. Odpowiedź jest krótka: OCZYWIŚCIE, że można! Nowe metryki powstają na bazie telemetrii, którą wysyłacie jako MetricTelemetry.

1.	public void Track(string metricName, double value)
2.	{
3.	        var metric = new MetricTelemetry();
4.	        metric.Name = metricName;
5.	        metric.Sum = value;
6.	        _teleClient.TrackMetric(metric);
7.	}

Po zebraniu kilku telemetrii, przy definiowaniu alertu pojawi się Wasza metryka:

Automatyzacja

Dużo naszych definicji alertów powtarza się dla każdego kolejnego mikroserwisu – stąd powstała potrzeba automatyzacji ich definiowania. Z pomocą przychodzą template’y ARM, które pozwalają definiować alerty bez klikania w portalu:

{
  "type": "microsoft.insights/alertrules",
  "name": "Failed_requests",
  "apiVersion": "2014-04-01",
  "location": "West Europe",
  "scale": null,
  "properties": {
    "name": "Failed_requests",
    "description": " over last 5 minutes",
    "isEnabled": true,
    "condition": {
      "odata.type": "Microsoft.Azure.Management.Insights.Models.ThresholdRuleCondition",
      "dataSource": {
        "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleMetricDataSource",
        "resourceUri": "[resourceId('microsoft.insights/components', resource-name)]",
        "metricNamespace": null,
        "metricName": "requestFailed.count"
      },
      "operator": "GreaterThan",
      "threshold": 0,
      "windowSize": "PT5M"
    },
    "action": null
  },
  "dependsOn": [
    "[resourceId('microsoft.insights/components', resource-name)]"
  ]
}

Zbierając wszystkie klocki w całość, dodając większą parametryzację (uzyskujecie większą re-używalność), możecie osiągnąć bardzo skuteczną metodę informowania o niedobrych sytuacjach w Waszym środowisku. W FinAi alerty sprawdzają się bardzo dobrze.

W niedalekiej przyszłości planujemy budowę powiadomień do wyświetlania na dużym ekranie (ponad 50 cali), do czego również chcemy wykorzystać alerty Application Insights oraz Azure Functions. Dzięki opcji webhooka możliwości alertów są niemal nieograniczone.

Dostosowanie alertów do Slacka, Facebooka, itp. to tylko kwestia budowy odpowiedniego JSON-a – stąd łatwo możecie zintegrować się z dowolnym systemem istniejącym w Waszej organizacji. Zachęcam do spróbowania i własnej oceny sprawności alertów w Application Insights!

Location icon Facebook icon Twitter icon Google+ icon LinkedIn icon Technology icon Business icon Marketing icon Phone icon Mail icon User icon Tag icon Bubble icon Arrow right icon Arrow left icon Calendar PR Contact