Azure Container Instances (ACI) fait partie des nombreuses façons d’exécuter des containers sur Azure. Un des avantages d’ACI par rapport à App Service (présenté dans un précédent article) est la capacité à exposer plusieurs ports.
Ainsi, nous allons exposer un Grafana et une base InfluxDB. Grafana est une solution open source pour la création de tableau de bord. InfluxDB est son compagnon de choix pour le stockage de série temporelle (time series).
Dans cet article, pour des raisons de simplicité, je n’exposerai pas Grafana en HTTPS. Il faudra se référer à mon précédent article Certificat Let’s Encrypt sur Azure Container Instances et NGINX pour la mise en place de HTTPS. |
La solution
Comme évoqué, la solution tournera sur Azure Container Instances.
Grafana a besoin d’une base de données pour sa configuration. Grafana supporte 3 bases de données :
-
SQLite
-
MySQL
-
PostgreSQL
Pour éviter de perdre la configuration à chaque redémarrage, la persistance des données est assurée par Azure Files.
Malheureusement, à cause de certaines limitations, il n’a pas été possible de faire persister SQLite (embarqué par défaut dans Grafana) (une sombre histoire de verrou) ou d’utiliser l’image de base de PostgreSQL (une sombre histoire de propriétaire sur des fichiers). |
On utilisera donc l’image docker de MySQL.
MySQL ne sera pas exposé sur Internet |
Les sources de données Grafana
Il est possible de fournir une liste de sources de données par fichier YAML.
Nous allons utiliser ce mécanisme pour configurer automatiquement InfluxDB.
Ainsi la configuration de la source de données en YAML sera :
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
Ce fichier sera passé comme secret.
On utilisera la fonction base64
d’ARM template pour passer le fichier YAML en secret.
Note sur les groupes de container dans Azure Container Instances
Azure Container Instances utilisent un format de données "propriétaire" qui n’est ni Docker Compose, ni un fichier de déploiement Kubernetes.
Le point important à garder en tête est que l’on va déclarer chaque port exposé par un container, puis ensuite quels sont les ports exposés par le groupe de container. Il n’est pas possible de changer le port exposé par un container par rapport au port du groupe (par une translation), comme rappelé dans la FAQ.
La communication entre les containers se fait non pas en utilisant le nom du container (comme dans Docker Compose) mais localhost !
Ainsi Grafana pointera dans sa configuration vers localhost:3036 pour sa base de données et localhost:8086 pour le cas d’InfluxDB.
Automatisation du déploiement
Il est possible d’utiliser du YAML ou de l’ARM template. Comme évoqué dans mon précédent article, je recommande fortement l’utilisation d’ARM template qui va permettre de rendre dynamique certains paramètres de déploiement.
Pour autant, je vais limiter les paramètres possibles.
Variables d’environnement
Les variables d’environnement que nous allons utiliser sont décrites ci-dessous.
Nom |
Description |
MYSQL_DATABASE |
Nom de la base crée au démarrage |
MYSQL_USER, MYSQL_PASSWORD |
Nom de l’utilisateur et mot de passe propriétaire de la base |
MYSQL_RANDOM_ROOT_PASSWORD |
Va générer un mot de passe aléatoire pour l’utilisateur root |
D’autres variables d’environnement sont disponibles et sont décrites sur la page de l’image Docker de MySQL.
Nom |
Description |
INFLUXDB_DB |
Nom de la base crée au démarrage |
INFLUXDB_ADMIN_USER, INFLUXDB_ADMIN_PASSWORD |
Nom de l’utilisateur et mot de passe administrateur de la base |
D’autres variables d’environnement sont disponibles et sont décrites sur la page de l’image Docker d’InfluxDB.
Grafana dispose d’un mécanisme très extensible qui permet de définir n’importe quel paramètre à partir de variables d’environnement.
Nom |
Description |
|
URL de connexion à MySQL |
|
Mot de passe de l’utilisateur admin (cela évitera de passer dans l’assistant de configuration) |
Dans mon précédent article, j’avais également utilisé la variable GF_SERVER_DOMAIN
pour configurer Grafana derrière NGINX.
ARM template
L’ARM template et les paramètres sont définis ci-dessous.
{
"$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"
}
}
}
Il est possible de déployer par PowerShell :
New-AzResourceGroupDeployment -ResourceGroupName $rg -TemplateFile .\influxdb-grafana.json -TemplateParameterFile .\influxdb-grafana.parameters.json -Verbose
Egalement, en Az CLI :
az group deployment create --resource-group $rg --template-file ./influxdb-grafana.json --parameters @influxdb-grafana.parameters.json --handle-extended-json-format
Il faudra bien penser au paramètre --handle-extended-json-format
qui apporte le support du multiligne en JSON.
Conclusion
En quelques minutes, il est possible de monter un Grafana et une base InfluxDB.
Les cas d’usage d’InfluxDB et Grafana ne manquent pas : IoT, surveillance applicative, etc. Un cas d’utilisation pourrait être aussi des tests de charge avec Locust. A suivre…