This commit is contained in:
Waylon S. Walker 2025-05-23 10:24:41 -05:00
parent 0a4b32dffe
commit 774ea44af1
9 changed files with 160 additions and 115 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
.null-ls_806023_main.py .null-ls_806023_main.py
.null-ls_378979_main.py

View file

@ -1,21 +1,41 @@
forward_loki: # export KUBECONFIG=kubeconfig.yaml
kind-up:
kind create cluster --name grafana
kind get kubeconfig --name grafana > kubeconfig.yaml
kubectl create namespace meta && kubectl create namespace prod
helm repo add grafana https://grafana.github.io/helm-charts && helm repo update
helm install loki grafana/loki --namespace meta --values loki-values.yaml
helm install kps prometheus-community/kube-prometheus-stack --namespace meta -f prometheus-values.yaml
helm install grafana grafana/grafana --values grafana-values.yml --namespace meta
helm install k8s-monitoring grafana/k8s-monitoring --values ./k8s-monitoring-values.yml -n meta --create-namespace
kubectl apply -f pyservice.yaml
kind-down:
kind delete cluster --name grafana
forward-loki:
kubectl port-forward --namespace meta svc/loki-gateway 3100:80 kubectl port-forward --namespace meta svc/loki-gateway 3100:80
forward_grafana:
forward-grafana:
#!/bin/bash #!/bin/bash
export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}") export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace meta port-forward $POD_NAME 3000 kubectl --namespace meta port-forward $POD_NAME 3000
forward_alloy_logs: forward-alloy-logs:
#!/bin/bash #!/bin/bash
export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=alloy-logs,app.kubernetes.io/instance=k8s" -o jsonpath="{.items[0].metadata.name}") export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=alloy-logs,app.kubernetes.io/instance=k8s" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace meta port-forward $POD_NAME 12345 kubectl --namespace meta port-forward $POD_NAME 12345
forward_otel_collector: forward-otel-collector:
#!/bin/bash #!/bin/bash
export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=otel-collector,app.kubernetes.io/instance=k8s" -o jsonpath="{.items[0].metadata.name}") export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=otel-collector,app.kubernetes.io/instance=k8s" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace meta port-forward $POD_NAME 4317 kubectl --namespace meta port-forward $POD_NAME 4317
get_inotify_max_user_watches: get-inotify-max-user-watches:
#!/bin/bashcat /proc/sys/fs/inotify/max_user_watches #!/bin/bashcat /proc/sys/fs/inotify/max_user_watches
export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}") export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}")

View file

@ -24,6 +24,7 @@ podLogs:
trace_id: ${body.trace_id} trace_id: ${body.trace_id}
span_id: ${body.span_id} span_id: ${body.span_id}
service_name: ${body.service_name} service_name: ${body.service_name}
app: "${kubernetes.labels.app}"
structuredMetadata: structuredMetadata:
pod: pod # Set structured metadata "pod" from label "pod" pod: pod # Set structured metadata "pod" from label "pod"
namespaces: [] namespaces: []
@ -36,8 +37,8 @@ nodeExporter:
enabled: true enabled: true
kube-state-metrics: kube-state-metrics:
enabled: true enabled: true
nodeLogs: # nodeLogs:
enabled: true # enabled: true
kubeletMetrics: kubeletMetrics:
enabled: true enabled: true
cAdvisor: cAdvisor:

20
kubeconfig.yaml Normal file
View file

@ -0,0 +1,20 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJYXAwZDdtK0k2bVV3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBMU1qSXlNREk1TkRGYUZ3MHpOVEExTWpBeU1ETTBOREZhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURENXVQVnJEVVFuQkd4bGtKYmdSWm1Xc21yWDgwYlR1R3RIOXZsWFNaTmd3L3NFZy9uK1FXRncrd3oKbGFlbytWNnpFQStjSkVvMVFyVm5kTUFUSjdjUkxra0oyenVpM2pCMVpTRm5lcjZKMXVpRmcyYmN0ZjJ0R29jQQpZbGx1KzVqdmlOamNmcHZhcUFqaFR2SzdzVE1nc1pYTHFlQ3I5ZGoybnVUWG12QlhEVHFtSmJ2MDAyaVJ5UEV6ClNPUENYOG13QUxuVFlteVgyRVlDbU00K3NqNzhQWVoyZ3FqV1VtZGd5Mnl1WjczOGNtc0tEeVNXZlRmcXhEMS8KamRoN2pWbUtwZXZaM1MrVnhkaHdKR0lSbm1oK3NMd3NCQjFhamNwZWY4QVp2U3hwdlJ0YWxIeVgxTCs5SmVJegpKSHptTVJ3U21SRGRnQUZucFIyR3I1YitjQXdIQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTd2l0dDRPM2gydGd3L1hFUi8zZ1BXbjRMczd6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQmxhaVNGckttUQpGWFIvQWhKUUZjY3U3L2N3by9HMCtZT0xDbElsZ2tGaHNqMVlhUERkci9FTWxVeWpwMkNNaGYrTjBYUXJyM0wvCnNnMCt1R1J2dzV6VDI0VzhYbXBURjhiTGdtMlo3azMrMWdyVzlVUHVKczVVQzJURWJYWnhXZUtnRkl2Mm56OGoKUWMwVGdqaUtNLzNMaTdlWWQ4QUNZbzlyNnMvMUJXNVlxR01VOWt5ZktlTE1wQy8wMHlPRWJBRHRHRzVEeDFtbApweko2bURwQml5WERNQjE1VUJyVW96SllxUys5Mk1VcUM1WXZHTXpKTm1UMzhpcWY4TzlxMzVNWUJvSWI3WjM1CjM4S0h3MGgwZFhocnB5by8yYW1JRSs0a0lXc2hPWEl2Q0RrVCsyWDVUd1o0RkFmU1htazlNeC9jS1l1VkJ0Y0oKMld3VVFtYXdiWUFiCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://127.0.0.1:42441
name: kind-grafana
contexts:
- context:
cluster: kind-grafana
user: kind-grafana
name: kind-grafana
current-context: kind-grafana
kind: Config
preferences: {}
users:
- name: kind-grafana
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJRExoYllYczFrcGt3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBMU1qSXlNREk1TkRGYUZ3MHlOakExTWpJeU1ETTBOREZhTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETVBKZmMKVjY0MElNRXYzVFM5R3pkTS9zMkgvTGEwcWRXdDNlOHY0TlRRUTBKN3EzbldldlpMYmp2TXVJNDQ3czRxRGY3Qgo1VjVpMFY1UUZIREVXWHFOM2h5Mnc0dG1zVVUrQ3R0UCt4QWp0RFlDaC9pT0NrUFA3UEJ1eG5ndm82R2Qyczl3CnF4emd0ZW56UHZ1ekdsMzdaSDN2L0t2bDVoMWVoMXo4OHh5RjNFZUNmN0RGUW44aFFMSStNOUt1d0ZLNXdqcUUKeGRXWlRrbWg4WElIVy8vQVRybHhEZjAra3ZuaUgzNFVVSGZxSnhCUHpDYjNsQk9WMmRpOTlOdWdvcEp2QTZxdQorcXRXRGhqTFR5MFF5WWdYcmE0ZjFVYUpGVGZqS2lXTy9VWm1FcjE4bEd1THd6TVQrRUp6SitSUERRVlo0YVA5CkZNRDQzNE96MmQxMGJjZWpBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkxDSzIzZzdlSGEyREQ5YwpSSC9lQTlhZmd1enZNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJSNWZjNVEzNEtiZmZLclVyU3lNMng1STh2CnNUdDhYeTdsOTFoeHd4N1RObXk3ZkUrdUdHSzRWVDlMNnZ4dUw4UHRLbXZNSUtpM0pSbUtTYXVRZXJMeW16bGEKNE42eTQzbStJdTdxRjRwbG1RMUlNL1kzY1JFQTBhcXhwOVhUUVBjRDROeGxFNy81MEhTZ3NZeitNWXVYQUliMgpGYi8xWmRQMkUrWElGam9JbVFtNE1lNkV2ZjlkWW1mdFN1Sm1qOVVtM3h5ZG1jelRsbkdEK051UDZ2T01IS2o5CmJUUlBUUEVCUk5pMGJzck5ycXBLUkQzUno1UWZqcUVaUE9FNVFMTThBRUUzTkQ1SHQrUnVUcmZ3a2lGdlo2SVEKZk42eFFtOWp4VFpRSEx1WFUvZy9UdzFOajlrZDJqSm1HOGFBRkR4OEw0MFJMSXFqUmNtU1F3SS9MOVcvCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBekR5WDNGZXVOQ0RCTDkwMHZSczNUUDdOaC95MnRLblZyZDN2TCtEVTBFTkNlNnQ1CjFucjJTMjQ3ekxpT09PN09LZzMrd2VWZVl0RmVVQlJ3eEZsNmpkNGN0c09MWnJGRlBncmJUL3NRSTdRMkFvZjQKamdwRHorendic1o0TDZPaG5kclBjS3NjNExYcDh6NzdzeHBkKzJSOTcveXI1ZVlkWG9kYy9QTWNoZHhIZ24rdwp4VUovSVVDeVBqUFNyc0JTdWNJNmhNWFZtVTVKb2ZGeUIxdi93RTY1Y1EzOVBwTDU0aDkrRkZCMzZpY1FUOHdtCjk1UVRsZG5ZdmZUYm9LS1Nid09xcnZxclZnNFl5MDh0RU1tSUY2MnVIOVZHaVJVMzR5b2xqdjFHWmhLOWZKUnIKaThNekUvaENjeWZrVHcwRldlR2ovUlRBK04rRHM5bmRkRzNIb3dJREFRQUJBb0lCQUVia0VXSXVIUTJEQVF0NApJTUl6SERMaGpyM1EvaW9mZVVmc2JRV1ZhTWtSVDVaVm91akxyWW5wTFdDVi91Zk1IRXVFcUJUdFpLR3dRcWhSCk1BTTNlODZhZGlVS0l1ejJReTZSM2lZWTR6VkJiQzNjdkcyeEtuQ1ZzYThCdk4zc1VrRFVub1JoSUpqdkM5R2UKM3diLzgvYzZubXdhckNBVWk4VXYveHJkMTl5dVJuYnRsV1F5UmpPTVh0RHl4bnlIVy8vZnViZVl4dUhBdU9VQgpFZjFOaHc5SXhZaFh1WlJoYTZoUHM1M1FZaS9RSnNkb2w0TmFHcU1LbmkybUZOTGQxUXAvNXdvNmhKanYzeHBZCkdGc3psSVVvbExpMmRWV0pXVEl3UUJiMjlwc1dxN0pVY0FKWGc5NmhSNU5qNHF1cW5kSDY1bURxdDFvL3hrSmUKOUhwWi9VRUNnWUVBNDlyUExRSS9xZEgybjJzK1FKY1ovWFJ6MWpuRjNRNGc2NS8va0EvZlZMODVGRHB6QmozWgp4T2lYRXVqelN3N3NDcGpucmhsRXl4cXFnZU1yakJxL3VhcWRCS1BjTERaNXhLZURLMlVZeXk4MmVFblFZKzFvCm5XVmk0UkZUblpkUXFiY3lXejNrZFo1ZkI0UHQySmhoQTVyS2RRNlEwcHMvdkdId2poTWhXUHNDZ1lFQTVYYncKRW52dnJNYVlVL0lpLzBMdTc2NDlPU0dhWTZhWWhKeHAvbTg3Yis5OFVMVXNWMngwbW8zUnY3QytSYjlYQ1pjMQpndHpDMEduWERSMzlvcHdqZlQrQm1HRkNuSjFCZ0M4OVhtak1nK2tzYTFnZkd4b1hUWnhxMDRNZXhVZHR4eVR5CndxckpTYjh2WUJCMExvOW5oYms3QXQwM29MUStJcTBaRlhuazIza0NnWUI3TUFsaVpCelhTMVR5eTZCVWUxenAKMHRQdHRqNXJUUUF2WThsZnNiVWt0RjIvdWZvR0hkcG13dmtxbUJjeE5WZ1lRcUsvVlpvRDFON3FhazlZNS8xUAovQlg0TkQ4TkNFYTlNM09QT3BFMUNNbUNMeVlqWUc5MjZTR1VYVEcvdWRjNmFua25LMGNnOEFhZ29Zc3QxdlJjClpvdWV6Y2t1bEJEWllIb1YxZkhwa1FLQmdRREtWd3p2WDdaREJvUkFVZDRtZFNFNDNNNUQyS3ZKZjVneUo3TVgKbDRJei9Gd0UxeDJZb1p4WXhRSFdKTVpEdnF2RFcwRG1la0NYZ3gwTkJnc29Ic0wwcU5GZ1N6TnY0d05sUTBLOApRM0ZFU0pMUXZVNEFtZ3MrZHRXRVdiVUNoUy82VVV4MytCMnpHQkZ3aGxITTFNdVdrWFhGMnNnNHYzZWpJRHhrClFhNWJFUUtCZ1FDK05Hbk5vU2lUQTBqUG9XVERwQ3BOekg5OEtJUFBYMUl4STgvWEw1TXJ2ZTNiQndMVDVoeFAKTkh3RHpRL2ZQQUtHL3B4V1cxRmNyVVIrTVFPZlNLbVA0WkVOVVY0NndJek14WlhoR3ppSTNaTktkVzlROXczUworaVcxYnBYMmhzVGU0V3pmZEdnclBzNHdSc1pxNlRGdHJacFdwYzFGRXVXUnROdFNEbmdXN2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=

26
logspammer.yaml Normal file
View file

@ -0,0 +1,26 @@
apiVersion: v1
kind: Namespace
metadata:
name: logspammer
---
apiVersion: v1
kind: Pod
metadata:
name: logspammer
namespace: prod
labels:
app: logger
service_name: logspammer-service-name
spec:
restartPolicy: Always
containers:
- name: logspammer
image: busybox:latest
command: ["/bin/sh", "-c"]
args:
- |
while true; do
echo "$(date -Is) ⚙️ Hello from the Kubernetes logger pod!"
sleep 5
done
---

75
main.py
View file

@ -1,36 +1,42 @@
import time import time
import random import random
from flask import Flask from flask import Flask
from opentelemetry import trace from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
import logging import logging
from opentelemetry.trace import get_current_span from opentelemetry.trace import get_current_span
# === Resource ===
resource = Resource(attributes={"service.name": "my-flask-app"})
# Configure tracing # === Tracing ===
trace.set_tracer_provider( trace.set_tracer_provider(TracerProvider(resource=resource))
TracerProvider(resource=Resource.create({"service.name": "my-flask-app"}))
)
tracer = trace.get_tracer(__name__) tracer = trace.get_tracer(__name__)
span_processor = BatchSpanProcessor(OTLPSpanExporter()) # trace_exporter = OTLPSpanExporter(
trace.get_tracer_provider().add_span_processor(span_processor) # endpoint="http://otel-collector.meta.svc:4318/v1/traces"
# )
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(trace_exporter))
# === Metrics ===
reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint="http://otel-collector.meta.svc:4318/v1/metrics")
)
provider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(provider)
# Setup logging with trace_id # === Logging with trace ID ===
class TraceIdFilter(logging.Filter): class TraceIdFilter(logging.Filter):
def filter(self, record): def filter(self, record):
span = trace.get_current_span() span = trace.get_current_span()
ctx = span.get_span_context() ctx = span.get_span_context()
if ctx.trace_id != 0: record.trace_id = format(ctx.trace_id, "032x") if ctx.trace_id != 0 else None
record.trace_id = format(ctx.trace_id, "032x") record.span_id = format(ctx.span_id, "016x") if ctx.span_id != 0 else None
record.span_id = format(ctx.span_id, "016x")
else:
record.trace_id = None
record.span_id = None
return True return True
@ -45,43 +51,22 @@ logger = logging.getLogger("myapp")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
logger.addHandler(handler) logger.addHandler(handler)
# Setup Tracer # === Flask App ===
resource = Resource(
attributes={
"service.name": "python-otel-demo-app", # 👈 choose any name you like
}
)
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)
# Configure OTLP HTTP exporter
otlp_exporter = OTLPSpanExporter(
endpoint="http://otel-collector.meta.svc:4318/v1/traces",
# insecure=True,
)
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
# Flask App
app = Flask(__name__) app = Flask(__name__)
FlaskInstrumentor().instrument_app(app) # FlaskInstrumentor().instrument_app(app) # auto-instruments metrics + traces
def get_trace_id():
span = get_current_span()
if span:
return span.get_span_context().trace_id
return None
@app.route("/") @app.route("/")
def root(): def root():
with tracer.start_as_current_span("handle_homepage"): with tracer.start_as_current_span("handle_homepage"):
logger.info("handling request", extra={"trace_id": get_trace_id()}) logger.info(
"handling request",
extra={"trace_id": get_current_span().get_span_context().trace_id},
)
time.sleep(random.random() * 0.05) # simulate request parsing time.sleep(random.random() * 0.05) # simulate request parsing
fetch_user() fetch_user()
calculate_recommendations() calculate_recommendations()
return "Hello from / with traces!" return "Hello from / with traces and metrics!"
def fetch_user(): def fetch_user():

View file

@ -15,11 +15,18 @@ spec:
spec: spec:
containers: containers:
- name: otel-collector - name: otel-collector
image: otel/opentelemetry-collector-contrib:0.81.0 image: otel/opentelemetry-collector:latest
args: ["--config=/etc/otel/otel-collector-config.yaml"] args: ["--config=/etc/otel/otel-collector-config.yaml"]
volumeMounts: volumeMounts:
- name: otel-config - name: otel-config
mountPath: /etc/otel mountPath: /etc/otel
ports:
- name: prom
containerPort: 8889
- name: grpc
containerPort: 4317
- name: http
containerPort: 4318
volumes: volumes:
- name: otel-config - name: otel-config
configMap: configMap:
@ -40,19 +47,12 @@ data:
http: http:
endpoint: 0.0.0.0:4318 endpoint: 0.0.0.0:4318
processors:
spanmetrics:
dimensions:
- name: operation
default: unknown
metrics_exporter: prometheus
aggregation_temporality: "AGGREGATION_TEMPORALITY_CUMULATIVE"
exporters: exporters:
otlp: otlp:
endpoint: tempo:4317 endpoint: tempo:4317
tls: tls:
insecure: true insecure: true
prometheus: prometheus:
endpoint: "0.0.0.0:8889" endpoint: "0.0.0.0:8889"
@ -60,18 +60,20 @@ data:
pipelines: pipelines:
traces: traces:
receivers: [otlp] receivers: [otlp]
processors: [spanmetrics]
exporters: [otlp] exporters: [otlp]
metrics: metrics:
receivers: [] receivers: [otlp]
exporters: [prometheus] exporters: [prometheus]
processors: []
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: otel-collector name: otel-collector
namespace: meta namespace: meta
labels:
release: kps # this MUST match the `release:` label used by your Prometheus install
app: otel-collector
spec: spec:
ports: ports:
- name: grpc - name: grpc
@ -80,5 +82,23 @@ spec:
- name: http - name: http
port: 4318 port: 4318
targetPort: 4318 targetPort: 4318
- name: prom
port: 8889
targetPort: 8889
selector: selector:
app: otel-collector app: otel-collector
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: otel-collector
namespace: meta
labels:
release: kps # again, must match Prometheus's release label
spec:
selector:
matchLabels:
app: otel-collector
endpoints:
- port: prom
interval: 15s

View file

@ -2,49 +2,42 @@ apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: my-python-app name: my-python-app
namespace: meta namespace: prod
data: data:
main.py: | main.py: |
import time import time
import random import random
from flask import Flask from flask import Flask
from opentelemetry import trace from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.instrumentation.flask import FlaskInstrumentor
import logging import logging
from opentelemetry.trace import get_current_span from opentelemetry.trace import get_current_span
SERVICE_NAME = "my-flask-app" # match your OTEL resource name
# === Resource ===
resource = Resource(attributes={"service.name": "my-flask-app"})
# === Tracing ===
trace.set_tracer_provider( trace.set_tracer_provider(TracerProvider(resource=resource))
TracerProvider(resource=Resource.create({"service.name": "my-flask-app"}))
)
tracer = trace.get_tracer(__name__) tracer = trace.get_tracer(__name__)
span_processor = BatchSpanProcessor(OTLPSpanExporter())
trace.get_tracer_provider().add_span_processor(span_processor)
# === Logging with trace ID ===
class TraceIdFilter(logging.Filter): class TraceIdFilter(logging.Filter):
def filter(self, record): def filter(self, record):
span = trace.get_current_span() span = trace.get_current_span()
ctx = span.get_span_context() ctx = span.get_span_context()
if ctx.trace_id != 0: record.trace_id = format(ctx.trace_id, "032x") if ctx.trace_id != 0 else None
record.trace_id = format(ctx.trace_id, "032x") record.span_id = format(ctx.span_id, "016x") if ctx.span_id != 0 else None
record.span_id = format(ctx.span_id, "016x")
record.service_name = SERVICE_NAME
else:
record.trace_id = None
record.span_id = None
return True return True
formatter = logging.Formatter( formatter = logging.Formatter(
'{"message": "%(message)s", "trace_id": "%(trace_id)s", "span_id": "%(span_id)s", "service_name": "my-flask-app"}' '{"message": "%(message)s", "trace_id": "%(trace_id)s", "span_id": "%(span_id)s"}'
) )
handler = logging.StreamHandler() handler = logging.StreamHandler()
handler.setFormatter(formatter) handler.setFormatter(formatter)
@ -54,43 +47,22 @@ data:
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
logger.addHandler(handler) logger.addHandler(handler)
# === Flask App ===
resource = Resource(
attributes={
"service.name": "python-otel-demo-app", # 👈 choose any name you like
}
)
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)
otlp_exporter = OTLPSpanExporter(
endpoint="http://otel-collector.meta.svc:4318/v1/traces",
# insecure=True,
)
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
app = Flask(__name__) app = Flask(__name__)
FlaskInstrumentor().instrument_app(app) FlaskInstrumentor().instrument_app(app) # auto-instruments metrics + traces
def get_trace_id():
span = get_current_span()
if span:
return span.get_span_context().trace_id
return None
@app.route("/") @app.route("/")
def root(): def root():
with tracer.start_as_current_span("handle_homepage"): with tracer.start_as_current_span("handle_homepage"):
logger.info("handling request", extra={"trace_id": get_trace_id()}) logger.info(
"handling request",
extra={"trace_id": get_current_span().get_span_context().trace_id},
)
time.sleep(random.random() * 0.05) # simulate request parsing time.sleep(random.random() * 0.05) # simulate request parsing
fetch_user() fetch_user()
calculate_recommendations() calculate_recommendations()
return "Hello from / with traces!" return "Hello from / with traces and metrics!"
def fetch_user(): def fetch_user():
@ -112,7 +84,7 @@ apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: python-otel name: python-otel
namespace: meta namespace: prod
labels: labels:
app: python-otel app: python-otel
spec: spec:
@ -156,7 +128,7 @@ apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: python-otel name: python-otel
namespace: meta namespace: prod
spec: spec:
selector: selector:
app: python-otel app: python-otel