Skip to main content

Deploy Docker containers fast to Microsoft Azure

DEPLOY DOCKER CONTAINERS FAST TO MICROSOFT AZURE

It’s hard to ignore the fact that Docker is a way to move forward for rapid application development, distributed architectures and microservices.
For developers Docker offers great advantages as they can build their containers specifically for the task they work on. They grab a base image of a container, modify it for their purpose and prepare the functionality inside the container.
Quality, testing and security teams now have a single instance to look at and ensure all functional and regulatory requirements are met. System engineers now don’t have to worry about providing a system with the required specs as the container is already provisioned for that purpose.
Containers in the cloud
But where do you deploy your Docker containers? You can set up your existing bare metal infrastructure to allow them to run containers, but this also means you need to learn about securing your container infrastructure, which is not an easy task. Luckily “the cloud” offers container services like Google Cloud, RedHat OpenShift and Heroku. The most popular cloud solution providers are also stepping into the game of containers. Amazon Web Services offers Elastic Container Services that allows you deploy your Docker containers directly on their infrastructure.
In this article I’m looking at how to deploy Docker containers on Microsoft Azure. For one because I’m a true fan of their cloud solutions, but also because they truly offer a solution that allows me to take an existing container application “as is” and deploy it into their cloud infrastructure using the command line, without having to add additional configurations in my application code base.

Preparation

Docker container software

To follow along in this tutorial you need to have Docker installed on your computer. If you don’t have it yet, you can download the community edition for your platform of choice at www.docker.com/community-edition.

Microsoft Azure

You also need to have a Microsoft Azure account. If you don’t have one yet, sign up for a FREE account at azure.microsoft.com/free/.
I’m using the Microsoft Azure CLI SDK as this allows me to automate the build and deploy my containers using my terminal. Read the details on how to install it at docs.microsoft.com/cli/azure. The examples in this blog post are using this CLI SDK.

Demo application

The example application is build using basic PHP and uses a SQLite database. The source code is available on my GitHub account. In order to run the application, we use the vanilla PHP Docker image.
ACI HelloWorld app is available on DragonBe/aci-helloworld.
cd /path/to/workspace
git clone https://github.com/DragonBe/aci-helloworld.git
cd aci-helloworld/
I’ve based this demo app on the example given on the Microsoft Azure Documentation portal “Create your first container in Azure Container Instances” and modified it a bit for specific PHP purposes and added a bit more useful example app.

Getting started

Once you have completed the preparation step, it’s time to get our hands dirty. 

Link your Azure SDK to your Azure subscription

Before we can get started, we need to make sure that we’re using the correct Microsoft Azure account. The best way would be to log in your account on the command line.
az login
If your installation was successful, you will see the following output. Of course, your code will be different.
To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code C9LZEAJ77 to authenticate.
This will launch your browser. If you’re not yet logged in for Microsoft Azure, you need to provide your credentials first.

Log in on Microsoft Live
Next it will take you to a web page where you can enter your code.

Enter the application code
Once verified with Microsoft, you'll get to see your subscription details back into the command line interface.
[
  {
    "cloudName": "AzureCloud",
    "id": "abcd1234-abcd-1234-abcd-123456abcdef",
    "isDefault": true,
    "name": "Pay-As-You-Go",
    "state": "Enabled",
    "tenantId": "1234abcd-1234-abcd-1234-abcdef123456",
    "user": {
      "name": "michelangelo@in2it.be",
      "type": "user"
    }
  }
]

Create a resource group

The easiest way to manage your applications is to define a resource group. The purpose of this group is to have all related applications grouped together so it’s easy to enable, scale and disable them referencing a single group name.
The name for this resource group needs to be unique, so be creative. I was lucky to find aciDemoApp not being used yet, but it could be that the chosen name is already taken. Adding a prefix of your company or a postfix with current date and time can also do the trick. Be creative!
az group create --name aciDemoApp --location westeurope
Once the resource group is created, you'll get again a confirmation in your command line interface.
{
  "id": "/subscriptions/abcd1234-abcd-1234-abcd-123456abcdef/resourceGroups/aciDemoApp",
  "location": "westeurope",
  "managedBy": null,
  "name": "aciDemoApp",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null
}

Create an Azure container registry

Because we need to make our image available for Microsoft Azure we create our own container registry.
az acr create --resource-group aciDemoApp --name aciDemoMvp --sku Basic
This command will create an Active Directory account on Microsoft Azure and assigns permissions for your application to access the registry.
Create a new service principal and assign access:
  az ad sp create-for-rbac --scopes /subscriptions/abcd1234-abcd-1234-abcd-123456abcdef/resourceGroups/aciDemoApp/providers/Microsoft.ContainerRegistry/registries/aciDemoMvp --role Owner --password 

Use an existing service principal and assign access:
  az role assignment create --scope /subscriptions/abcd1234-abcd-1234-abcd-123456abcdef/resourceGroups/aciDemoApp/providers/Microsoft.ContainerRegistry/registries/aciDemoMvp --role Owner --assignee 
{
  "adminUserEnabled": false,
  "creationDate": "2018-02-09T14:10:02.075876+00:00",
  "id": "/subscriptions/abcd1234-abcd-1234-abcd-123456abcdef/resourceGroups/aciDemoApp/providers/Microsoft.ContainerRegistry/registries/aciDemoMvp",
  "location": "westeurope",
  "loginServer": "acidemomvp.azurecr.io",
  "name": "aciDemoMvp",
  "provisioningState": "Succeeded",
  "resourceGroup": "aciDemoApp",
  "sku": {
    "name": "Basic",
    "tier": "Basic"
  },
  "status": null,
  "storageAccount": null,
  "tags": {},
  "type": "Microsoft.ContainerRegistry/registries"
}

Log in into your Azure container registry

Before we can create our container images and push them into our newly created container registry, we need to login into the Active Directory we just created.
az acr login --name aciDemoMvp
This will return you a successful status message.
Login Succeeded

Prepare your docker setup

Up until this point we have only set up our Microsoft Azure account to accept our containers. But we need to have our containers defined in the first place. So here we’re going to ensure everything is working locally before we push it into the cloud.

Build and run locally

The easiest way to figure out if your Docker container is ready is to build and run it on your workstation.
docker build . -t aci-tutorial-app
Once the build is complete, we can run it to see if everything is working.
docker run -p 8000:80 --rm --name aci_tutorial_webapp aci-tutorial-app

Make sure the image is available

docker images
Should provide you a listing of your current running apps
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
aci-tutorial-app    latest              d24e209f7cb6        2 hours ago         70.7MB

Tag your image with the Azure Image Registry

Use the following command if you need to find your registry URI that you’ll need to push your image to.
az acr show --name aciDemoMvp --query loginServer
This will give you the URI you can use for tagging your image
"acidemomvp.azurecr.io"
Now tag your image and make sure you also provide a version (like v1) as this will make it easier in the future to scale your application and have clean deployments of your application containers in the cloud.
docker tag aci-tutorial-app acidemomvp.azurecr.io/aci-tutorial-app:v1
Check again your Docker images to see the tag being applied correctly.
docker images
REPOSITORY                               TAG                 IMAGE ID            CREATED             SIZE
acidemomvp.azurecr.io/aci-tutorial-app   v1                  d24e209f7cb6        2 hours ago         70.7MB
aci-tutorial-app                         latest              d24e209f7cb6        2 hours ago         70.7MB

Push your Docker image to Azure registry

Now it’s time to push our tagged container image into the Microsoft Azureregistry.
docker push acidemomvp.azurecr.io/aci-tutorial-app:v1
This might take some time to push your whole image upstream, but once it’s complete you should receive something similar to this.
The push refers to repository [acidemomvp.azurecr.io/aci-tutorial-app]
f9e1772b82d2: Pushed
9d3a6c539310: Pushed
29e829473f65: Pushed
5df56ca2422c: Pushed
0fe69bd96626: Pushed
4f65eb3ac611: Pushed
8a3436067bb2: Pushed
6443b41f72f9: Pushed
993df43bf684: Pushed
530e50cf94e4: Pushed
86700745f1ec: Pushed
a6f0d3e3e138: Pushed
550c61595337: Pushed
ab779a053abe: Pushed
cec8466f473c: Pushed
d45e3781d4b2: Pushed
8d8f451305fc: Pushed
e416a4904ee4: Pushed
2ec5c0a4cb57: Pushed
v1: digest: sha256:9b59f4b5156a089737ccf440cae52e4e7abd5d0d5db0aec021318f1370f35ddd size: 4285

Enable administrative rights on the registry

We now enable administrative permissions on our registry so we can actually deploy our container.
az acr update -n aciDemoMvp --admin-enabled true
{
  "adminUserEnabled": true,
  "creationDate": "2018-02-09T14:10:02.075876+00:00",
  "id": "/subscriptions/abcd1234-abcd-1234-abcd-123456abcdef/resourceGroups/aciDemoApp/providers/Microsoft.ContainerRegistry/registries/aciDemoMvp",
  "location": "westeurope",
  "loginServer": "acidemomvp.azurecr.io",
  "name": "aciDemoMvp",
  "provisioningState": "Succeeded",
  "resourceGroup": "aciDemoApp",
  "sku": {
    "name": "Basic",
    "tier": "Basic"
  },
  "status": null,
  "storageAccount": null,
  "tags": {},
  "type": "Microsoft.ContainerRegistry/registries"
}

Get the password needed for deployment

We need to provide a password to run our container on Microsoft Azure cloud. Retrieving it is as simple as querying ACR credentials.
az acr credential show --name aciDemoMvp --query "passwords[0].value"
Will return something like this. Make sure that you keep this password SECRET as this is your administrative account. You can also use Microsoft Azure Key Vault for managing these privileges, but that’s material for another blog article when we discuss identity management.
"0tXXLUN+/Ye+8RBotZRRst7OU+I=CteG"

Deploy your docker container

Finally we can deploy our container into production on Microsoft Azurecontainer services.
az container create --resource-group aciDemoApp --name aci-tutorial-app --image acidemomvp.azurecr.io/aci-tutorial-app:v1 --cpu 1 --memory 1 --ip-address public --ports 80 --registry-password "0tXXLUN+/Ye+8RBotZRRst7OU+I=CteG"
This command creates the application container and starts running it. In the details you get back from the service you get full details about your deployed container.
{
  "containers": [
    {
      "command": null,
      "environmentVariables": [],
      "image": "acidemomvp.azurecr.io/aci-tutorial-app:v1",
      "instanceView": null,
      "name": "aci-tutorial-app",
      "ports": [
        {
          "port": 80,
          "protocol": null
        }
      ],
      "resources": {
        "limits": null,
        "requests": {
          "cpu": 1.0,
          "memoryInGb": 1.0
        }
      },
      "volumeMounts": null
    }
  ],
  "id": "/subscriptions/abcd1234-abcd-1234-abcd-123456abcdef/resourceGroups/aciDemoApp/providers/Microsoft.ContainerInstance/containerGroups/aci-tutorial-app",
  "imageRegistryCredentials": [
    {
      "password": null,
      "server": "acidemomvp.azurecr.io",
      "username": "acidemomvp"
    }
  ],
  "instanceView": {
    "events": [],
    "state": "Pending"
  },
  "ipAddress": {
    "ip": "40.68.171.88",
    "ports": [
      {
        "port": 80,
        "protocol": "TCP"
      }
    ]
  },
  "location": "westeurope",
  "name": "aci-tutorial-app",
  "osType": "Linux",
  "provisioningState": "Creating",
  "resourceGroup": "aciDemoApp",
  "restartPolicy": "Always",
  "tags": null,
  "type": "Microsoft.ContainerInstance/containerGroups",
  "volumes": null
}
To get the status of your container you can use az container show --resource-group aciDemoApp --name aci-tutorial-app --query instanceView.state, which will return you the status.
Immediately after you’ve started your container, your container needs to boot up.
"Pending"
But shortly after you’ve deployed your container, you’ll see the status indicating everything is a go.
"Running"
To get the IP of your container use az container show --resource-group aciDemoApp --name aci-tutorial-app --query ipAddress.ip
"40.68.171.88"
Now you can point your browser to 40.68.171.88 and you should see your web application smiling back to you.

Your application smiling back at you

A book recommendation for you

See the log output of your container

az container logs --resource-group aciDemoApp --name aci-tutorial-app
This will give you insights into the container activity
listening on port 80
::ffff:10.240.255.106 - - [10/Feb/2018:10:47:10 +0000] "GET /favicon.ico HTTP/1.1" 404 150 "http://13.95.165.162/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
::ffff:10.240.255.105 - - [10/Feb/2018:10:50:07 +0000] "GET / HTTP/1.1" 200 1663 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
In your Microsoft Azure Portal you can see the container resource group up and running with a whole bunch of more details.

Your containers in Microsoft Azure Portal

Updating your container

No project is perfect the first time. Neither are your services you put inside a container. Updating your container is just a matter of tagging a new version and putting it live.
To make it easy for you I’ve provided a branch called “update” so you can go immediately started.
git checkout update
docker build . -t aci-tutorial-app
docker run -p 8000:80 --rm --name aci_tutorial_webapp aci-tutorial-app
Now we just go to localhost:8000/books to verify our affiliation links are there.

Now that we’ve verified everything is working, it’s time to tag it again and push it upstream.
docker tag aci-tutorial-app acidemomvp.azurecr.io/aci-tutorial-app:v2
docker push acidemomvp.azurecr.io/aci-tutorial-app:v2
It could be your session got timed out, the following message will give you more details.
The push refers to repository [acidemomvp.azurecr.io/aci-tutorial-app]
7d6222784cfc: Preparing
135f6b00eec7: Preparing
29e829473f65: Preparing
5df56ca2422c: Preparing
0fe69bd96626: Preparing
4f65eb3ac611: Waiting
8a3436067bb2: Waiting
6443b41f72f9: Waiting
993df43bf684: Waiting
530e50cf94e4: Waiting
86700745f1ec: Waiting
a6f0d3e3e138: Waiting
550c61595337: Waiting
ab779a053abe: Waiting
cec8466f473c: Waiting
d45e3781d4b2: Waiting
8d8f451305fc: Waiting
e416a4904ee4: Waiting
2ec5c0a4cb57: Waiting
unauthorized: authentication required
To login again, the following statement will help you further.
az acr login --name aciDemoMvp
This should give you a success statement.
Login Succeeded
The moment authentication is again approved, you can now safely push your container upstream.
docker push acidemomvp.azurecr.io/aci-tutorial-app:v2
Again a new checksum will be calculated and returned.
The push refers to repository [acidemomvp.azurecr.io/aci-tutorial-app]
7d6222784cfc: Pushed
135f6b00eec7: Pushed
29e829473f65: Layer already exists
5df56ca2422c: Layer already exists
0fe69bd96626: Layer already exists
4f65eb3ac611: Layer already exists
8a3436067bb2: Layer already exists
6443b41f72f9: Layer already exists
993df43bf684: Layer already exists
530e50cf94e4: Layer already exists
86700745f1ec: Layer already exists
a6f0d3e3e138: Layer already exists
550c61595337: Layer already exists
ab779a053abe: Layer already exists
cec8466f473c: Layer already exists
d45e3781d4b2: Layer already exists
8d8f451305fc: Layer already exists
e416a4904ee4: Layer already exists
2ec5c0a4cb57: Layer already exists
v2: digest: sha256:68f34616e57313dfbb604e21e0085f044c10ab033ffdefe929947ef04d75717e size: 4285
Now it’s time to upgrade the container.
az container create --resource-group aciDemoApp --name aci-tutorial-app --image acidemomvp.azurecr.io/aci-tutorial-app:v2 --cpu 1 --memory 1 --ip-address public --ports 80 --registry-password "0tXXLUN+/Ye+8RBotZRRst7OU+I=CteG"
Notice we used the same password as before, but this time we’re using acidemomvp.azurecr.io/aci-tutorial-app:v2, our updated container tag.
{
  "containers": [
    {
      "command": null,
      "environmentVariables": [],
      "image": "acidemomvp.azurecr.io/aci-tutorial-app:v1",
      "instanceView": null,
      "name": "aci-tutorial-app",
      "ports": [
        {
          "port": 80,
          "protocol": null
        }
      ],
      "resources": {
        "limits": null,
        "requests": {
          "cpu": 1.0,
          "memoryInGb": 1.0
        }
      },
      "volumeMounts": null
    }
  ],
  "id": "/subscriptions/abcd1234-abcd-1234-abcd-123456abcdef/resourceGroups/aciDemoApp/providers/Microsoft.ContainerInstance/containerGroups/aci-tutorial-app",
  "imageRegistryCredentials": [
    {
      "password": null,
      "server": "acidemomvp.azurecr.io",
      "username": "acidemomvp"
    }
  ],
  "instanceView": {
    "events": [],
    "state": "Pending"
  },
  "ipAddress": {
    "ip": "40.68.171.88",
    "ports": [
      {
        "port": 80,
        "protocol": "TCP"
      }
    ]
  },
  "location": "westeurope",
  "name": "aci-tutorial-app",
  "osType": "Linux",
  "provisioningState": "Creating",
  "resourceGroup": "aciDemoApp",
  "restartPolicy": "Always",
  "tags": null,
  "type": "Microsoft.ContainerInstance/containerGroups",
  "volumes": null
}
This means that your container is updated, using the same IP address as we had before. Point your browser to 40.68.171.88 and see your updated application running as a container on Microsoft Azure.

Your updated application running in the cloud

Now with affiliation links

Destroy your container

az group delete --name  aciDemoApp
It will ask for your confirmation
Are you sure you want to perform this operation? (y/n): y

Next articles in the pipeline

Now that we have learned how to get a basic Docker application deployed on Microsoft Azure, let’s dig a little bit deeper in how we can fully utilise the services provided by Azure.
This are a few articles I’m working on, so come back regularly to learn how you can easily deploy applications on the cloud using Docker and Microsoft Azure.
Please leave feedback and let me know if this tutorial was interesting and what you like to see in the future as well.

Comments

Popular posts from this blog

PHP 7 and Apache on macOS Sierra

I posted several talks about compiling PHP from source, but everyone was trying to convince me that a package manager like Homebrew was a more convenient way to install. The purpose of Homebrew is simple: a package manager for macOS that will allow you to set up and install common packages easily and allows you to update frequently using simple commands. I used a clean installation of macOS Sierra to ensure all steps could be recorded and tested. In most cases you already have done work on your Mac, so chances are you can skip a few steps in this tutorial. APACHE AND PHP WITH HOMEBREW I’ve made this according to the installation instructions given on GetGrav. The installation procedures These installation procedures will set up your macOS Sierra with PHP 7.1 and Apache 2.4. Install Xcode command line tools (if not done yet)xcode-select --install Install Homebrew/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" Set up for in…

Speeding up database calls with PDO and iterators

When you review lots of code, you often wonder why things were written the way they were. Especially when making expensive calls to a database, I still see things that could and should be improved.
No framework development When working with a framework, mostly these database calls are optimized for the developer and abstract the complex logic to improve and optimize the retrieval and usage of data. But then developers need to build something without a framework and end up using the basics of PHP in a sub-optimal way.

$pdo = new \PDO( $config['db']['dsn'], $config['db']['username'], $config['db']['password'] ); $sql = 'SELECT * FROM `gen_contact` ORDER BY `contact_modified` DESC'; $stmt = $pdo->prepare($sql); $stmt->execute(); $data = $stmt->fetchAll(\PDO::FETCH_OBJ); echo 'Getting the contacts that changed the last 3 months' . PHP_EOL; foreach ($data as $row) { $dt = new \DateTime('2015-04-…

Sessions in PHP 7.1 and Redis

In case you have missed it, PHP 7.1.0 has been released recently. Now you can’t wait to upgrade your servers to the latest and greatest PHP version ever. But hold that thought a second… With PHP 7 lots of things have changed underneath the hood. But these changed features can also put unexpected challenges on your path. Our challenge One of these challenges that we faced was getting PHP 7.1 to play nice storing sessions in our Redis storage. In order to store sessions in Redis, we needed to install the Redis PHP extension that not only provides PHP functions for Redis, but also installs the PHP session handler for Redis. Because we upgraded our servers to PHP 7.1, we were looking to use the latest provided version for this Redis extension: redis-3.1.0. Once installed, we bumped against a nasty problem. Warning: session_start(): Failed to read session data: redis (path: tcp://127.0.0.1:6379) Searching the internet for this error, we didn’t got many hits that could point us into a dire…