Automating Docker image deployments using Azure Container Instances

From my archive - originally published on 3 December 2017

Azure’s Container Instances service provides an easy and quick way to run containers without having to operate an orchestration platform or manage a cluster of virtual machines.

It's similar to Amazon's Elastic Container Service except that it provides full support for Windows Containers without any caveats or limitations. You only pay for what you use by the second and there are no overhead charges or need to pre-provision capacity. This makes it ideal for short-lived workloads such as batch processed where you need to spin up capacity for a relatively short period of time. Note that all this service provides is a means of spinning up container instances easily. There’s nothing to help you stand up complicated arrangements of containers or manage their lifecycle. The Azure Management Libraries can help you to automate deployment and management, but the documentation does not give a lot away. Once you unravel their mysteries then they can be paired with something like Azure Functions to provide an automated means of standing up, monitoring and tearing down container instances, all with negligible cost.

Creating a registry

Before you start you will need to publish your Docker images in a registry that Azure can access. You can use Docker’s public registry for this, but you will probably feel more comfortable running your own using an instance of the Azure Container Registry service. When creating this it will make life easier in the short term by enabling the Admin user – this allows easier, though less secure, login to the registry. Once this has been deployed you can access the login details in the Azure portal by visiting the “Access Keys” section of the resource.

Pushing containers to the registry

Once you have a registry you will need to push your compiled Docker images to them. Ideally this should be done as part of your build and deploy pipeline along with the actual image creation. Pushing the container can be done using three separate Docker commands. The examples below assume you have an registry called "my-registry" and an image called "my-image". The login command authenticates with the registry you have just created with a username and password:
docker login --username [username] --password [passowrd] my-registry.azurecr.io
The tag command marks your image for upload to the registry.
docker tag my-image my-registry.azurecr.io/my-image:latest
The push command publishes your tagged image to the registry:
docker push my-registry.azurecr.io/my-image
You can verify that the image has made it to the registry by visiting the “Repositories” section of the registry in the Azure portal.

Deploying the container

Once you have the containers published to the registry you can deploy them using the Azure management SDK. This provides a fluent SDK that allows you to stand up, query and tear down Azure Container Instances. Before you start you’ll need to create a service principal. This is a way of creating an account that is associated with your identity to which you can grant the minimum privileges required to run. To create a service principal you’ll need to install Azure Powershell and follow Microsoft’s guide which will give you a client id and secret along with a tenant identifier. These details should let you login to Azure using the fluent SDK as shown below. This will need a reference to the Microsoft.Azure.Management.Fluent package and the resulting IAzure object can be used for all your subsequent requests:
AzureCredentials credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(
    clientId: "[Client ID]",
    clientSecret: "[Client secret]",
    tenantId: "[Tenant ID]",
    environment: AzureEnvironment.AzureGlobalCloud);
 
IAzure azure = Azure
    .Configure()
    .WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
    .Authenticate(credentials)
    .WithDefaultSubscription();
To deploy your image you’ll need to create a container group. This is similar to a Kubernetes pod in that it’s a collection of containers on the same host machine that share the same life-cycle, local network and storage. The code below shows how to deploy a single, basic image to a container:
IContainerGroup containerGroup = azure.ContainerGroups.Define(instanceName)
    // This is the data centre
    .WithRegion(Region.EuropeWest)
    // Select a resource group - you can also create a new one
    .WithExistingResourceGroup("[A resource group]")
    // Use Windows-based containers
    .WithWindows()
    // Specify the private image registry
    .WithPrivateImageRegistry("[Registry address]""[Username]""[Password]")
    // To attach a disc use DefineVolume() or WithNewAzureFileShareVolume()
    .WithoutVolume()
    // Set out the container instance, i.e. the image, ports, CPUs, memory and environment variables
    .DefineContainerInstance(instanceName)
        .WithImage("[Docker Image ID]")
        .WithoutPorts()
        .WithEnvironmentVariable("[Name]""[Value]")
        .WithCpuCoreCount(1)
        .WithMemorySizeInGB(1)
        .Attach()
    // Create the instance
    .Create();
All this example does is set some environment variables and specify the size of container. You can add internal and external ports by replacing the WithoutPorts() method call. You can also specify an external volume by creating an Azure file share , though at the time of writing this is only available for Linux-based containers.

Querying and killing container instances

You can use the ContainerGroups collection to retrieve the details of your running containers, query the logs and delete them as shown below:
// List the available groups
var groups = azure.ContainerGroups.ListByResourceGroup("[Resource Group]");
 
// Get the logs for an individual container
var logs = azure.ContainerGroups.GetLogContent("[Resource Group]""[Container Group]""[Container]");
 
// Delete a container
azure.ContainerGroups.DeleteById("[Container Group]");