学习像专业人士一样监控你的 Python 应用程序!🧙♂️🪄
TL;DR
在这个易于理解的教程中,您将了解如何使用分布式跟踪来监视 Python 应用程序。
你将学到:✨
- 如何在 Python 中构建微服务🐍。
- 为微服务设置 Docker 容器📦。
- 配置 Kubernetes 来管理微服务。
- 集成跟踪后端以实现跟踪可视化🕵️♂️。
准备好提升你的 Python 应用程序监控技能了吗?🔥

设置说明
🚨 在博客的这一部分,我们将构建一个虚拟的 Python 微服务应用程序。如果您已经有一个应用程序并且正在按照步骤操作,请跳过此部分。
为您的应用程序创建初始文件夹结构,如下所示。👇
mkdir python-microservices && cd python-microservices
mkdir src && cd src
mkdir microservice1 microservice2
设置服务器🖥️
为了演示目的,我将创建两个相互通信的微服务,最终我们可以使用它来可视化分布式跟踪。
构建和 Docker 化微服务 1
在目录中/microservice1
,创建一个新的 Python 虚拟环境,安装必要的依赖项,并初始化 Flask 应用程序。
🚨 我假设您是在基于 Unix 的计算机上进行操作。如果您使用的是 Windows 计算机,某些命令可能会有所不同。
cd microservice1
python -m venv .venv
source .venv/bin/activate
💡 如果您使用的是 fish shell,请运行以下命令来激活虚拟环境。
source .venv/bin/activate.fish
安装所需的依赖项:
pip install Flask requests
获取已安装依赖项的列表,requirements.txt
以便我们稍后可以在容器中使用它来安装依赖项。
pip freeze > requirements.txt
创建一个名为的新文件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)
💡 如果你注意到了,我们正在从 请求数据
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>
我们的第一个微服务已经准备就绪。让我们将它 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" ]
创建一个.dockerignore
文件,其中包含我们不想推送到容器的文件的名称。
__pycache__
.venv
README.md
Dockerfile
.dockerignore
现在,这就是我们第一个微服务的完整设置。✨
构建和 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)
该@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>
现在,是时候将此微服务也 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
这个 Docker Compose 文件定义了两个服务,microservice1和microservice2Dockerfile
。每个服务都使用其在目录中的相应位置构建/src
,其中microservice1映射到端口 5000,microservice2映射到端口 5001。
生成的镜像分别标记为microservice1-image:1.0
和microservice2-image:1.0
。两个服务都设置为始终重启,以确保如果容器失败,它会重新启动。
现在,使用以下命令构建图像:
docker compose build
在 Kubernetes 上部署
确保已安装Minikube ,或按照此链接获取安装说明。👀
运行以下命令创建一个新的本地 Kubernetes 集群。设置 Odigos 和 Jaeger 时需要用到它。
启动 Minikube:🚀
minikube start
由于我们在本地 Kubernetes 环境中运行,因此我们需要指向我们的 shell 以使用 minikube 的 docker-daemon。
要将您的 shell 指向 minikube 的 docker-daemon,请运行:
minikube -p minikube docker-env | source
现在,当运行任何 Docker 命令(例如docker images
或docker 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
此配置部署了一个名为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
它与的清单类似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
通过执行以下命令检查我们的部署是否正在运行:
kubectl get pods
最后,我们的应用程序已准备就绪,并使用必要的部署配置部署在 Kubernetes 上。🎊
安装 Odigos 🧑💻
💡 Odigos是一个开源的可观察性控制平面,可帮助组织创建和维护其可观察性管道。简而言之,我们将使用 Odigos 来自动检测我们的 Python 应用程序。
ℹ️如果您在 Mac 上运行,请运行以下命令在本地安装 Odigos。
brew install keyval-dev/homebrew-odigos-cli/odigos
ℹ️ 如果您使用的是 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
如果您需要有关安装的更多简要说明,请点击此链接。
现在,Odigos 已准备好运行🚀。我们可以执行它的 UI,配置跟踪后端,并相应地发送跟踪信息。
将 Odigos 连接到跟踪后端✨
💡 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
在这里,我们创建一个tracing
命名空间并在该命名空间中应用 Jaeger 的部署配置📃。
此命令设置自托管 Jaeger 实例及其服务。
运行以下命令来获取正在运行的 pod 的状态:
kubectl get pods -A -w
等待所有三个 pod 都运行完毕后再继续下一步。
现在,要在本地查看 Jaeger 界面,我们需要进行端口转发。将流量从本地机器的 16686 端口转发到 Kubernetes 集群中选定 Pod 的 16686 端口。
kubectl port-forward -n tracing svc/jaeger 16686:16686
此命令在本地机器和 Jaeger pod 之间创建一个隧道,公开 Jaeger UI 以便您与其交互。
现在,在 上http://localhost:16686
,您应该能够看到 Jaeger 实例正在运行。
配置 Odigos 与 Jaeger 配合使用
ℹ️ 对于 Linux 用户,请转到从 GitHub 版本下载 Odigos 二进制文件的文件夹,然后运行以下命令来启动 Odigos UI。
./odigos ui
ℹ️ 对于 Mac 用户,只需运行:
odigos ui
访问http://localhost:3000
后,您将看到 Odigos 界面,您将在其中看到default
命名空间中的两个部署。
选择这两个,然后单击“下一步”。在下一页上,选择 Jaeger 作为后端,并在出现提示时添加以下详细信息:
- 目标名称:输入您想要的任何名称,比如说python-tracing。
- 端点🎯:
jaeger.tracing:4317
为端点添加。
就这样——Odigos 已设置好向 Jaeger 后端发送追踪信息。就这么简单。
查看分布式跟踪🧐
当我们设置 Odigos 与 Jaeger 协同作为我们的跟踪后端时,Odigos 已经开始将我们的应用程序的跟踪发送到 Jaeger。
访问http://localhost:16686
并在下拉菜单中选择我们的微服务。
向我们的端点发出一些请求,最终,Jaeger 将开始填充跟踪。
单击任意请求并探索其踪迹。
这一切都是在没有更改任何一行代码的情况下完成的。🔥
总结!⚡
到目前为止,您已经学会了使用分布式跟踪密切监控👀您的 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在下面的评论部分写下你的想法。👇