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.
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 :
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 :
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.
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 de cette solution sont au nombre de deux : le tracing distribué et les collectors.
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).
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é.
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 :
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