Part 4: Email service health report with Azure serverless

June 17, 2018 by rdagumampan

This is part of a five-part series in my exploration of Azure microservices facilities.

  1. Kick-start containerized microservice CI/CD with Azure DevOps
  2. Build containerized microservices with Docker and .NET Core 2.1
  3. Build containerized web dashboard with ASP.NET Core
  4. Email service health report with Azure serverless
  5. Scale out services with Managed Kubernetes

AWS rocks the cloud computing world with the introduction of AWS Lamda; AWS Lamda pioneered serverless computing based on a simple promise: “You write your code, the rest is on us”. That means the entire infrastructure; server, storage, network and auto-scaling on demand peaks is managed by the cloud provider. I see three major value-for-its-use:

  1. Deliver fast. Serverless computing helps developers deliver value on less effort. From scripts that rebuild database indexes, business-driven logic, to complex reactive data processing tasks.

  2. Scale fast. Serverless computing allows us to address large and high frequency requests fast. Devs doesn’t have to deal with complex distributed computing setup like service clustering, load balancing, fail-overs, resource governance.

  3. Cost transparent. This is what I really like with cloud. The cost is transparent and easily verifiable and we only pay for the compute units we use.

Sending service health report with Azure serverless

Based on from what we built so far, it would be nice to send a service health report to our team in Malaysia every 09:00. While they receive alerts when services are down, it would help to get summary before we start the day. This time, we’ll do with serverless in Azure.

  1. Create an azure function

    A function is a unit of work compiled and executed on the cloud. If you use LINQPad, JSFiddle or CodeChef its bit similar but more. An azure function executes as a result of an external trigger. A trigger could be a built-it timer or external sources like queue, event pipe, http endpoint or an http call.

    Create a function that triggers on a CRON schedule. Azure Function

  2. Create SendGrid account

    SendGrid allows you to send 100 emails free, forever. Sign-up via https://sendgrid.com/ Create an API key and set this on the Manage section. Do not put secrets in any of your code files.

    Azure Function

  3. Implement the function

    Azure function allows you to import built-in libraries or upload your own library and use inside function. The #r directives imports SendGrid and Newtonsoft package.

    #r "SendGrid"
    #r "Newtonsoft.Json"
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    
    using SendGrid.Helpers.Mail;
    using Microsoft.Azure.WebJobs.Host;
    
    public static Mail Run(TimerInfo timer, TraceWriter log)
    {
        var today = DateTime.Today.ToLongDateString();
            
        var serviceClient = new ServiceHealthClientService();
        var serviceResults = serviceClient.GetDataAsync().Result.ToList();
    
        var serviceFailedResults = serviceResults.Where(s => s.AliveSince > 600).ToList();
        var failedMessage = MessageHelper.CreateMessage(serviceFailedResults);
    
        var serviceAliveResults = serviceResults.Where(s => s.AliveSince <= 600).ToList();
        var aliveMessage = MessageHelper.CreateMessage(serviceAliveResults);
    
        var fullMessage = $"Here's state of your services as of {today} {DateTime.Now.ToString("HH:mm:ss")} UTC<br>";
        fullMessage += "<a href=\"https://servicehealth-web.azurewebsites.net/\">View real-time status: https://servicehealth-web.azurewebsites.net/<a/><br><br>";
        fullMessage += $"<style color=red><b>Failing Services<b></style></br>{failedMessage}</br></br>";
        fullMessage += $"<style color=green><b>Running Services<b></style></br>{aliveMessage}<br/>";
        fullMessage += "/ Service built with serverless / azure function";
    
        Mail message = new Mail() {
            Subject = $"ServiceHealth Report | {today}"
        };
    
        Content content = new Content {
            Type = "text/html",
            Value = fullMessage
        };
    
        message.AddContent(content);
    
        var personalization = new Personalization();
        personalization.AddTo(new Email("rdagumampan/AT/gmail.com"));
        message.AddPersonalization(personalization);
    
        return message;
    }
    
    public class ServiceHealthClientService {
        public ServiceHealthClientService() {
        }
    
        public async Task<List<ServiceHealthDto>> GetDataAsync() {
            var results = new List<ServiceHealthDto>();
    
            string baseUrl = "https://servicehealth-api.azurewebsites.net/api/servicehealth";
            using (HttpClient client = new HttpClient())
            using (HttpResponseMessage response = await client.GetAsync(baseUrl))
            using (HttpContent content = response.Content) {
                string data = await content.ReadAsStringAsync();
                if (data != null) {
                    results = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ServiceHealthDto>>(data);
                }
            }
    
            return results.OrderBy(s=> s.ServiceName).ToList();
        }
    }
    
    public class ServiceHealthDto {
        public string ServiceId { get; set; }
        public string ServiceName { get; set; }
        public string Description { get; set; }
        public DateTime LastPing { get; set; }
        public string Location { get; set; }
        public string LastStatus { get; set; }
        public double AliveSince { get; set; }
    }
    
    public class MessageHelper {
    
        public static string CreateMessage (List<ServiceHealthDto> serviceResults){
            var serviceReport = string.Empty;
            if(serviceResults.Any()) {
                serviceReport += "<table>";
                serviceReport += $"<tr><td><b>ServiceId</b></td><td><b>Location</b></td><td><b>Status</b></tr>";
    
                serviceResults.ForEach(s => {
                    var status = s.AliveSince < 300 ? $"Running" : "Last heartbeat " + s.AliveSince.ToString("#") +" secs ago, verify ASAP";
                    var reportLine = $"<tr><td>{s.ServiceId}</td><td>{s.Location}</td><td>{status}</tr>";
                    serviceReport += $"{reportLine}";
                });
    
                serviceReport += "</table>";
            }
            return serviceReport;
        }
    }
    
  4. Test it

    Azure Function

Summary

This has been smooth experience and I noted key take-ways.

  • A serverless function is stateless. The function must do an Input-Process-Output and cannot hold state in its lifecycle. Yes, Durabale Functions but my gut-feel says its an anti-patern to serverless.
  • A serverless function is network agnostic. It runs and live on its own somewhere in data center. A function could easily breach perimter of your enterprise and its hard to detect. Certainly, MS and other security players must be building products for this.
  • A serverless may promote bad code. IMO scripts are dirty code and normally defy SOLID and OOP principles as the primary motivation is to make it work. And I just did as you saw on code at top :)

References:

Hear it from Martin Fowler’s fellow
https://www.martinfowler.com/articles/serverless.html

Azure serverless architecture
https://azure.microsoft.com/en-us/services/functions/

SendGrid
https://sendgrid.com/

© 2017 | About | Contact | Follow me on Twitter | Powerered by Hucore & Hugo