Image mise en avant pour l'article

Principes et implémentation d’OpenTelemetry dans des projets PHP

23 juillet 2024
PHP
Le 24 mai dernier a eu lieu l’AFUP Day 2024. Cet événement, organisé par l’Association Française des Utilisateurs de PHP, s’est déroulé dans quatre villes de France (Lille, Lyon, Nancy et Poitiers). Des dizaines de conférences se sont tenues simultanément et certains d’entre nous chez Adimeo ont eu la chance d’assister à l’événement organisé à Nancy. Dans cet article, j’ai décidé de vous faire un retour sur une des conférences qui m’a particulièrement intéressé, celle de Danielle KAYUMBI HODIEB sur OpenTelemetry.


En tant que développeurs ou DevOps, nous avons déjà été quelques fois confrontés au crash de nos applications en production.

Les causes peuvent être nombreuses : erreurs dans le code source malgré l’implémentation de tests unitaires ou fonctionnels, les attaques type déni de service ou les failles de sécurité pour ne citer que celles-ci.

Dans cette situation, les impacts peuvent être désastreux que ce soit d’un point de vue business ou pour la réputation de l’entreprise. La panne qu’a subie AWS en 2021 en est le parfait exemple. L’équipe infra avait déployé un job automatique pour scaler les services managés. Ce déploiement en production a engendré un trop grand volume de requêtes, créant une congestion dans le système.

Sur la photo, un développeur travaille sur le code d'un projet digital

La clé du succès : l’observabilité

Pour éviter que ce genre de situation n’arrive, il est nécessaire d’anticiper au maximum les sources d’erreurs, de les comprendre et d’agir rapidement pour limiter les impacts sur le système.

Deux problématiques se présentent alors :

  • Quelle est la cause de la panne ? Erreurs de l’application ou causées par des services tiers, latences et goulots d’étranglement, etc.
  • Comment envoyer rapidement des alertes ?

Il faut donc observer le système ! Par exemple, monitorer les opérations sur des intervalles de temps est possible, mais cette méthode a ses limites car elle se concentre sur l’analyse de données prédéfinies récoltées de différents systèmes.

Pour mieux anticiper les pannes, l’analyse de manière continue en se basant sur les expériences passées est la clé. C’est ce qu’on appelle l’observabilité. Elle se repose sur les données télémétriques qui vont permettre de récolter les données émises par les composants et ainsi comprendre les comportements du système.

Les données télémétriques se composent :

  • des traces qui permettent de suivre le parcours de la requête,
  • des métriques qui fournissent des informations quantitatives sur l'état et la performance des systèmes, comme le taux de requêtes, le temps de réponse, l'utilisation des ressources, etc.,
  • des logs qui sont les messages émis par les systèmes pour le diagnostic et l’analyse des incidents.

Une fois ces données générées, collectées et exportées, on va pouvoir les exploiter dans un framework d’observabilité comme OpenTelemetry.

 

Qu’est-ce-qu’OpenTelemetry ?

OpenTelemetry est un projet open source visant à fournir une suite unifiée d'outils pour la collecte, la transformation et l'exportation de données télémétriques.

Il aide à surveiller les performances et à diagnostiquer les problèmes dans des environnements distribués. En 2019, OpenTelemetry intègre la Cloud Native Computing Foundation (CNCF), une organisation à but non lucratif créée pour promouvoir et soutenir les technologies cloud-native. Le SDK (Software Development Kit ou en français kit de développement logiciel) est disponible dans de nombreux langages tels que C++, PHP, Java, Python et Ruby pour ne citer qu’eux.

Les fondamentaux d’OpenTelemetry

Les fondamentaux de cette solution sont au nombre de deux : le tracing distribué et les collectors.

Le tracing distribué

Le tracing distribué va permettre de suivre le parcours de la requête de la source à la destination quel que soit les systèmes par lesquels elle a transité contrairement au système traditionnel où on ne peut suivre la requête que dans un seul système. Pour cela, on va avoir trois éléments de base : les logs, les spans et les traces.

> Les logs respectent en général le standard syslog à savoir une date d’émission du log, la criticité (« DEBUG », « INFO », « ERROR », « CRITICAL », etc.) mais cela ne suffit pas car il manque les informations contextuelles. C’est là qu’entrent en jeu les spans et les traces.

> Un span va permettre de tracer une opération spécifique au niveau du code.

En voici un exemple :


{ 
    "trace_id": "0af7651916cd43dd8448eb211c80319c", 
    "span_id": "b7ad6b7169203331", 
    "parent_span_id": "348a2b12bc1c632a", 
    "name": "get_cart_details", 
    "start_time": "2023-05-01T12:45:00.000Z", 
    "end_time": "2023-05-01T12:45:10.000Z", 
    "status": "OK", 
    "attributes": { 
        "http.method": "GET", 
        "user_id": 1111, 
        "card_id": 2222 
    }, 
    "links": [], 
    "trace_state": "" 
}

Il se compose obligatoirement d’un nom, d’une date de début, d’une date de fin et des attributs tels que les paramètres injectés à la requête et ainsi avoir plus de contexte.

Pour injecter un span, il est conseillé de le faire dans une classe bien spécifique (dans un controller, un handler, un manager, etc.).

Ci-dessous un exemple d’injection de span dans un controller Symfony :


namespace App\Controller; 
 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\Routing\Annotation\Route; 
use App\Service\OpenTelemetryService; 
use OpenTelemetry\Context\Context; 
use Symfony\Component\Security\Core\Security; 
 
class CartController extends AbstractController 
{ 
    private $openTelemetryService; 
    private $security; 
 
    public function __construct(OpenTelemetryService $openTelemetryService, Security $security) 
    { 
        $this->openTelemetryService = $openTelemetryService; 
        $this->security = $security; 
    } 
 
    /** 
     * @Route("/api/cart/{cartId}", name="get_cart_details", methods={"GET"}) 
     */ 
    public function getCartDetails(int $cartId): JsonResponse 
    { 
 // Injection du span 
        $tracer = $this->openTelemetryService->getTracer(); 
        $span = $tracer->spanBuilder('get_cart_details') 
            ->setParent(Context::getCurrent()->getSpan()) 
            ->startSpan(); 
 
        try { 
            $user = $this->security->getUser(); 
 
            if (!$user) { 
                throw new \Exception('Unauthenticated user'); 
            } 
 
            $userId = $user->getId(); 
 
            $span->setAttribute('http.method', 'GET'); 
            $span->setAttribute('user_id', $userId); 
            $span->setAttribute('cart_id', $cartId); 
 
            $cartDetails = [ 
                'cart_id' => $cartId, 
                'user_id' => $userId, 
                'items' => [ 
                    ['product_id' => 1, 'quantity' => 2], 
                    ['product_id' => 2, 'quantity' => 1], 
                ], 
                'total_price' => 300.00, 
            ]; 
 
            $span->end(); 
 
            return new JsonResponse($cartDetails); 
 
        } catch (\Exception $e) { 
            $span->recordException($e); 
            $span->end(); 
 
            return new JsonResponse(['error' => 'Unable to retrieve cart details'], 500); 
        } 
    } 
} 

> Une trace quant à elle est une collection de spans. Elle va permettre de suivre le parcours de la requête à travers les différents services. Entre chaque trace, on va avoir le contexte (trace context) et c’est sa propagation qui va permettre de maintenir la corrélation entre les signaux (via des en-têtes HTTP par exemple).

Schéma explicatif de la relation entre les "trace" et les "spans"

Les collectors

Les collectors, quant à eux, vont permettre de récupérer, traiter et exporter les données vers des backend d’observabilité (Prometheus, Zipkin ou des services cloud comme AWS X-Ray et Google Cloud Trace par exemple). Ils vont se baser en général sur le protocole OTLP (OpenTeLemety Protocol) qui va décrire le mécanisme d’encodage et de transport des données télémétriques et une fois ces données exportées, on va pouvoir utiliser des outils APM (Application Performance Management) tels que Datadog, New Relic ou Elastic APM et ainsi avoir la map des services dans laquelle la requête a transité.

Un exemple de map sur Datadog permettant de mettre en exergue des métriques sur les temps de réponse et les services qui ont crashé.

Capture d'écran d'un exemple de map sur Datadog permettant de mettre en exergue des métriques sur les temps de réponse et les services qui ont crashé

En conclusion de cet article, vous l’aurez très certainement compris, l’observabilité est très importante car elle va permettre d’anticiper les erreurs et donner la possibilité aux équipes d’agir le plus rapidement possible.

Pour la mise en place de ce framework, trois étapes sont essentielles :

  • La prévention (Prevent) à travers la qualité du code. Cela passe par la mise en place de tests unitaires, de tests fonctionnels, de tests d’intégration et de l’analyse statique, l’implémentation de design patterns, du concept SOLID ou du TDD (Test Driven Development).
  • La découverte (Discover) via la mise en place du monitoring pour détecter le plus rapidement possible les erreurs qui peuvent se déclencher dans vos applications.
  • La résolution (Resolve) qui dépend de votre expertise technique et mais aussi de la communication au sein de vos équipes qui est primordiale à cette étape.


Pour en savoir plus, je vous propose d’autres contenus :

Et si cet article vous a plu, je vous propose de poursuivre votre lecture avec d’autres retours sur les conférences « Afup day 2024 » de Nancy :

Crédit photo : Arsenii Palivoda

Image mise en avant pour l'article
Maurice Nguyen
Lead développeur • Pôle Framework & DevOps
WEBINAR
Les tests : une garantie pour le ROI de vos projets web !
Voir le webinar !
Quelle technologie choisir pour votre projet digital ?
Drupal, Symfony, WordPress..., nos experts vous conseillent la meilleure solution technique pour votre projet
Contactez-nous !