This the translation of a previous article published in French.

Azure Container Instances (ACI) is one of many ways to run containers on Azure. One of the advantages of ACI over App Service (presented in a previous article (in French)) is the ability to expose multiple ports.

So we’re going to expose a Grafana instance and an InfluxDB database. Grafana is an open source solution for creating dashboard. InfluxDB is its companion of choice for time series storage.

In this article, for simplicity sake, I will not expose Grafana over HTTPS. You will have to refer to my previous article Certificat Let’s Encrypt sur Azure Container Instances et NGINX (in French) for the implementation of HTTPS.

The solution

As mentioned, the solution will run on Azure Container Instances.

Grafana needs a database for its configuration. Grafana supports 3 kind of databases:

  1. Sqlite

  2. Mysql

  3. Postgresql

To avoid losing the configuration if the container restarts, the data is persistently stored on Azure Files.

Unfortunately, due to some limitations, it was not possible to persist on Azure Files the SQLite database (embarked by default in Grafana) (a dark story about locks) or to use the Docker image provided by PostgreSQL (a dark story about files and their owner).

We will therefore use MySQL and its Docker image.

MySQL will not be published on the Internet.

Grafana data sources

We will use this mechanism to automatically set up InfluxDB as a data source.

So, the configuration of the data source in YAML will be:

influxdb.yaml
apiVersion: 1

deleteDatasources:
  - name: InfluxDB
    orgId: 1

datasources:
  - name: InfluxDB
    type: influxdb
    access: proxy
    orgId: 1
    database: PERF
    user: influxadmin
    password: influxadmin
    url: http://localhost:8086

This file will be passed as a secret. The base64 function of ARM template will be used to encode the content and pass it as a secret.

Note about container groups in Azure Container Instances

Azure Container Instances use a "proprietary" format that is neither Docker Compose nor a Kubernetes deployment file.

The important point to keep in mind is that we will declare each port expose by a container, and then which of them are exposed by the container group. It is not possible to translate ports, as recall in the FAQ.

Also, beware that communication between containers is not using the name of the container (as in Docker Compose) but localhost!

Thus, Grafana will point in its configuration to localhost:3036 for its database and localhost:8086 for the case of InfluxDB.

Automated deploiement

It is possible to use YAML or ARM template. As mentioned in my previous article (in French),I highly recommend the use of ARM template that will allow to have some dynamic settings that can be changed during deployment.

However, I will limit the number of parameters.

Environment variables

The environment variables we will use are described below.

Table 1. MySQL

Name

Description

MYSQL_DATABASE

The name of a database to be created on image startup.

MYSQL_USER, MYSQL_PASSWORD

Username and password of a user created that has superuser permissions on the database

MYSQL_RANDOM_ROOT_PASSWORD

Will generate a random password for the root user

Other environment variables are available and are described on MySQL’s Docker image page.

Table 2. InfluxDB

Name

Description

INFLUXDB_DB

The name of a database to be created on image startup.

INFLUXDB_ADMIN_USER, INFLUXDB_ADMIN_PASSWORD

Username and password of database admin.

Other environment variables are available and are described on InfluxDB’s Docker image page.

Grafana has a very extensible mechanism that allows to define any parameter from environment variables. We will use the following environment variable.

Table 3. Grafana

Name

Description

GF_DATABASE_URL

Connection URL to MySQL

GF_SECURITY_ADMIN_PASSWORD

Password for admin (this will avoid going through the configuration assistant)

In my previous article, I also used the variable GF_SERVER_DOMAIN to set up Grafana behind NGINX.

ARM template

The ARM template and settings are shown below.

grafana.json
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "grafanaAdminPassword": {
            "type": "securestring",
            "metadata": {
                "description": "Password for Grafana admin."
            }
        },
        "containerGroupName": {
            "type": "string",
            "metadata": {
                "description": "Container Group name."
            }
        },
        "dnsLabel": {
            "type": "string",
            "defaultValue": "",
            "metadata": {
                "description": "DNS label used to by the container group. The FQDN is <dnsLabel>.<region>.azurecontainer.io"
            }
        },
        "storageAccountName": {
            "type": "string",
            "metadata": {
                "description": "Name of the Storage Account"
            }
        },
        "storageAccountType": {
            "type": "string",
            "defaultValue": "Standard_LRS",
            "allowedValues": [
                "Standard_LRS",
                "Standard_GRS",
                "Standard_ZRS",
                "Premium_LRS"
            ],
            "metadata": {
                "description": "Storage Account type"
            }
        },
        "accessTier": {
            "type": "string",
            "defaultValue": "Hot",
            "allowedValues": [
                "Hot",
                "Cool"
            ],
            "metadata": {
                "description": "The access tier used for billing."
            }
        },
        "storageAccountKind": {
            "type": "string",
            "defaultValue": "StorageV2",
            "allowedValues": [
                "StorageV2",
                "Storage",
                "BlobStorage",
                "FileStorage",
                "BlockBlobStorage"
            ],
            "metadata": {
                "description": "Storage Account type"
            }
        },
        "advancedThreatProtectionEnabled": {
            "type": "bool",
            "defaultValue": false,
            "metadata": {
                "description": "Enable or disable Advanced Threat Protection."
            }
        },
        "shares": {
            "type": "array",
            "metadata": {
                "description": "List of the file share names."
            }
        },
        "location": {
            "type": "string",
            "defaultValue": "[resourceGroup().location]",
            "metadata": {
                "description": "The region to deploy the resources into"
            }
        },
        "tagValues": {
            "type": "object",
            "defaultValue": {
            }
        }
    },
    "variables": {
        "dnsLabel": "[if(empty(parameters('dnsLabel')), parameters('containerGroupName'), parameters('dnsLabel'))]",
        "fqdn": "[toLower(concat(variables('dnsLabel'),'.',replace(parameters('location'), ' ', ''),'.azurecontainer.io'))]"
    },
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "name": "[parameters('storageAccountName')]",
            "location": "[parameters('location')]",
            "apiVersion": "2018-07-01",
            "sku": {
                "name": "[parameters('storageAccountType')]"
            },
            "kind": "[parameters('storageAccountKind')]",
            "properties": {
                "accessTier": "[parameters('accessTier')]",
                "encryption": {
                    "keySource": "Microsoft.Storage",
                    "services": {
                        "blob": {
                            "enabled": true
                        },
                        "file": {
                            "enabled": true
                        }
                    }
                },
                "supportsHttpsTrafficOnly": true
            },
            "resources": [
                {
                    "condition": "[parameters('advancedThreatProtectionEnabled')]",
                    "type": "providers/advancedThreatProtectionSettings",
                    "name": "Microsoft.Security/current",
                    "apiVersion": "2017-08-01-preview",
                    "dependsOn": [
                        "[resourceId('Microsoft.Storage/storageAccounts/', parameters('storageAccountName'))]"
                    ],
                    "properties": {
                        "isEnabled": true
                    }
                }
            ]
        },
        {
            "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
            "apiVersion": "2019-04-01",
            "name": "[concat(parameters('storageAccountName'), '/default/', parameters('shares')[copyIndex()])]",
            "copy": {
                "name": "sharecopy",
                "count": "[length(parameters('shares'))]"
            },
            "dependsOn": [
                "[parameters('storageAccountName')]"
            ]
        },
        {
            "name": "[parameters('containerGroupName')]",
            "type": "Microsoft.ContainerInstance/containerGroups",
            "dependsOn": [
                "sharecopy"
            ],
            "apiVersion": "2018-10-01",
            "location": "[parameters('location')]",
            "properties": {
                "containers": [
                    {
                        "name": "mysql",
                        "properties": {
                            "image": "mysql",
                            "environmentVariables": [
                                {
                                    "name": "MYSQL_USER",
                                    "value": "grafana"
                                },
                                {
                                    "name": "MYSQL_PASSWORD",
                                    "secureValue": "grafana"
                                },
                                {
                                    "name": "MYSQL_RANDOM_ROOT_PASSWORD",
                                    "value": "yes"
                                },
                                {
                                    "name": "MYSQL_DATABASE",
                                    "value": "grafana"
                                }
                            ],
                            "resources": {
                                "requests": {
                                    "cpu": 0.5,
                                    "memoryInGb": 0.5
                                }
                            },
                            "ports": [
                                {
                                    "port": 3306
                                },
                                {
                                    "port": 443
                                }
                            ],
                            "volumeMounts": [
                                {
                                    "name": "mysql-data",
                                    "mountPath": "/var/lib/mysql"
                                }
                            ]
                        }
                    },
                                        {
                        "name": "influxdb",
                        "properties": {
                            "image": "influxdb",
                            "environmentVariables": [
                                {
                                    "name": "INFLUXDB_DB",
                                    "value": "PERF"
                                },
                                {
                                    "name": "INFLUXDB_ADMIN_USER",
                                    "value": "influxadmin"
                                },
                                {
                                    "name": "INFLUXDB_ADMIN_PASSWORD",
                                    "secureValue": "influxadmin"
                                }
                            ],
                            "resources": {
                                "requests": {
                                    "cpu": 0.5,
                                    "memoryInGb": 0.5
                                }
                            },
                            "ports": [
                                {
                                    "port": 8086
                                }
                            ],
                            "volumeMounts": [
                                {
                                    "name": "influxdb-volume",
                                    "mountPath": "/var/lib/influxdb"
                                }
                            ]
                        }
                    },
                    {
                        "name": "grafana",
                        "properties": {
                            "image": "grafana/grafana",

                            "ports": [
                                {
                                    "port": 3000
                                }
                            ],
                            "environmentVariables": [
                                {
                                    "name": "GF_SECURITY_ADMIN_PASSWORD",
                                    "secureValue": "[parameters('grafanaAdminPassword')]"
                                },
                                {
                                    "name": "GF_DATABASE_URL",
                                    "secureValue": "mysql://grafana:grafana@localhost:3306/grafana"
                                }
                            ],
                            "resources": {
                                "requests": {
                                    "cpu": 1,
                                    "memoryInGb": 0.5
                                }
                            },
                            "volumeMounts": [
                                {
                                    "name": "grafana-volume",
                                    "mountPath": "/var/lib/grafana"
                                },
                                {
                                    "name": "grafana-provisioning",
                                    "mountPath": "/etc/grafana/provisioning/datasources"
                                }
                            ]
                        }
                    }
                ],
                "osType": "Linux",
                "restartPolicy": "OnFailure",
                "ipAddress": {
                    "type": "Public",
                    "ports": [
                        {
                            "port": 3000
                        },
                        {
                            "port": 8086
                        }
                    ],
                    "dnsNameLabel": "[variables('dnsLabel')]"
                },
                "volumes": [
                    {
                        "name": "mysql-data",
                        "azureFile": {
                            "shareName": "mysql-data",
                            "storageAccountName": "[parameters('storageAccountName')]",
                            "storageAccountKey": "[listKeys(resourceId('Microsoft.Storage/storageAccounts',parameters('storageAccountName')),'2017-10-01').keys[0].value]"
                        }
                    },
                    {
                        "name": "influxdb-volume",
                        "azureFile": {
                            "shareName": "influxdb-volume",
                            "storageAccountName": "[parameters('storageAccountName')]",
                            "storageAccountKey": "[listKeys(resourceId('Microsoft.Storage/storageAccounts',parameters('storageAccountName')),'2017-10-01').keys[0].value]"
                        }
                    },
                    {
                        "name": "grafana-volume",
                        "azureFile": {
                            "shareName": "grafana-volume",
                            "storageAccountName": "[parameters('storageAccountName')]",
                            "storageAccountKey": "[listKeys(resourceId('Microsoft.Storage/storageAccounts',parameters('storageAccountName')),'2017-10-01').keys[0].value]"
                        }
                    },
                    {
                        "name": "grafana-provisioning",
                        "secret": {
                            "influxdb.yaml": "[base64('apiVersion: 1

deleteDatasources:
  - name: InfluxDB
    orgId: 1

datasources:
  - name: InfluxDB
    type: influxdb
    access: proxy
    orgId: 1
    database: PERF
    user: influxadmin
    password: influxadmin
    url: http://localhost:8086
                            ')]"
                        }
                    }
                ]
            }
        }

    ],
    "outputs": {
    }
}
grafana.parameters.json
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "grafanaAdminPassword": {
            "value": "grafanapwd"
        },
        "containerGroupName": {
            "value": "test-grafana-influxdb"
        },
        "storageAccountName": {
            "value": "stotestacmenginx"
        },
        "shares": {
            "value": [
                "mysql-data",
                "influxdb-volume",
                "grafana-volume"
            ]
        },
        "location": {
            "value": "West Europe"
        }
    }
}

It is possible to deploy by using the following PowerShell command:

 New-AzResourceGroupDeployment -ResourceGroupName $rg -TemplateFile .\influxdb-grafana.json -TemplateParameterFile .\influxdb-grafana.parameters.json -Verbose

Also, by using Az CLI:

az group deployment create --resource-group $rg --template-file ./influxdb-grafana.json --parameters @influxdb-grafana.parameters.json --handle-extended-json-format

You will have to use the setting --handle-extended-json-format that provides support for multiline in JSON.

Conclusion

Within a few minutes, it is possible to assemble a Grafana and an InfluxDB database.

There is a lot of use cases that might benefits for an InfluxDB and Grafana: IoT, application monitoring, etc. A use case could also be load tests with Locust. To be continued…​