学习像专业人士一样监控你的 Python 应用程序!🧙‍♂️🪄

2025-06-07

学习像专业人士一样监控你的 Python 应用程序!🧙‍♂️🪄

TL;DR

在这个易于理解的教程中,您将了解如何使用分布式跟踪来监视 Python 应用程序。

你将学到:✨

  • 如何在 Python 中构建微服务🐍。
  • 为微服务设置 Docker 容器📦。
  • 配置 Kubernetes 来管理微服务。
  • 集成跟踪后端以实现跟踪可视化🕵️‍♂️。

准备好提升你的 Python 应用程序监控技能了吗?🔥

火 GIF


设置说明

🚨 在博客的这一部分,我们将构建一个虚拟的 Python 微服务应用程序。如果您已经有一个应用程序并且正在按照步骤操作,请跳过此部分。

为您的应用程序创建初始文件夹结构,如下所示。👇

mkdir python-microservices && cd python-microservices
mkdir src && cd src
mkdir microservice1 microservice2
Enter fullscreen mode Exit fullscreen mode

设置服务器🖥️

为了演示目的,我将创建两个相互通信的微服务,最终我们可以使用它来可视化分布式跟踪。

构建和 ​​Docker 化微服务 1

在目录中/microservice1,创建一个新的 Python 虚拟环境,安装必要的依赖项,并初始化 Flask 应用程序。

🚨 我假设您是在基于 Unix 的计算机上进行操作。如果您使用的是 Windows 计算机,某些命令可能会有所不同。

cd microservice1
python -m venv .venv
source .venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

💡 如果您使用的是 fish shell,请运行以下命令来激活虚拟环境。

source .venv/bin/activate.fish
Enter fullscreen mode Exit fullscreen mode

安装所需的依赖项:

pip install Flask requests
Enter fullscreen mode Exit fullscreen mode

获取已安装依赖项的列表,requirements.txt以便我们稍后可以在容器中使用它来安装依赖项。

pip freeze > requirements.txt
Enter fullscreen mode Exit fullscreen mode

创建一个名为的新文件app.py并添加以下代码行:

# 👇 src/microservice1/app.py
import socket
import requests
from flask import Flask, jsonify, render_template

app = Flask(__name__)

def user_os_details():
    hostname = socket.gethostname()
    hostip = socket.gethostbyname(hostname)
    return hostname, hostip

@app.route("/")
def index():
    return "<p>Welcome to Flask microservice 1</p>"

@app.route("/health")
def health():
    return jsonify(status="Microservice 1 Running...")

@app.route("/get-users")
def get_users():
    response = requests.get("http://microservice2:5001/get-gh-users")
    return render_template("index.html", users=response.json())

@app.route("/os-details")
def details():
    host_name, host_ip = user_os_details()
    return jsonify(hostname=host_name, hostip=host_ip)

if __name__ == "__main__":
    app.run("0.0.0.0", 5000)
Enter fullscreen mode Exit fullscreen mode

💡 如果你注意到了,我们正在从 请求数据http://microservice2:5001/get-gh-users。你可能会想,这个 microservice2 是什么?其实,我们可以在 Docker 中将服务名称用作同一网络内的主机名。我们稍后完成此微服务的编写和 Docker 化后,再构建此服务。

如你所见,这是一个非常简单的 Flask 应用程序,包含几个端点。该user_os_details()函数用于获取计算机的主机名IP 地址。

@app.route("/")和装饰@app.route("/health")定义了 Flask 应用的根和 "/health" 端点。稍后将使用 "/health" 端点来检查容器的健康状况 ❤️‍🩹 Dockerfile

@app.route("/get-users")和装饰@app.route("/os-details")定义了“/get-users”和“/os-details”端点。“/get-users”端点从microservice2获取 GitHub 用户,并将其作为 props 传递给index.html文件进行渲染。同时,“/os-details”端点显示系统详细信息。最后,该应用程序在 5000 端口上运行。

现在,让我们创建一个index.html文件,用于呈现从 microservice2 接收到的用户。

创建一个新文件夹/templates并添加index.html包含以下内容的文件:

<!-- 👇 src/microservice1/templates/index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Microservice 1</title>
</head>
<body>
  <h1>Microservice 1</h1>
  <h2>This is the data received from microservice2:</h2>
  <p>{{ users }}</p>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

我们的第一个微服务已经准备就绪。让我们将它 docker 化。在目录Dockerfile中创建一个 docker-compose.yml 文件/microservice1,并添加以下代码行:

🚨 确保其名称准确Dockerfile且不带扩展名。

# 👇 src/microservice1/Dockerfile
# Use Python alpine as the base image.
FROM python:3.12.1-alpine3.18
# Optional: Upgrade pip to the latest version.
RUN pip install --upgrade pip
# Create a new user with fewer permissions.
RUN adduser -D lowkey
# Switch to user lowkey
USER lowkey
# Change the working directory to ~/app
WORKDIR /home/lowkey/app
# Copy the requirements.txt file required to install the dependencies.
COPY --chown=lowkey:lowkey requirements.txt requirements.txt
# Install the dependencies
RUN pip install -r requirements.txt
# Copy the rest of the files to the current directory in the docker container.
COPY --chown=lowkey:lowkey . .
# Expose port 5000
EXPOSE 5000
# Switch to the root user just for installing curl. It is required.
USER root
# Install curl. Alpine uses apk as its package manager.
RUN apk --no-cache add curl
# Switch back to user lowkey
USER lowkey
# Check the health of the container. The "/health" endpoint is used 
# to verify the container is up and running.
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=5 \ 
            CMD curl -f http://localhost:5000/health || exit 1
# Finally, start the application.
ENTRYPOINT [ "python", "app.py" ]
Enter fullscreen mode Exit fullscreen mode

创建一个.dockerignore文件,其中包含我们不想推送到容器的文件的名称。

__pycache__
.venv
README.md
Dockerfile
.dockerignore
Enter fullscreen mode Exit fullscreen mode

现在,这就是我们第一个微服务的完整设置。✨

构建和 ​​Docker 化微服务 2

我们将拥有一个类似于microservice1 的设置,只需进行一些更改。

像我们对microservice1所做的那样进行精确的初始设置。之后,在/microservice2文件夹中创建一个app.py文件并添加以下代码行:

# 👇 src/microservice2/app.py
import random
import requests
from flask import Flask, jsonify, render_template

app = Flask(__name__)

def get_gh_users():
    url = "https://api.github.com/users?per_page=5"

    # Choose a random timeout between 1 and 5 seconds
    timeout = random.randint(3, 6)

    try:
        response = requests.get(url, timeout=timeout)
        return response.json()
    except requests.exceptions.Timeout:
        return {"error": "Request timed out after {} seconds".format(timeout)}
    except requests.exceptions.RequestException as e:
        return {"error": "Request failed: {}".format(e)}

@app.route("/")
def index():
    return "<p>Welcome to Flask microservice 2</p>"

@app.route("/get-gh-users")
def get_users():
    results = []

    # Loop through the number of requests and append the results to the list
    for _ in range(3):
        result = get_gh_users()
        results.append(result)

    # Return the list of results as a JSON response
    return jsonify(results)

@app.route("/health")
def health():
    return jsonify(status="Microservice 2 Running...")

@app.route("/os-details")
def details():
    try:
        response = requests.get("http://microservice1:5000/os-details").json()
        host_name = response["hostname"]
        host_ip = response["hostip"]
        return render_template("index.html", hostname=host_name, hostip=host_ip)
    except requests.exceptions.Timeout as errt:
        return {"error": "Request timed out after {} seconds".format(errt)}
    except requests.exceptions.RequestException as e:
        return {"error": "Request failed: {}".format(e)}

if __name__ == "__main__":
    app.run("0.0.0.0", 5001)
Enter fullscreen mode Exit fullscreen mode

@app.route("/")装饰器定义了 Flask 应用的根端点,用于返回欢迎消息。此外,该@app.route("/health")装饰器还定义了“/health”端点,可用于检查容器的健康状态。

装饰@app.route("/get-gh-users")器定义了“/get-gh-users”端点,该端点使用该get_gh_users()函数获取 GitHub 用户并将其作为 JSON 响应返回。最后,装饰器定义了“/os-details”端点,该端点从microservice1@app.route("/os-details")检索操作系统详细信息并将其呈现在文件中。最后,该应用程序在端口 5001 上运行。index.html

现在,让我们创建一个index.html文件,用于呈现从microservice2接收到的用户。

创建一个新文件夹/templates并添加index.html包含以下内容的文件:

<!-- 👇 src/microservice2/templates/index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Microservice 2</title>
</head>
<body>
  <h1>Microservice 2</h1>
  <h2>This is the hostname and IP address received from the microservice1:</h2>
  <p>{{ hostname }} - {{ hostip }}</p>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

现在,是时候将此微服务也 docker 化了。复制并粘贴microservice1Dockerfile的全部内容,并将端口从 5000 更改为 5001。

另外,添加一个文件并包含我们在创建microservice1.dockerignore时添加的相同文件

现在,这也是我们第二个微服务的完整设置。✨


使用 Docker Compose 构建 Dockerfile

我们将遵循镜像构建的最佳实践,使用 Docker Compose 而不是手动构建每个镜像。这里我们只有两个镜像,但想象一下,如果我们有数百或数千个 Dockerfile,手动构建每个镜像将是一项繁琐的任务。😴

在项目的根目录中,创建一个名为的新文件docker-compose.yaml并添加以下代码:

services:
  microservice1:
    build:
      context: ./src/microservice1
      dockerfile: Dockerfile
    image: microservice1-image:1.0
    ports:
      - "5000:5000"
    restart: always

  microservice2:
    build:
      context: ./src/microservice2
      dockerfile: Dockerfile
    image: microservice2-image:1.0
    ports:
      - "5001:5001"
    restart: always
Enter fullscreen mode Exit fullscreen mode

这个 Docker Compose 文件定义了两个服务,microservice1microservice2Dockerfile 。每个服务都使用其在目录中的相应位置构建/src,其中microservice1映射到端口 5000,microservice2映射到端口 5001。

生成的镜像分别标记为microservice1-image:1.0microservice2-image:1.0。两个服务都设置为始终重启,以确保如果容器失败,它会重新启动。

现在,使用以下命令构建图像:

docker compose build
Enter fullscreen mode Exit fullscreen mode

Docker Compose 构建输出


在 Kubernetes 上部署

确保已安装Minikube ,或按照链接获取安装说明。👀

运行以下命令创建一个新的本地 Kubernetes 集群。设置 Odigos 和 Jaeger 时需要用到它。

启动 Minikube:🚀

minikube start
Enter fullscreen mode Exit fullscreen mode

启动 Minikube

由于我们在本地 Kubernetes 环境中运行,因此我们需要指向我们的 shell 以使用 minikube 的 docker-daemon。

要将您的 shell 指向 minikube 的 docker-daemon,请运行:

minikube -p minikube docker-env | source
Enter fullscreen mode Exit fullscreen mode

现在,当运行任何 Docker 命令(例如docker imagesdocker ps)时,您将看到 Minikube 内部的内容,而不是系统本地的内容。

现在我们已经准备好两个微服务并将其 dockerized,现在是时候设置 Kubernetes 来管理这些服务了。

在项目根目录下创建一个新文件夹/k8s/manifests。在此文件夹中,我们将为两个微服务添加部署和服务配置。

  • 部署配置📜:用于在 Kubernetes 集群上实际部署容器。
  • 服务配置📄:将 pod 暴露给集群内部和集群外部。

首先,让我们为创建清单microservice1。创建一个新文件microservice1-deployment-service.yaml并添加以下内容:

// 👇 k8s/manifests/microservice1-deployment-service.yaml
version: apps/v1
kind: Deployment
metadata:
  name: microservice1
spec:
  selector:
    matchLabels:
      app: microservice1
  template:
    metadata:
      labels:
        app: microservice1
    spec:
      containers:
        - name: microservice1
          image: microservice1-image:1.0
          imagePullPolicy: Never
          resources:
            limits:
              memory: "200Mi"
              cpu: "500m"
          ports:
            - containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
  name: microservice1
  labels:
    app: microservice1
spec:
  type: NodePort
  selector:
    app: microservice1
  ports:
    - port: 8080
      targetPort: 5000
      nodePort: 30001
Enter fullscreen mode Exit fullscreen mode

此配置部署了一个名为microservice1的微服务,其资源限制为200MB 内存🗃️ 和0.5 个 CPU 核心。它通过 Deployment 在端口 5000 上向内部公开该微服务,并通过 Service在NodePort 30001 上向外部公开该微服务。

🤔 还记得docker-compose build我们构建 Dockerfile 时使用的命令,尤其是镜像名称吗?我们使用相同的镜像来创建容器。

它可以在集群内的 8080 端口上访问。我们假设microservice1-image:1.0可以在本地使用imagePullPolicy: Never。如果没有,它会尝试从 Docker Hub 🐋 拉取镜像,但会失败。

现在,让我们为microservice2创建清单。创建一个名为 的新文件microservice2-deployment-service.yaml并添加以下内容:

// 👇 k8s/manifests/microservice2-deployment-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: microservice2
spec:
  selector:
    matchLabels:
      app: microservice2
  template:
    metadata:
      labels:
        app: microservice2
    spec:
      containers:
        - name: microservice2
          image: microservice2-image:1.0
          imagePullPolicy: Never
          resources:
            limits:
              memory: "200Mi"
              cpu: "500m"
          ports:
            - containerPort: 5001
---
apiVersion: v1
kind: Service
metadata:
  name: microservice2
  labels:
    app: microservice2
spec:
  type: NodePort
  selector:
    app: microservice2
  ports:
    - port: 8081
      targetPort: 5001
      nodePort: 30002
Enter fullscreen mode Exit fullscreen mode

它与的清单类似microservice1,只有少量变化。

此配置部署了一个名为microservice2的微服务,并通过 Deployment 在端口 5001 上向内部公开,并通过 Service在NodePort 30002 上向外部公开。

可在集群内的端口 8081 上访问,假设microservice2-image:1.0可在本地使用imagePullPolicy: Never

完成所有操作后,请确保应用这些配置并使用这些服务启动 Kubernetes 集群。将目录更改为/manifests并执行以下命令:

kubectl apply -f microservice1-deployment-service.yaml
kubectl apply -f microservice2-deployment-service.yaml
Enter fullscreen mode Exit fullscreen mode

通过执行以下命令检查我们的部署是否正在运行:

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

Kubernetes Pod

最后,我们的应用程序已准备就绪,并使用必要的部署配置部署在 Kubernetes 上。🎊


安装 Odigos 🧑‍💻

💡 Odigos是一个开源的可观察性控制平面,可帮助组织创建和维护其可观察性管道。简而言之,我们将使用 Odigos 来自动检测我们的 Python 应用程序。

Odigos - 监控工具

ℹ️如果您在 Mac 上运行,请运行以下命令在本地安装 Odigos。

brew install keyval-dev/homebrew-odigos-cli/odigos
Enter fullscreen mode Exit fullscreen mode

ℹ️ 如果您使用的是 Linux 系统,请考虑通过执行以下命令从 GitHub 版本进行安装。请确保根据您的 Linux 发行版更改文件。

ℹ️ 如果 Odigos 二进制文件不可执行,请chmod +x odigos在运行安装命令之前运行此命令使其可执行。

curl -LJO https://github.com/keyval-dev/odigos/releases/download/v1.0.15/cli_1.0.15_linux_amd64.tar.gz
tar -xvzf cli_1.0.15_linux_amd64.tar.gz
./odigos install
Enter fullscreen mode Exit fullscreen mode

Odigos 安装

如果您需要有关安装的更多简要说明,请点击此链接

现在,Odigos 已准备好运行🚀。我们可以执行它的 UI,配置跟踪后端,并相应地发送跟踪信息。


将 Odigos 连接到跟踪后端✨

💡 Jaeger是一个开源的端到端分布式跟踪系统。

Jaeger - 分布式追踪平台

设置 Jaeger ✨

在本教程中,我们将使用Jaeger 🕵️‍♂️,这是一个流行的开源平台,用于查看微服务应用程序中的分布式跟踪信息。我们将使用它来查看 Odigos 生成的跟踪信息。

有关 Jaeger 的安装说明,请点击此链接。👀

要在 Kubernetes 集群上部署 Jaeger,请运行以下命令:

kubectl create ns tracing
kubectl apply -f https://raw.githubusercontent.com/keyval-dev/opentelemetry-go-instrumentation/master/docs/getting-started/jaeger.yaml -n tracing
Enter fullscreen mode Exit fullscreen mode

在这里,我们创建一个tracing命名空间并在该命名空间中应用 Jaeger 的部署配置📃。

此命令设置自托管 Jaeger 实例及其服务。

运行以下命令来获取正在运行的 pod 的状态:

kubectl get pods -A -w
Enter fullscreen mode Exit fullscreen mode

等待所有三个 pod 都运行完毕后再继续下一步。

Kubernetes Pod

现在,要在本地查看 Jaeger 界面,我们需要进行端口转发。将流量从本地机器的 16686 端口转发到 Kubernetes 集群中选定 Pod 的 16686 端口。

kubectl port-forward -n tracing svc/jaeger 16686:16686
Enter fullscreen mode Exit fullscreen mode

此命令在本地机器和 Jaeger pod 之间创建一个隧道,公开 Jaeger UI 以便您与其交互。

Jaeger 端口转发

现在,在 上http://localhost:16686,您应该能够看到 Jaeger 实例正在运行。

配置 Odigos 与 Jaeger 配合使用

ℹ️ 对于 Linux 用户,请转到从 GitHub 版本下载 Odigos 二进制文件的文件夹,然后运行以下命令来启动 Odigos UI。

./odigos ui
Enter fullscreen mode Exit fullscreen mode

ℹ️ 对于 Mac 用户,只需运行:

odigos ui
Enter fullscreen mode Exit fullscreen mode

访问http://localhost:3000后,您将看到 Odigos 界面,您将在其中看到default命名空间中的两个部署。

Odigos 初始用户界面

选择这两个,然后单击“下一步”。在下一页上,选择 Jaeger 作为后端,并在出现提示时添加以下详细信息:

  • 目标名称:输入您想要的任何名称,比如说python-tracing
  • 端点🎯jaeger.tracing:4317为端点添加。

就这样——Odigos 已设置好向 Jaeger 后端发送追踪信息。就这么简单。

Odigos 工具设置,用于将跟踪信息发送到 Jaeger Tracing


查看分布式跟踪🧐

当我们设置 Odigos 与 Jaeger 协同作为我们的跟踪后端时,Odigos 已经开始将我们的应用程序的跟踪发送到 Jaeger。

访问http://localhost:16686并在下拉菜单中选择我们的微服务。

用于选择微服务的 Jaeger UI 下拉菜单

向我们的端点发出一些请求,最终,Jaeger 将开始填充跟踪。

单击任意请求并探索其踪迹。

Jaeger 请求跟踪

这一切都是在没有更改任何一行代码的情况下完成的。🔥

哇哦 GIF


总结!⚡

到目前为止,您已经学会了使用分布式跟踪密切监控👀您的 Python 应用程序,并使用Odigos作为应用程序和跟踪后端Jaeger之间的中间件

本教程的源代码可以在这里找到:

https://github.com/shricodev/blogs/tree/main/odgs-monitor-PY-like-a-pro

非常感谢你的阅读!🎉 🫡

在下面的评论部分写下你的想法。👇

在社交媒体上关注我🐥

文章来源:https://dev.to/shricodev/learn-to-monitor-your-python-application-like-a-pro-15pg
PREV
(Neo)Vim 让你成为 10 倍开发者,我可没开玩笑
NEXT
像专业人士一样使用 LLAMA-3 打造你自己的 SIRI!🧙‍♂️ 🪄