長話短說

在這個易於理解的教程中,您將了解如何使用分散式追蹤來監視 Python 應用程式。

您將學到什麼:✨

  • 如何使用 Python 建立微服務 🐍。

  • 為微服務設定 Docker 容器📦。

  • 配置 Kubernetes 以管理微服務。

  • 整合追蹤後端以可視化追蹤🕵️‍♂️。

準備好提升您的 Python 應用程式監控技能了嗎? 🔥

火 gif


設定說明

🚨 在部落格的這一部分中,我們將建立一個虛擬的 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請求資料。您可能想知道,這個微服務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

現在,這就是我們第一個微服務的完整設定。 ✨

建置和 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 回應傳回。最後, @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 建置映像的最佳實踐,而不是手動建置每個映像。這裡我們只有兩個映像,但想像一下如果我們有數百或數千個 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 檔案定義了兩個服務: microservice1microservice2 。每個服務都是使用位於/src目錄中各自的Dockerfile建構的,其中microservice1映射到連接埠 5000, microservice2映射到連接埠 5001。

產生的映像分別標示為microservice1-image:1.0microservice2-image:1.0 。這兩個服務都設定為始終重新啟動,以確保容器失敗時會重新啟動。

現在,使用以下命令建立映像:

docker compose build

Docker Compose 建置輸出


在 Kubernetes 上部署 🧑‍🚀

確保已安裝Minikube ,或點擊連結以取得安裝說明。 👀

透過執行以下命令建立新的本機 Kubernetes 叢集。我們在設定 Odigos 和 Jaeger 時將需要它。

啟動 Minikube:🚀

minikube start

啟動 Minikube

由於我們在本地 Kubernetes 環境上執行,因此我們需要將 shell 指向使用 minikube 的 docker-daemon。

若要將您的 shell 指向 minikube 的 docker-daemon,請執行:

minikube -p minikube docker-env | source

現在,當執行任何 Docker 命令(例如docker imagesdocker 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 Pod

最後,我們的應用程式已準備就緒,並使用必要的部署配置部署在 Kubernetes 上。 🎊


安裝 Odigos 🧑‍💻

💡 Odigos是一個開源可觀察性控制平面,使組織能夠建立和維護其可觀察性管道。簡而言之,我們將使用 Odigos 來自動檢測我們的 Python 應用程式。

Odigos - 監控工具

ℹ️ 如果您在 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 ✨

在本教程中,我們將使用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 都執行,然後再繼續。

Kubernetes Pod

現在,要在本地查看 Jaeger 介面,我們需要連接埠轉送。將流量從本機電腦上的連接埠 16686 轉送至 Kubernetes 叢集中選定 pod 上的連接埠 16686。

kubectl port-forward -n tracing svc/jaeger 16686:16686

此命令在本機電腦和 Jaeger pod 之間建立一條隧道,公開 Jaeger UI,以便您可以與其互動。

Jaeger 連接埠轉發

現在,在http://localhost:16686上,您應該可以看到 Jaeger 實例正在執行。

配置 Odigos 以與 Jaeger 一起工作 🌟

ℹ️ 對於 Linux 用戶,請前往從 GitHub 版本下載 Odigos 二進位檔案的資料夾,然後執行以下命令來啟動 Odigos UI。

./odigos ui

ℹ️ 對於 Mac 用戶,只需執行:

odigos ui

造訪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


包起來! ⚡

到目前為止,您已經學會了使用Odigos作為應用程式和追蹤後端Jaeger之間的中間件,透過分散式追蹤來密切監控您的 Python 應用程式。

本教學的源程式碼可在此處取得:

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

非常感謝您的閱讀! 🎉🫡

在下面的評論部分寫下你的想法。 👇

https://linktr.ee/shricodev

在社群上關注我 🐥

https://dev.to/shricodev


原文出處:https://dev.to/shricodev/learn-to-monitor-your-python-application-like-a-pro-15pg


共有 0 則留言