Сквозное наблюдение (observability) в микросервисах

—

от автора

Привет, Хабр!

Сегодня мы поговорим о чем-то, что является неотъемлемой частью современной микросервисной архитектуры, что-то, без чего трудно представить себе успешное и надежное приложение в мире распределенных систем. Да, вы правильно догадались, мы говорим о сквозном наблюдении, или, как его еще называют, «observability.»

Если вы читаете эту статью, то, вероятно, уже являетесь опытным разработчиком и хорошо знакомы с микросервисами. Вы, возможно, создали и поддерживаете множество микросервисов, и, скорее всего, столкнулись с ситуацией, когда отладка и мониторинг становятся настоящей головной болью.

Именно здесь на сцену выходит сквозное наблюдение. Это не просто новомодное словечко или набор инструментов, это фундаментальный компонент, который позволяет нам видеть и понимать, что происходит в наших микросервисах в реальном времени. С его помощью мы можем следить за запросами, анализировать производительность, идентифицировать проблемы и, конечно же, обеспечивать бесперебойную работу наших приложений.

Разберемся подробнее с первым из ключевых компонентов сквозного наблюдения — трассировкой.

Трассировка

1. Контекстное распространение запросов

Контекстное распространение запросов является фундаментальной частью трассировки. Оно позволяет отслеживать путь запроса от момента поступления в систему до его завершения. Для этого контекст (или специальная информация о запросе) передается между различными компонентами системы, такими как микросервисы, базы данных, брокеры сообщений и другие.

Код Python с использованием библиотеки OpenTelemetry:

import opentelemetry.trace as trace from opentelemetry import propagate, trace  tracer = trace.get_tracer(__name__)  # Создание корневой трассы with tracer.start_as_current_span("main-request"):     # Отправка запроса к микросервису A     with tracer.start_as_current_span("microservice-A"):         # Здесь можно вставить логику для обработки запроса к микросервису A         pass      # Отправка запроса к микросервису B     with tracer.start_as_current_span("microservice-B"):         # Здесь можно вставить логику для обработки запроса к микросервису B         pass 

2. Генерация и агрегирование трасс

После того как мы создали трассу запроса, необходимо собрать информацию о каждом ее сегменте и агрегировать данные для анализа. Это может включать в себя информацию о времени выполнения, статусе запроса, идентификаторах запроса и многое другое.

Пример кода JavaScript с использованием OpenTelemetry:

const { trace, context } = require('@opentelemetry/api'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');  const tracer = trace.getTracer('example-tracer');  // Экспортер для отправки данных трассировки в Zipkin const exporter = new ZipkinExporter({ serviceName: 'my-service' });  // Установка экспортера tracer.addSpanProcessor(new SimpleSpanProcessor(exporter));  async function handleRequest(request) {   const span = tracer.startSpan('handleRequest');      // Добавление контекста к запросу   context.with(trace.setSpan(context.active(), span), () => {     // Здесь происходит обработка запроса   });      span.end(); } 

3. Применение стандартов, таких как OpenTelemetry и Jaeger

Для обеспечения совместимости и эффективной работы трассировки в микросервисной среде, стоит использовать стандарты и библиотеки, такие как OpenTelemetry и Jaeger.

OpenTelemetry предоставляет API для трассировки, метрик и журналов, что делает его мощным инструментом для сквозного наблюдения.

Jaeger, с другой стороны, является популярным инструментом для сбора и визуализации данных трассировки. Он предоставляет дружественный интерфейс для анализа трассировок и поиска проблем.

Пример кода для настройки Jaeger:

from jaeger_client import Config  config = Config(     config={         'sampler': {             'type': 'const',             'param': 1,         },         'logging': True,     },     service_name='my-service', )  tracer = config.initialize_tracer() 

Помимо вышеуказанных компонентов, трассировка также может включать в себя передачу данных о запросах между микросервисами с помощью HTTP заголовков или метаданных сообщений в системе сообщений.

Основные компоненты сквозного наблюдения включают в себя не только трассировку, как было описано ранее, но и метрики. Метрики — это ценный источник информации о производительности, доступности и общем состоянии ваших микросервисов.

Метрики

1. Сбор, агрегирование и визуализация метрик

Сбор метрик — это процесс собирания данных о производительности и состоянии приложения. Эти данные могут включать в себя информацию о загрузке CPU, использовании памяти, количестве обработанных запросов, времени ответа и многом другом. Метрики могут быть собраны внутри кода приложения и отправлены в инструменты мониторинга.

Код на Python с использованием библиотеки Prometheus:

from prometheus_client import start_http_server, Summary  # Создаем объект для сбора метрик request_latency = Summary('request_latency_seconds', 'Request latency in seconds')  # Ваш код обработки запросов @request_latency.time() def process_request():     # Здесь происходит обработка запроса     pass  if __name__ == '__main__':     # Запускаем HTTP сервер для сбора метрик Prometheus     start_http_server(8000) 

2. Инструменты для мониторинга производительности

Для эффективного сбора и анализа метрик, необходимо использовать специализированные инструменты мониторинга производительности. Один из самых популярных инструментов — Prometheus. Это open-source система мониторинга и тревожной системы, спроектированная для сбора, агрегации, запросов и визуализации данных.

Grafana — еще один мощный инструмент для визуализации метрик и создания информативных дашбордов. Он может интегрироваться с Prometheus и позволяет создавать красочные графики и диаграммы, что делает мониторинг производительности более наглядным.

3. Применение Prometheus и Grafana

Пример кода для запуска Prometheus и Grafana в Docker-контейнерах:

# Запуск Prometheus docker run -d -p 9090:9090 --name prometheus prom/prometheus  # Запуск Grafana docker run -d -p 3000:3000 --name grafana grafana/grafana 

Затем вы можете настроить Prometheus для сбора метрик из ваших микросервисов и настроить Grafana для создания красочных дашбордов.

Примечание: Приведенные выше примеры кода и команды для Docker предоставляют только базовое представление о том, как можно начать использовать Prometheus и Grafana. Реальная настройка и интеграция могут потребовать дополнительных шагов и настроек, в зависимости от вашей конкретной архитектуры и потребностей.

Журналирование (Logging)

Журналирование — это еще один важный компонент сквозного наблюдения, который позволяет вам получать ценные данные о работе ваших микросервисов. Систематически собирая и анализируя журналы, вы можете выявлять проблемы, идентифицировать ошибки и отслеживать поток выполнения ваших приложений.

1. Структурированные логи и их хранение

Хороший подход к журналированию включает использование структурированных логов. Вместо простых строк вы можете сохранять логи в формате JSON или других структурированных форматах. Это позволяет вам легко фильтровать, анализировать и сопоставлять логи. Например:

{   "timestamp": "2023-09-13T12:00:00.000Z",   "level": "INFO",   "message": "User 'john.doe' logged in",   "application": "auth-service",   "component": "authentication",   "user_id": 123 } 

Такие структурированные логи бывают особенно полезными при работе с большим количеством микросервисов и несколькими экземплярами каждого из них.

2. Централизованный сбор и анализ журналов

Чтобы обеспечить эффективное журналирование в микросервисной архитектуре, централизованный сбор и анализ журналов является жизненно важным. Это позволяет вам собирать все журналы в одном месте, где вы можете легко их анализировать.

Один из популярных подходов — использование ELK Stack (Elasticsearch, Logstash и Kibana):

  • Elasticsearch: Это мощный поисковый и аналитический движок, который может использоваться для хранения журналов в распределенном хранилище с возможностью быстрого поиска и анализа.

  • Logstash: Этот компонент занимается сбором и фильтрацией журналов из различных источников, а затем передает их в Elasticsearch для хранения.

  • Kibana: Интерфейс для визуализации и анализа данных, хранящихся в Elasticsearch. С его помощью вы можете создавать интерактивные дашборды и графики для мониторинга приложений.

Примеры кода

Python с использованием библиотеки Loguru:

from loguru import logger  # Настройка логгера logger.add("app.log", rotation="500 MB")  # Пример журналирования def process_request(request):     logger.info("Request received: {}", request)     # Ваш код обработки запроса 

Node.js с использованием библиотеки Winston:

const winston = require('winston');  // Настройка логгера const logger = winston.createLogger({   level: 'info',   format: winston.format.json(),   transports: [     new winston.transports.File({ filename: 'app.log' })   ] });  // Пример журналирования function processRequest(request) {   logger.info('Request received: %s', request);   // Ваш код обработки запроса } 

Java с использованием библиотеки SLF4J и Logback:

import org.slf4j.Logger; import org.slf4j.LoggerFactory;  // Настройка логгера Logger logger = LoggerFactory.getLogger(MyClass.class);  // Пример журналирования public void processRequest(Request request) {     logger.info("Request received: {}", request);     // Ваш код обработки запроса } 

Журналирование — важнейший компонент сквозного наблюдения, который помогает вам не только выявлять проблемы и ошибки в приложениях, но и понимать, как ваше приложение работает в реальном времени. Управление журналами с использованием структурированных логов и централизованных инструментов анализа, таких как ELK Stack, может значительно улучшить процесс мониторинга и отладки в микросервисной архитектуре.

Интеграция сквозного наблюдения

Интеграция сквозного наблюдения в ваши микросервисы требует использования специализированных инструментов и библиотек, которые позволят вам собирать и передавать данные о трассировке, метриках и логах. Несколько популярных инструментов и библиотек, которые мы рассматривали ранее для разных языков и фреймворков:

  1. OpenTelemetry: OpenTelemetry — это ведущий стандарт для сквозного наблюдения. Он поддерживает множество языков программирования и интеграцию с различными фреймворками. Например, вы можете интегрировать OpenTelemetry в ваше приложение на Python следующим образом:

from opentelemetry import trace  tracer = trace.get_tracer(__name__)  # Создание корневой трассы with tracer.start_as_current_span("main-request"):     # Обработка запроса 
  1. Jaeger: Jaeger — это популярная реализация системы сбора и анализа трассировок, которая также предоставляет библиотеки для интеграции с различными языками. Вот пример интеграции с Node.js:

const { initTracer } = require('jaeger-client'); const { Tags, FORMAT_HTTP_HEADERS } = require('opentracing');  const config = {   serviceName: 'my-service',   sampler: {     type: 'const',     param: 1,   },   reporter: {     logSpans: true,   }, };  const options = {   tags: { [Tags.SPAN_KIND]: Tags.SPAN_KIND_RPC_SERVER },   format: FORMAT_HTTP_HEADERS, };  const tracer = initTracer(config, options);  // Создание и отправка трассы const span = tracer.startSpan('my-operation'); span.setTag(Tags.HTTP_METHOD, 'GET'); span.finish(); 

Чтобы упростить процесс создания трассировки, метрик и логов в ваших микросервисах, вы можете воспользоваться автоматизированными инструментами. Эти инструменты автоматически инструментируют ваше приложение, добавляя трассировку и сбор метрик без необходимости вручную вставлять код в каждый сервис.

Пример инструмента для автоматической интеграции — OpenTelemetry Auto-Instrumentation. Этот инструмент поддерживает автоматическую интеграцию с различными фреймворками и библиотеками. Например, для Python вы можете использовать opentelemetry-instrumentation:

from opentelemetry.instrumentation.auto_instrumentation import install_all_patches  install_all_patches() 

Настройка сквозного наблюдения может сильно различаться в зависимости от языка программирования и фреймворка. Вот несколько примеров настройки сквозного наблюдения для разных языков:

Java с использованием Spring Boot и OpenTelemetry:

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import io.opentelemetry.exporter.trace.jaeger.JaegerGrpcSpanExporter; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.trace.Tracer;  @SpringBootApplication public class MyApplication {      public static void main(String[] args) {         // Инициализация и настройка трассировки         Tracer tracer = TracerProvider.createTracer();         JaegerGrpcSpanExporter exporter = JaegerExporterProvider.createExporter();         SimpleSpanProcessor spanProcessor = SimpleSpanProcessor.create(exporter);          SpringApplication.run(MyApplication.class, args);     } } 

Node.js с использованием Express и Jaeger:

const express = require('express'); const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { LogLevel, BasicTracerProvider } = require('@opentelemetry/tracing');  const app = express();  const tracerProvider = new BasicTracerProvider({   logger: console,   logLevel: LogLevel.ERROR, }); tracerProvider.addSpanProcessor(new BatchSpanProcessor(new JaegerExporter())); tracerProvider.register();  app.get('/', (req, res) => {   const span = tracer.startSpan('my-operation');   // Ваша обработка запроса   span.end();   res.send('Hello, World!'); });  app.listen(3000, () => {   console.log('Server is running on port 3000'); }); 

Интеграция сквозного наблюдения с вашими микросервисами — это безусловная необходимость, которя обеспечить мониторинг и отладку в микросервисной архитектуре.

Применение на практике

Распределенные отладочные сценарии могут быть сложной задачей в микросервисной архитектуре, но они необходимы для выявления и устранения проблем в системе:

from opentelemetry import trace import time  tracer = trace.get_tracer(__name__)  def perform_distributed_debugging():     with tracer.start_as_current_span("distributed-debugging"):         # Логика для отладки         try:             # Вызов микросервиса A             with tracer.start_as_current_span("microservice-A"):                 # Логика для отладки микросервиса A                 pass              # Вызов микросервиса B             with tracer.start_as_current_span("microservice-B"):                 # Логика для отладки микросервиса B                 pass         except Exception as e:             # Обработка ошибок и логирование             tracer.get_current_span().set_status(trace.Status(StatusCode.ERROR, str(e)))         finally:             # Завершение трассы             tracer.get_current_span().end()  if __name__ == "__main__":     perform_distributed_debugging() 

Профилирование производительности помогает выявить узкие места в вашем коде и оптимизировать его для повышения эффективности:

import cProfile import pstats  def slow_function():     for _ in range(1000000):         pass  if __name__ == "__main__":     profiler = cProfile.Profile()     profiler.enable()      # Вызываем функцию для профилирования     slow_function()      profiler.disable()          # Сохраняем статистику профилирования     stats = pstats.Stats(profiler)     stats.sort_stats(pstats.SortKey.TIME)     stats.print_stats() 

Обнаружение и решение проблем в реальном времени — это важная часть поддержки микросервисов в рабочем состоянии:

import logging  def main():     try:         # Ваша логика выполнения микросервиса         result = perform_operation()         logging.info("Operation successful: %s", result)     except Exception as e:         # Логирование ошибки и отправка уведомления         logging.error("An error occurred: %s", str(e))         send_notification("Error in microservice", str(e))  def perform_operation():     # Логика операции     return 42  def send_notification(subject, message):     # Логика отправки уведомления     pass  if __name__ == "__main__":     logging.basicConfig(level=logging.INFO)     main() 

Мониторинг и управление версиями микросервисов играют ключевую роль в устойчивости системы:

from flask import Flask, request from prometheus_client import Counter, generate_latest, CONTENT_TYPE_LATEST  app = Flask(__name__)  # Счетчик для мониторинга версий микросервиса version_counter = Counter("microservice_version", "Microservice version information")  @app.route("/version", methods=["GET"]) def get_version():     version = request.args.get("version")     version_counter.labels(version).inc()     return f"Microservice version: {version}"  @app.route("/metrics", methods=["GET"]) def metrics():     return generate_latest(), 200, {"Content-Type": CONTENT_TYPE_LATEST}  if __name__ == "__main__":     app.run(host="0.0.0.0", port=8080) 

Обеспечение безопасности и управление доступом к микросервисам — это неотъемлемая часть их разработки:

const express = require('express'); const passport = require('passport'); const LocalStrategy = require('passport-local').Strategy;  const app = express();  // Конфигурация Passport passport.use(new LocalStrategy(   (username, password, done) => {     // Здесь происходит проверка логина и пароля в базе данных       if (isValidUser(username, password)) {       return done(null, { username });     } else {       return done(null, false, { message: 'Неверный логин или пароль' });     }   } ));  passport.serializeUser((user, done) => {   done(null, user.username); });  passport.deserializeUser((username, done) => {   // Здесь можно получить пользователя из базы данных   done(null, { username }); });  // Маршруты для аутентификации и доступа app.post('/login',   passport.authenticate('local', {     successRedirect: '/dashboard',     failureRedirect: '/login',     failureFlash: true   }) );  app.get('/dashboard', (req, res) => {   if (req.isAuthenticated()) {     // Защищенная страница     res.send('Добро пожаловать на защищенную страницу');   } else {     // Перенаправление на страницу входа     res.redirect('/login');   } });  // Запуск сервера app.listen(3000, () => {   console.log('Сервер запущен на порту 3000'); });  function isValidUser(username, password) {   // Здесь можно реализовать проверку логина и пароля в базе данных   return username === 'user' && password === 'password'; } 

Масштабирование микросервисов и управление нагрузкой важно для обеспечения высокой производительности системы:

from flask import Flask app = Flask(__name__)  @app.route('/') def hello_world():     return 'Hello, World!'  if __name__ == '__main__':     app.run(host='0.0.0.0', port=8080) 

Этот микросервис может быть легко масштабирован с использованием Kubernetes, позволяя управлять высокой нагрузкой и обеспечивая высокую доступность.

Заключение

Применение сквозного наблюдения и лучших практик разработки микросервисов может значительно улучшить управление и мониторинг вашей микросервисной архитектуры. Профессиональные примеры кода и понимание основных концепций позволят вам разрабатывать и поддерживать надежные и высокопроизводительные микросервисы.

А подробнее про микросервисную архитектуру вы можете узнать в рамках одноименного курса от моих коллег из OTUS. Подробнее о курсе.


ссылка на оригинал статьи https://habr.com/ru/articles/766584/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *