254 lines
7.4 KiB
YAML
254 lines
7.4 KiB
YAML
---
|
|
# Example instrumented application to test the observability stack
|
|
# This is a simple Python Flask app with OpenTelemetry instrumentation
|
|
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: demo-app
|
|
namespace: observability
|
|
data:
|
|
app.py: |
|
|
from flask import Flask, jsonify
|
|
import logging
|
|
import json
|
|
import time
|
|
import random
|
|
|
|
# OpenTelemetry imports
|
|
from opentelemetry import trace, metrics
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
from opentelemetry.sdk.metrics import MeterProvider
|
|
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
|
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
|
|
from opentelemetry.instrumentation.flask import FlaskInstrumentor
|
|
from opentelemetry.sdk.resources import Resource
|
|
from prometheus_flask_exporter import PrometheusMetrics
|
|
|
|
# Configure structured logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(message)s'
|
|
)
|
|
|
|
class JSONFormatter(logging.Formatter):
|
|
def format(self, record):
|
|
log_obj = {
|
|
'timestamp': self.formatTime(record, self.datefmt),
|
|
'level': record.levelname,
|
|
'message': record.getMessage(),
|
|
'logger': record.name,
|
|
}
|
|
if hasattr(record, 'trace_id'):
|
|
log_obj['trace_id'] = record.trace_id
|
|
log_obj['span_id'] = record.span_id
|
|
return json.dumps(log_obj)
|
|
|
|
handler = logging.StreamHandler()
|
|
handler.setFormatter(JSONFormatter())
|
|
logger = logging.getLogger(__name__)
|
|
logger.addHandler(handler)
|
|
logger.setLevel(logging.INFO)
|
|
|
|
# Configure OpenTelemetry
|
|
resource = Resource.create({"service.name": "demo-app"})
|
|
|
|
# Tracing
|
|
trace_provider = TracerProvider(resource=resource)
|
|
trace_provider.add_span_processor(
|
|
BatchSpanProcessor(
|
|
OTLPSpanExporter(
|
|
endpoint="http://tempo.observability.svc.cluster.local:4317",
|
|
insecure=True
|
|
)
|
|
)
|
|
)
|
|
trace.set_tracer_provider(trace_provider)
|
|
tracer = trace.get_tracer(__name__)
|
|
|
|
# Create Flask app
|
|
app = Flask(__name__)
|
|
|
|
# Prometheus metrics
|
|
metrics = PrometheusMetrics(app)
|
|
|
|
# Auto-instrument Flask
|
|
FlaskInstrumentor().instrument_app(app)
|
|
|
|
# Sample data
|
|
ITEMS = ["apple", "banana", "orange", "grape", "mango"]
|
|
|
|
@app.route('/')
|
|
def index():
|
|
span = trace.get_current_span()
|
|
trace_id = format(span.get_span_context().trace_id, '032x')
|
|
|
|
logger.info("Index page accessed", extra={
|
|
'trace_id': trace_id,
|
|
'endpoint': '/'
|
|
})
|
|
|
|
return jsonify({
|
|
'service': 'demo-app',
|
|
'status': 'healthy',
|
|
'trace_id': trace_id
|
|
})
|
|
|
|
@app.route('/items')
|
|
def get_items():
|
|
with tracer.start_as_current_span("fetch_items") as span:
|
|
# Simulate database query
|
|
time.sleep(random.uniform(0.01, 0.1))
|
|
|
|
span.set_attribute("items.count", len(ITEMS))
|
|
trace_id = format(span.get_span_context().trace_id, '032x')
|
|
|
|
logger.info("Items fetched", extra={
|
|
'trace_id': trace_id,
|
|
'count': len(ITEMS)
|
|
})
|
|
|
|
return jsonify({
|
|
'items': ITEMS,
|
|
'count': len(ITEMS),
|
|
'trace_id': trace_id
|
|
})
|
|
|
|
@app.route('/item/<int:item_id>')
|
|
def get_item(item_id):
|
|
with tracer.start_as_current_span("fetch_item") as span:
|
|
span.set_attribute("item.id", item_id)
|
|
trace_id = format(span.get_span_context().trace_id, '032x')
|
|
|
|
# Simulate processing
|
|
time.sleep(random.uniform(0.01, 0.05))
|
|
|
|
if item_id < 0 or item_id >= len(ITEMS):
|
|
logger.warning("Item not found", extra={
|
|
'trace_id': trace_id,
|
|
'item_id': item_id
|
|
})
|
|
return jsonify({'error': 'Item not found', 'trace_id': trace_id}), 404
|
|
|
|
item = ITEMS[item_id]
|
|
logger.info("Item fetched", extra={
|
|
'trace_id': trace_id,
|
|
'item_id': item_id,
|
|
'item': item
|
|
})
|
|
|
|
return jsonify({
|
|
'id': item_id,
|
|
'name': item,
|
|
'trace_id': trace_id
|
|
})
|
|
|
|
@app.route('/slow')
|
|
def slow_endpoint():
|
|
with tracer.start_as_current_span("slow_operation") as span:
|
|
trace_id = format(span.get_span_context().trace_id, '032x')
|
|
|
|
logger.info("Slow operation started", extra={'trace_id': trace_id})
|
|
|
|
# Simulate slow operation
|
|
time.sleep(random.uniform(1, 3))
|
|
|
|
logger.info("Slow operation completed", extra={'trace_id': trace_id})
|
|
|
|
return jsonify({
|
|
'message': 'Operation completed',
|
|
'trace_id': trace_id
|
|
})
|
|
|
|
@app.route('/error')
|
|
def error_endpoint():
|
|
with tracer.start_as_current_span("error_operation") as span:
|
|
trace_id = format(span.get_span_context().trace_id, '032x')
|
|
|
|
logger.error("Intentional error triggered", extra={'trace_id': trace_id})
|
|
span.set_attribute("error", True)
|
|
|
|
return jsonify({
|
|
'error': 'This is an intentional error',
|
|
'trace_id': trace_id
|
|
}), 500
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=8080)
|
|
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: demo-app
|
|
namespace: observability
|
|
labels:
|
|
app: demo-app
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: demo-app
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: demo-app
|
|
annotations:
|
|
prometheus.io/scrape: "true"
|
|
prometheus.io/port: "8080"
|
|
prometheus.io/path: "/metrics"
|
|
spec:
|
|
containers:
|
|
- name: demo-app
|
|
image: python:3.11-slim
|
|
command:
|
|
- /bin/bash
|
|
- -c
|
|
- |
|
|
pip install flask opentelemetry-api opentelemetry-sdk \
|
|
opentelemetry-instrumentation-flask \
|
|
opentelemetry-exporter-otlp-proto-grpc \
|
|
prometheus-flask-exporter && \
|
|
python /app/app.py
|
|
ports:
|
|
- name: http
|
|
containerPort: 8080
|
|
volumeMounts:
|
|
- name: app-code
|
|
mountPath: /app
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
cpu: 500m
|
|
memory: 512Mi
|
|
volumes:
|
|
- name: app-code
|
|
configMap:
|
|
name: demo-app
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: demo-app
|
|
namespace: observability
|
|
labels:
|
|
app: demo-app
|
|
annotations:
|
|
prometheus.io/scrape: "true"
|
|
prometheus.io/port: "8080"
|
|
prometheus.io/path: "/metrics"
|
|
spec:
|
|
type: ClusterIP
|
|
ports:
|
|
- port: 8080
|
|
targetPort: http
|
|
protocol: TCP
|
|
name: http
|
|
selector:
|
|
app: demo-app
|