OpenWebUI のパフォーマンス監視と負荷テスト方法

# OpenWebUI のパフォーマンス監視と負荷テスト方法

## はじめに

**目的**：ローカルで動かす OpenWebUI（v0.6.5）＋自作Mockupバックエンドを対象に、**リソース監視**と**負荷テスト**のベストプラクティスをまとめたチュートリアル。

**対象読者**：Docker・Prometheus/Grafana・Apache JMeter をある程度使ったことがある中級者向け。

**ソースコード**:git上のプロジェクトは下記。mockupは10秒ごとに10回適当な回答をする超軽量llmモックアップ。
https://github.com/notfolder/openwebui-load-test

**⚠️注意**
モックアップの同時接続律速の可能性があるので、モックアップの同時接続について対応を考える必要あり。


## 前提環境とバージョン

* **OS**：macOS 15.5
* **OpenWebUI**：ghcr.io/open-webui/open-webui\:v0.6.5
* **バックエンド**：自作Mockup（`OLLAMA_BASE_URL`で接続）
* **Docker Desktop**：v4.42.0
* **Prometheus**：v2.48.0
* **Grafana**：v9.5.0
* **cAdvisor**：v0.49.1
* **JMeter**：5.6.3

## テスト履歴

- 同時200まではエラーなし
- 250で5%エラー。NoHttpResponseException
- nginx.confに下記を追記
```
worker_processes auto;

events {
    worker_connections 10240;
    multi_accept on;
}
```
- 250でエラーなし。パフォーマンスに大きな影響なし
- 500にしても平均応答時間は10.111秒(理論値10秒:sleep(1)で10メッセージ返しているので)
- 1000だと平均13秒、max79秒
- mockをgnicorn化するためDockerfileにした
- 1000でmax12秒,平均10.711秒.問題なし
- 2000でmax20秒,平均15秒

## 目次

1. 構成とリソース監視のセットアップ
2. Grafana ダッシュボードの導入
3. JMeter を用いた負荷テスト
4. まとめ・次のステップ

---

## 1. 構成とリソース監視のセットアップ

### 1.1 リポジトリ構成

```
openwebui-load-test/
├─ docker-compose.yml
├─ nginx.conf
├─ prometheus.yml
└─ jmeter/
   └─ openwebui-test-plan.jmx
```

### 1.2 docker-compose.yml（抜粋と注意点）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490869/12f21e81-d8c9-4bda-abc4-6f74caa9c204.png)

```yaml
version: "3.8"
services:
  openwebui:
    image: ghcr.io/open-webui/open-webui:v0.6.5
    ports: ["3000:8080"]
    environment:
      - OLLAMA_BASE_URL=http://host.docker.internal:11434  # Docker Desktop for Mac の場合
      - ENABLE_OPENAI_API_COMPAT=true                       # API互換モードを有効化
    extra_hosts:
      - "host.docker.internal:host-gateway"              # host.docker.internal の解決
    networks: [monitor-net]

  nginx:
    image: nginx:alpine
    ports: ["80:80"]
    volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro"]
    depends_on: [openwebui]
    networks: [monitor-net]

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    privileged: true
    ports: ["8081:8080"]
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    networks: [monitor-net]

  prometheus:
    image: prom/prometheus
    ports: ["9090:9090"]
    volumes: ["./prometheus.yml:/etc/prometheus/prometheus.yml"]
    networks: [monitor-net]

  grafana:
    image: grafana/grafana
    ports: ["3001:3000"]
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    networks: [monitor-net]

networks:
  monitor-net:
    driver: bridge
```

**docker-compose.ymlでの注意点**

* **`extra_hosts`**: macOS Docker Desktopでは `host.docker.internal` を指定し、バックエンドMockupへの接続を確保
* **`version: "3.8"` 警告**: Docker Compose v2以降は `version` 属性が非推奨。`docker compose` 利用時は省略可

### 1.3 nginx.conf（SSE／WebSocket対応）

```nginx
events {}
http {
  server {
    listen 80;

    location / {
      proxy_pass http://openwebui:8080;
      proxy_http_version 1.1;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      # SSE・WebSocket 用ヘッダー
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";

      # ストリーミングを途切れさせない設定
      proxy_buffering off;
      proxy_cache off;
      proxy_set_header Accept-Encoding "";
      chunked_transfer_encoding off;
    }

    location /nginx_status {
      stub_status;
      allow all;
    }
  }
}
```

**nginx.confでの引っかかった点**

* **`Upgrade`/`Connection`**: ストリーミング応答を扱う際に必要。未設定だとクライアント側でJSONパースエラーが発生
* **`proxy_buffering off`**: イベントストリームがバッファリングされて遅延する問題の回避

### 1.4 prometheus.yml（Exporter 指定の注意）

```yaml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

  - job_name: 'nginx'
    static_configs:
      - targets: ['nginx-exporter:9113']
```

**prometheus.ymlでのポイント**

* **`nginx-exporter`**: Nginxの同時接続数を取得するには `nginx-prometheus-exporter` を `/nginx_status` 経由でスクレイプ
* **ネットワーク名不一致**: Docker Compose v2では `monitor-net` がプロジェクト名付きのネットワーク名になる場合があるため、`docker network ls` で実際の名前を確認

---

## 2. Grafana ダッシュボードの導入

* **推奨テンプレート**：ID **13496** “Docker and system monitoring”

**インポート手順**:

1. Grafana → **＋ > Import**
2. ID `13496` を入力 → **Load**
3. Prometheus を選択 → **Import**

**表示項目**:

* ホスト Load Average, ディスク使用率
* コンテナ別 CPU／メモリ／ネットワーク I/O
* Nginx Active connections (stub\_status)

---

## 3. JMeter を用いた負荷テスト

### 3.1 テストプラン概要

* **同時ユーザー**：10
* **Ramp-up**：5秒
* **ループ**：5回
* **エンドポイント**：`POST http://localhost:3000/api/chat/completions`
* **ヘッダー**: `Content-Type: application/json`

### 3.2 jmeter/openwebui-test-plan.jmx（抜粋）

```xml
<ThreadGroup num_threads="10" ramp_time="5" loops="5">…</ThreadGroup>
<HTTPSamplerProxy testname="Chat Completion" method="POST" domain="localhost" port="3000" path="/api/chat/completions">
  <stringProp name="HTTPSampler.postBodyRaw">true</stringProp>
  <stringProp name="Argument.value">{
    "model":"dummy:latest",
    "messages":[{"role":"user","content":"こんにちは"}]
  }</stringProp>
</HTTPSamplerProxy>
```

### 3.3 実行コマンド

```bash
jmeter -n -t jmeter/openwebui-test-plan.jmx -l results.jtl
```

---

## 4. まとめ・次のステップ

* **今回の内容**: Docker Compose + Nginx + cAdvisor + Prometheus + Grafana + JMeter による監視＆負荷テスト
* **次のステップ**:

  * 本番環境向け API Gateway 設定
  * 実サービスモデルへの切り替え
  * アラート設定と運用フロー構築

**タグ**: `#Docker` `#OpenWebUI` `#Prometheus` `#Grafana` `#JMeter` `#負荷テスト`

## 5. この記事の作成過程
https://chatgpt.com/share/6844021e-b800-8009-b77f-cbf37e5769be