在這個易於理解的教程中,您將了解如何使用分散式追蹤來監視 Python 應用程式。
您將學到什麼:✨
如何使用 Python 建立微服務 🐍。
為微服務設定 Docker 容器📦。
配置 Kubernetes 以管理微服務。
整合追蹤後端以可視化追蹤🕵️♂️。
準備好提升您的 Python 應用程式監控技能了嗎? 🔥
🚨 在部落格的這一部分中,我們將建立一個虛擬的 Python 微服務應用程式。如果您已經有一個並且正在跟進,請隨意跳過這一部分。
為您的應用程式建立初始資料夾結構,如下所示。 👇
mkdir python-microservices && cd python-microservices
mkdir src && cd src
mkdir microservice1 microservice2
出於演示目的,我將建立兩個相互通信的微服務,最終我們可以使用它來視覺化分散式追蹤。
在/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
請求資料。您可能想知道,這個微服務2是什麼?那麼,我們可以在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化。在/microservice1
目錄中建立一個Dockerfile
並新增以下程式碼行:
🚨 確保將其準確命名為
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
現在,這就是我們第一個微服務的完整設定。 ✨
我們將有一個類似於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 回應傳回。最後, @app.route("/os-details")
裝飾器定義了「/os-details」端點,它從microservice1檢索作業系統詳細資訊並將其呈現在index.html
檔案中。最後,應用程式在連接埠 5001 上執行。
現在,讓我們建立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 化了。複製並貼上microservice1的整個Dockerfile
內容,並將連接埠從 5000 變更為 5001。
另外,新增一個.dockerignore
檔案並包含我們在建立microservice1時新增的相同檔案。
現在,這也是我們第二個微服務的完整設定。 ✨
我們將遵循使用 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和microservice2 。每個服務都是使用位於/src
目錄中各自的Dockerfile
建構的,其中microservice1映射到連接埠 5000, microservice2映射到連接埠 5001。
產生的映像分別標示為microservice1-image:1.0
和microservice2-image:1.0
。這兩個服務都設定為始終重新啟動,以確保容器失敗時會重新啟動。
現在,使用以下命令建立映像:
docker compose build
透過執行以下命令建立新的本機 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 內部的內容,而不是系統本地的內容。
現在我們已經準備好並 Docker 化了兩個微服務,是時候設定 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 核心。它透過部署在連接埠 5000 上公開微服務,並透過服務在NodePort 30001 上公開微服務。
🤔 還記得我們在建置 Dockerfile 時所使用的
docker-compose build
指令,尤其是映像名稱嗎?我們使用相同的映像來建立容器。
可透過集群內的連接埠 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 來自動檢測我們的 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,配置追蹤後端,並相應地發送追蹤。
💡 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 實例正在執行。
ℹ️ 對於 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 將開始填充痕跡。
點擊任何請求並探索痕跡。
這一切都是在沒有更改一行程式碼的情況下完成的。 🔥
到目前為止,您已經學會了使用Odigos作為應用程式和追蹤後端Jaeger之間的中間件,透過分散式追蹤來密切監控您的 Python 應用程式。
本教學的源程式碼可在此處取得:
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