使用 OpenTelemetry、Flask 和 Prometheus 对 MinIO 进行指标监控

Metrics with MinIO using OpenTelemetry, Flask, and Prometheus

可观测性是关于收集信息(跟踪、日志、指标),其目标是提高性能、可靠性和可用性。很少出现仅凭其中一项就能查明事件根本原因的情况,更多时候,当我们关联这些信息以形成一个叙述时,我们才能更好地理解。

在最近几篇关于可观测性的博文中,我们讨论了如何在 MinIO 中实现跟踪 以及将 MinIO 日志发送到 ElasticSearch 。在本博文中,我们将讨论指标。

从一开始,MinIO 不仅专注于性能和可扩展性,还专注于可观测性。MinIO 具有内置端点 /minio/v2/metrics/clusterPrometheus 可以抓取并从该端点收集指标。您还可以 发布事件Kafka,并触发依赖于 MinIO 中执行的操作的警报和其他流程。最后,您可以在一个漂亮的 Grafana 仪表盘 中将所有这些指标汇总起来,甚至可以启动警报。

云原生 MinIO 与云原生可观测性工具(如 OpenTelemetry)无缝集成。我们将在指标中使用与跟踪相同的 OpenTelemetry API,只是使用不同的导出程序集和函数。

为了让您了解可观测性流程以及可用的信息深度,我们将构建一个小型 Flask Python 应用程序并在 Grafana 中对其进行可视化。

  1. 我们将启动 MinIO、Prometheus 和 Grafana 容器。
  2. 使用 OpenTelemetry 编写一个示例 Python 应用程序,以收集各种类型的指标,如计数器和直方图。
  3. 使用 MinIO SDK 对 MinIO 执行操作。
  4. 为我们编写的示例应用程序构建 Docker 镜像。
  5. 在 Grafana 仪表盘中可视化 Prometheus 抓取的指标。

我们将逐步指导您完成整个过程。

启动基础设施

上一篇博文 类似,我们将使用从头开始编写的 Python 应用程序,只是这次我们将发送指标而不是发送跟踪。

MinIO

有几种方法可以在各种环境中 安装 MinIO 。在本博文中,我们将使用 Docker 启动 MinIO,但在生产环境中,请确保以 分布式方式安装Kubernetes 上。

在本地机器上创建一个目录,MinIO 将在此目录中持久保存数据。

$ mkdir -p /minio/data

使用 Docker 启动 MinIO 容器。

$ docker run -d \
  -p 9000:9000 \
  -p 9001:9001 \
  --name minio \

   --hostname minio \
  -v /minio/data:/data \
  -e "MINIO_ROOT_USER=minio" \
  -e "MINIO_ROOT_PASSWORD=minioadmin" \
  quay.io/minio/minio server /data --console-address ":9001"

注意:请保留上面使用的凭据的副本;您稍后需要它们才能访问 MinIO。

通过 http://localhost:9001/ 使用浏览器登录,并使用上面启动容器时使用的凭据验证您是否可以访问 MinIO。

Prometheus

我们需要将从应用程序收集的指标存储在某个地方。Prometheus 在这种情况下就是存储位置,但 OpenTelemetry 也支持其他导出程序。

首先,创建一个名为 prometheus.yml 的文件,其中包含以下内容:

scrape_configs:
  - job_name: 'metrics-app'
    scrape_interval: 2s
    static_configs:
      - targets: ['metrics-app:8000']

请注意 `- targets: ['metrics-app:8000']`;之后当我们在 Docker 容器中启动应用程序时,我们将把主机名设置为 `metrics-app` 来匹配目标。

启动 Prometheus 容器

docker run -d \
    -p 9090:9090 \
    --name prom \
    --hostname prom \
    -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
    prom/prometheus

请记住上面的主机名和端口;之后在 Grafana 中配置数据源时,您将需要它们。

Grafana

您知道 Grafana 最初是作为 Kibana 的一个分支吗?在这个例子中,我们将使用 Grafana 来为我们上面用 MinIO 收集和存储的指标提供可视化。通过在 Grafana 中可视化,我们可以看到一些模式,而这些模式我们仅仅通过查看数字或日志是看不到的。

启动 Grafana 容器

docker run -d \
  -p 3000:3000 \
  --name grafana \
  --hostname grafana \
  grafana/grafana:latest

通过浏览器访问 http://localhost:3000 并按照以下说明设置 Prometheus 数据源。

Flask 应用程序

之前我们编写了一个 Python 应用程序来发送跟踪,但这次我们将编写一个 Python 中的 Flask Web 应用程序,以便更好地了解集成。Flask 是一个 Python Web 框架,它可以帮助我们轻松地构建简单的面向 Web 的应用程序或 API 应用程序。在这个例子中,我们只展示了一个简化版本,以便我们能够简洁地演示这些技术。

检测指标

我们将继续把整个 Flask 应用程序粘贴到这里,然后对其进行剖析,以深入研究代码库,了解我们如何检测指标。

创建一个名为 `app.py` 的文件,并粘贴以下代码

from flask import Flask, request, g

from prometheus_client import start_http_server

from minio import Minio


from opentelemetry import metrics
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.instrumentation.flask import FlaskInstrumentor

import time

# Service name is required for most backends
resource = Resource(attributes={
    SERVICE_NAME: "minio-service"
})

# Start Prometheus client
start_http_server(port=8000, addr="0.0.0.0")
reader = PrometheusMetricReader()
provider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(provider)
meter = metrics.get_meter(__name__, True)

minio_app_visits_counter = meter.create_counter(
    name="minio_app_visits", description="count the number of visit to various routes", unit="1",
)

minio_request_duration = meter.create_histogram(
    name="minio_request_duration",
    description="measures the duration of the inbound MinIO App HTTP request",
    unit="milliseconds")

config = {
  "minio_endpoint": "minio:9000",
  "minio_username": "minio",
  "minio_password": "minioadmin",
}

minio_client = Minio(config["minio_endpoint"],
              secure=False,
              access_key=config["minio_username"],
              secret_key=config["minio_password"]
              )

app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)

@app.before_request
def before_request_func():
    g.start_time = round(time.time()*1000)
    minio_app_visits_counter.add(1, attributes={"request_path": request.path})

@app.after_request
def after_request_func(response):

    total_request_time = round(time.time()*1000) - g.start_time
    minio_request_duration.record(total_request_time, {"request_path": request.path})

    return response

@app.route('/')
def hello_world():
    return '<h1>Hello World MinIO!</h1>'

@app.route('/makebucket/<name>')
def make_bucket(name):
    if not minio_client.bucket_exists(name):
 
        minio_client.make_bucket(name)

    return f"""
        <html>
        <h3>Make Bucket {name} Successful</h3>
        </html>
        """

@app.route('/removebucket/<name>')
def remove_bucket(name):
    if minio_client.bucket_exists(name):
 
        minio_client.remove_bucket(name)

    return f"""
        <html>
        <h3>Removed Bucket {name} Successful</h3>
        </html>
        """

if __name__ == "__main__":
    app.run(debug=True)

Flask 是为我们的路由提供服务的 Web 框架

from flask import Flask, request, g

为了让服务器抓取我们的指标,我们需要启动一个 Prometheus 客户端。现在只需导入包。

from prometheus_client import start_http_server

为了执行 MinIO 操作,也导入 minio 包。

from minio import Minio

OpenTelemetry 是一个标准化我们发送给 Prometheus 的指标的框架。我们也会导入这些包。

from opentelemetry import metrics
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.instrumentation.flask import FlaskInstrumentor

Prometheus 客户端用于将我们收集到的指标暴露给 Prometheus 抓取器。

start_http_server(port=8000, addr="0.0.0.0")

提醒一下,端口应该与 prometheus.yml 中的配置匹配。

创建一个指标类型 计数器,它将允许我们发送整数。这里我们正在计算应用程序的访问次数。计数器可以统计以下内容:

  • API 访问次数
  • 每个进程的页面错误次数

minio_app_visits_counter = meter.create_counter(
    name="minio_app_visits", description="统计访问不同路由的次数", unit="1",
)

除了页面访问,我们还想了解请求的处理时间;为此,我们将创建一个指标类型 直方图。在本例中,我们使用毫秒衡量入站请求时间,但我们可以显示其他指标,例如

  • 上传到特定 MinIO 存储桶的对象大小
  • 上传对象所花费的时间

minio_request_duration = meter.create_histogram(
    name="minio_request_duration",
    description="测量入站 MinIO App HTTP 请求的持续时间",
    unit="毫秒")

初始化 MinIO 客户端

config = {

  "minio_endpoint": "minio:9000",

  "minio_username": "minio",

  "minio_password": "minioadmin",

}


minio_client = Minio(config["minio_endpoint"],
              secure=False,
              access_key=config["minio_username"],
              secret_key=config["minio_password"]
              )

初始化 Flask 应用程序和 OpenTelemetry 指标仪器

app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)

def before_request_func(): 中,我们执行了两个操作

  • 计数器函数,每次有人访问任何路由时增加 1
  • 开始时间,用于以毫秒为单位检查加载时间

g.start_time = round(time.time()*1000)

minio_app_visits_counter.add(1, attributes={"request_path": request.path})

def after_request_func(): 中,我们捕获请求的结束时间并将增量传递以记录直方图

total_request_time = round(time.time()*1000) - g.start_time
minio_request_duration.record(total_request_time, {"request_path": request.path})

这些指标封装在这些前后装饰器中,以便我们保持代码 DRY。您可以添加任意数量的额外路由,它们将自动拥有我们自定义指标的仪器。

@app.before_request
def before_request_func():
    ...

@app.after_request
def after_request_func(response):
  ...

    return response

最后,一些使用 MinIO Python SDK 执行操作的示例路由。这些路由的 cURL 调用看起来像这样

  • curl http://<host>:<port>/makebucket/aj-test
  • curl http://<host>:<port>/removebucket/aj-test

@app.route('/')
def hello_world():
    return '<h1>Hello World MinIO!</h1>'

@app.route('/makebucket/<name>')
def make_bucket(name):
    if not minio_client.bucket_exists(name):
 
        minio_client.make_bucket(name)

    return f"""
        <html>
        <h3>创建存储桶 {name} 成功</h3>
        </html>
        """

@app.route('/removebucket/<name>')
def remove_bucket(name):
    if minio_client.bucket_exists(name):
 
        minio_client.remove_bucket(name)

    return f"""
        <html>
        <h3>删除存储桶 {name} 成功</h3>
        </html>
        """

如您所见,我们的应用程序非常简单,这就是它的重点。我们想让您了解基础知识,但您可以以此为基础,添加自己的仪器。

构建和部署应用程序

我们编写了应用程序,现在我们需要将应用程序构建为 Docker 镜像,以便我们可以将它与其他基础设施一起部署,这样所有组件都可以相互通信。

创建一个名为 Dockerfile 的文件,并添加以下内容

# syntax=docker/dockerfile:1

FROM python:3.8-slim-buster

WORKDIR /metrics_app

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

COPY app.py app.py

CMD [ "flask", "run", "--host", "0.0.0.0"]

我们需要安装我们在应用程序中导入的所有包。创建一个名为requirements.txt的文件,内容如下:

minio
flask
prometheus_client
opentelemetry-api
opentelemetry-sdk
opentelemetry-exporter-prometheus
opentelemetry-instrumentation-flask

使用名称ot-prom-metrics-app构建应用程序,我们稍后将使用它作为图像名称来启动容器。

docker build --tag ot-prom-metrics-app .

使用我们刚刚创建的图像在端口5000上启动应用程序容器。

docker run -d \
    -p 5000:5000 \
    --name metrics-app \
    --hostname metrics-app \
    ot-prom-metrics-app

使用以下 curl 命令测试创建存储桶。

$ curl http://127.0.0.1:5000/makebucket/aj-test

        <html>
        <h3>Make Bucket aj-test Successful</h3>
        </html>

现在我们已经部署了所有部分,是时候在 Grafana 中观察它们了。

观察指标

我们部署了一个可以生成一些指标的应用程序,一个数据存储(prometheus)来保存这些指标,以及 Grafana 用于历史视图和上下文。

Grafana 查询

登录 Grafana 并按照以下说明操作

您会注意到我们的指标都没有在那里。这是因为您要么没有对 flask 应用程序路由进行任何调用,要么是在 5 分钟前进行了调用。从技术上讲,如果您扩展了可见性的时间范围,您应该看到您在上节中进行的 curl 调用的注册,但无论如何,让我们再次进行测试调用,然后检查。

$ curl http://127.0.0.1:5000/makebucket/aj-test

        <html>
        <h3>Make Bucket aj-test Successful</h3>
        </html>

刷新 Grafana 页面并按照以下说明操作

这很有趣吧?让我们做一些更有趣的事情,通过进行多次调用,但将它们错开。每次后续调用将比前一次多花费一秒钟。换句话说,第一次调用将花费一秒钟,第二次调用将花费两秒钟,第三次调用将花费三秒钟,依此类推。继续运行以下命令。

for i in {1..10}; do curl http://127.0.0.1:5000/makebucket/aj-test && sleep $i; done;

在 Grafana 中,单击“运行查询”,待上述命令执行完毕后。结果应类似于此,注意最右边?那是我们的 11 个请求,或者您发出的任何数量的请求。


虽然这很有趣,我们现在知道了访问应用程序的 *总* 次数,但我们不知道这些访问发生的速率:每秒一次?每秒几次?这就是范围函数派上用场的地方。请按照以下说明进行操作

现在您拥有一个简单的应用程序,您可以根据需要在其上构建进一步的仪器。一些其他常见的用例是

  • 监控上传的对象的大小
  • 监控获取对象数量的速率
  • 监控下载对象的速率

这些只是一些例子;您监控的越多,您拥有的可观察性就越多。

最后的想法

可观察性是多方面的;您通常需要查看跟踪、指标和日志的组合,以确定根本原因。您可以使用混沌工程工具(例如 Gremlin、ChaosMonkey 等)来破坏您的系统,并观察指标中的模式。

例如,也许您正在收集 HTTP 请求状态,并且通常您会看到 200s,但突然您看到 500s 的峰值。您查看日志并发现最近进行了部署或数据库停机进行维护。或者,如果您正在监控 对象存储性能 指标,您可以将任何服务器端问题与这些数据关联起来。正是这类事件通常会导致最大的痛苦,在这些情况下拥有可见性至关重要。

如果您想了解更多关于混沌工程的信息,或者在您的 MinIO 应用程序中有一个很酷的指标实现,请在 Slack 上联系我们!我们很乐意看看您有什么。