OllamaとFastAPIでカスタムAIマイクロサービスを構築する方法|認証・レート制限・ロギングを追加してチームAPIを本番運用する

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)ローカルLLM > OllamaとFastAPIでカスタムAIマイクロサービスを構築する方法|認証・レート制限・ロギングを追加してチームAPIを本番運用する
「OllamaのデフォルトAPIポートをそのままチームに公開するのは認証がなくて不安だ」
「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で即座に絞り込める


OllamaとFastAPIでカスタムAIマイクロサービスを構築する方法|認証・レート制限・ロギングを追加してチームAPIを本番運用する

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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

3.10以下の場合は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 }

OllamaからのレスポンスがFastAPI経由で返れば疎通成功です。このままでは誰でも叩けるため、次のステップでAPIキー認証を追加します。

モデル選定に迷う場合は、ローカル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

本番ではSystemdのEnvironmentFileや.envファイルから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

11件目以降が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"}

5秒を超えた遅延リクエストだけを抽出する場合は次のコマンドが使えます。

$ 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

これでFastAPIの8000番は社内ネットワークからのみアクセス可能になります。

よくあるトラブルと対処法

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のアーキテクチャは拡張性が高く、今回の構成に「利用量の月次集計エンドポイント」「部署ごとのモデルアクセス制限」「Webhookによる通知」を追加することも難しくありません。既製のプロキシツールでは届かない業務固有のロジックを自社コードで実装できる点が、FastAPIラッパーを選ぶ最大の理由です。

FastAPIでOllamaを本番運用する2日間ハンズオン

APIゲートウェイの設計から認証・レート制限・監視・デプロイまで、実機GPU環境で手を動かしながら体系的に習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。

>> ローカルAIマスターセミナーの詳細を確認する
ローカルLLMの構築・運用に関する関連記事もあわせて参考にしてください。

Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。