Blog | WeScale

Vector, une solution pour l'observabilité

Rédigé par David Drugeon-Hamon | 22/02/2023

Vector est une solution permettant de collecter des données d'observabilité (traces, fichiers journaux et métriques) depuis différentes sources de données, pour les transformer avant de les envoyer vers une ou plusieurs systèmes externes comme par exemple des outils d'observabilité ou  SIEM.

Créé par la société Timber.io pour améliorer l'ingestion des logs vers leur plateforme, cet outil écrit en Rust a été mis en code ouvert depuis 2020 sous licence Mozilla Public Licence v2. Depuis le rachat de cette société par Datadog, il est intègré dans l'offre Datadog pour définir et superviser des pipelines de données d'observabilité. Ce projet se veut agnostique de la plateforme qui indexe ces données.

Des pipelines de données

L'observabilité nous permet de comprendre l'état d'un système en analysant les données produites sous différentes formes. Un des problèmes les plus communs est que ces données sont produites dans différents formats. Il est ainsi difficile de pouvoir les analyser efficacement. 

Pour donner un exemple concret, lorsqu'une requête HTTP est envoyée sur une infrastructure classique, différents composants peuvent être sollicités. Chaque composant génère une entrée dans son fichier journal dans un format spécifique (load balancer, serveur web, cache, serveur d'application). 

Nous avons donc besoin d'un outil qui permet d'agréger ces données dans un format commun (généralement au format JSON pour faciliter l'indexation), ou qui permet de supprimer des données sensibles avant l'indexation.

Un autre besoin récurrent est de pouvoir envoyer les données d'observabilité vers différentes solutions : envoyer vers un bucket S3 les données brutes pour archivage, vers Datadog pour les métriques et traces et vers Elastic pour les fichiers journaux.

De même, toutes les données ne sont pas nécessaires à l’exploitation de nos infrastructures de production.. Par exemple, nous pouvons envisager de router certaines données issues des infrastructures de développement ou de pré-production vers un système dédié. Ou encore éviter d'envoyer les logs au niveau DEBUG pour réduire les coûts engendrés.

Pipeline d’observabilité

C'est ce qui définit un pipeline de données d'observabilité. Dans la terminologie propre à Vector, nous avons trois étapes nécessaires pour les définir :

  • Sources : une source de données comme par exemple un agent Datadog, un agent Fluent Bit, un fichier au format Syslog, un endpoint Prometheus à interroger etc...
  • Transform : des étapes de transformation de nos données. Pour enrichir les données en ajoutant des labels spécifiques ou de la localisation au niveau du pays de l'utilisateur pour anonymiser, ou les transformer dans un autre type de données d'observabilité (à partir des fichiers journaux, en déduire des métriques intéressantes) etc...
  • Sinks : ce sont les destinations où seront envoyées nos données transformées.

Pour pouvoir réaliser ces étapes de transformation, Vector réalisera un décodage de la source de données pour obtenir une représentation interne et uniformisée sous forme d'évènements. Nous pourrons alors réaliser des opérations à l'aide de fonctions prédéfinies et d'un langage spécifique (VRL ou Vector Remap Language). A noter que nous pouvons définir plusieurs étapes de transformation qui serviront à envoyer les données vers différentes destinations.

La définition des pipelines se fait à l'aide d'un ou plusieurs fichiers de configuration au format TOM, YAML ou JSON. Dans l'exemple suivant issu de la documentation officielle, nous définissons le pipeline suivant :

# Set global options
data_dir = "/var/lib/vector"

# Vector's API (disabled by default)
# Enable and try it out with the `vector top` command

[api]
enabled = false

# Ingest data by tailing one or more files
[sources.apache_logs]
type         = "file"
include      = ["/var/log/apache2/*.log"]    # supports globbing
ignore_older = 86400                         # 1 day

# Structure and parse via Vector's Remap Language
[transforms.apache_parser]
inputs = ["apache_logs"]
type   = "remap"
source = '''
. = parse_apache_log(.message)
'''

# Sample the data to save on cost
[transforms.apache_sampler]
inputs = ["apache_parser"]
type   = "sample"
rate   = 2                    # only keep 50% (1/`rate`)

# Send structured data to a short-term storage
[sinks.es_cluster]
inputs     = ["apache_sampler"]             # only take sampled data
type       = "elasticsearch"
endpoints  = ["http://79.12.221.222:9200"]  # local or external host
bulk.index = "vector-%Y-%m-%d"              # daily indices

# Send structured data to a cost-effective long-term storage
[sinks.s3_archives]
inputs          = ["apache_parser"]    # don't sample for S3
type            = "aws_s3"
region          = "us-east-1"
bucket          = "my-log-archives"
key_prefix      = "date=%Y-%m-%d"      # daily partitions, hive friendly format
compression     = "gzip"               # compress final objects
framing.method  = "newline_delimited"  # new line delimited...
encoding.codec  = "json"               # ...JSON
batch.max_bytes = 10000000             # 10mb uncompressed

Comment déployer Vector

Vector est un agent multi-plateforme. Il peut être ainsi déployé directement sur une machine Linux, Windows ou Mac ou sous forme de conteneur. Dans ce cas, il peut être déployé à l'aide d'un orchestrateur de conteneurs comme Kubernetes ou encore AWS Elastic Container Service. Il existe principalement trois modèles d'architecture pour l'intégrer dans nos infrastructures.

Modèle décentralisé

Architecture décentralisée

Dans cette solution, nous déployons un agent Vector par machine hôte ou en sidecar de nos conteneurs applicatifs. C'est la solution la plus simple. Elle convient pour des infrastructures relativement simples et cette solution s'adapte au nombre de machines ou conteneurs déployés.

Un des principaux écueils de ce modèle est que nos systèmes d'observabilité seront fortement sollicités en fonction du nombre de machines ou conteneurs déployés. De plus, les données traitées par Vector sont stockées dans une mémoire tampon qui sera perdue si l'instance ou le conteneur est supprimé.

Modèle basé sur les flux de données

 

Architecture basée sur les flux de données

Ce modèle est basé sur des flux de données tels qu'Apache Kafka, AWS Kinesis. Dans cette solution, des agents Vector sont toujours déployés au plus près de la source des données et les publie dans un Topic Kafka / Kinesis. Un ou plusieurs agents Vector sont alors déployés sur des hôtes spécifiques et jouent le rôle d'agrégateur. Ils s'abonnent au Topic pour récupérer les données au fil de l'eau afin de les transformer et les envoyer aux différentes destinations.

Cette architecture est la plus robuste et la plus scalable pour le déploiement de Vector au sein de nos infrastructures. De plus, elle permet de récupérer les données déjà publiées dans le topic Kafka en cas de problème sur nos agents agrégateurs.

Malheureusement, cette solution ajoute de la complexité, de la gestion et des coûts supplémentaires.

Modèle centralisé

Architecture centralisée

Dans ce modèle, un agent Vector est toujours déployé au plus près des sources de données puis les envoie vers un ou plusieurs agents Vector qui serviront d'agrégation des données. Ces agents contrôlent toutes les sources pour efficacement les router vers la bonne destination.

Ce modèle est un compromis entre le modèle décentralisé et le modèle basé sur les flux de données. Néanmoins, nous perdons la résilience en cas de défaillance de nos agents agrégateurs.

Comme toute architecture, il est nécessaire de voir quels seront les compromis que vous concédez pour choisir la meilleure façon de déployer vector dans vos infrastructures (résilience, évolutivité, simplicité etc.).

Un exemple d'utilisation


Architecture de la démo

Pour illustrer cet article, nous allons déployer le projet emojivoto sur un cluster Kubernetes. Puis, nous utiliserons Vector pour :

  • Récupérer les métriques du cluster Kubernetes
  • Récupérer les fichiers journaux et les métriques prometheus de l'application emojivoto
  • Transformer ces métriques et journaux
  • Envoyer ces données vers plusieurs destinations à savoir Datadog et Grafana Cloud (Prometheus pour les métriques et Loki pour les logs).

Déploiement du projet Emojivoto

Emojivoto est un simple projet développé par Buoyant pour illustrer le déploiement du service mesh Linkerd. Cette application est une application web simple développée en Go et qui permet de voter pour un ou plusieurs emojis soit via une API Rest soit par gRPC.

Cette application expose des métriques au format prometheus sur le endpoint /metrics.

Pour l'installer sur le cluster, il suffit d'exécuter la commande suivante dans votre terminal :

curl --proto '=https' --tlsv1.2 -sSfL 
https://run.linkerd.io/emojivoto.yml \
  | kubectl apply -f -

Puis une fois déployée, vous pouvez y accéder via un transfert de port à l'aide de la commande suivante :

kubectl -n emojivoto port-forward svc/web-svc 8080:80

puis aller sur le site http://localhost:8080 pour voir la page de l'application

Emojivoto homepage

Déploiement de Vector

Vector peut être déployé via le chart Helm officiel. La configuration des pipelines se fait directement dans le fichier values.yaml.

Pour des raisons de sécurité, Vector permet de récupérer les clés d'API depuis un secret. Il est alors nécessaire de le créer avant le déploiement de la charte Helm sur notre cluster.

Dans notre exemple, nous voulons envoyer les métriques à la fois dans Datadog et dans Grafana Cloud. Il est alors nécessaire de récupérer des clés d'API et de générer le secret associé.

kubectl create secret generic vector \
--namespace vector \
--from-literal=datadog_api_key=XXX \
--from-literal=grafana_cloud_api_key=YYY \
--from-literal=grafana_cloud_prometheus_user=ZZZ \
--from-literal=grafana_cloud_loki_user=WWW \
--from-literal=grafana_cloud_prometheus_endpoint=https://...grafana.net/api/prom/push \
--from-literal=grafana_cloud_loki_endpoint=https://....grafana.net

Le chart Helm est disponible ici: https://helm.vector.dev

helm repo add vector https://helm.vector.dev

helm repo update

Nous pouvons alors déployer Vector sur notre cluster avec les valeurs suivantes :

image:
 repository: quay.io/domino/vector
 tag: 0.26.X-distroless-static
env:
 - name: GRAFANA_CLOUD_PROMETHEUS_USERNAME
   valueFrom:
     secretKeyRef:
       name: vector
       key: grafana_cloud_prometheus_user
 - name: GRAFANA_CLOUD_LOKI_USERNAME
   valueFrom:
     secretKeyRef:
       name: vector
       key: grafana_cloud_loki_user
 - name: GRAFANA_CLOUD_API_KEY
   valueFrom:
     secretKeyRef:
       name: vector
       key: grafana_cloud_api_key
 - name: GRAFANA_CLOUD_PROMETHEUS_ENDPOINT
   valueFrom:
     secretKeyRef:
       name: vector
       key: grafana_cloud_prometheus_endpoint
 - name: GRAFANA_CLOUD_LOKI_ENDPOINT
   valueFrom:
     secretKeyRef:
       name: vector
       key: grafana_cloud_loki_endpoint
 - name: DATADOG_API_KEY
   valueFrom:
     secretKeyRef:
       name: vector
       key: datadog_api_key

service:
 enabled: true
serviceHeadless:
 enabled: true

customConfig:
 sources:
   host_metrics:
     type: host_metrics
   vector_metrics:
     type: internal_metrics
   emojivoto_metrics:
     type: prometheus_scrape
     endpoints:
       - http://emoji-svc.emojivoto.svc.cluster.local:8801/metrics
     scrape_interval_secs: 60
   fake_logs:
     type: demo_logs
      format: apache_common

 transforms:
   add_mandatory_tags:
     type: remap
     inputs:
       - host_metrics
       - emojivoto_metrics
       - vector_metrics
     source: |-
       .tags.environment = "demo"
       del(.tags.host)
       .tags.host = "demo-vector"
   apache_logs_to_json:
     type: remap
     inputs:
       - fake_logs
     source: |-
       . = parse_apache_log!(.message, format: "common")
   apache_json_logs_remove_user:
     type: remap
     inputs:
       - apache_logs_to_json
     source: |-
         del(.user)
   apache_json_logs_to_metric:
     type: log_to_metric
     inputs:
       - apache_json_logs_remove_user
     metrics:
       - type: counter
         field: status
         name: response_total
         namespace: front
         tags:
             host: demo-vector
             environment: "demo"
             status: |- 
               
   metrics_aggregate:
     type: aggregate
     inputs:
        - apache_json_logs_to_metric

 sinks:
   grafana_cloud:
     type: prometheus_remote_write
     inputs:
       - add_mandatory_tags
     endpoint: ${GRAFANA_CLOUD_PROMETHEUS_ENDPOINT}
     healthcheck:
       enabled: false
     auth:
       strategy: basic
       user: ${GRAFANA_CLOUD_PROMETHEUS_USERNAME}
       password: ${GRAFANA_CLOUD_API_KEY}
   datadog:
     type: datadog_metrics
     inputs:
       - add_mandatory_tags
     default_api_key: ${DATADOG_API_KEY}
     site: datadoghq.eu
   loki:
     type: loki
     inputs:
       - apache_json_logs_remove_user
     auth:
       strategy: basic
       user: ${GRAFANA_CLOUD_LOKI_USERNAME}
       password: ${GRAFANA_CLOUD_API_KEY}
     endpoint: ${GRAFANA_CLOUD_LOKI_ENDPOINT}
     healthcheck:
       enabled: false
     encoding:
       codec: json
     labels:
       cluster: minikube-local
   trash:
     type: blackhole
     inputs:
        - metrics_aggregate

 

Il ne reste plus qu'à déployer Vector avec le chart Helm :

helm install --namespace vector \

vector \

vector/vector \

--values ./values.yaml

Déchiffrons la configuration de nos pipelines

Pipeline d’observabilité de la démo

Sources

Nous avons défini quatre sources de données que nous aimerions traiter, à savoir : 

  • Les métriques système de notre machine hôte
  • Les métriques internes de Vector (volumétrie en entrée, volumétrie en sortie, temps de traitement etc.)
  • Les métriques de notre application emojivoto
  • Des logs Apache générés à des fins de démo

Transformations

Nous définissons les traitements à effectuer sur nos sources de données :

  • *add_mandatory_tags* permet d'ajouter des labels ou tags pour donner du contexte à nos données. Nous utilisons le VRL pour réaliser ce traitement sur plusieurs sources en entrée.
  • *apache_logs_to_json* permet de parser les logs Apache et de les retranscrire au format JSON. Ainsi les logs seront plus faciles à manipuler dans les traitements suivants.
  • *apache_json_logs_remove_user* permet de supprimer des informations sensibles (ici le nom de l'utilisateur qui est logué sur le système)
  • apache_json_logs_to_metric transforme nos logs Apache en métriques. Nous générons ainsi un compteur sur le nombre de requêtes par statut.
  • metrics_aggregate permet d'agréger plusieurs métriques pour réduire le nombre de requêtes vers nos destinations.

Destinations

Enfin, nous pouvons définir les destinations vers lesquelles seront envoyées les données.

  • Grafana_cloud définit la façon d’envoyer nos métriques à Grafana Cloud (Prometheus managé)
  • Datadog définit comment envoyer nos métriques à Datadog
  • Loki définit comment envoyer nos logs Apache dont les données sensibles ont été enlevées vers un Loki managé sur Grafana Cloud
  • Trash définit la façon de traiter les métriques ou logs qui ne seront pas utilisés.

Benchmark

Dans les pré-requis de cet outil, il est mentionné que les traitements sont consommateurs de CPU. En effet, voici les chiffres théoriques du débit de données par vCPU (1 CPU ARM ou 0.5 CPU x86)

Mesure

Taille du message

Vector Throughput

Unstructured log

~256 octets

~10 Mib/s/vCPU

structure log

~768 octets

~25 Mib/s/vCPU

Métrique

~256 octets

~25 Mib/s/vCPU

Trace span

~1Ko

~25 Mib/s/vCPU


Des tests sont nécessaires pour ajuster la taille des instances dédiées. Ainsi, la documentation officielle recommande au minimum 8 vCPU et 16 Gib (2 Gib / vCPU) à savoir les types d’instances suivantes:

Cloud Provider

Type d’instance

AWS

c6i.2xlarge (recommended) or c6g.2xlarge

Azure

f8

GCP

c2 (8 vCPUs, 16 GiB memory)

Vector propose ainsi un projet pour réaliser des benchmarks. Ce projet permet de déployer une instance EC2 et de réaliser ces tests de performances.

Tests

Vector

FluentBit

Logstash

TCP to blackhole

86 mib/s

64.4 mib/s

40.6 mib/s

File to TCP

76.7 mib/s

35 mib/s

3.1 mib/s

Regex parsing

13.2 mib/s

20.5 mib/s

4.6 mib/s

TCP to HTTP 

26.7 mib/s

19.6 mib/s

2.7 mib/s

TCP to TCP 

69.9 mib/s

67.1 mib/s

70.4 mib/s

Je reprends les résultats de ces tests de performance. Pour plus de précisions, je vous convie à aller sur le dépôt GitHub de Vector.

D'autres tests de performances sont disponibles. Je vous recommande l'article de Ajay Gupta de la société IBM Cloud sur une comparaison entre Vector, Fluent Bit et Fluentd.

En conclusion

Après avoir expérimenté Vector sur plusieurs cas d'utilisation, c’est une solution élégante pour définir ses pipelines de données d'observabilité. Il est capable d'ingérer des données issues de différentes sources et peut les envoyer vers différentes destinations. Néanmoins, j'ai rencontré des difficultés à l'intégrer avec différentes solutions. Par exemple, je n'ai pas réussi à récupérer les logs envoyés par l'agent Grafana car cette fonctionnalité est absente. De même, d'autres sources sont encore en alpha ou béta. Ainsi, il n'est pas possible de s'interfacer avec l'agent OpenTelemetry pour récupérer les métriques ou les traces.

La documentation du projet est riche et c'est une des forces de cet outil. Les différents exemples permettent de comprendre et utiliser le langage VRL pour des cas d'utilisation courants.

Même si Datadog l'intègre dans sa suite avec la notion de pipelines, le développement du produit continue mais suit la roadmap définie par les cas d'utilisation des clients de Datadog. Ainsi, la vue pipeline d’observabilité propose de manière graphique de les construire et leur observabilité.

Références