「FastAPIでAIエンドポイントを作りたいが、Ollamaとどう連携させればいいかわからない」
そんな悩みを持つLinuxエンジニアや情シス担当者は多いはずです。この記事では、OllamaのREST APIをFastAPIでラップし、APIキー認証・レート制限・構造化ロギングを追加してチーム向けAIマイクロサービスとして本番運用する手順を解説します。
NginxリバースプロキシはネットワークレベルのルーティングやSSL終端は得意ですが、「部署ごとに異なるモデルだけ許可する」「リクエスト数を月次集計する」といった業務ロジックの組み込みにはPythonコードが必要です。FastAPIを使えばそうした制御を自社コードで完全に実装でき、OllamaのポートはLocalhost内に完全に閉じ込められます。Ubuntu Server 22.04 LTS・Python 3.11以降の環境を前提に、コピーして動く形で解説します。
この記事のポイント
・FastAPIとhttpxでOllamaの/api/chatをラップするカスタムAPIサーバーを構築し、Ollamaポートを外部から完全に隔離できる
・APIKeyHeaderとDependencyを使ってチームメンバーごとにAPIキーを発行し、不正利用を403でブロックできる
・slowapiで1分あたりのリクエスト上限を設定し、Ollamaが過負荷で応答不能になるリスクを防げる
・structlogで構造化JSON形式のログを出力し、チーム名・レスポンス時間・リクエストIDをjqで即座に絞り込める
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
FastAPIでOllamaをラップする設計思想と全体構成
Ollamaは起動するとhttp://localhost:11434でREST APIを提供します。デフォルトでは認証もレート制限もないため、このポートを社内ネットワークにそのまま開放するのは避けるべきです。かといって既製のプロキシツールでは業務固有のロジックを組み込む余地が限られます。FastAPIでラッパーを作ると、リクエストの流れは次のようになります。
外部クライアント(curl・Python・社内ツール)→ FastAPI(8000番ポート)→ Ollama(11434番・Localhost専用)
FastAPIがリクエストを受け取り、APIキー検証・レート制限チェック・ロギングを済ませてからOllamaに転送します。OllamaはSystemdのoverride.confで
OLLAMA_HOST=127.0.0.1に縛り、外部から直接叩けない状態にします。このアーキテクチャはマイクロサービスにおけるAPIゲートウェイパターンそのものです。知人のインフラエンジニアがこの構成を社内導入したところ、「誰がどのモデルを何回呼んだか月次でCSV出力できるようになった」と話していました。外部向けのNginxリバースプロキシと組み合わせて、NginxがFastAPIの8000番をHTTPSで公開するという構成も一般的です。
Ollamaの初期セットアップや基本的なサービス起動がまだの方は、Ubuntu ServerでローカルLLMを構築する方法を先に参照してください。本記事はOllamaが動作済みの前提で進めます。
環境を準備する|Python・FastAPI・uvicornのインストール手順
1. Pythonバージョンの確認
Python 3.11以降が必要です。まずバージョンを確認します。$ python3 --version Python 3.11.9
deadsnakesリポジトリでPython 3.11を追加インストールしてください。
$ sudo add-apt-repository ppa:deadsnakes/ppa $ sudo apt update $ sudo apt install python3.11 python3.11-venv -y
2. 仮想環境の作成と依存ライブラリのインストール
システムPythonを汚さないよう、専用の仮想環境を作成します。$ sudo mkdir -p /opt/ollama-api $ sudo chown ubuntu:ubuntu /opt/ollama-api $ python3 -m venv /opt/ollama-api/venv $ source /opt/ollama-api/venv/bin/activate $ pip install fastapi uvicorn httpx slowapi structlog
・
fastapi: WebフレームワークとAPIキー認証機構・
uvicorn: ASGIサーバー(FastAPIを動かす実行エンジン)・
httpx: FastAPIからOllamaへの非同期HTTPリクエスト・
slowapi: レート制限(Flask-LimiterのFastAPI版)・
structlog: JSON形式の構造化ロギング3. OllamaをLocalhost専用に変更する
FastAPIが唯一の窓口になるよう、Ollamaを127.0.0.1のみで待受けさせます。$ sudo mkdir -p /etc/systemd/system/ollama.service.d $ sudo tee /etc/systemd/system/ollama.service.d/override.conf << 'EOF' [Service] Environment="OLLAMA_HOST=127.0.0.1" EOF $ sudo systemctl daemon-reload $ sudo systemctl restart ollama $ curl -s http://127.0.0.1:11434/api/tags | python3 -m json.tool | head -5
curlがJSONを返せばOllamaはLocalhost専用に変更されています。
OllamaへのチャットエンドポイントをFastAPIで実装する
1. 基本骨格の作成
/opt/ollama-api/main.pyを作成し、まず基本的なFastAPIアプリとOllamaへの転送エンドポイントを実装します。
from fastapi import FastAPI, Request from fastapi.responses import JSONResponse import httpx app = FastAPI(title="Ollama Gateway") OLLAMA_BASE = "http://127.0.0.1:11434" @app.post("/v1/chat") async def chat(request: Request): body = await request.json() async with httpx.AsyncClient(timeout=180) as client: resp = await client.post( f"{OLLAMA_BASE}/api/chat", json=body, ) return resp.json()
2. 動作確認
uvicornで起動してcurlで疎通確認します。$ cd /opt/ollama-api && source venv/bin/activate $ uvicorn main:app --host 0.0.0.0 --port 8000 & $ curl -s -X POST http://localhost:8000/v1/chat \ -H "Content-Type: application/json" \ -d '{"model":"llama3.3:8b","messages":[{"role":"user","content":"ping"}],"stream":false}' \ | python3 -m json.tool { "model": "llama3.3:8b", "message": { "role": "assistant", "content": "pong. How can I help you?" }, "done": true }
モデル選定に迷う場合は、ローカルLLMのモデルを比較する方法でLlama3.3・Mistral・Gemma 3・Phi-4の使い分けポイントをまとめていますので参考にしてください。
APIキー認証をFastAPIのDependencyで組み込む
FastAPIのDependency Injection機構を使うと、認証ロジックをエンドポイントから切り離して再利用できます。X-API-Keyヘッダーを受け取り、登録済みキーと照合するDependencyを実装します。
1. APIキー検証ロジックの実装
from fastapi import Depends, HTTPException from fastapi.security import APIKeyHeader import os api_key_header = APIKeyHeader(name="X-API-Key", auto_error=True) # 本番では環境変数からパースする(コードに認証情報を埋め込まない) # 例: API_KEYS="devkey123:dev_team,opskey456:ops_team" def _load_keys() -> dict: raw = os.environ.get("API_KEYS", "devkey123:dev_team") return dict(pair.split(":") for pair in raw.split(",")) VALID_KEYS = _load_keys() async def verify_api_key(api_key: str = Depends(api_key_header)) -> str: if api_key not in VALID_KEYS: raise HTTPException(status_code=403, detail="Invalid API key") return VALID_KEYS[api_key] # チーム名を返す
2. エンドポイントにDependencyを追加する
@app.post("/v1/chat") async def chat( request: Request, team: str = Depends(verify_api_key), ): body = await request.json() async with httpx.AsyncClient(timeout=180) as client: resp = await client.post(f"{OLLAMA_BASE}/api/chat", json=body) return resp.json()
3. 認証の動作確認
# APIキーなし → 403 $ curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:8000/v1/chat \ -H "Content-Type: application/json" \ -d '{"model":"llama3.3:8b","messages":[{"role":"user","content":"test"}],"stream":false}' 403 # 正しいAPIキーあり → 200 $ curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:8000/v1/chat \ -H "Content-Type: application/json" \ -H "X-API-Key: devkey123" \ -d '{"model":"llama3.3:8b","messages":[{"role":"user","content":"test"}],"stream":false}' 200
API_KEYSを読み込む形にします。注意: コードリポジトリに認証情報を埋め込まないことが最優先です。チームへの安全な公開方針については、社内でChatGPTが使えないときの代替手段でガバナンス面の整理も参考にしてください。
slowapiでレート制限を追加してOllamaを過負荷から守る
LLMの推論はGPU・CPU・メモリを大量に消費します。1ユーザーが短時間に大量リクエストを送ると、他のメンバーのレスポンスが遅延したりOllamaが応答不能に陥ったりします。slowapiでIPアドレスまたはAPIキー単位の上限を設けます。
1. slowapiの初期設定
from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
2. エンドポイントにレート制限デコレータを付ける
「1分あたり10リクエスト」に制限する例です。@app.post("/v1/chat") @limiter.limit("10/minute") async def chat( request: Request, team: str = Depends(verify_api_key), ): body = await request.json() async with httpx.AsyncClient(timeout=180) as client: resp = await client.post(f"{OLLAMA_BASE}/api/chat", json=body) return resp.json()
3. レート制限の確認
$ for i in $(seq 1 12); do CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:8000/v1/chat \ -H "Content-Type: application/json" \ -H "X-API-Key: devkey123" \ -d '{"model":"llama3.3:8b","messages":[{"role":"user","content":"test"}],"stream":false}') echo "req${i}: ${CODE}" done req1: 200 req2: 200 ... req10: 200 req11: 429 req12: 429
key_funcをAPIキーベースにするカスタム関数を実装し、チーム名に応じて上限値を切り替えます。たとえば開発チームは「20/minute」、一般ユーザーは「5/minute」のように設定できます。
structlogで構造化ロギングとリクエストIDを実装する
print()や標準のloggingモジュールでは、チーム名・モデル名・レスポンス時間を一度にまとめて記録するのが面倒です。structlogを使えばJSON形式でログが出力され、jqで任意のフィールドを即座に絞り込めます。
1. structlogの設定
main.pyの先頭付近に追加します。
import structlog import uuid import time structlog.configure( processors=[ structlog.processors.TimeStamper(fmt="iso"), structlog.processors.JSONRenderer(), ] ) log = structlog.get_logger()
2. ミドルウェアでリクエストIDとレスポンス時間を自動付与する
from starlette.middleware.base import BaseHTTPMiddleware class LoggingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): req_id = str(uuid.uuid4())[:8] start = time.monotonic() response = await call_next(request) elapsed_ms = round((time.monotonic() - start) * 1000) log.info( "http_request", req_id=req_id, method=request.method, path=request.url.path, status=response.status_code, elapsed_ms=elapsed_ms, ) return response app.add_middleware(LoggingMiddleware)
3. ログの確認と活用
実際のログ出力はJSON形式で以下のように出力されます。{"event": "http_request", "req_id": "a3f1c9e2", "method": "POST", "path": "/v1/chat", "status": 200, "elapsed_ms": 1842, "timestamp": "2026-07-26T10:00:01Z"}
$ journalctl -u ollama-api -o cat | jq 'select(.elapsed_ms > 5000)'
log.info("chat_request", team=team, model=body.get("model"))を追加すれば、チーム名・モデル名もログに記録されます。月次でチームごとの利用量を集計するスクリプトをjq+awkで簡単に書けます。
systemdとDocker Composeで本番デプロイする
開発段階ではuvicorn main:app --reloadで十分ですが、本番ではサーバー再起動後の自動起動と、プロセス異常終了時の自動再起動が必要です。
1. systemdサービスとして登録する
$ sudo tee /etc/systemd/system/ollama-api.service << 'EOF' [Unit] Description=Ollama FastAPI Gateway After=network.target ollama.service Requires=ollama.service [Service] User=ubuntu WorkingDirectory=/opt/ollama-api EnvironmentFile=/opt/ollama-api/.env ExecStart=/opt/ollama-api/venv/bin/uvicorn main:app \ --host 0.0.0.0 --port 8000 --workers 2 Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF
EnvironmentFileに指定した/opt/ollama-api/.envにはAPI_KEYS=devkey123:dev_team,opskey456:ops_teamのように認証情報を書き、パーミッションを600に設定してください。
# enable and verify the service $ sudo systemctl daemon-reload $ sudo systemctl enable --now ollama-api $ sudo systemctl status ollama-api * ollama-api.service - Ollama FastAPI Gateway Active: active (running) since 2026-07-26 10:00:00 JST Main PID: 12345 (uvicorn)
2. Docker Composeで運用する場合
コンテナ化するケースではdocker-compose.ymlを用意します。
version: "3.9" services: ollama-api: build: . ports: - "8000:8000" env_file: .env environment: - OLLAMA_BASE=http://host.docker.internal:11434 extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped
3. ufwでアクセス元を限定する
どちらの方式でも、ポート8000を社内IPレンジのみに制限します。$ sudo ufw allow from 192.168.1.0/24 to any port 8000 comment "ollama-api internal" $ sudo ufw reload $ sudo ufw status numbered | grep 8000 [ 5] 8000 ALLOW IN 192.168.1.0/24
よくあるトラブルと対処法
httpx.ReadTimeout が頻発する大きなモデルや長いプロンプトではOllamaの処理に数十秒かかります。
httpx.AsyncClient(timeout=180)の値を処理内容に応じて300秒程度まで延ばしてください。ストリーミングモード("stream": true)を使う場合はtimeout=NoneにしてStreamingResponseで返す構成が安全です。正規ユーザーに429エラーが頻発する
NginxやロードバランサーをFastAPIの前段に置いていると、クライアントIPが全員NginxのIPに統一されます。
get_remote_addressの代わりにX-Forwarded-ForヘッダーをパースするカスタムKey関数を実装してください。Ollamaに接続できない(Connection refused)
OLLAMA_HOST=127.0.0.1設定後にOllamaのサービス再起動を忘れているケースが大半です。systemctl status ollamaでActiveを確認後、curl http://127.0.0.1:11434/api/tagsで疎通確認してください。FastAPI起動時にアドレスが使用中のエラー
8000番をすでに使うプロセスがある場合は
ss -tlnp | grep 8000で確認し、--port 8080などに変更します。Docker内からOllamaに接続できない
LinuxのDocker本番環境では
host.docker.internalが自動で解決されない場合があります。extra_hosts: ["host.docker.internal:host-gateway"]の記述が必要です。ホストGateway IPはdocker network inspect bridge | jq '.[0].IPAM.Config[0].Gateway'で確認できます。
まとめ:OllamaカスタムAPIサーバーの要点
この記事ではOllamaをFastAPIでラップしてチーム向けAIマイクロサービスを構築する手順を解説しました。APIキー認証・レート制限・構造化ロギングの3点を追加するだけで、Ollamaを本番運用に耐えるAPIゲートウェイとして仕上げられます。主要な操作をまとめます。| 項目 | コマンド・設定 | 備考 |
|---|---|---|
| 仮想環境作成 | python3 -m venv /opt/ollama-api/venv |
システムPythonを汚さない |
| 依存ライブラリ一括インストール | pip install fastapi uvicorn httpx slowapi structlog |
requirements.txtで管理推奨 |
| OllamaのLocalhost縛り | Environment="OLLAMA_HOST=127.0.0.1" |
override.confに記載後restart |
| FastAPI起動(開発) | uvicorn main:app --host 0.0.0.0 --port 8000 --reload |
本番では--reloadを外す |
| レート制限デコレータ | @limiter.limit("10/minute") |
slowapiで1行追加するだけ |
| systemdサービス有効化 | sudo systemctl enable --now ollama-api |
自動起動・自動再起動を実現 |
| ファイアウォール設定 | sudo ufw allow from 192.168.1.0/24 to any port 8000 |
社内IP以外をブロック |
| 遅延リクエスト抽出 | journalctl -u ollama-api -o cat | jq 'select(.elapsed_ms > 5000)' |
structlogのJSON活用例 |
FastAPIでOllamaを本番運用する2日間ハンズオン
APIゲートウェイの設計から認証・レート制限・監視・デプロイまで、実機GPU環境で手を動かしながら体系的に習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:OllamaとWhisperでローカル音声文字起こし・要約パイプラインを構築する方法|会議録をローカルLLMで議事録に自動変換する手順
- この記事の属するカテゴリ:ローカルLLMへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます