Supervision et visualisation des données : des domaines de l'informatique qui nous permettent de surveiller, analyser une infra, des données afin de comprendre et éventuellement prédire des dysfonctionnements.
De nombreux outils existent et excellent dans chacune de ces tâches. Les accorder ensemble afin d'unifier l'information permet ainsi de faciliter la prise de décision.
C'est ce que l'on appelle l'hypervision.
Canopsis se veut une solution d'hypervision (on évite l'appellation hyperviseur qui reste dans le langage courant spécifique à la virtualisation). Solution open-source sous licence AGPL3 développée par la société française Capensis, elle se veut simple d'utilisation, et suffisamment souple pour répondre à un maximum de besoin.
Sommaire
- La problématique
- Objectif : la solution
- Le bac à événements
- Sélecteur et widget météo : simplifier la supervision
- Monitoring et séries
- Les tâches ordonnancées et les notifications
- Conclusion
La problématique
Dans une infra hétérogène, on dispose de nombreuses sources d'information :
- côté supervision, on peut avoir un mélange de :
- Nagios/Icinga
- Centreon
- Shinken
- HPOV
- Zabbix
- SNMP
- …
- côté récolte de données, on peut avoir :
- CollectD
- Logstash
- Munin
- Telegraf (de la pile TICK)
- …
- ou encore :
- des logs
- des données stockées en base de données
- un résultat de tests unitaires et fonctionnels (jMeter, Sikuli…)
La mise en place d'un accès à l'ensemble de ces informations peut être fastidieuse, et dans la plupart des cas, l'utilisateur (l'administrateur technique et/ou fonctionnel) devra accéder à plusieurs interfaces et maîtriser plusieurs outils.
Cela empêche d'avoir une vue d'ensemble cohérente et rend difficile l'anticipation ainsi que la prise de décision.
Objectif : la solution
C'est là que Canopsis intervient.
Étape 1 : les connecteurs
Dans un premier temps, nous devons récupérer les informations produites par cet ensemble hétérogène.
Ainsi, pour chaque source de données, nous sommes en mesure de développer un connecteur, capable d'extraire les informations voulues, et de les transmettre à Canopsis sous la forme d'événements standardisés.
Un certain nombre de connecteurs sont déjà disponibles sur le Gitlab.
Le cas le plus simple est donc celui ci :
#!/usr/bin/env python
from time import time
# lib qui nous permet de nous connecter au bus de données (cf étape 2)
from kombu import Connection
# module qui sera utilisé pour envoyer les événements
from kombu.pools import producers
# construction de l'événement selon le standard Canopsis
event = {
"timestamp": int(time()),
# émetteur de l'événement
"connector": "myconnector",
"connector_name": "myconnector-instance0",
# nature de l'événement
"event_type": "check",
# source de l'événement
"source_type": "resource",
"component": "<hostname>",
"resource": "<job's name>",
# données portées par l'événement
"state": 0, # 0 = INFO, 1 = MINOR, 2 = MAJOR, 3 = CRITICAL
"output": "<message>"
}
# construction de la routing_key, qui sert à identifier l'événement et à le router
routing_key = "{0}.{1}.{2}.{3}.{4}".format(
event['connector'],
event['connector_name'],
event['event_type'],
event['source_type'],
event['component']
)
if event['source_type'] == "resource":
routing_key = "{0}.{1}".format(
routing_key,
event['resource']
)
# Connexion
uri = 'amqp://cpsrabbit:canopsis@localhost:5672/canopsis'
with Connection(uri) as conn:
# Création de l'émetteur
with producers[conn].acquire(block=True) as producer:
# Publication
producer.publish(
event,
serializer='json',
exchange='canopsis.events',
routing_key=routing_key
)
Bien évidemment, du moment qu'un connecteur envoie ses événements, il peut être développé dans n'importe quel langage, c'est le cas du connecteur Nagios qui se présente comme un Nagios Event Broker, et est donc développé en C.
Étape 2 : le bus de données et les moteurs
Les événements produits par les connecteurs sont transmis au bus de données de Canopsis, basé sur RabbitMQ.
Source : http://igm.univ-mlv.fr/~dr/XPOSE2011/rabbitmq/usages.html
Ces événements vont être consommés par des daemons que nous appelons moteurs.
Leur but est simple :
- traiter la donnée
- enregistrer les informations pertinentes en base de données
- transmettre, si nécessaire, l'événement à un ou plusieurs autres moteurs
Nous avons, par exemple :
- le moteur
event_filter
qui se charge de filtrer/modifier les événements entrants - le moteur
context
qui se charge d'enregistrer les informations sur l'entité cible de l'événement (connecteur source, composant/ressource qui a produit l'événement…) - le moteur
perfdata
qui s'occupe d'historiser les données de performance (comme remontées par Nagios ou CollectD) - …
Comme pour les connecteurs, les moteurs peuvent être développés dans n'importe quel langage, bien que pour le moment nous les avons tous faits en Python.
Étant un daemon, un moteur dispose de 2 fonctions :
- une pour consommer les événements (dans un thread à part)
- une pour exécuter une tâche régulièrement (recharger la configuration, envoyer des stats…)
Ainsi que la configuration suivante :
[engine:myengine]
# chemin Python de la méthode de consommation
event_processing=canopsis.myfeature.process.event_processing
# chemin Python de la méthode exécutée régulièrement
beat_processing=canopsis.myfeature.process.beat_processing
# nom de l'exchange AMQP sur lequel écouter (par défaut: amq.direct)
exchange_name=canopsis.event
# RK à consommer
routing_keys=nagios.#,shinken.#
# intervalle entre 2 exécutions du beat_processing (en secondes)
beat_interval=60
# liste des moteurs sur lesquels retransmettre l'événement reçu, possiblement modifié (par défaut: aucun)
next=myengine2,myengine3
Et donc l'implémentation se résume à :
def event_processing(engine, event, **_):
# traiter l'événement
return event
def beat_processing(engine, **_):
# faire des choses
Étape 3 : les schémas
Toutes les données qui véhiculent dans le bus et qui sont sauvegardées en base sont munies de schémas les décrivant.
Ces schémas servent à plusieurs choses :
- valider que la donnée est bien formatée
- décrire comment la donnée sera représentée
- décrire comment la donnée sera éditée
Ces deux derniers points permettent de générer une partie du code de l'UI (cf étape 5).
À terme, ils serviront également à :
- décrire comment la donnée sera utilisée
- décrire comment la donnée pourra être transformée
Ce qui permettra de générer une partie du code backend (cf étape 4).
Le formalisme qui permet d'écrire un schéma est actuellement inspiré du standard JSON Schema :
{
"title": "MyData",
"description": "Schéma décrivant la donnée, comment l'afficher et l'éditer"
// description de la donnée
"type": "object",
"properties": {
"color": {
"type": "string",
"required": true,
/* comment la donnée sera affichée/éditée
* le rôle 'color' :
* - affichera un carré de couleur lorsqu'on l'affichera
* - affichera un colorpicker lorsqu'on l'éditera
*/
"role": "color",
// les champs suivants servent pour le formulaire
"title": "Couleur de la donnée",
"description": "Tooltip d'aide"
}
},
// les champs suivants aident à générer le formulaire d'édition
"categories": [
{
"title": "General",
"keys": ["color"]
}
]
}
L'id
d'un schéma est construit de la manière suivante :
mydata
-
mydata.mydata2
: icimydata2
hérite demydata
On obtient donc en base de données :
{
"_id": "<id du schema>",
"schema": // le schéma à proprement parler
}
Étape 4 : gérer et servir la donnée
Maintenant que nous avons la structure pour récupérer la donnée, et que nous sommes en mesure de la schématiser, il faut mettre en place les mécanismes permettant d'interagir avec et de la servir à l'UI.
A. Les managers
La gestion de la donnée est prise en charge par ce que l'on appelle les managers. Pour bien comprendre son rôle, il faut plonger un peu dans le code.
Un configurable est un objet Python dont les propriétés sont définies par son fichier de configuration :
from canopsis.configuration.configurable import Configurable
from canopsis.configuration.configurable.decorator import conf_paths
from canopsis.configuration.configurable.decorator import add_category
from canopsis.configuration.model import Parameter
CONF_PATH = 'myfeature/manager.conf' # {sys.prefix}/etc/{CONF_PATH}
CATEGORY = 'MYFEATURE'
# Définition du contenu de la catégorie
CONTENT = [
Parameter('foo'),
Parameter('bar', parser=int)
]
# ajoute un fichier de configuration à lire par le configurable
@conf_paths(CONF_PATH)
# permet de spécifier la catégorie de configuration depuis laquelle on peut lire les paramètres
@add_category(CATEGORY, content=CONTENT)
class MyFeatureManager(Configurable):
pass
Ainsi, avec le fichier de configuration suivant :
[MYFEATURE]
foo=bar
bar=42
Ou :
{
"MYFEATURE": {
"foo": "bar",
"bar": 42
}
}
En effet, le configurable va tester différents drivers, actuellement on dispose de INI et JSON mais il est envisageable d'avoir un driver MongoDB ou autre
Lorsque l'on instanciera la classe, on obtiendra :
obj = MyFeatureManager()
assert obj.foo == "bar"
assert obj.bar == 42
Et à partir de cette classe Configurable
on va définir l'arbre d'héritage suivant :
Un ConfigurableRegistry
permet de spécifier, dans un paramètre de configuration, un autre Configurable
à instancier :
otherconfigurable_value = canopsis.myotherfeature.manager.MyOtherFeatureManager
Et on y accèdera, dans l'instance, de la manière suivante :
assert isinstance(self['otherconfigurable'], MyOtherFeatureManager)
Le MiddlewareRegistry
fait de même pour les Middleware
(qui identifient un protocole ainsi qu'un type de données) :
mymiddleware_uri = protocol-datatype-datascope://
De même que pour le ConfigurableRegistry
, on y accède de la manière suivante :
assert self['mymiddleware'].connected()
En général, un manager sera un MiddlewareRegistry
, ce qui permettra de changer de techno utilisée, sans modifier le code :
[MYFEATURE]
mystorage_uri = mongodb-timed-mydata://
# mystorage_uri = influxdb-timed-mydata://
mymanager_value = canopsis.myotherfeature.manager.MyOtherFeatureManager
# mymanager_value = canopsis.myotherfeature.manager2.MyOtherFeatureManager2
Et ce manager sera utilisé par le moteur et le webservice.
En reprenant l'exemple du moteur :
from canopsis.common.utils import singleton_per_scope
from canopsis.myfeature.manager import MyFeatureManager
def event_processing(engine, event, manager=None, **_):
if manager is None:
# instancie la classe une seule fois par module
manager = singleton_per_scope(MyFeatureManager)
# faire des choses avec l'événement et le manager
return event
def beat_processing(engine, event, manager=None, **_):
if manager is None:
manager = singleton_per_scope(MyFeatureManager)
# faire des choses avec le manager
B. Les webservices
Afin de servir la donnée à l'UI, on dispose d'une application WSGI découpée en modules, que l'on appelle webservice.
Ces derniers se trouvent dans le paquet Python canopsis.webcore.services
.
Et grâce au code suivant (à placer dans le __init__.py
), on peut avoir plusieurs paquets Python fournissant du code à cet emplacement :
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
Bref, un webservice s'écrit très simplement :
# couche d'abstraction qui nous permettra de passer de Bottle à Flask
from canopsis.common.ws import route
from canopsis.common.utils import singleton_per_scope
from canopsis.myfeature.manager import MyFeatureManager
# Sera appelé par l'application WSGI pour charger les routes HTTP
def exports(ws):
# ici `ws` désigne donc le webserver
manager = singleton_per_scope(MyFeatureManager)
# ici on créé la route /foo qui accepte la méthode GET
@route(ws.application.get)
def foo():
return manager.foo
# l'API retournera :
# {
# "total": 1,
# "data": ["bar"],
# "success": true
# }
# cette fois ci, on créé la route /bar/:baz
@route(ws.application.get)
def bar(baz):
return (manager.bar == baz)
Étape 5 : l'application web
Côté backend, on dispose désormais du nécessaire pour fournir à l'UI tout ce dont elle a besoin.
Ainsi, nous avons également travaillé la modularité de cette UI, basée sur Ember, au maximum.
A. Les briques
Afin de répondre à cette problématique de modularité, nous avons mis en place un système de briques, permettant de sélectionner les fonctionnalités effectivement chargées.
Concrètement, on peut voir une brique comme étant un addon apportant plusieurs fonctionnalités telles que :
- nouveaux éléments graphiques (composants)
- nouveaux outils de rendus (widgets, renderers)
- nouveaux outils d'éditions (éditors)
- …
Pour construire une brique, il suffit de créer un paquet NPM avec le package.json
suivant :
{
"name": "<nom de la brique>",
"description": "<description de la brique>",
"version": "0.1.0",
// il s'agit du fichier principal de la brique, il pointera vers la version de dev ou minifiée
"main": "init.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"compile": "rm -Rf tmp/build && broccoli build tmp/build && cp tmp/build . -RT",
"lint": "eslint src",
"doc": "./node_modules/canopsis-ui-toolbelt/scripts/buildDoc",
"minify": "node node_modules/canopsis-ui-toolbelt/scripts/minify.js"
},
"repository": {
"type": "git",
"url": "<url du dépôt>"
},
"author": "<auteur>",
"license": "AGPL-3.0",
"devDependencies": {
"broccoli": "^0.16.9",
"broccoli-funnel": "^1.0.1",
"broccoli-merge-trees": "^1.0.0",
"broccoli-sourcemap-concat": "^1.1.6",
"broccoli-template": "^0.1.1",
"broccoli-uglify-js": "^0.1.3",
// outil de gestion des briques Canopsis
"canopsis-ui-toolbelt": "https://git.canopsis.net/canopsis/canopsis-ui-toolbelt/repository/archive.tar.gz?ref=<branche git de canopsis-ui-toolbelt>",
"jsdoc": "^3.3.0",
"pre-commit": "^1.1.1"
},
"pre-commit": [
"lint",
"doc"
]
}
Puis, dans un dossier src
on placera le code source de la brique :
components/mycomponent/component.js
components/mycomponent/template.hbs
renderers/renderer-myrole.hbs
editors/editor-myrole.hbs
widgets/mywidget/controller.js
widgets/mywidget/mywidget.hbs
mixins/mymixin.js
L'outil canopsis-ui-toolbelt
se charge de :
- récupérer récursivement tout le code JS dans le dossier
src
- référencer le code JS dans le fichier
init.js
qui représente la brique - récupérer récursivement tout les templates (
*.hbs
) dans le dossiersrc
- référencer les templates également dans le fichier
init.js
- référencer les templates dans
Ember.TEMPLATES
:- pour un composant, il est requis d'avoir le dossier
components/<mycomponent>
, afin que le template soit reconnu comme étant un template de composant - pour le reste, le nom du template dans Ember sera le nom du fichier sans extension
- pour un composant, il est requis d'avoir le dossier
NB: Le découpage n'est donc obligatoire que pour les composants, le reste peut être mis en vrac dans src
.
Une fois que l'on a créé tout cela (ou récupéré le dépôt Git), on peut finaliser la brique :
$ npm install
$ npm run compile
Chaque fichier source contiendra au moins un Ember Initializer :
Ember.Application.initializer({
name: 'MyModule',
after: ['Dependency1', 'Dependency2'],
initialize: function(container, application) {
var Dependency1 = container.lookupFactory('deptype:dependency1'),
Dependency2 = container.lookupFactory('deptype:dependency2');
// do stuff
application.register('modtype:mymodule', /* stuff */);
}
});
Cela permet ainsi de s'assurer du bon chargement de chaque module.
Au final, pour installer une brique, il suffit de :
- cloner le dépôt finalisé dans :
/opt/canopsis/var/www/canopsis
- lancer la commande
webmodulemanager enable mybrick
afin de l'activer au chargement de l'UI
L'ensemble des briques existantes (hormis celle par défaut livrées avec Canopsis) sont disponible ici.
B. Les composants
Le composant, l'élément le plus basique de l'UI, sera utilisé par tout les autres éléments.
Il s'agit simplement d'un composant Ember avec un template à fournir.
On définira notre composant dans src/components/mycomponent/component.js
:
Ember.Application.initializer({
name: 'MyComponent',
after: [],
initialize: function(container, application) {
var MyComponent = Ember.Component.extend({
init: function() {
this._super.apply(this, arguments);
// faire des choses
},
actions: {
// on implémente les actions, déclenchable depuis le template
foo: function() {
alert('bar');
}
}
});
application.register('component:component-mycomponent', MyComponent);
}
});
Et le template dans src/components/mycomponent/template.hbs
:
<h1>My awesome component</h1>
<a {{action foo}}>Launch action</a>
C. Les renderers/éditeurs
Une fois nos composants définis, on est en mesure de les utiliser dans des renderers ou des éditeurs.
Le but de ces éléments est simple :
- lorsqu'on demande l'affichage d'une donnée avec le composant
renderer
:- le champ
role
du schéma JSON est également lu - si un renderer du même nom est trouvé, il est utilisé dans le template
- sinon, on affiche la donnée telle quelle
- le champ
- lorsqu'un formulaire d'édition est généré à partir du schéma JSON, en utilisant le composant
editor
:- le champ
role
est lu - si un éditeur du même nom est trouvé, il est utilisé dans le formulaire
- sinon, on utilise l'éditeur par défaut
- le champ
Les renderers/éditeurs ne sont donc que de simple templates HTMLBars.
On aura le renderer dans src/renderers/renderer-myrole.hbs
:
<p>My rendered data: {{value}}</p>
{{component-mycomponent}}
Et l'éditeur dans src/editors/editor-myrole.hbs
:
<p>My data is being edited :</p>
{{input type="password" value=attr.value}}
{{component-mycomponent}}
D. Les widgets
Enfin, on aura les widgets, qui seront en mesure d'utiliser composants et
renderers afin d'implémenter des outils de visualisation de données plus complexes.
Un widget est un MVC complet :
- on implémente un contrôleur
- on implémente des mixins qui viendront s'appliquer à la vue
- on écrit un template
Ce qui donne :
Ember.Application.initializer({
name: 'MyWidget',
after: ['WidgetFactory'],
initialize: function(container, application) {
var WidgetFactory = container.lookupFactory('factory:widget');
var widgetOptions = {};
var MyWidgetViewMixin = Ember.Mixin.create({
didInsertElement: function() {
this._super.apply(this, arguments);
// faire des choses
},
willDestroyElement: function() {
this._super.apply(this, arguments);
// faire des choses
},
actions: {
// actions de la vue
}
});
var widget = WidgetFactory('mywidget', {
viewMixins: [
MyWidgetViewMixin
],
actions: {
// actions du contrôleur
},
init: function() {
// faire des choses
},
findItems: function() {
// méthode appelée lors de la récupération des données pour le template
}
}, widgetOptions);
application.register('widget:mywidget');
}
});
Ensuite, il est nécessaire de créer un schéma widget.mywidget.json
qui sera utilisé pour configurer le widget lors de son ajout dans une vue :
{
"title": "MyWidget",
"description": "Schéma de configuration de MyWidget",
"metadata": {
// icône dans le formulaire d'ajout de widget
"icon": "fa fa-cog"
},
"categories": [
{
"title": "General",
"keys": ["foo"]
}
],
"type": "object",
"properties": {
"foo": {
"title": "Foo", // nom du champ dans le formulaire
"description": "Foo field", // tooltip dans le formulaire
"type": "string",
"role": "myrole"
}
}
}
E. Les mixins
Lorsque l'on a des fonctionnalités communes à plusieurs widgets (comme le PeriodicRefresh
qui actualise le widget régulièrement), il convient de factoriser le code en un mixin.
Ce dernier pourra être ajouté et configuré au widget via l'UI, et ensuite appliquer la-dite configuration au contrôleur du widget :
Ember.Application.initializer({
name:'MyMixin',
after: ['MixinFactory'],
initialize: function(container, application) {
var Mixin = container.lookupFactory('factory:mixin');
var get = Ember.get;
var MyMixin = Mixin('mymixin', {
mixinsOptionsReady: function() {
this._super.apply(this, arguments);
var mixinOptions = get('mixinOptions.mymixin');
// faire des choses
}
});
application.register('mixin:mymixin', MyMixin);
}
});
Et il ne manque plus que le schéma mixin.mymixin.json
décrivant la configuration du mixin :
{
"title": "MyMixin",
"description": "Schéma de configuration de MyMixin",
"metadata": {
// description affiché dans l'UI
"description": "Add stuff to widget"
},
"categories": [
{
"title": "General",
"keys": ["bar"]
}
],
"type": "object",
"properties": {
"bar": {
"title": "Bar",
"description": "Bar field",
"type": "string"
}
}
}
F. Les vues
Tout les outils sont désormais à notre disposition pour construire nos dashboards.
Donc si on résume :
- une vue est composée de widgets
- un widget est composé de composants et de mixins
L'UI fournit les outils qui permettent de construire ces vues, le résultat final est un document JSON stocké en base :
{
"_id": "id de ma vue",
"description": "description de ma vue"
"crecord_name": "nom de ma vue",
"crecord_type": "view",
"author": "<user qui a créé la vue>",
"enable": true,
"internal": false,
"tags": [],
// le widget initial de la vue
"containerwidget": {
// identifiant du widget
"xtype": "widgetcontainer",
"title": "container title vbox",
// le widget "widgetcontainer" s'attend à avoir un champ "items"
"items": [
{
// le widget "widgetwrapper" sert à inclure des widgets dans un container pour les placer correctement
"xtype": "widgetwrapper",
"title": "wrapper",
// le widget encapsulé :
"widget": {
"xtype": "mywidget",
"title": "My awesome widget",
// configuration spécifique au widget
"foo": "bar",
// mixins appliqués via l'UI :
"mixins": [
{
"name": "periodicrefresh",
// paramètres du mixin
"refreshInterval": 60
},
{
"name": "mymixin",
// paramètres du mixin
"bar": "baz"
}
]
}
}
],
// le container de base de la vue à un widget de layout par défaut
"mixins": [
{
"name": "lightlayout"
}
]
}
}
La totalité de l'UI est générée à partir de ces vues JSON, et est donc complètement personnalisable.
Le bac à événements
Parmi les vues par défaut qui sont livrées avec Canopsis, on trouve le Bac à événements.
Cette vue fournit un dashboard de supervision commun, unifiant ainsi la totalité des superviseurs remontant des informations à Canopsis.
Depuis ce bac, on voit apparaître les différentes alarmes avec comme informations :
- leurs sources d'émission (le connecteur)
- leurs sources cible (composant/ressource, qui dans la plupart des cas correspond au couple host/service)
- le dernier message associé à un check (Nagios, Shinken, Centreon, …)
- le statut de l'alarme associé au check :
- Off : aucune alarme n'est présente
- On Going : un problème a été remonté et n'est toujours pas résolu
- Stealthy : une alarme a été remontée et est immédiatement repassée OK (durée paramétrable)
- Flapping : il y a eu X changements d'état en 1h sur l'alarme (durée et fréquence paramétrable)
- Cancelled : l'alarme a été annulée par un utilisateur (pour éviter les faux-positifs)
- l'état du check :
- INFO : tout va bien
- MINOR : équivalent au Warning de Nagios
- MAJOR : équivalent au Critical de Nagios
- CRITICAL : les Unknown de Nagios sont remontés en tant que tel, mais cela ne se limite pas à cette notion
-
UNKNOWN : état non pris en charge à l'heure actuelle, c'est tout ce qui est supérieur à CRITICAL (
3
)
- la présence d'un acquittement et/ou d'un ticket :
- les connecteurs pour Nagios, Shinken, etc… peuvent remonter les acquittements posés
- depuis Canopsis, on peut en poser manuellement
- la date du dernier changement d'état
Sur chaque alarme, on peut réaliser différentes actions :
- l'acquittement (permet de déverrouiller les autres actions), cela émettra le même événement qui serait remonté par un superviseur, soit un événement de type
ack
- une suppression de l'acquittement, cela émettra un événement de type
ackremove
- une annulation de l'alarme, cela émettra un événement de type
cancel
- une fois l'alarme annulée, on peut annuler cette action, cela émettra un événement de type
uncancel
- une déclaration de ticket :
- cela émettra un événement de type
declareticket
- cet événement pourra être capturé par le moteur
event_filter
pour déclencher un job (voir plus bas) qui communiquera le ticket à un outil tiers (par exemple iTop)
- cela émettra un événement de type
- une association de ticket existant :
- cela émettra un événement de type
assocticket
- on peut imaginer que le job qui communique la déclaration du ticket à l'outil tiers récupère le numéro du ticket nouvellement créé, et l'associe automatiquement
- cela émettra un événement de type
- une requalification de l'événement :
- cela changera l'état du check manuellement, et ce dernier gardera cet état jusqu'à la résolution de l'alarme
- cela émettra un événement de type
check
, comme le superviseur - la seule différence est la présence d'un champ
keep_state
qui vauttrue
dans l'événement
Toutes ces actions permettent ainsi d'unifier une supervision hétérogène, et l'administrateur (technique) ne devra utiliser/maîtriser qu'un seul outil.
Sélecteur et widget météo : simplifier la supervision
Lorsque l'on supervise une grosse infrastructure, les informations remontées via les checks deviennent tout de suite beaucoup plus conséquentes. C'est pourquoi nous avons mis en place la possibilité d'agréger ces checks afin d'avoir une visibilité plus simple sur l'infra.
Cette agrégation se fait à l'aide des sélecteurs :
- on créé un filtre d'événements
- on applique un algorithme à l'état de chaque check qui matche le filtre (actuellement seul l'algo Worst State est disponible)
- on produit un événement de type
selector
qui contient :- l'état agrégé
- le champ
output
qui est le rendu du template spécifié dans la configuration du sélecteur
- si le sélecteur est configuré pour, on déclenche le calcul des SLA :
- sur une période de temps (spécifiée dans la configuration du sélecteur)
- on calcule le pourcentage de temps passé sur chaque état possible
- on produit une métrique pour chacune de ces métriques, ainsi qu'un événement de type
sla
- l'état remonté par l'événement correspond aux seuils de SLA configurés dans le sélecteur
Le résultat est finalement affichable avec un widget weather :
NB: Le sélecteur peut afficher également des checks unitairement
On peut ainsi noter les couleurs suivantes :
-
vert : l'état du sélecteur est
INFO
-
jaune : l'état du sélecteur est
MINOR
-
orange : l'état du sélecteur est
MAJOR
-
rouge : l'état du sélecteur est
CRITICAL
- violet : toutes les alarmes du sélecteur ont été acquittée
- la couleur du widget est celle du pire état des sélecteurs positionnés dans celui ci
Un clic sur le sélecteur dans le widget nous redirigera sur le Bac à événements, filtré avec le filtre du sélecteur.
Monitoring et séries
Chaque connecteur, moteur, et sélecteur produisent des données de performances :
- temps d'exécution d'un check
- usage CPU/RAM/Disque
- temps moyen passé sur un événement
- nombre moyen d'événements par seconde
- donnée de SLA
- …
Tout cela est remonté dans Canopsis dans un événement via le champ perf_data_array
:
{
// info de l'événement classique
"perf_data_array": [
{
"metric": "nom_de_ma_metrique",
"value": 42.1337,
"type": "GAUGE", // GAUGE, COUNTER, ABSOLUTE ou DERIVE
// champs optionnels
"unit": "...",
"min": 0,
"max": 1337.42,
"warn": 1000,
"crit": 1300
}
]
}
Ces données vont être historisée dans Canopsis. On peut donc noter 4 types de métriques :
-
GAUGE
: on historise la valeur telle quelle -
COUNTER
: lorsque l'on récupère la valeur, on fait l'addition des valeurs historisées -
ABSOLUTE
: on historise la valeur absolue -
DERIVE
: il s'agit de la valeur dérivée par rapport au temps
Une métrique est ensuite identifiée par :
- le composant de l'événement
- la ressource de l'événement
- le nom de la métrique dans le tableau de perfdata
Le tout peut être affiché dans un chart :
On est ainsi en mesure de sélectionner un ensemble de métrique avec un filtre basé sur des expressions régulières :
co:.*\.myhost re:cpu-.* me:system me:user me:wait
Qui se traduit en filtre MongoDB :
{
'$and': [
{'component': {'$regex': '.*\.myhost'}},
{'resource': {'$regex': 'cpu.*'}},
{
'$or': [
{'name': {'$regex': 'system'}},
{'name': {'$regex': 'user'}},
{'name': {'$regex': 'wait'}}
]
}
]
}
Une fois les identifiants de métriques récupérés, on peut aller demander les points stockés en base, dans une fenêtre de temps bien définie.
Une série est donc munie :
- d'un filtre de métrique
- d'une période d'agrégation avec un opérateur d'agrégation (le manager de perfdata nous retournera les données agrégées)
- d'une période de consolidation
- d'une formule de consolidation
Ici la partie consolidation sert à consolider les différents points agrégés en un seul, afin de produire une nouvelle métrique.
La formule se construit de la manière suivante :
- on a des opérateurs qui prennent en paramètre un filtre de métrique qui sera appliqué sur l'ensemble de métriques déjà sélectionnées
- ces opérateurs retournent un point consolidé
- on peut les utiliser dans une expression mathématique classique
Par exemple, SUM("me:.*") / COUNT("me:.*")
, permet de réaliser une moyenne.
Les tâches ordonnancées et les notifications
Parmi les moteurs de Canopsis, certains sont dédiés à une fonction précise : exécuter une tâche.
Il y a donc un moteur scheduler qui, régulièrement, va chercher à exécuter des jobs configurés selon une règle de récurrence.
En fonction du type de job, ce dernier sera redirigé au moteur correspondant, que l'on appellera un taskhandler.
Cela permet de construire un équivalent de crontab
au sein de Canopsis.
Ces taskhandlers ne servent pas uniquement à l'exécution de tâches ordonnancées, ils peuvent être utilisés en tant que notification :
- une règle du moteur event_filter peut déclencher l'exécution d'un job si l'événement reçu matche le filtre de la règle
- par exemple, à la réception d'un événement
declareticket
, on peut lancer l'exécution d'un job réalisant une requête d'insertion de ticket à un outil tiers
Conclusion
Grâce à tout ces éléments, Canopsis est en mesure de répondre à de nombreux besoins, allant de la supervision simple, à l'analyse poussée de données afin de générer des rapports sur une infrastructure (ou autre).
Notre objectif premier est la modularité du projet, afin de pouvoir fournir une solution sur mesure et de ne pas transformer l'outil en énorme usine à gaz. Pour résumer, on a répondu à cette problématique avec :
- le découpage du backend en projets Python embarquant : un manager, éventuellement un moteur et un webservice
- le découpage du frontend en briques embarquant : des composants, des renderers, des éditeurs, des mixins, des widgets
- la schématisation des données et à l'avenir des actions possibles sur cette dernière (transformation, schématisation de l'API des managers, …)
- le développement d'API générique permettant le changement de technologies sans modification du code
Beaucoup de choses ont été faites, et beaucoup de travail reste à faire, notamment :
- la finalisation des rôles Ansible
- l'intégration d'une notion de graphe pour les entités qui sont le centre de toutes les données stockées par Canopsis, afin de rendre le système complètement réflexif
- une séparation totale du backend et du frontend, permettant d'utiliser l'un sans l'autre
- génération de code à partir des schémas
- …
Bref, Canopsis est en constante évolution, et touche à de nombreuses problématiques toutes plus intéressantes les unes que les autres.
Aller plus loin
- Gitlab de Canopsis (753 clics)
- Capensis (797 clics)
- Présentation d'un hyperviseur (490 clics)
- Hypervision - Piloter votre supervision (431 clics)
- Hypervision sur monitoring-fr (421 clics)
# jouer sur les mots
Posté par DerekSagan . Évalué à 0.
Je suis le seul à voir les deux problèmes de cette phrase ?
1) La subtile différence entre -ion et -eur qui d'une part est très confusante, et d'autre part est juste fausse vu le nombre de personne qui parlent d'hypervision pour la virtualisation.
2) Le super gros lol sur "langage courant": j'en ai parlé dans la rue et personne n'a compris.
Bon et pour rigoler: hyper en grec ça veut dire "trop", ça veut pas dire "encore plus gros que ce que j'ai appelé super hier". Mais ça c'est pas ta faute, le jargon est ce qu'il est on est tous obligé de suivre. D'où d'ailleurs le problème du 1.
[^] # Re: jouer sur les mots
Posté par David Delassus (site web personnel) . Évalué à 5.
Quand je dis "langage courant" ça reste bien sûr dans le domaine de l'informatique. Tu le dis toi même : "vu le nombre de personne qui parlent d'hypervision pour la virtualisation".
Donc bien sûr que c'est un jeu de mot, avec "hypervision" on accepte beaucoup plus facilement l'idée que cela se place au dessus (étymologie de hyper) de la supervision.
https://link-society.com - https://kubirds.com - https://github.com/link-society/flowg
[^] # Re: jouer sur les mots
Posté par BAud (site web personnel) . Évalué à 2.
autant parler d'übervision donc :-) euh, wait…
# Ouf
Posté par barmic . Évalué à 3.
Merci de cette dépêche !
Ça a l'air vraiment très sophistiqué…. Probablement trop pour moi.
J'ai un peu du mal à comprendre la différence que tu fais entre le monitoring et l'hypervision. Les 2 servent à vérifier que ton système fonctionne.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Ouf
Posté par David Delassus (site web personnel) . Évalué à 2.
Merci pour le retour :)
Personnellement, je pense que la différence est la même qu'entre supervision et monitoring, c'est à dire qu'on introduit une méthodologie pour la résolution d'éventuels problèmes (identification, acquittement, déclaration de ticket, résolution).
Ici l'hypervision complète cette méthodologie, en unifiant les différentes solutions de supervision, et en remontant des statistiques sur cette méthodologie (SLA sur les durées d'acquittement, de résolution des problèmes et de disponibilité des services par exemple).
Le monitoring permet de surveiller (vérifier que le système fonctionne), et la supervision/hypervision permettent de prendre une décision quand il ne fonctionne pas (quel est le problème et la procédure à appliquer pour le résoudre ?).
Je pense aussi qu'on ne peut avoir l'un sans l'autre pour être réellement efficace.
https://link-society.com - https://kubirds.com - https://github.com/link-society/flowg
[^] # Re: Ouf
Posté par Anonyme . Évalué à 1.
Monitoring et Supervision, c’est a priori la même chose, mais ça ne fait pas d’Hypervision.
Monitoring et Supervision : collecte d’informations et décisions sommaires comme envoi de mails… pour rester dans les vieux trucs. Peut embarquer de la métrologie, mais ne s’en contente pas. Sinon c’est qu’on a un Munin ou un Cacti, qui ne font pas de supervision/monitoring. Nagios fait de la supervision ou du monitoring, et l’aspect métrologie n’existe que parce qu’on peut interfacer ce qu’il remonte à d’autres outils.
Hypervision : ajoute de l’intelligence à la sup / le monitoring, ce que fait canopsis quoi.
[^] # Re: Ouf
Posté par flan (site web personnel) . Évalué à 1.
J'ai un peu l'impression que Shinken ajoute cette même intelligence, alors que c'est vendu comme une solution de supervision.
[^] # Re: Ouf
Posté par Anonyme . Évalué à 1.
Shinken, il se contente de planter.
[^] # Re: Ouf
Posté par Kerro . Évalué à 2.
Chez-moi-ça-marche (tm)
[^] # Re: Ouf
Posté par Anonyme . Évalué à 1.
Tu veux dire, quand tu lui demandes de faire le strict minimum ? C’est à dire pas de HA et tout ? Dans ce cas là, oui, ça marche.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.