XinCTO/prometheus - bien démarrer en python

Ecrit le Sat, 26 May 2018 15:46:31 +0000

prometheus - bien démarrer en python

La solution de collete et traitement des métriques prometheus tire parti de l’instrumentation du code développé. Cet article pour vous aider à bien démarrer en python.

Maintenant que les principes de base de prometheus n’ont plus de secret pour vous, voici de quoi bien démarrer en python. Nous y verrons le principe de fonctionnement sur un web service simple développé avec Flask et qui pourra être intégré dans un écosystème prometheus. Nous verrons dans un prochain article un exemple complet de web service qui fait du sens et son intégration dans prometheus et grafana.

Si vous n’avez pas vu toute la série sur le sujet, voici la table des sujets abordés dans les différents articles :

  1. prometheus - concepts de base : introduction à prometheus, principes de fonctionnement, usages
  2. prometheus - bien démarrer en python : comment instrumenter son code python afin de fournir les services de métrologie pour prometheus
  3. prometheus et grafana : intégration de prometheus avec l’outil de dataviz grafana
  4. prometheus avec consul : intégration avec consul pour la découverte automatique des services à intégrer dans prometheus
  5. prometheus - bien démarrer en java : comment instrumenter du code java spring afin de remonter les métriques dans prometheus

Vous trouverez les sources sur le repository git intitulé lab-prometheus dans la section 01-python-hello-world, chaque fichier srvXX.py représente une étape de l’avancement de notre exemple en phyton, vous pourrez sautez directement au 6^ème^ pour le résultat final.

environnement

Si vous souhaitez effectuer les tests il vous faudra récupérer les codes sur le repository git et préparer un environnement. Si vous n’avez plus les étapes en tête, les voici :

1git clone https://github.com/achauvinhameau/lab-prometheus.git
2cd lab-prometheus/01-python-hello-world
3virtualenv lab01
4source lab01/bin/activate
5pip install -r requirements.txt
6python ./srv1.py
7deactivate
  • ligne 1 clone le repository.
  • ligne 3 crée un environnement python spécifique dans lequel seront installées les librairies nécessaires sans casser la configuration de votre poste. Ceci est par ailleurs une bonne pratique et permet d’isoler les activités et les travaux python sur son poste de travail ou un serveur.
  • ligne 4 lance l’environnement virtuel, vous aurez alors un prompt spécifique mentionnant (lab01).
  • ligne 5 permet d’installer les dépendances logicielles requises, ici à minima Flask et la librairie de prometheus.
  • ligne 6 lance le serveur qui pourra alors recevoir vos requêtes, par exemple avec postman.
  • enfin, la ligne 7 sera utilisée pour sortir de l’environnement de travail.

le web service nu

Fichier : srv01.py

Si vous souhaitez plus d’information sur le fonctionnement de Flask, vous trouverez beaucoup de litterature, les informations importantes pour nous sont les suivantes.

L’application est démarrée dans la section __main__ :

app.run(host='0.0.0.0')

Le simple web service utilisé ici retourne une structure au format json avec la date actuelle sous plusieurs formes :

@app.route('/now')
def ws_now():
    """Now service to get date time on the server."""
    now = datetime.datetime.now()
    return make_response(jsonify({
        'epoch': now.timestamp(),
        'ctime': now.ctime(),
        'date': str(now)
    }), 200)

La première ligne est un décorateur (nous y reviendrons plus loin) utilisé par Flask afin de déterminer le point d’accès au web service, ici nous n’avons spécifié que l’url. Un appel sur celle-ci retournera donc le contenu en json, par exemple : http://192.168.16.205:5000/now

{
  "ctime": "Sat May 26 16:24:01 2018",
  "date": "2018-05-26 16:24:01.258457",
  "epoch": 1527344641.258457
}

ajout de prometheus

Fichier : srv02.py

Embellissons notre web service par les services de base de prometheus. Pour cela, on ajoute la librairie et le endpoint permettant à prometheus de récupérer les métriques, ici /metrics.

Il suffit donc d’ajouter 2 blocs :

from prometheus_client import (generate_latest,
                               CONTENT_TYPE_LATEST )

Cette partie est pour la librairie dont nous n’utilisons que deux composants pour l’instant, le générateur et un type de présentation.

@app.route('/metrics')
def metrics():
    """Flask endpoint to gather the metrics, will be called by Prometheus."""
    return Response(generate_latest(),
                    mimetype=CONTENT_TYPE_LATEST)

Le endpoint utilise Flask simplement en retournant une réponse de type texte, chaque ligne présentant une valeur. Les résultats par défaut pour python sont assez sommaires, nous verrons plus tard que dans le cas de java nous avons plus de richesse.

http://192.168.16.205:5000/metrics

# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="4",patchlevel="8",version="3.4.8"} 1.0
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 385454080.0
# HELP process_resident_memory_bytes Resident memory size in bytes.
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 22761472.0
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1527346717.67
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 0.29
# HELP process_open_fds Number of open file descriptors.
# TYPE process_open_fds gauge
process_open_fds 8.0
# HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge
process_max_fds 32.0

A ce stade, nous pourrions ajouter directement cette cible à notre prometheus, il collecterait alors régulièrement le contenu de ce endpoint.

ajout d’une metrique métier simple

Fichier : srv03.py

Le but de notre instrumentation n’est pas simplement de récupérer la version du python et sa consommation en mémoire ou CPU, c’est un peu pauvre. Ajoutons une métrique de type métier dans notre appel de web service, ici un simple compteur du nombre d’appel étant tombé sur un numéro de seconde paire (pas beaucoup d’intérêt si ce n’est la simplicité).

Nous utilisons la métrique de type compteur (Counter) qui ne peut être qu’incrémentée, mais c’est ce que nous chercons. Si on souhaite une valeur qui puisse varier à la hausse et à la baisse on construira alors une gauge. A noter que les incréments ne sont pas obligatoirement entiers.

Il suffit d’ajouter la définition du compteur, elle comporte le nom de la variable qui sera manipulée dans prometheus (ou grafana) et une description. Ces deux informations seront véhiculées dans le résultat sous la forme d’un commentaire.

COUNTER_NOW_EVEN = Counter('ws_srv_is_now_even',
                           'count even now aligned on second')

il faudra également ajouter Counter dans l’import de prometheus.

Nous ajoutons ces lignes dans la fonction ws_now

    if int(now.timestamp()) % 2 == 0:
        COUNTER_NOW_EVEN.inc()

Le résultat (additionnel) obtenu après quelques appels ressemble à cela :

http://192.168.16.205:5000/metrics

# HELP ws_srv_is_now_even count even now aligned on second
# TYPE ws_srv_is_now_even counter
ws_srv_is_now_even 5.0

variabilisation de la métrique

Fichier : srv04.py

Définir des métriques pour chaque acte métier pourrait être fastidieux d’autant que souvent ils ne sont que des déclisaisons de valeurs similaires. Pour cela prometheus nous offre la possibilité d’ajouter des labels à nos compteurs.

La définition du compteur est légèrement modifiée afin de faire apparaître le ou les labels :

COUNTER_NOW_EVEN = Counter('ws_srv_is_now_even',
                           'count even and odd calls to now aligned on second',
                           ['even'])

et on assigne le label lors de l’incrément de notre compteur, cette fois-ci on enrichit notre analyse en comptant les appels sur des secondes paires et ceux sur les secondes impaires (ws_now()):

    if int(now.timestamp()) % 2 == 0:
        COUNTER_NOW_EVEN.labels(even='yes').inc()
    else:
        COUNTER_NOW_EVEN.labels(even='no').inc()

Dans le résultat, on retrouve donc :

http://192.168.16.205:5000/metrics

# HELP ws_srv_is_now_even count even and odd calls to now aligned on second
# TYPE ws_srv_is_now_even counter
ws_srv_is_now_even{even="yes"} 6.0
ws_srv_is_now_even{even="no"} 10.0

Dans prometheus on pourra bien entendu utiliser ces labels pour effectuer des filtres et des calculs différenciés.

ajout de métriques d’usage

Fichier : srv05.py

En plus de données métier, il peut être intéressant d’avoir des données sur l’utilisation de notre application, par exemple le temps passé et le nombre d’appel dans des fonctions particulières. Pour cela prometheus offre un décorateur à appliquer sur la fonction dont on souhaite suivre les performances et l’utilisation, cette fois-ci l’objet est un résumé (Summary). Un summary suit le compte et le volume d’un objet, chaque appel à sa méthode observe(x) ajoute un appel et la valeur x incrémente le volume.

On défini la métrique :

COUNT_FCT_NOW = Summary('ws_srv_func_now', 'stats of function now')

et on l’applique en tant que décorateur sur notre fonction en utilisant le suivi du temps passé comme observation (dans ws_now()).

@app.route('/now')
@COUNT_FCT_NOW.time()
def ws_now():

Si vous n’êtes pas familiers avec les décorateurs en python, un peu de lecture pourra être utile.

Le résultat présente les lignes suivantes en complément : http://192.168.16.205:5000/metrics

# HELP ws_srv_func_now stats of function now
# TYPE ws_srv_func_now summary
ws_srv_func_now_count 12.0
ws_srv_func_now_sum 0.004556973995931912

On trouve donc le nombre d’appel à la fonction (ici le web service) et le temps passé cumulé dans cette fonction. En instrumentant les fonctions principales vous aurez ainsi un accès simple à de la métrologie de type profiling sur votre application, de quoi, par exemple, ne porter son attention de refactoring que sur les fonctions très utilisées et qui consomment beaucoup. Ceci sera particulièrement intéressant sur les travaux batch qui peuvent être amenés à consommer beaucoup de ressources en lien avec les volumes de données manipulées.

industrialisation des métriques

Fichier : srv06.py

Si vous souhaitez approfondir dans l’usage de prometheus avec python, certaines actions d’enrichissement ou de création peuvent alors devenir fastidieuses. Je vous propose un découpage du code et une centralisation des définitions dans un fichier spécifique metrics.py qui propose également un décorateur supplémentaire.

Le principe consiste à centraliser toutes les métriques par famille, ceci simplifie notamment le nomage qui est important dans l’usage ensuite sur prometheus (metrics.py):

PROM_METRICS = {
    "summary": {
        "ws_srv_func_now" : Summary('ws_srv_func_now',
                                    'stats of function now')
    },

    "counter": {
        "endpoint_call": Counter('ws_srv_endpoint_call',
                                 'Number of calls to this url',
                                 ['method', 'endpoint']),

        # business counter
        "ws_srv_is_now_even" : Counter('ws_srv_is_now_even',
                                       'count even now aligned on second',
                                       ['even'])
    }
}

Le décorateur proposé permet de conserver les appels des web services, il utilise une métrique de type counter et collecte la méthode (GET, POST, …) et le chemin du endpoint. L’itérêt réside dans la simplicité d’utilisation, par exemple on décorre la fonction de notre web service avec @MetricEndpointCall et les collectes démarrent directement (dans ws_now()).

@app.route('/now')
@MetricEndpointCall
@COUNT_FCT_NOW.time()
def ws_now():

Le résultat ressemble à cela : http://192.168.16.205:5000/metrics

# HELP ws_srv_endpoint_call Number of calls to this url
# TYPE ws_srv_endpoint_call counter
ws_srv_endpoint_call{endpoint="/now",method="GET"} 14.0

Conclusion

Et voilà en quelques lignes de code une instrumentation simple mais directement opérationnelle. Nous regarderons dans un prochain article comment les utiliser, mais vous avez un aperçu de l’effort modeste nécessaire pour disposer d’une métrologie au coeur de votre application python.

Bibliographie


Photo from Kelly Sikkema