CloudKitty

Valorisez vos métriques Prométheus avec Cloudkitty : tutoriel avec Traefik

Ce post/billet de blog explique comment tarifer des métriques Prometheus avec Cloudkitty. Dans ce tutoriel, c’est Traefik qui est utilisé comme source de métriques.
Public visé: Administrateurs systèmes et développeurs intéressés par les conteneurs et par Prometheus, qui veulent évaluer et suivre la consommation des ressources de leurs applications.

Par Martin CAMEY, Développeur CloudKitty & Consultant Cloud @Objectif Libre

Cloudkitty est actuellement en phase de développement intensif, et plusieurs fonctionnalités sont en cours d’ajout. Cloudkitty est et restera la solution officielle de rating et de chargeback d’OpenStack. Cependant, avec l’émergence des conteneurs et des solutions de monitoring, Cloudkitty tend à devenir nativement utilisable avec les conteneurs, et plus largement avec tout type de métriques.

Pré-requis

Pour pouvoir suivre ce tutoriel, une machine sous Linux est nécessaire, avec Git, Docker et Docker-compose d’installés, ainsi qu’une connexion internet pour récupérer les images Docker.
Le tutoriel est basé sur les distributions Linux utilisant apt comme package manager, mais il est possible d’adapter les commandes suivantes pour suivre ce tutoriel sur d’autres distributions.

Design et fonctionnement

L’architecture modulable de Cloudkitty utilise des classes appelées « Collecteur » pour récupérer des métriques depuis leur source. Une fois récupérées, les métriques sont aggrégées et ensuite évaluées, grâce à un ensemble de règles définies par les opérateurs.
L’étape de valorisation signifie que Cloudkitty affecte une valeur pour la consommation de chaque ressource. Ces ressources peuvent être de type cpu, mémoire, espace disque, bande passante réseau ou de n’importe quel autre type. Ces valeurs serviront ensuite à la génération de rapports de valorisation au format json ou csv, pour chaque instance d’application.

Le travail en cours pour le Collecteur Prometheus de Cloudkitty permet de valoriser les métriques de cette solution déjà très utilisée de monitoring. Il sera bientôt ajouté à une branche de développement, avant d’être intégré à une version stable. Couplé à Cloudkitty, son utilisation est très simple, puisque Prometheus est fait pour gérer l’agrégation et la récupération de métriques. Les seules choses à faire sont de spécifier : les requêtes à effectuer vers Prometheus ; ainsi que les règles de valorisation correspondantes.

Tutorial pas-à-pas avec Traefik

Pour le Proof-of-Concept, nous utilisons Traefik (load-balancer et reverse proxy) comme source de métriques. Traefik supporte nativement l’export de métriques vers Prometheus, utilisant une configuration minimaliste.

Exemple avec un cas simple

Prenons un cas basique : deux conteneurs différents, tous deux exposés par Traefik. Nous voulons tarifer différement le nombre de requêtes effectuées pour chaque conteneur.

L’architecture globale des services déployés et de leurs interactions est la suivante :

Schema CloudKitty Prometheus

 

Nous allons configurer Cloudkitty pour enregistrer des datasets (= des structures de données contenant à la fois les valeurs tarifées et les métadonnées des métriques) pour chaque heure d’exécution de notre application. En tarifant le nombre de requêtes par heure, il est possible de générer des rapports de valorisation par heure, par jour, par semaine ou encore à l’année.

Pour cet exemple, nous utiliserons les images Docker officielles pour Prometheus, Traefik, RabbitMQ et MySQL (utilisés par Cloudkitty). Nous utiliserons également deux conteneurs exposant de simples fichiers statiques en HTML, du type « Hello World », grâce à l’image officielle Nginx. Tous les services cités ci-dessus sont lancés avec Docker-compose.

Configuration

Commençons par définir l’infrastructure pour cette démonstration. Nous utilisons le fichier docker-compose.yml suivant pour déployer les services :

docker-compose.yml

version: "2.2"

services:

  prometheus:
    image: prom/prometheus
    hostname: prometheus
    ports:
      - "9090:9090"
    volumes:
      - /path/to/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus:/prometheus
    networks:
      - cloudkitty_prometheus_traefik_net

  traefik:
    image: traefik:1.6
    hostname: traefik
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /path/to/traefik.toml:/etc/traefik/traefik.toml:ro
    networks:
      - cloudkitty_prometheus_traefik_net

  cloudkitty_db:
    image: mysql:5.7
    hostname: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=asecurepassword
      - MYSQL_USER=cloudkitty
      - MYSQL_PASSWORD=anothersecurepassword
      - MYSQL_DATABASE=cloudkitty
    volumes:
      - mysql:/var/lib/mysql
    ports:
      - "3306:3306"
    networks:
      - cloudkitty_prometheus_traefik_net

  cloudkitty_queue:
    image: rabbitmq:3.7.5-alpine
    hostname: rabbitmq
    environment:
      - RABBITMQ_DEFAULT_USER=cloudkitty
      - RABBITMQ_DEFAULT_PASS=asecurepassword
    volumes:
      - rabbitmq:/var/lib/rabbitmq:rw
    ports:
      - "5672:5672"
    networks:
      - cloudkitty_prometheus_traefik_net

  app1:
    image: nginx:alpine
    labels:
      traefik.frontend.rule: "Host:app1"
    volumes:
      - /path/to/html/app1/:/usr/share/nginx/html:ro
    networks:
      - cloudkitty_prometheus_traefik_net

  app2:
    image: nginx:alpine
    labels:
      traefik.frontend.rule: "Host:app2"
    volumes:
      - /path/to/html/app2/:/usr/share/nginx/html:ro
    networks:
      - cloudkitty_prometheus_traefik_net

  volumes:
    prometheus:
    mysql:
    rabbitmq:

  networks:
    cloudkitty_prometheus_traefik_net:

Notez que certains volumes sont créés pour prévenir toute perte de données en cas de re-démarrage des conteneurs.
Nous pouvons dès à présent préparer les configurations pour chaque composant de notre architecture. Ces fichiers sont utilisés par les conteneurs créés et gérés par le fichier Docker-compose ci-dessus.

prometheus.yml

scrape_configs:
  - job_name: 'traefik'
    scrape_interval: 5s

    static_configs:
      - targets: ['traefik:8080']
      labels:
        group: 'traefik_group'

traefik.toml

[entryPoints]
  [entryPoint.traefik]
    address = ":8080"
  [entryPoint.http]
    address = ":80"

[api]
  entryPoint = "traefik"

[docker]
  endpoint = "unix:///var/run/docker.sock"
  domain = "docker.localhost"
  watch = true

[metrics]
  [metrics.prometheus]
    entryPoint = "traefik"

Enfin, nous pouvons démarrer tous nos services définis dans docker-compose.ymlen utilisant la commande suivante :

$ docker-compose up -d

Installation de Cloudkitty

Dans ce tutoriel, nous utiliserons un virtualenv Python pour installer Cloudkitty.

Commençons par récupérer le code source de Cloudkitty depuis GitHub, puis par installer les dépendances et par préparer la configuration et les dossiers de logs de Clouditty. Les commandes suivantes sont à coupler avec les commandes chown et chmod pour adapter les droits d’accès en fonction de votre poste de travail.

Dans un terminal :

(ck_env) $ sudo mkdir /etc/cloudkitty /var/log/cloudkitty
(ck_env) $ cp etc/cloudkitty/api_paste.ini /etc/cloudkitty

Création d’un virtualenv et installation de Cloudkitty :

$ sudo apt install python-virtualenv
...
$ virtualenv ck_env
...
$ source ck_env/bin/activate
(ck_env) $ git clone https://github.com/openstack/cloudkitty.git
...
(ck_env) $ cd cloudkitty
(ck_env) $ pip install pymysql
...
(ck_env) $ pip install -r requirements.txt
...
(ck_env) $ python setup.py install
...

Configurons maintenant la nouvelle installation de Cloudkitty :

/etc/cloudkitty/cloudkitty.conf

[DEFAULT]
verbose = True
log_dir = /var/log/cloudkitty
auth_strategy = noauth
transport_url = rabbit://cloudkitty:asecurepassword@localhost:5672/

[database]
connection = mysql+pymysql://cloudkitty:password@localhost/cloudkitty

[collect]
fetcher = source
collector = prometheus
window = 1800
period = 3600
services = compute, volume, network.bw.in, network.bw.out, network.floating, image
metrics_conf = /etc/cloudkitty/metrics.yml

[storage]
backend = sqlalchemy

Maintenant que Cloudkitty est installé et configuré, nous allons initialiser la base de données de Cloudkitty.

(ck_env) $ cloudkitty-dbsync upgrade
...
(ck_env) $ cloudkitty-storage-init

Enfin, nous avons besoin de définir les métriques que nous voulons valoriser. Le metrics.yml est le fichier de configuration de Cloudkitty concernant la métrologie.

Dans notre cas, la métrique Traefik que nous allons valoriser s’appelle traefik_backend_requests_total. Cette métrique est stockée dans Prometheus comme un Compteur. Il compte simplement le nombre de requêtes effectuées vers Traefik, séparées par backend et par code de retour, et ne fait qu’augmenter. Pour pouvoir générer des rapports de valorisation par heure, Cloudkitty a besoin de connaitre le nombre exact de nouvelles requêtes effectuées chaque heure. Nous allons récupérer un range vector en passant les timestamp start et stop en paramètres, en plus de la fonction increase() fournie par Prometheus. Cette fonction calcule l’augmentation d’une métrique entre deux instants T1 et T2.

Même s’il y a un compteur par backend et par code de retour pour la même métrique, nous allons tous les récupérer en une seule requête, grâce aux labels Docker et Prometheus. Les seules choses que nous avons besoin de faire sont : de spécifier les requêtes PromQL à effectuer dans le fichier metrics.yml configuration file; de Cloudkitty ; et d’ajouter les règles de valorisation grâce à lacloudkitty-api. Nous avons déjà défini le champquery dans la configuration de CloudKitty.

/etc/cloudkitty/metrics.yml

name: Prometheus

fetcher: source
collector: prometheus

period: 3600 # An hour in seconds
wait_periods: 1
window: 1800

url: http://localhost:9090/api/v1/

services_objects:
  compute: instance
  volume: volume
  network.bw.out: instance_network_interface
  network.bw.in: instance_network_interface
  network.floating: network
  image: image
  radosgw.usage: ceph_account

metrics:
  traefik_backend_requests_total:
    endpoint: query_range
    query: 'increase(traefik_backend_requests_total[$period])'
    unit: request
    metadata:
      - backend

Il est temps de pousser nos fomules de valorisation en utilisant la cloudkitty-api.

Premièrement, commençons par démarrer la cloudkitty-api:

(ck_env) $ cloudkitty-api -p 8889
...
********************************************************************************
STARTING test server cloudkitty.api.app.build_wsgi_app
Available at http://localhost:8889/
...
********************************************************************************

Une fois lancée, ouvrons un nouveau terminal. Nous allons utiliser les commandes suivantes pour pousser les formules de valorisation, modestement tarifées à 10 centimes la requête pour la première application, et 30 centimes la requête pour la seconde.

Commençons par créér un group ‘request_number’ pour les formules de valorisation :

$ curl -X POST -H 'Content-Type: application/json' \
> -d '{"name": "request_number"}' \
> 'http://localhost:8889/v1/rating/module_config/hashmap/groups'
La réponse sera de la forme :
{"group_id": "8bcea13d-102f-44f5-b164-152e39745865", "name": "request_number"}
Créons un ‘service’ de valorisation à appliquer aux métriques récupérées :
$ curl -X POST -H 'Content-Type: application/json' \
> -d  '{"name": "traefik_backend_requests_total"}' \
> 'http://localhost:8889/v1/rating/module_config/hashmap/services'
Le résultat ressemblera à ceci :
{
  "service_id": "7f3a1d40-c91b-470e-bd62-930468e36dc2",
  "name": "traefik_backend_requests_total"
}
Créons maintenant un ‘champ’ qui correspondra à chacun de nos backends :
$ curl -X POST -H 'Content-Type: application/json' \
> -d '{"service_id": "7f3a1d40-c91b-470e-bd62-930468e36dc2", "name": "backend"}' \
> 'http://localhost:8889/v1/rating/module_config/hashmap/fields'
Une fois ce champ créé, l’API retournera une réponse sous la forme :
{
  "service_id": "7f3a1d40-c91b-470e-bd62-930468e36dc2",
  "field_id": "2310d614-fdaf-4040-a336-75e49a98456d",
  "name": "backend"
}

C’est presque terminé ! Nous avons juste besoin de définir les différents taux de valorisation pour nos formules.
Commençons par le premier backend en créant une association entre un groupe, la valeur d’un champ et le taux de valorisation :

$ curl -X POST -H 'Content-Type: application/json' \
> -d '{"group_id": "8bcea13d-102f-44f5-b164-152e39745865", \
> "service_id": "7f3a1d40-c91b-470e-bd62-930468e36dc2", \
> "field_id": "2310d614-fdaf-4040-a336-75e49a98456d", \
> "value": "backend-app1-cloudkitty-prometheus-traefik", "cost": "0.1"}' \
> 'http://localhost:8889/v1/rating/module_config/hashmap/mappings'
Le résultat de l’association créée :
{
  "tenant_id": null,
  "field_id": "2310d614-fdaf-4040-a336-75e49a98456d",
  "value": "backend-app1-cloudkitty-prometheus-traefik",
  "mapping_id": "6b4bf3a1-9d43-4c92-93d5-06c87a6e6bfb",
  "cost": "0.1000000",
  "service_id": null,
  "group_id": "8bcea13d-102f-44f5-b164-152e39745865",
  "type": "flat"
}
Même opération pour la seconde application :
$ curl -X POST -H 'Content-Type: application/json' \
> -d '{"group_id": "8bcea13d-102f-44f5-b164-152e39745865", \
> "service_id": "7f3a1d40-c91b-470e-bd62-930468e36dc2", \
> "field_id": "2310d614-fdaf-4040-a336-75e49a98456d", \
> "value": "backend-app2-cloudkitty-prometheus-traefik", "cost": "0.3"}' \
> 'http://localhost:8889/v1/rating/module_config/hashmap/mappings'
Et son résultat :
{
  "tenant_id": null,
  "field_id": "2310d614-fdaf-4040-a336-75e49a98456d",
  "value": "backend-app1-cloudkitty-prometheus-traefik",
  "mapping_id": "3e077906-629b-493f-9b9b-1cb32b5f405d",
  "cost": "0.3000000",
  "service_id": null,
  "group_id": "8bcea13d-102f-44f5-b164-152e39745865",
  "type": "flat"
}

Et voilà ! Nous avons créé nos règles de valorisation qui sont dorénavant enregistrées dans la base de données de Cloudkitty.
Enfin, nous avons juste à activer le module HashMap responsable de l’application des formules :

$ curl -X PUT -H 'Content-type: application/json' \
> -d '{"module_id": "hashmap", "enabled": "true"}' \
> 'http://localhost:8889/v1/rating/modules'

Déployer la solution de valorisation Cloudkitty

Maintenant que tous nos composants sont configurés, démarrons le cloudkitty-processor, le programme responsable du rating et du chargeback.

Nous pouvons arrêter lacloudkitty-api dans le premier terminal et lancer la commande suivante pour démarrer le cloudkitty-processor:

(ck_env) $ cloudkitty-processor --config-file /etc/cloudkitty/cloudkitty.conf \
> --log-file /var/log/cloudkitty/processor.log

C’est quasiment fini ! Nous avons juste besoin de générer des requêtes vers nos deux applications précédemment déployées, qui seront ensuite valorisées par Cloudkitty.

Dans ce tutoriel, nous utiliserons ApacheBench (ou ab), mais vous pouvez utiliser le client HTTP de votre choix.
Générons des requêtes pour nos deux applications.

Dans un autre terminal, installons le paquet nécessaire :

$ sudo apt install apache2-utils

Nous allons effectuer 150 requêtes pour la première application et 90 pour la seconde :

$ ab -H "Host:app1" -n 150 -c 10 http://localhost/
$ ab -H "Host:app2" -n 90 -c 10 http://localhost/

C’est terminé !
Traefik utilise le header HTTP utilisé dans le commande ci-dessus pour rediriger les requêtes vers le bon backend.  Nous pouvons maintenant observer les métriques dans Prometheus en allant sur http://localhost:9090 depuis un navigateur, en plus des backends et codes de retour dans Traefik à l’adresse http://localhost:8080.

La génération des rapports de valorisation feront l’objet d’un nouvel article. Restez connectés !

Conclusion

Bien que Cloudkitty ait vocation à rester la solution de rating et de chargeback d’OpenStack, il évolue et son architecture modulaire lui permet d’étendre son champ d’action à tout l’écosystème cloud natif. Le Collecteur Prometheus pour Cloudkitty reflète cette évolution, même s’il est encore en cours de développement.

Cloudkitty a besoin de contributeurs ! Si vous avez besoin de fonctionnalités dans le Collecteur Prometheus, ou tout simplement d’un autre collecteur, venez en proposer et en implémenter de nouveaux !