Friday, April 17, 2020

Azure cost saving: Shutdown Containers and VMs

While developing a new solution in Azure it's common to spin up resources and try stuff out. Some resources are pricier than others. Virtual Machines and Azure Container Instances (ACI) can be rather pricey but... you can shut them down after hours to save some money.

Shutdown resources through Azure API

Azure has very nice APIs and command line tooling that allow you to quickly provision and control resources. The portal will also do nicely for incidental control. In a development environment though, resource are created and destroyed regularly and it becomes hard to keep track of all and their cost.

So shutting down resources after work hours is one of those things you'll want to automate.

Azure Automation is the solution promoted by Microsoft for this type of automation, but I found it hard to setup and not that suitable for developers.
An Azure Function can do the exact same thing in a couple of lines of code.

Azure Automation with Functions

An Azure function, given the right permissions, can access the Azure API, enumerate the resources and shutdown the ones you don't need after hours.

// List virtual machines
var vms = await azure.VirtualMachines.ListAsync();

// Stop all machines that are running
await ResourceHelper.ProcessResources(vms,
         vm => vm.PowerState == PowerState.Running,
         vm => Stop(vm, log),
The ProcessResource function is an extension method to process all resources in a list. To prevent shutting down resources that you shouldn't a filter is applied using tags:
public static async Task ProcessResources<TResource>( 
    IEnumerable<TResource> resources,
    Func<Tresource,bool> needsStateChange,
    Func<TResource, Task> action,
    ILogger log)
    where TResource:IResource
       var azure = Configuration.GetAzureApi();
       var taggedResources =  resources.
             // Apply a tag filter
            .Where(vm => vm.HasTags(Configuration.Tags()))

        log.LogInformation($"Found {taggedResources.Count()} resources");

        // Fan out and change the state of all the resources in parallel
        var tasks = taggedResources
           .Where(resource => needsStateChange(resource) && !resource.AlwaysOn())

        // Wait for all the state changes to complete
        await Task.WhenAll(tasks);


Shutting down Containers

Azure Container Instances can also be stopped and restarted as desired. The main thing to remember is that containers are stateless so if you need any kind of state you'll have to put it in a database or storage account. Cointainer instances are a bit less mature than VMs in Azure so managing them is a bit less straight forward.

Stopping a container instance is easy, but starting it again not so. It seems that the paradigm that ACI was designed for is provision / run / exit / done. The restart function is simply not in the published API.

A bit of digging around yields the following code for starting a container after it's been stopped:

public static Task Start(IContainerGroup containerGroup)
  return ContainerGroupsOperationsExtensions

Full source code is up on GitHub.

Friday, April 10, 2020

ASP.NET Core - test function creation

In a previous post I talked about testing controller creation in an ASP.NET Core MVC or WebAPI type of application. Azure Functions don't use controllers though. They're a little more loosly defined by marking a method with an attribute. You can still use a similar test to verify that all functions in your project can be instantiated with dependency injection.

The integration test

This is an xUnit test and you can run this inside your CI/CD pipeline. The Functions method returns the types all classes that contain function methods. This works with both 'regular' Azure Functions and Azure Durable Functions and the activities that go with it.

In the Arrange phase of the test, the Function host is constructed and the type containing the function is added explicitly so we can easily verify the type can be instantiated.

By using GetRequiredService we force the container to return a proper error which includes any types missing form the dependency graph.

Further reading