{"id":1825,"date":"2025-10-19T23:20:04","date_gmt":"2025-10-19T20:20:04","guid":{"rendered":"https:\/\/upcloud.com\/global\/us\/resources\/tutorials\/how-to-deploy-opentelemetry-on-upcloud-managed-kubernetes\/"},"modified":"2025-10-19T23:20:04","modified_gmt":"2025-10-19T20:20:04","slug":"how-to-deploy-opentelemetry-on-upcloud-managed-kubernetes","status":"publish","type":"tutorial","link":"https:\/\/upcloud.com\/global\/resources\/tutorials\/how-to-deploy-opentelemetry-on-upcloud-managed-kubernetes\/","title":{"rendered":"How to Deploy OpenTelemetry on UpCloud Managed Kubernetes"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">OpenTelemetry gives you a unified, open-source framework for collecting metrics, traces, and logs across your cloud-native infrastructure. In this tutorial, you\u2019ll learn how to deploy a complete OpenTelemetry pipeline on <strong><a href=\"https:\/\/upcloud.com\/global\/products\/managed-kubernetes\/\">UpCloud Managed Kubernetes<\/a><\/strong> from setting up the cluster and deploying the OpenTelemetry Collector, to instrumenting a Python application and verifying telemetry flow into your observability backend.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you want to understand the <em>why<\/em> behind OpenTelemetry before jumping into the <em>how<\/em>, check out our overview guide:<br>\ud83d\udc49 <strong><a href=\"https:\/\/upcloud.com\/global\/blog\/what-is-opentelemetry-understanding-the-standard-for-cloud-native-observability\/\">What is OpenTelemetry? Understanding the Standard for Cloud-Native Observability<\/a><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By the end of this walkthrough, you\u2019ll have a working, vendor-neutral observability setup ready for production built entirely on UpCloud\u2019s fast, reliable infrastructure.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Prerequisites<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before you begin, make sure you have the following tools and access in place:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Basic Kubernetes knowledge (pods, services, deployments).<\/li>\n\n\n\n<li>An UpCloud account with access to Managed Kubernetes.<\/li>\n\n\n\n<li>kubectl and <a href=\"https:\/\/upcloudltd.github.io\/upcloud-cli\/\" target=\"_blank\" rel=\"noopener\">upctl<\/a> installed locally.<\/li>\n\n\n\n<li>Python and Docker installed locally.<\/li>\n\n\n\n<li><em>(Optional)<\/em> Access to an observability backend (e.g., Grafana Cloud, Jaeger).<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step 1: Provision and Access Your UpCloud Managed Kubernetes Cluster<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The first step is to set up a Kubernetes cluster on UpCloud. This cluster will host both the OpenTelemetry Collector and your instrumented Python application. If you already have a cluster, you can skip to the next section. Otherwise, follow these steps to get started:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Create a Private Network<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Before creating your Kubernetes cluster, you\u2019ll need a private network for it to use. This network should have DHCP enabled so that your cluster nodes can automatically receive IP addresses. You can create the network using the <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/upcloud-command-line-interface\/\">UpCloud CLI<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">upctl network create --name \"monitoring-net\" --zone de-fra1 --ip-network address=10.0.1.0\/24,dhcp=true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This command creates a new network named monitoring-net in the de-fra1 zone, with a subnet of 10.0.1.0\/24 and DHCP enabled.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Provision the Kubernetes Cluster<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">With your network in place, you can now create the Kubernetes cluster itself. The following command provisions a new cluster named monitoring-cluster, attaches it to the network you just created, and sets up a node group with three nodes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">upctl k8s create \\\n  --name monitoring-cluster \\\n  --network monitoring-net \\\n  --zone de-fra1 \\\n  --plan \"production-small\" \\\n  --version 1.30 \\\n  --node-group name=monitoring-nodes,count=3,plan=4xCPU-8GB<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s what each flag means:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>&#8211;name: The name of your cluster.<\/li>\n\n\n\n<li>&#8211;network: The private network to attach the cluster to.<\/li>\n\n\n\n<li>&#8211;zone: The UpCloud data center region.<\/li>\n\n\n\n<li>&#8211;plan: The control plane size (choose based on your needs).<\/li>\n\n\n\n<li>&#8211;version: The Kubernetes version to deploy.<\/li>\n\n\n\n<li>&#8211;node-group: The name, count, and size of worker nodes.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Provisioning may take a few minutes. Once complete, you can now proceed with the rest of the tutorial.<em>\ud83d\udca1 <\/em><strong><em>Note:<\/em><\/strong><em> UpCloud\u2019s Managed Kubernetes isn\u2019t free, but pricing is transparent and pay-as-you-go. You can check the latest details here: <\/em><a href=\"https:\/\/upcloud.com\/global\/pricing\"><em>UpCloud Pricing<\/em><\/a><em>.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Configure kubectl for Cluster Access<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To interact with your new cluster, you\u2019ll need to configure kubectl with the appropriate credentials. You can download the kubeconfig file for the UpCloud cluster via upctl and merge it with your local configuration:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">upctl k8s config monitoring-cluster &#8211;output yaml &#8211;write ~\/.kube\/config<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This command fetches the kubeconfig for your cluster and writes it to your default kubeconfig location, allowing you to use kubectl commands as usual.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Verify Cluster Connectivity<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Before moving on, it\u2019s a good idea to verify that your cluster is up and that you can connect to it. Try listing the nodes and namespaces:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl get nodes<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl get namespaces<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you see your nodes and the default Kubernetes namespaces, you\u2019re all set!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step 2: Deploy the OpenTelemetry Collector<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With your Kubernetes cluster up and running, the next step is to deploy the OpenTelemetry Collector. The Collector acts as a central point for receiving, processing, and exporting telemetry data (traces, metrics, and logs) from your applications. You\u2019ll use the official Helm chart to simplify the deployment process.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Create a Namespace for Observability<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">It\u2019s a good practice to keep observability components separate from your application workloads. This makes it easier to manage permissions, apply resource limits, and keep things organized, especially as your cluster grows. Start by creating a dedicated namespace:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl create namespace observability<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This command creates a new namespace called observability where you\u2019ll deploy the Collector and any related resources.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Add the OpenTelemetry Helm Repository<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Helm makes it easy to install and manage Kubernetes applications. First, add the official OpenTelemetry Helm chart repository and update your local chart list:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">helm repo add open-telemetry https:\/\/open-telemetry.github.io\/opentelemetry-helm-charts<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">helm repo update<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This ensures you have access to the latest version of the OpenTelemetry Collector chart.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Prepare the Collector Configuration<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The OpenTelemetry Collector is highly configurable. You\u2019ll need a configuration file (e.g., config.yaml) that defines which receivers, processors, and exporters you want to use. For this tutorial, you can use the following configuration by saving it in a file named config.yaml:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">mode: 'deployment'\n\nimage:\n  repository: \"otel\/opentelemetry-collector-contrib\"\n\nreplicaCount: 1\n\npresets:\n  # enables the k8sclusterreceiver and adds it to the metrics pipelines\n  clusterMetrics:\n    enabled: true\n  # enables the k8sobjectsreceiver to collect events only and adds it to the logs pipelines\n  kubernetesEvents:\n    enabled: true\n\nconfig:\n  receivers:\n    otlp:\n      protocols:\n        grpc:\n          endpoint: 0.0.0.0:4317\n        http:\n          endpoint: 0.0.0.0:4318\n\n  exporters:\n    debug:\n      verbosity: detailed\n   \n\n  extensions:\n    health_check:\n      endpoint: \"0.0.0.0:13133\"  \n    \n\n  service:\n    extensions:\n      [\n        health_check\n      ]\n    pipelines:\n      traces:\n        receivers: [otlp]\n        exporters: [debug]\n      metrics:\n        receivers: [otlp]\n        exporters: [debug]\n      logs:\n        receivers: [otlp]\n        exporters: [debug]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s a quick explanation of each of the nodes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>mode: &#8216;deployment&#8217;: Tells Helm to deploy the Collector as a Kubernetes Deployment.<\/li>\n\n\n\n<li>image: Specifies the container image to use for the Collector.<\/li>\n\n\n\n<li>replicaCount: Number of Collector pods to run (usually 1 for a basic setup).<\/li>\n\n\n\n<li>presets: Enables built-in receivers for Kubernetes cluster metrics and events.<\/li>\n\n\n\n<li>config.receivers: Defines how the Collector receives telemetry data. Here, OTLP is enabled over both gRPC and HTTP.<\/li>\n\n\n\n<li>config.exporters: Specifies where the Collector sends data. The debug exporter just logs data for testing.<\/li>\n\n\n\n<li>config.extensions: Adds a health check endpoint for monitoring the Collector\u2019s health.<\/li>\n\n\n\n<li>config.service: Ties everything together, defining which receivers and exporters are used for traces, metrics, and logs pipelines.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This example uses the otel\/opentelemetry-collector-contrib image, which includes a wide range of receivers, processors, and exporters contributed by the community. This is ideal if you need integrations beyond the official core components.The other option is the otel\/opentelemetry-collector image, which contains only the officially supported, stable components. You should use the core image if you want a smaller, more stable footprint and do not require community-contributed features.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Install the Collector Using Helm<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now you\u2019re ready to deploy the Collector. Use the following command to install it in your cluster using the configuration file you saved above:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">helm install otel-collector-cluster open-telemetry\/opentelemetry-collector -n observability --values config.yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Helm will create the necessary Kubernetes resources (Deployment, Service, ConfigMap, etc.) for the Collector.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Verify the Collector Deployment<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">After a minute or two, check that the Collector pod is running using the following command:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl get pods -n observability<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You should see a pod with a name like otel-collector-cluster-&#8230; in the Running state. If it\u2019s not in the running state, use kubectl describe pod &lt;pod-name&gt; -n observability to troubleshoot.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At this point, your OpenTelemetry Collector is live and ready to receive telemetry data from your applications!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step 3: Instrument the Python Application<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now that your OpenTelemetry Collector is running, let\u2019s instrument a Python application to generate and export telemetry data (traces, metrics, and logs).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Creating the Python App<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Below is the code for a simple Flask app instrumented with OpenTelemetry:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">from flask import Flask\nimport logging\nfrom opentelemetry import trace\n\napp = Flask(__name__)\n\ntracer = trace.get_tracer_provider().get_tracer(__name__)\n\n@app.route(\"\/\")\ndef hello():\n    with tracer.start_as_current_span(\"foo\"):\n\n        current_span = trace.get_current_span()\n        current_span.add_event(\"This is a span event\")\n\n        logging.getLogger().error(\"Saying hello from the root endpoint!\")\n        return \"Hello from OpenTelemetry!\"\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=5000)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This Python application is a simple Flask web server instrumented with OpenTelemetry for tracing and logging.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It starts by importing Flask for handling HTTP requests, Python\u2019s built-in logging module, and the OpenTelemetry tracing API. The app instance is created, and a tracer is obtained from the OpenTelemetry provider. The root endpoint (\/) is defined to start a new trace span named \u201cfoo\u201d for each incoming request. Within this span, a custom event is added to provide additional context in the trace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The application also emits a log message using Python\u2019s standard logger. Thanks to OpenTelemetry\u2019s zero-code (automatic) instrumentation for logging, simply using the logger is enough for these logs to be captured and exported\u2014no extra code is needed for log integration. You can then use opentelemetry-instrument to run the app and set up auto-instrumentation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once the app is ready, you will need to containerize it. Here\u2019s the Dockerfile you will need to create the container image:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">FROM python:3.13-slim\n\nWORKDIR \/app\nCOPY . .\n\nENV OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true\n\nRUN pip install \\\n    flask \\\n    opentelemetry-instrumentation-flask \\\n    opentelemetry-distro \\\n    opentelemetry-exporter-otlp\n\nRUN opentelemetry-bootstrap --action=install\n\nCMD [\"sh\", \"-c\", \"opentelemetry-instrument --traces_exporter console,otlp  --metrics_exporter console,otlp   --logs_exporter console,otlp flask run --host=0.0.0.0\"]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This image uses the slim Python 3.13 base image. It first sets the working directory and copies the app code into it. Then, it enables auto-instrumentation for logging using the OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED environment variable. This is necessary if you are using zero-code instrumentation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next up, it installs Flask and OpenTelemetry packages for Flask, the OTel distribution, and the OTLP exporter. Then, it installs any additional dependencies required for auto-instrumentation via the <a href=\"https:\/\/opentelemetry.io\/docs\/zero-code\/python\/#:~:text=The%20opentelemetry%2Dbootstrap%20%2Da%20install%20command%20reads%20through%20the%20list%20of%20packages%20installed%20in%20your%20active%20site%2Dpackages%20folder%2C%20and%20installs%20the%20corresponding%20instrumentation%20libraries%20for%20these%20packages%2C%20if%20applicable\" target=\"_blank\" rel=\"noopener\">opentelemetry-bootstrap<\/a> command. Finally, it runs the app using opentelemetry-instrument, which automatically instruments supported libraries and exports traces, metrics, and logs to both the console and OTLP endpoints.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019ll now need to build the Docker image and push it to a container registry like Docker Hub. To build the image, run the following command, replacing &lt;your-dockerhub-username&gt; with your actual Docker Hub username:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">docker build -t &lt;your-dockerhub-username&gt;\/otel-python-app:latest .<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>If you are using an ARM architecture machine (such as a Mac with an Apple Silicon chip), you will need to use Docker Buildx to ensure that the Docker image is built for <\/em><em>linux\/amd64<\/em><em>, not ARM:<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">docker buildx build --platform linux\/amd64 -t &lt;your-dockerhub-username&gt;\/otel-python-app:latest.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once the image is built, push it to Docker Hub so it can be accessed by your Kubernetes cluster:<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">docker push &lt;your-dockerhub-username&gt;\/otel-python-app:latest<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Kubernetes Deployment and Service<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the Kubernetes manifest (python-app.yaml) to deploy your instrumented app:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: python-app\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: python-app\n  template:\n    metadata:\n      labels:\n        app: python-app\n    spec:\n      containers:\n        - name: python-app\n          image: &lt;your-docker-hub-username&gt;\/otel-python-app:latest\n          ports:\n            - containerPort: 5000\n          env:\n            - name: OTEL_SERVICE_NAME\n              value: \"python-app\"\n            - name: OTEL_EXPORTER_OTLP_PROTOCOL\n              value: \"http\/protobuf\"\n            - name: OTEL_EXPORTER_OTLP_ENDPOINT\n              value: \"http:\/\/otel-collector-cluster-opentelemetry-collector.observability.svc.cluster.local:4318\"\n            - name: OTEL_METRICS_EXPORTER\n              value: \"otlp\"\n            - name: OTEL_TRACES_EXPORTER\n              value: \"otlp\"\n            - name: OTEL_LOGS_EXPORTER\n              value: \"otlp\"\n            - name: OTEL_RESOURCE_ATTRIBUTES\n              value: \"service.name=my-app,service.namespace=default,deployment.environment=production\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: python-app\nspec:\n  selector:\n    app: python-app\n  ports:\n    - protocol: TCP\n      port: 80\n      targetPort: 5000<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Explanation:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This Kubernetes manifest defines a deployment and a service resource for the instrumented Python application.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The deployment specifies the container image and a set of environment variables that are essential for OpenTelemetry auto-instrumentation and for connecting to the OpenTelemetry Collector. These environment variables configure how the app exports telemetry data:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>OTEL_SERVICE_NAME sets the logical name for the service, which will appear in your observability backend.<\/li>\n\n\n\n<li>OTEL_EXPORTER_OTLP_PROTOCOL specifies the protocol used for exporting telemetry data; here, it is set to http\/protobuf for compatibility with the Collector.<\/li>\n\n\n\n<li>OTEL_EXPORTER_OTLP_ENDPOINT provides the URL of the OpenTelemetry Collector\u2019s OTLP HTTP endpoint, so the app knows where to send its traces, metrics, and logs.<\/li>\n\n\n\n<li>OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER, and OTEL_LOGS_EXPORTER enable exporting all three types of telemetry data (metrics, traces, and logs) using the OTLP protocol.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">OTEL_RESOURCE_ATTRIBUTES attaches additional metadata to all telemetry data, such as the service name, namespace, and deployment environment, which helps with filtering and grouping in observability tools.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apply the manifest to your cluster using:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl apply -f python-app.yaml<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This will create the deployment and service resources in the default namespace of your cluster.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step 4: Test Telemetry and Visualize Data<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With your instrumented Python application running in the cluster and the OpenTelemetry Collector deployed, it\u2019s time to confirm that telemetry data is flowing as expected.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Check the Collector Pod Logs<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The simplest way to validate that your app is sending telemetry data is to inspect the logs of the OpenTelemetry Collector pod. Since the sample configuration uses the debug exporter, all received telemetry will be printed to the Collector\u2019s logs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First, get the name of the Collector pod:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl get pods -n observability<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then, retrieve the logs for the pod (replace &lt;otel-collector-pod-name&gt; with the actual pod name):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">kubectl logs -n observability &lt;otel-collector-pod-name&gt;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can try searching for \u201cThis is a span event\u201d in the logs:<\/p>\n\n\n\n\n\n<p class=\"wp-block-paragraph\">You can also try looking up the log \u201cSaying hello from the root endpoint!\u201d:<\/p>\n\n\n\n\n\n<p class=\"wp-block-paragraph\">This confirms that you have set up the Collector and instrumented your application perfectly!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>[Optional] Step 5: Visualize in an Observability Backend<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To take things a step further, you can consider configuring the Collector to export data to an observability backend (such as Grafana Cloud, Jaeger, or another supported system) to be able to view your service\u2019s traces, metrics, and logs conveniently.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, you can <a href=\"https:\/\/grafana.com\/auth\/sign-up\/create-user\" target=\"_blank\" rel=\"noopener\">create a new Grafana Cloud account<\/a> (if you don\u2019t already have one) and follow <a href=\"https:\/\/grafana.com\/docs\/opentelemetry\/collector\/opentelemetry-collector\/\" target=\"_blank\" rel=\"noopener\">this guide<\/a> to set up your OpenTelemetry Collector as a data source.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s a lean OpenTelemetry Helm Chart values file to help you get started quickly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">mode: 'deployment'\n\nimage:\n  repository: \"otel\/opentelemetry-collector-contrib\"\n\nreplicaCount: 1\n\npresets:\n  # enables the k8sclusterreceiver and adds it to the metrics pipelines\n  clusterMetrics:\n    enabled: true\n  # enables the k8sobjectsreceiver to collect events only and adds it to the logs pipelines\n  kubernetesEvents:\n    enabled: true\n\nconfig:\n  receivers:\n    otlp:\n      protocols:\n        grpc:\n          endpoint: 0.0.0.0:4317\n        http:\n          endpoint: 0.0.0.0:4318\n\n  exporters:\n    debug:\n      verbosity: detailed\n    otlphttp\/grafana_cloud:\n      endpoint: \"[GRAFANA_CLOUD_OTLP_ENDPOINT]\"\n      auth:\n        authenticator: basicauth\/grafana_cloud\n\n  extensions:\n    health_check:\n      endpoint: \"0.0.0.0:13133\"  \n    basicauth\/grafana_cloud:\n      client_auth:\n        username: \"[GRAFANA_CLOUD_INSTANCE_ID]\"\n        password: \"[GRAFANA_CLOUD_API_KEY]\"\n\n  connectors:\n    grafanacloud:\n      host_identifiers: [\"host.name\"]\n\n  service:\n    extensions:\n      [\n        basicauth\/grafana_cloud,\n        health_check\n      ]\n    pipelines:\n      traces:\n        receivers: [otlp]\n        exporters: [debug, otlphttp\/grafana_cloud, grafanacloud]\n      metrics:\n        receivers: [otlp]\n        exporters: [debug, otlphttp\/grafana_cloud]\n      metrics\/grafanacloud:\n        receivers: [grafanacloud]\n        exporters: [otlphttp\/grafana_cloud]\n      logs:\n        receivers: [otlp]\n        exporters: [debug, otlphttp\/grafana_cloud]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In this configuration file, exporters are configured to forward telemetry to Grafana Cloud using OTLP over HTTP, with authentication handled by a basic auth extension. Additionally, a health check extension exposes a status endpoint for monitoring the Collector itself. Finally, the file defines pipelines for traces, metrics, and logs so that all three signal types are captured and exported appropriately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Just make sure to replace GRAFANA_CLOUD_OTLP_ENDPOINT, GRAFANA_CLOUD_INSTANCE_ID, and GRAFANA_CLOUD_API_KEY with their correct values from your Grafana Cloud account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once the OpenTelemetry Collector installation is updated, you can try generating new logs and traces by sending GET requests to your app and wait for 2-3 minutes for Grafana Cloud to receive the telemetry data from your Collector. Then, you should be able to search for your service name (e.g., python-app) and see the telemetry data generated by your application.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-263.png\" alt=\"-\" class=\"wp-image-66689\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In the traces tab, you can see individual traces generated by your app along with its associated events:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-264.png\" alt=\"-\" class=\"wp-image-66690\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You can also explore the logs associated with each trace in detail:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-265.png\" alt=\"-\" class=\"wp-image-66692\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">If you don\u2019t see telemetry data, you can try out a few things, such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Double-check the environment variables in your deployment manifest.<\/li>\n\n\n\n<li>Ensure the Collector pod is running and healthy.<\/li>\n\n\n\n<li>Look for errors in the Collector and application pod logs.<\/li>\n\n\n\n<li>Verify network connectivity between your app and the Collector (the OTLP endpoint).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Once you\u2019ve confirmed that telemetry is flowing, you\u2019ve successfully set up end-to-end observability for your Python app on UpCloud Kubernetes!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In this tutorial, you set up a full OpenTelemetry observability pipeline on UpCloud Managed Kubernetes. You provisioned a Kubernetes cluster, deployed the OpenTelemetry Collector, instrumented a Python application with support for traces, metrics, and logs, and verified that telemetry data was flowing correctly. You also learned how to preview this data using the debug exporter or a connected observability backend like Grafana Cloud.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By default, the OpenTelemetry Collector does not retain telemetry data. It simply receives, processes, and forwards it. When using the debug exporter, data is only visible in the Collector\u2019s logs and disappears after processing. Similarly, Grafana Cloud provides short-term retention: trace data is typically retained for 3 days on the free tier, while metrics are kept for 14 days, and logs vary based on plan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For many teams, especially those operating in production environments, this short-term visibility is not enough. Long-term storage is essential for historical analysis, capacity planning, compliance, and post-incident reviews.That\u2019s exactly what Part 2 of this series will cover: how to integrate <a href=\"https:\/\/thanos.io\/\" target=\"_blank\" rel=\"noopener\">Thanos<\/a> with your OpenTelemetry pipeline to enable cost-effective long-term metrics storage and scalable querying. Stay tuned!<\/p>\n","protected":false},"author":19,"featured_media":0,"comment_status":"open","ping_status":"closed","template":"","community-category":[223,229,238],"class_list":["post-1825","tutorial","type-tutorial","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1825","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial"}],"about":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/types\/tutorial"}],"author":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/users\/19"}],"replies":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/comments?post=1825"}],"version-history":[{"count":0,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1825\/revisions"}],"wp:attachment":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/media?parent=1825"}],"wp:term":[{"taxonomy":"community-category","embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/community-category?post=1825"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}