tutoriales.com

Observabilidad en Pipelines CI/CD con OpenTelemetry: Monitoreo Unificado y Trazabilidad Extrema

Este tutorial profundiza en la implementación de OpenTelemetry para llevar la observabilidad a tus pipelines de Integración Continua y Despliegue Continuo (CI/CD). Descubre cómo obtener una visibilidad profunda de cada etapa de tu pipeline, desde el build hasta el despliegue, utilizando métricas, logs y trazas unificadas. Mejorarás la detección de problemas y optimizarás el rendimiento.

Intermedio20 min de lectura17 views
Reportar error

La observabilidad se ha convertido en un pilar fundamental en el desarrollo de software moderno. En el contexto de los pipelines CI/CD, una buena observabilidad significa tener la capacidad de entender el estado interno de tus procesos de automatización a partir de datos externos. Cuando un build falla, un despliegue se ralentiza o una prueba no detecta un problema, ¿cómo puedes identificar la causa raíz rápidamente?

Aquí es donde OpenTelemetry entra en juego. OpenTelemetry es un conjunto de herramientas, APIs y SDKs de código abierto que proporciona una forma unificada de instrumentar, generar, recolectar y exportar datos de telemetría (métricas, logs y trazas) desde tus aplicaciones y sistemas. Integrarlo en tus pipelines CI/CD te permite obtener una visión holística y detallada de cada etapa, facilitando la depuración, la optimización y la mejora continua.

🎯 ¿Por qué Observabilidad en CI/CD es Crucial?

Un pipeline CI/CD es una secuencia de pasos interconectados. Un fallo en cualquier punto puede detener el proceso y retrasar la entrega de valor. Sin una visibilidad adecuada, diagnosticar problemas puede ser un verdadero desafío. Considera estos puntos:

  • Detección Rápida de Fallos: Identifica exactamente dónde y por qué falla un build, una prueba o un despliegue.
  • Optimización del Rendimiento: Analiza los cuellos de botella en tu pipeline y reduce los tiempos de ejecución.
  • Mejora de la Fiabilidad: Asegura que tus despliegues son predecibles y que los problemas se detectan antes de llegar a producción.
  • Auditoría y Conformidad: Mantén un registro detallado de todas las actividades del pipeline para fines de auditoría y cumplimiento.
  • Trazabilidad Extrema: Sigue una solicitud desde su commit inicial hasta su despliegue en producción, pasando por cada etapa del pipeline.
💡 Consejo: La observabilidad no es solo recopilar datos; es la capacidad de responder cualquier pregunta sobre el estado de tu sistema sin tener que desplegar nuevo código o instrumentación.

📖 Entendiendo OpenTelemetry: Pilares de la Observabilidad

OpenTelemetry unifica los tres pilares tradicionales de la observabilidad:

  1. Métricas: Datos numéricos agregados que representan el estado del sistema en un momento dado (ej. duración del build, número de tests ejecutados, errores por etapa).
  2. Trazas (Traces): Representan el camino de una solicitud o proceso a través de múltiples servicios o componentes. Cada operación dentro de la traza se llama span. Esto es invaluable para entender el flujo de trabajo de tu pipeline.
  3. Logs: Registros de eventos discretos que ocurren en un sistema (ej. inicio/fin de una tarea, mensajes de error, warnings).

OpenTelemetry proporciona una API estándar para instrumentar tus aplicaciones y pipelines para generar estos datos, y un Collector para procesarlos y exportarlos a tu backend de observabilidad preferido (Jaeger, Prometheus, Grafana, ELK Stack, etc.).

🛠️ Componentes Clave de OpenTelemetry

  • APIs y SDKs: Bibliotecas para diferentes lenguajes que permiten instrumentar el código y generar telemetría.
  • Collector: Un proxy extensible que recibe, procesa y exporta datos de telemetría. Puede ejecutarse como un agente o como un gateway.
  • Formatos de Exportación: Estándares para enviar datos (OTLP es el formato nativo, pero soporta otros como Jaeger, Prometheus, Zipkin).
OpenTelemetry SDKs & APIs Métricas Trazas Logs OpenTelemetry Collector Prometheus / Grafana Jaeger ELK Stack

✨ Integrando OpenTelemetry en tu Pipeline CI/CD: Un Enfoque Práctico

Para este tutorial, asumiremos un pipeline CI/CD genérico (por ejemplo, GitLab CI/CD, GitHub Actions, Jenkins) y mostraremos cómo integrar OpenTelemetry en sus etapas. El concepto es universalmente aplicable.

Paso 1: Configuración del OpenTelemetry Collector

El Collector es el corazón de la recopilación de telemetría. Puedes ejecutarlo en un servidor dedicado, en un contenedor Docker, o incluso como un sidecar en Kubernetes. Para empezar, una configuración básica de otel-collector-config.yaml podría verse así:

receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:
    send_batch_size: 100
    timeout: 10s

exporters:
  logging:
    verbosity: detailed
  otlp/jaeger:
    endpoint: "jaeger:14250"
    tls:
      insecure: true
  otlp/prometheus:
    endpoint: "prometheus:9090"
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging, otlp/jaeger]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging, otlp/prometheus]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging]

Este Collector está configurado para:

  • Recibir datos OTLP (gRPC y HTTP).
  • Procesar los datos en lotes.
  • Exportar trazas a un backend Jaeger (asumiendo que está disponible en jaeger:14250).
  • Exportar métricas a un backend Prometheus (asumiendo que está disponible en prometheus:9090).
  • Imprimir logs y métricas en la consola para depuración (logging).

Puedes ejecutarlo en Docker:

docker run -d \
  -p 4317:4317 -p 4318:4318 \
  -v $(pwd)/otel-collector-config.yaml:/etc/otel-collector-config.yaml \
  otel/opentelemetry-collector-contrib:latest \
  --config=/etc/otel-collector-config.yaml

Paso 2: Instrumentación del Pipeline CI/CD

La instrumentación es clave. Implica añadir código o comandos en tu pipeline que generen los datos de telemetría. Para tareas de CI/CD, esto a menudo significa usar el SDK de OpenTelemetry en scripts, o herramientas que ya tienen integración con OTel, o incluso envolver comandos con herramientas de traza como opentelemetry-instrument.

Consideraremos un pipeline con etapas de build, test y deploy.

2.1. Configuración de Variables de Entorno

Para que los SDKs de OpenTelemetry sepan dónde enviar los datos, es crucial configurar variables de entorno en el entorno de ejecución de tu pipeline. Estas variables son estándar en OpenTelemetry.

export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4317" # OTLP/gRPC endpoint
export OTEL_SERVICE_NAME="ci-cd-pipeline" # Nombre de tu servicio
export OTEL_RESOURCE_ATTRIBUTES="pipeline.name=my-app-pipeline,job.id=${CI_JOB_ID},branch=${CI_COMMIT_BRANCH}" # Atributos del recurso
export OTEL_PROPAGATORS="tracecontext,baggage"
📌 Nota: Ajusta `otel-collector:4317` a la dirección y puerto de tu OpenTelemetry Collector. Las variables como `${CI_JOB_ID}` o `${CI_COMMIT_BRANCH}` son ejemplos de GitLab CI/CD; adáptalas a tu sistema CI/CD.

2.2. Generación de Trazas y Spans para las Etapas

Crear spans para cada etapa y tarea del pipeline te permite visualizar el flujo y la duración de cada componente.

Para un script de shell genérico, puedes usar opentelemetry-cli o wrappers simples:

#!/bin/bash

# Función para envolver comandos con trazas
run_with_span() {
  local span_name="$1"
  shift
  echo "Iniciando span: $span_name"
  # Inicia un span (ejemplo con un wrapper simple)
  # En un entorno real, usarías opentelemetry-instrument o un SDK
  START_TIME=$(date +%s%N)

  # Ejecuta el comando real
  "$@"
  COMMAND_STATUS=$?

  END_TIME=$(date +%s%N)
  DURATION_MS=$(( (END_TIME - START_TIME) / 1000000 ))

  # Simulación de exportación de span
  echo "Span '$span_name' terminado en ${DURATION_MS}ms con estado $COMMAND_STATUS"
  # Aquí es donde el SDK de OpenTelemetry enviaría el span real al Collector
  # En la práctica, se usaría un SDK de OTel en el lenguaje del script o un wrapper CLI.
  return $COMMAND_STATUS
}

# Ejemplo de uso en un script de build
run_with_span "build-application" mvn clean install
run_with_span "run-unit-tests" mvn test

Con lenguajes de programación:

Si tus scripts de CI/CD están en Python, Node.js, etc., puedes usar los SDKs de OpenTelemetry directamente.

# build_script.py
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

# Configurar TracerProvider globalmente (normalmente se haría una vez al inicio del pipeline)
resource = Resource.from_attributes({
    "service.name": "ci-cd-pipeline-python",
    "pipeline.stage": "build",
    "job.id": "${CI_JOB_ID}"
})

provider = TracerProvider(resource=resource)
# Para enviar al Collector
provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4317")))
# Para ver en consola
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)


def build_app():
    tracer = trace.get_tracer(__name__)
    with tracer.start_as_current_span("compile-code") as span:
        print("Compilando código...")
        # Simular trabajo
        import time; time.sleep(2)
        print("Código compilado.")
        span.set_attribute("build.compiler", "maven")
        span.set_attribute("build.result", "success")

    with tracer.start_as_current_span("package-artifact") as span:
        print("Empaquetando artefacto...")
        time.sleep(1)
        print("Artefacto empaquetado.")
        span.set_attribute("artifact.name", "my-app.jar")

if __name__ == "__main__":
    build_app()
⚠️ Advertencia: La instrumentación manual con SDKs requiere modificar tus scripts. Para herramientas como Docker o comandos CLI, puedes necesitar wrappers o ejecutar los comandos con `opentelemetry-instrument` si están disponibles para tu shell o CI/CD.

2.3. Adición de Logs Estructurados

Los logs tradicionales son excelentes, pero los logs estructurados con atributos adicionales son mucho más poderosos en OpenTelemetry.

En Python, por ejemplo:

import logging
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import ConsoleLogExporter, SimpleLogRecordProcessor
from opentelemetry.sdk.resources import Resource
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter

# ... configuración de LoggerProvider similar a TracerProvider ...
resource = Resource.from_attributes({
    "service.name": "ci-cd-pipeline-logger",
    "pipeline.stage": "test",
})
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(SimpleLogRecordProcessor(OTLPLogExporter(endpoint="http://otel-collector:4317")))

# Obtener un logger estándar de Python
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Asignar el handler de OTel al logger
handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider)
logger.addHandler(handler)

# Ahora puedes loggear como de costumbre, y los logs serán enriquecidos
# automáticamente con el contexto de traza y exportados por OTLP.
logger.info("Iniciando fase de pruebas unitarias", extra={'test_suite': 'frontend_tests'})
# ... ejecutar pruebas ...
logger.error("Fallo en la prueba 'LoginPageTest'. Detalles: Elemento no encontrado", 
             extra={'error_code': '404', 'component': 'UI', 'test_name': 'LoginPageTest'})
logger.info("Pruebas unitarias completadas.")

Estos logs se asociarán con el span y la trace actuales si existen, proporcionando un contexto increíblemente valioso.

2.4. Recopilación de Métricas

Las métricas te darán una visión cuantitativa del rendimiento de tu pipeline. Puedes medir la duración de las etapas, el número de intentos de despliegue, la cantidad de tests pasados/fallados, etc.

# metrics_script.py
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.resources import Resource

# ... configuración de MeterProvider ...
resource = Resource.from_attributes({
    "service.name": "ci-cd-pipeline-metrics",
    "pipeline.stage": "deploy",
})

reader = PeriodicExportingMetricReader(OTLPMetricExporter(endpoint="http://otel-collector:4317"))
provider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(provider)

# Obtener un meter
meter = metrics.get_meter(
    "ci-cd-pipeline-meter",
    version="1.0.0",
)

# Crear un contador para despliegues
deployment_counter = meter.create_counter(
    "ci_cd.deployment.total",
    description="Número total de despliegues",
    unit="1"
)

# Crear un medidor de duración (histograma) para el tiempo de despliegue
deployment_duration_gauge = meter.create_histogram(
    "ci_cd.deployment.duration_seconds",
    description="Duración del despliegue",
    unit="s"
)

# Simulando un despliegue
def perform_deployment():
    print("Iniciando despliegue...")
    start_time = time.monotonic()

    # Simular proceso de despliegue
    import random
    if random.random() > 0.1: # 90% de éxito
        print("Despliegue exitoso.")
        deployment_counter.add(1, {"status": "success", "env": "production"})
        duration = time.monotonic() - start_time
        deployment_duration_gauge.record(duration, {"status": "success", "env": "production"})
    else:
        print("Despliegue fallido.")
        deployment_counter.add(1, {"status": "failure", "env": "production"})
        duration = time.monotonic() - start_time
        deployment_duration_gauge.record(duration, {"status": "failure", "env": "production"})

if __name__ == "__main__":
    perform_deployment()

Paso 3: Visualización de Datos de Observabilidad

Una vez que el Collector esté enviando los datos, necesitarás un backend de observabilidad para visualizarlos. Ejemplos populares:

  • Jaeger (Trazas): Para visualizar las trazas y los spans de tu pipeline, identificando latencias y dependencias.
  • Prometheus + Grafana (Métricas): Para crear dashboards con gráficos de duración de etapas, tasas de éxito/fallo, etc.
  • ELK Stack (Logs): Para buscar, filtrar y analizar logs de manera centralizada.

Ejemplo con Jaeger UI

Después de ejecutar tu pipeline instrumentado, deberías poder ver las trazas en la UI de Jaeger (normalmente en http://localhost:16686).

Jaeger UI Trace: ci-cd-pipeline 0ms 400ms 800ms 1200ms ci-cd-pipeline 1200ms ▼ build-application 900ms compile-code 250ms package-artifact 150ms run-unit-tests 500ms perform-deployment 300ms

En esta vista, puedes ver:

  • La duración total de cada ejecución del pipeline.
  • La duración de cada etapa (build-application, run-unit-tests, perform-deployment).
  • Los sub-spans dentro de cada etapa (ej. compile-code, package-artifact).
  • Atributos (build.compiler, build.result, artifact.name) asociados a cada span, que proporcionan contexto.

Ejemplo con Grafana (para Métricas)

Configurando Prometheus para recolectar métricas de tu OpenTelemetry Collector y luego usando Grafana, puedes crear dashboards potentes.

🔥 Importante: Asegúrate de que Prometheus esté configurado para "raspar" (scrape) las métricas del OpenTelemetry Collector si usas un exporter de Prometheus dentro del Collector, o que el Collector exporte directamente a Prometheus si tiene esa capacidad.

Por ejemplo, podrías crear un panel en Grafana para mostrar la duración promedio del ci_cd.deployment.duration_seconds o la cantidad de ci_cd.deployment.total por estado.

Deployment Duration (avg) Deployment Status Success (90%) Failure (10%) Latest Deployments JOB ID BRANCH STATUS DURATION #1204 main SUCCESS 2m 15s #1203 dev-feature-x SUCCESS 1m 50s #1202 hotfix/api FAILURE 45s #1201 main SUCCESS 2m 05s

💡 Casos de Uso Avanzados y Buenas Prácticas

  • Propagación de Contexto: Asegúrate de que el contexto de traza (traceparent) se propague correctamente a través de los límites del proceso. Por ejemplo, si tu pipeline despliega un servicio, el servicio debe heredar el traceparent del despliegue para que las trazas de la aplicación se conecten con las trazas del pipeline. Esto se logra generalmente inyectando headers en las llamadas o en la configuración del servicio.
  • Atributos de Recursos y Spans: Utiliza atributos ricos y estandarizados para enriquecer tus métricas, trazas y logs. Incluye IDs de commit, nombres de ramas, IDs de jobs, nombres de entornos, etc. Esto es crucial para filtrar y analizar datos.
  • Etapas Condicionales: Instrumenta las etapas condicionales de tu pipeline. Por ejemplo, si una etapa de seguridad solo se ejecuta en ciertas ramas, asegúrate de que esto se refleje en tus trazas.
  • Errores y Excepciones: Registra explícitamente los errores y excepciones como eventos en tus spans o como logs con atributos que permitan una fácil búsqueda y filtrado (span.set_status(StatusCode.ERROR)).
  • Monitorización de Dependencias: Si tu pipeline interactúa con servicios externos (registros de contenedores, repositorios de paquetes, sistemas de despliegue), instrumenta estas interacciones para identificar latencias o fallos externos.
  • Alerteos: Configura alertas en tu backend de observabilidad para condiciones críticas, como un aumento en la duración del build, una disminución en la tasa de éxito de los tests o fallos de despliegue.

Tabla de Comparación: Observabilidad sin vs. con OpenTelemetry

CaracterísticaSin OpenTelemetry (Método Tradicional)Con OpenTelemetry (Enfoque Unificado)
---------
Recopilación de DatosMúltiples herramientas, formatos y agentes (ej. logs separados, métricas separadas)Un conjunto estándar de APIs y SDKs para métricas, trazas y logs
CorrelaciónDifícil correlacionar eventos entre diferentes herramientas y componentesCorrelación automática de métricas, trazas y logs con un trace_id unificado
---------
TrazabilidadLimitada, a menudo requiere análisis manual de logsVisibilidad completa del flujo de trabajo a través de spans y traces
DiagnósticoConsume mucho tiempo, requiere cambiar entre varias herramientasRápido, con un contexto rico en un solo lugar
---------
PortabilidadAcoplamiento a herramientas específicas de un vendorVendor-agnostic, puedes cambiar el backend sin re-instrumentar
💡 Consejo: Inicia con las trazas, ya que te proporcionan la visión más directa de la secuencia de eventos y las dependencias de tu pipeline. Luego, añade métricas clave y logs estructurados.

🚀 Implementación en un Pipeline real (ejemplo GitLab CI/CD)

Vamos a ver cómo integrar esto en un archivo .gitlab-ci.yml de ejemplo. Asumimos que el OpenTelemetry Collector está accesible en otel-collector.example.com.

image: docker:latest

variables:
  DOCKER_HOST: tcp://docker:2375/
  DOCKER_TLS_CERTDIR: ""
  # Variables de OpenTelemetry
  OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.example.com:4317"
  OTEL_SERVICE_NAME: "my-app-ci-pipeline"
  OTEL_RESOURCE_ATTRIBUTES: "pipeline.name=my-app-pipeline,job.id=$CI_JOB_ID,branch=$CI_COMMIT_REF_NAME,commit.sha=$CI_COMMIT_SHA,ci.platform=gitlab"

services:
  - docker:dind

stages:
  - build
  - test
  - deploy

.opentelemetry_before_script:
  before_script:
    - apk add --no-cache curl python3 py3-pip
    - pip install opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-system_metrics
    # Otras dependencias de OTel o wrappers si se usan para ejecutar comandos
    - export OTEL_TRACER_PROVIDER="sdk"
    - export OTEL_METRIC_READER_TYPE="periodic"
    - export OTEL_METRIC_EXPORTER_TYPE="otlp"

build_job:
  stage: build
  extends: .opentelemetry_before_script
  script:
    - |-
      python3 -c "\
        from opentelemetry import trace, metrics, _logs \
        from opentelemetry.sdk.resources import Resource \
        from opentelemetry.sdk.trace import TracerProvider \
        from opentelemetry.sdk.trace.export import OTLPSpanExporter, SimpleSpanProcessor \
        from opentelemetry.sdk.metrics import MeterProvider \
        from opentelemetry.sdk.metrics.export import OTLPMetricExporter, PeriodicExportingMetricReader \
        from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter \
        from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler \
        import logging, time, os \
        \
        resource = Resource.from_env() \
        \
        # Tracer Provider \
        provider_trace = TracerProvider(resource=resource) \
        provider_trace.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter())) \
        trace.set_tracer_provider(provider_trace) \
        \
        # Meter Provider \
        reader = PeriodicExportingMetricReader(OTLPMetricExporter()) \
        provider_meter = MeterProvider(resource=resource, metric_readers=[reader]) \
        metrics.set_meter_provider(provider_meter) \
        meter = metrics.get_meter('build-stage') \
        build_duration_histogram = meter.create_histogram('build_stage_duration_seconds') \
        \
        # Logger Provider \
        logger_provider = LoggerProvider(resource=resource) \
        logger_provider.add_log_record_processor(SimpleLogRecordProcessor(OTLPLogExporter())) \
        logger = logging.getLogger('build_logger') \
        logger.setLevel(logging.INFO) \
        handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider) \
        logger.addHandler(handler) \
        \
        tracer = trace.get_tracer('build-job-tracer') \
        with tracer.start_as_current_span('build-image') as span: \
            logger.info('Starting Docker build', extra={'image_name': 'my-app', 'tag': '$CI_COMMIT_SHA'}) \
            start_time = time.time() \
            # Simulate docker build command \
            os.system('docker build -t my-app:$CI_COMMIT_SHA .') \
            if os.WEXITSTATUS(os.system_result) != 0: \
                span.set_status(trace.Status(trace.StatusCode.ERROR, description='Docker build failed')) \
                logger.error('Docker build failed', extra={'exit_code': os.WEXITSTATUS(os.system_result)}) \
                exit(1) \
            else: \
                span.set_attribute('build.result', 'success') \
            end_time = time.time() \
            duration = end_time - start_time \
            build_duration_histogram.record(duration, {'status': 'success'}) \
            logger.info('Docker build completed successfully', extra={'duration_seconds': duration}) \
      "

test_job:
  stage: test
  extends: .opentelemetry_before_script
  script:
    - |-
      python3 -c "\
        from opentelemetry import trace, metrics \
        from opentelemetry.sdk.resources import Resource \
        from opentelemetry.sdk.trace import TracerProvider \
        from opentelemetry.sdk.trace.export import OTLPSpanExporter, SimpleSpanProcessor \
        from opentelemetry.sdk.metrics import MeterProvider \
        from opentelemetry.sdk.metrics.export import OTLPMetricExporter, PeriodicExportingMetricReader \
        import time, os \
        \
        resource = Resource.from_env() \
        provider_trace = TracerProvider(resource=resource) \
        provider_trace.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter())) \
        trace.set_tracer_provider(provider_trace) \
        \
        reader = PeriodicExportingMetricReader(OTLPMetricExporter()) \
        provider_meter = MeterProvider(resource=resource, metric_readers=[reader]) \
        metrics.set_meter_provider(provider_meter) \
        meter = metrics.get_meter('test-stage') \
        test_result_counter = meter.create_counter('test_job_result_total') \
        \
        tracer = trace.get_tracer('test-job-tracer') \
        with tracer.start_as_current_span('run-unit-tests') as span: \
            print('Running unit tests...') \
            start_time = time.time() \
            # Simulate running tests (e.g., pytest, jest) \
            test_success = os.system('docker run --rm my-app:$CI_COMMIT_SHA /app/run_tests.sh') \
            if test_success != 0: \
                span.set_status(trace.Status(trace.StatusCode.ERROR, description='Unit tests failed')) \
                test_result_counter.add(1, {'status': 'failure'}) \
                exit(1) \
            else: \
                span.set_attribute('test.result', 'success') \
                test_result_counter.add(1, {'status': 'success'}) \
            print('Unit tests completed.') \
            end_time = time.time() \
            span.set_attribute('test.duration_seconds', end_time - start_time) \
      "

deploy_job:
  stage: deploy
  extends: .opentelemetry_before_script
  script:
    - |-
      python3 -c "\
        from opentelemetry import trace, metrics \
        from opentelemetry.sdk.resources import Resource \
        from opentelemetry.sdk.trace import TracerProvider \
        from opentelemetry.sdk.trace.export import OTLPSpanExporter, SimpleSpanProcessor \
        from opentelemetry.sdk.metrics import MeterProvider \
        from opentelemetry.sdk.metrics.export import OTLPMetricExporter, PeriodicExportingMetricReader \
        import time, os \
        \
        resource = Resource.from_env() \
        provider_trace = TracerProvider(resource=resource) \
        provider_trace.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter())) \
        trace.set_tracer_provider(provider_trace) \
        \
        reader = PeriodicExportingMetricReader(OTLPMetricExporter()) \
        provider_meter = MeterProvider(resource=resource, metric_readers=[reader]) \
        metrics.set_meter_provider(provider_meter) \
        meter = metrics.get_meter('deploy-stage') \
        deploy_status_counter = meter.create_counter('deploy_job_status_total') \
        \
        tracer = trace.get_tracer('deploy-job-tracer') \
        with tracer.start_as_current_span('deploy-to-staging') as span: \
            print('Deploying to staging environment...') \
            start_time = time.time() \
            # Simulate deployment to a Kubernetes cluster or similar \
            deploy_success = os.system('kubectl apply -f k8s/deployment.yaml') \
            if deploy_success != 0: \
                span.set_status(trace.Status(trace.StatusCode.ERROR, description='Deployment to staging failed')) \
                deploy_status_counter.add(1, {'status': 'failure', 'env': 'staging'}) \
                exit(1) \
            else: \
                span.set_attribute('deploy.env', 'staging') \
                span.set_attribute('deploy.result', 'success') \
                deploy_status_counter.add(1, {'status': 'success', 'env': 'staging'}) \
            print('Deployment to staging successful.') \
            end_time = time.time() \
            span.set_attribute('deploy.duration_seconds', end_time - start_time) \
      "

Este ejemplo de GitLab CI/CD utiliza scripts Python en línea para instrumentar cada etapa del pipeline. Cada script:

  1. Inicializa los Provider de OpenTelemetry para trazas, métricas y logs, utilizando las variables de entorno para la configuración del Collector.
  2. Crea spans para las operaciones principales (ej. build-image, run-unit-tests, deploy-to-staging).
  3. Registra logs estructurados con información contextual.
  4. Actualiza métricas (contadores para estados, histogramas para duraciones).
  5. Maneja los errores configurando el estado del span a ERROR y saliendo con un código de error.
📌 Nota: Los scripts Python in-line son para demostración. En un caso real, podrías tener un script Python separado o una herramienta de shell pre-construida para tu orquestador de CI/CD que maneje la instrumentación de OTel de manera más limpia.

✅ Conclusión

La integración de OpenTelemetry en tus pipelines CI/CD transforma la forma en que monitoreas y gestionas tus procesos de desarrollo y despliegue. Al unificar métricas, logs y trazas, obtienes una observabilidad sin precedentes, lo que te permite:

  • Identificar rápidamente cuellos de botella y fallos.
  • Optimizar los tiempos de ejecución del pipeline.
  • Mejorar la fiabilidad y la calidad de tus entregas.
  • Facilitar la colaboración entre equipos de desarrollo y operaciones.

Empieza por instrumentar las etapas más críticas de tu pipeline y expande gradualmente. La inversión en observabilidad con OpenTelemetry se traducirá en pipelines más eficientes, menos estrés y una mayor confianza en tus procesos de entrega de software.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!