Tuesday, July 31, 2018

Azure Container Instance (6): Socket Connections in ACI


This article is going to summarize how to have socket connections within ACI. The containers for socket server and client are in the same container group, so the server and client locate in the same virtual machine. The sample code, for both socket server and client, is in Node.js.

Define Socket Server

The following Node.js code creates a socket server, which listens on port 1337 of localhost:

var net = require('net');

function getTimePrefix() {
    return (new Date()).toString() + ' ';
}

var server = net.createServer(function(socket) {
    socket.on('data', function(data){
        textChunk = data.toString('utf8');
        console.log(getTimePrefix() + textChunk);
        socket.write(textChunk);
    });
   
    socket.on('error', function(err) {
       console.log(getTimePrefix() + err)
    })
});

server.listen(1337, 'localhost');

Define Socket Client

The following Node.js code creates 200 socket clients, which try to connect to the socket server concurrently:

var connections = 200;
const cp = require('child_process');
var net = require('net');

function getTimePrefix() {
    return (new Date()).toString() + ' ';
}

var onConnected = function(client, connection) {
    return function() {
        client.write(connection + ': Hello, server! Love, Client.');
    };
};

var onData = function(client, connection) {
    return function(data) {
        console.log(getTimePrefix() + 'Connection ' + connection + ' received: ' + data);
        client.destroy();
    };
}
   
var onClosed = function(connection) {
    return function() {
        console.log(getTimePrefix() + 'Connection ' + connection + ' closed');
    };
};

var onError = function(connection) {
    return function(err) {
        console.log(getTimePrefix() + 'Connection ' + connection + ' has error: ' + err);
    };
};

var startClient = function () {
    for (var i = 0; i < connections; ++i) {
        var client = new net.Socket();
        client.connect(1337, 'localhost', onConnected(client, i));
        client.on('data', onData(client, i));
        client.on('close', onClosed(i));
        client.on('error', onError(i));
    }
};

setInterval(startClient, 10000);

Build Docker Image

We can build both server and client applications into docker images. I’ve published the server image as containerinstance/socket-server, and client image as containerinstance/socket-client in Docker Hub.

Create Azure Container Instance

If we include the socket server and client into an Azure container instance, they can talk to each other. The following is a sample of Azure container instance definition to deploy both the socket server and client:

{
  "properties": {
    "containers": [
      {
        "name": "socket-server",
        "properties": {
          "command": [],
          "image": "containerinstance/socket-server:20180731",
          "ports": [
            {
              "port": 1337
            }
          ],
          "resources": {
            "requests": {
              "cpu": 1,
              "memoryInGb": 1.5
            }
          }
        }
      },
      {
        "name": "socket-client",
        "properties": {
          "command": [],
          "image": "containerinstance/socket-client:20180731",
          "ports": [],
          "resources": {
            "requests": {
              "cpu": 1,
              "memoryInGb": 1.5
            }
          }
        }
      }
    ],
    "osType": "Linux",
    "ipAddress": {
      "ports": [
        {
          "protocol": "TCP",
          "port": 1337
        }
      ],
      "type": "Public"
    }
  },
  "location": "westus"
}

The ACI logs API can demonstrate that these two containers talk smoothly.

Notes



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.

AKS (1) - Five seconds latency when resolving DNS

We intermittently meet 5s latencies in an AKS clusters with CNI when it’s resolving DNS. This article is to summarize what we have learned...