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:
-
Sqlite
-
Mysql
-
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
It is possible to provide a list of data sources per YAML file.
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:
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.
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.
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.
Name |
Description |
|
Connection URL to MySQL |
|
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.
{
"$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": {
}
}
{
"$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…