Thursday, March 8, 2018

Azure Container Instance (5): Building HTTPS Websites


HTTPS websites are more and more popular recently. Now you can also build websites with HTTPS connections via Azure Container Instance, with secret volumes and DNS Name Labels. This article is to demonstrate the steps about how to setup HTTS connections for a Node.js website.

Prerequisite: Have a Certificate

You may have a certificate for the SSL connection. If your DNS name label xyz, and your container group is going to be created in WestUS region, the fully qualified domain name looks like xyz.westus.azurecontainer.io and your certificate should match it. If you have a CName for your website, your certificate should match the CName.

Step 1: Build the Image

If you are going to build your website with Node.js, you may setup HTTPS connections with the following code:

const fs = require('fs');
const https = require('https');
const express = require('express');
const morgan = require('morgan');
const options = {
    pfx: fs.readFileSync('certificate.pfx'),
    passphrase: fs.readFileSync('certificatepassword.txt')
};

const app = express();
app.use(morgan('combined'));

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html')
});

var listener = https.createServer(options, app).listen(process.env.PORT || 443, function () {
    console.log('listening on port ' + listener.address().port);
});


Notice that the two files ‘certificate.pfx’ and ‘certificatepassword.txt’ are the certificate and its password. Before we start to run Node.js, these two files are copied from the path /mnt/secrets, as shown in Dockerfile:

CMD cp /mnt/secrets/sslcertificateData /usr/src/app/certificate.pfx && cp /mnt/secrets/sslcertificatePwd /usr/src/app/certificatepassword.txt && node /usr/src/app/index.js

We are going to pass these secrets from the Azure deployment template.

All other code for the Node.js website and the Docker file are shared at https://github.com/zhedahht/aci-ssl-helloworld. And the built image is shared at https://hub.docker.com/r/containerinstance/helloworld/.

Step2: Define the Deployment Template

The next step is to define Azure deployment template, and the following is a sample:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "containergroupname": {
      "type": "string",
      "metadata": {
        "description": "Name for the container group"
      }
    },
    "containername": {
      "type": "string",
      "metadata": {
        "description": "Name for the container"
      },
      "defaultValue": "container1"
    },
    "imagename": {
      "type": "string",
      "metadata": {
        "description": "Name for the image"
      },
      "defaultValue": "containerinstance/helloworld:ssl"
    },
    "volumename": {
      "type": "string",
      "metadata": {
        "description": "Name for the secret volume"
      },
      "defaultValue": "volume1"
    },
    "dnsnamelabel": {
      "type": "string",
      "metadata": {
        "description": "The DSN name label"
      }
    },
    "sslcertificateData": {
      "type": "securestring",
      "metadata": {
        "description": "Base-64 encoded authentication PFX certificate."
      }
    },
    "sslcertificatePwd": {
      "type": "securestring",
      "metadata": {
        "description": "Base-64 encoded password of authentication PFX certificate."
      }
    },
    "port": {
      "type": "string",
      "metadata": {
        "description": "Port to open on the container and the public IP address."
      },
      "defaultValue": "443"
    },
    "cpuCores": {
      "type": "string",
      "metadata": {
        "description": "The number of CPU cores to allocate to the container."
      },
      "defaultValue": "1.0"
    },
    "memoryInGb": {
      "type": "string",
      "metadata": {
        "description": "The amount of memory to allocate to the container in gigabytes."
      },
      "defaultValue": "1.5"
    }
  },
  "variables": {},
  "resources": [
    {
      "name": "[parameters('containergroupname')]",
      "type": "Microsoft.ContainerInstance/containerGroups",
      "apiVersion": "2018-02-01-preview",
      "location": "[resourceGroup().location]",
      "dependsOn": [],
      "properties": {
        "containers": [
          {
            "name": "[parameters('containername')]",
            "properties": {
              "command": [],
              "image": "[parameters('imagename')]",
              "ports": [
                {
                  "port": "[parameters('port')]"
                }
              ],
              "resources": {
                "requests": {
                  "cpu": "[parameters('cpuCores')]",
                  "memoryInGb": "[parameters('memoryInGb')]"
                }
              },
              "volumeMounts": [
                {
                  "name": "[parameters('volumename')]",
                  "mountPath": "/mnt/secrets",
                  "readOnly": false
                }
              ]
            }
          }
        ],
        "osType": "Linux",
        "ipAddress": {
          "type": "Public",
          "dnsNameLabel": "[parameters('dnsnamelabel')]",
          "ports": [
            {
              "protocol": "tcp",
              "port": "[parameters('port')]"
            }
          ]
        },
        "volumes": [
          {
            "name": "[parameters('volumename')]",
            "secret": {
              "sslcertificateData": "[parameters('sslcertificateData')]",
              "sslcertificatePwd": "[base64(parameters('sslcertificatePwd'))]"
            }
          }
        ]
      }
    }
  ],
  "outputs": {
    "containerIPAddressFqdn": {
      "type": "string",
      "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups/', parameters('containergroupname'))).ipAddress.fqdn]"
    }
  }
}

In the template above, the certificate is mounted as the file /mnt/secrets/sslcertificateData, and the password is mounted as the file /mnt/secrets/sslcertificatePwd. These files will be copied and pasted as /user/src/app/certificate.pfx and /user/src/app/certificatepassword.txt respondingly, and they are accessible to Node.js.

The DNS name labels is defined in the property dnsNameLabel of ipAddress. The fully qualified domain name is a concatenation of the DSN name label, the location and “azurecontainer.io”. 

The deployment template and the corresponding parameters are shared at https://github.com/Azure/azure-quickstart-templates/tree/master/201-aci-linuxcontainer-volume-secret.

Monday, December 4, 2017

Azure Container Instance (4): Integrating with Logic App


Azure Logic Apps are very useful tools to automate workflows and business processes. Azure Container Instances have been integrated with Logic Apps, and you have more flexibility to integrate systems and services for your organization with the integration.

This blog is going to show you how to utilize Azure Container Instances with Logic App. A container group is created when there is a new RSS feed and it would to uploaded to Azure file storage automatically. The high-level logic app workflow looks like:


Let’s monitor the RSS feed at http://feeds.reuters.com/reuters/topNews, and the workflow will be trigger when a new feed is available. The trigger looks like:


And then let’s create an action to create container group. Firstly, we set the subscription Id, resource group, container group name, and location:


 And then we define a container inside the container group. The following is an example:


Finally, we have more settings for the container group, as shown below:


An Azure file share is mounted as a volume in the container.  When a new RSS feed is available, its title will be echoed and appended into the file feed.txt of the mounted volume. We can read all titles of the feeds via Azure portal.

In your scenarios you may do more interesting works with containers, such analyzing the news, getting statistical information like word frequency, or sending you an email if the news is about a company you are interested in. You are free to do whatever you want as long as you define the container.

Friday, December 1, 2017

Azure Container Instance (3): Introducing ACI gitRepo Volume

A new type of volume named gitRepo volume was supported in Azure Container Instance since version 2017-12-01-preview.

What is gitRepo volume?

A gitRepo volume mounts an empty directory and clones a git repository into it for your container group to use.

When to use gitRepo volume?

The gitRepo is very useful when we are using GitHub as the source version control tool and we’d like to have containers to work on the source code, such as build and deploy automatically.

How to use gitRepo volume?

Firstly, a gitRepo object is added into the volumes array of the container group, and then a volume mount is added into the volumeMounts of a container. The name of the volume mount should match the name of the volume.

The following is an example container group definition in JSON:

{
  "properties": {
    "containers": [
      {
        "name": "demo1",
        "properties": {
          "command": [
            "bin/bash",
            "-c",
            "cp -R /mnt/gitrepos /mnt/azurefile && while sleep 50000; do echo sleep; done"
          ],
          "image": "nginx",
          "ports": [
            {
              "port": 80
            }
          ],
          "resources": {
            "requests": {
              "cpu": 1,
              "memoryInGb": 1.5
            }
          },
          "volumeMounts": [
            {
              "name": "gitrepo",
              "mountPath": "/mnt/gitrepos",
              "readOnly": false
            },
            {
              "name": "azurefile",
              "mountPath": "/mnt/azurefile",
              "readOnly": false
            }
          ]
        }
      }
    ],
    "volumes": [
      {
        "name": "gitrepo",
        "gitRepo": {
          "repository": "https://github.com/Azure-Samples/aci-helloworld.git"
        }
      },
      {
        "name": "azurefile",
        "azureFile": {
          "shareName": "<YourAzureFileShare>",
          "storageAccountName": "<YourAzureStorageAccountName>",
          "storageAccountKey": "<YouAzureStorageAccountKey>"
        }
      }
    ],
    "osType": "Linux",
    "ipAddress": {
      "ports": [
        {
          "protocol": "TCP",
          "port": 80
        }
      ],
      "type": "Public"
    }
  },
  "location": "westus"
}

In the JSON definition above, the source code files in GitHub reprository https://github.com/Azure-Samples/aci-helloworld are cloned to the path “/mnt/gitrepos”. Since we copy the directory “/mnt/gitrepos” to “/mnt/azurefile”, which is the mounted path of Azure file share. You may find all source code in the GitHub are uploaded to your Azure file storage if you check on your Azure portal.

The sample above just uploads source code in GitHub to Azure file storage. In your scenario you may do anything on the GitHub source code as you like, such as building and deploying code automatically.

Besides property “repository”, a gitRepo object can also have two properties: One is “directory”, which is target directory name. It must not contain or start with '..'.  If '.' is supplied, the volume directory will be the git repository.  Otherwise, if specified the volume will contain the git repository in the subdirectory with the given name. The other is “revision”, which is the commit hash for the specified revision.

Azure Container Instance (5): Building HTTPS Websites

HTTPS websites are more and more popular recently. Now you can also build websites with HTTPS connections via Azure Container Instance, w...