OCI無料サーバーでLLMをOpenAI互換APIとして公開した話

スポンサーリンク

 

OCI無料サーバーでLLMをOpenAI互換APIとして公開した話

OCIの無料枠サーバーにLLMを載せて、OpenAI互換のAPIとして外部に公開するまでの記録です。初心者がつまずきながら完成させた手順をそのまま残しています。


構成

項目 内容
サーバー OCI Always Free(Ubuntu、ARMアーキテクチャ)
LLMサーバー shimmy v1.8.1
モデル Hy-MT2-1.8B-GGUF(Tencent製多言語翻訳モデル)
リバースプロキシ nginx + Let's Encrypt(SSL)
認証プロキシ FastAPI + uvicorn(自作)
ドメイン 独自ドメイン + Cloudflare

STEP 1|システム更新とツールインストール

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget nginx certbot python3-certbot-nginx python3 python3-pip

huggingface-cli(モデルダウンロード用)をインストール:

pip3 install huggingface_hub --break-system-packages
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

STEP 2|shimmyをビルド(ARMの場合はコンパイルが必要)

OCIの無料枠はARMアーキテクチャなので、x86用バイナリは動きません。Rustでソースからビルドします。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
sudo apt install -y build-essential cmake pkg-config libssl-dev clang libclang-dev gcc-aarch64-linux-gnu libc6-dev
cargo install shimmy

ビルドには10〜20分かかります。


STEP 3|モデルをダウンロード

mkdir -p ~/models
hf download tencent/Hy-MT2-1.8B-GGUF \
  --include "*.gguf" \
  --local-dir ~/models/

STEP 4|モデルを認識させてテスト起動

shimmy discover
shimmy list
shimmy serve --bind 127.0.0.1:11435

別ターミナルで確認:

curl -s http://127.0.0.1:11435/v1/models | python3 -m json.tool

STEP 5|systemdサービスとして登録

sudo tee /etc/systemd/system/shimmy.service > /dev/null << 'EOF'
[Unit]
Description=Shimmy AI Inference Server
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu
ExecStart=/home/ubuntu/.cargo/bin/shimmy serve --bind 127.0.0.1:11435
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable shimmy
sudo systemctl start shimmy

STEP 6|nginxでリバースプロキシ設定

sudo tee /etc/nginx/sites-available/shimmy > /dev/null << 'EOF'
server {
    listen 80;
    server_name corota.pp.ua;

    location / {
        proxy_pass http://127.0.0.1:11436;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/shimmy /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

STEP 7|SSL証明書の取得

ハマりポイント:OCIのiptablesとCloudflareのプロキシ

certbotが失敗する原因が2つありました。

①iptablesのルール順序問題

OCIのデフォルト設定ではREJECTルールが先に来ているため、ポート80/443を追加しても後ろに入ってしまいブロックされます。

# 既存のルールを確認
sudo iptables -L INPUT --line-numbers

# REJECTより前にポート80/443を追加
sudo iptables -I INPUT 5 -m state --state NEW -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT 5 -m state --state NEW -p tcp --dport 443 -j ACCEPT
sudo netfilter-persistent save

②CloudflareのProxyをOFFにする

CloudflareのAレコードがオレンジ☁️(Proxy)になっていると、Let's EncryptがCloudflareのIPにアクセスしてしまい認証に失敗します。certbot実行前にDNS onlyのグレー🌥️に変更してください。

sudo certbot --nginx -d corota.pp.ua

取得後はCloudflareのプロキシを戻してもOKです。


STEP 8|APIキー認証プロキシを追加

shimmyはデフォルトで認証なしのため、FastAPIで簡易プロキシを作ってAPIキー認証を追加しました。ストリーミング・キャンセル対応済みです。

pip3 install fastapi uvicorn httpx --break-system-packages

cat > ~/shimmy-proxy.py << 'EOF'
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, StreamingResponse
import httpx
import asyncio

app = FastAPI()

API_KEY = "your-api-key-here"  # ここを変更

async def check_key(request: Request):
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer ") or auth[7:] != API_KEY:
        return False
    return True

@app.post("/v1/chat/completions")
async def proxy(request: Request):
    if not await check_key(request):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)
    body = await request.json()
    is_stream = body.get("stream", False)

    if is_stream:
        async def generate():
            try:
                async with httpx.AsyncClient(timeout=300) as client:
                    async with client.stream("POST", "http://127.0.0.1:11435/v1/chat/completions", json=body) as res:
                        async for chunk in res.aiter_bytes():
                            if await request.is_disconnected():
                                break
                            yield chunk
            except asyncio.CancelledError:
                pass
            except Exception:
                pass
        return StreamingResponse(generate(), media_type="text/event-stream")
    else:
        try:
            async with httpx.AsyncClient(timeout=300) as client:
                res = await client.post("http://127.0.0.1:11435/v1/chat/completions", json=body)
            return JSONResponse(res.json())
        except asyncio.CancelledError:
            return JSONResponse({"error": "Cancelled"}, status_code=499)

@app.get("/v1/models")
async def models(request: Request):
    if not await check_key(request):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)
    async with httpx.AsyncClient(timeout=30) as client:
        res = await client.get("http://127.0.0.1:11435/v1/models")
    return JSONResponse(res.json())
EOF

systemdに登録:

sudo tee /etc/systemd/system/shimmy-proxy.service > /dev/null << 'EOF'
[Unit]
Description=Shimmy Proxy
After=network.target shimmy.service

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu
ExecStart=/home/ubuntu/.local/bin/uvicorn shimmy-proxy:app --host 127.0.0.1 --port 11436
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable shimmy-proxy
sudo systemctl start shimmy-proxy

完成した構成

外部クライアント
    ↓ HTTPS (443)
nginx(SSL終端)
    ↓ HTTP
shimmy-proxy(FastAPI / ポート11436)
    ↓ 認証チェック
shimmy(LLMサーバー / ポート11435)

APIの使い方

ベースURL: https://your-domain/v1
APIキー:   Bearer your-api-key-here
モデル名:  hy-mt2-1.8b-q4-k-m

OpenAI互換なのでOpenAIのSDKやライブラリがそのまま使えます。


ハマりポイントまとめ

  1. ARMアーキテクチャ → x86バイナリは動かない。Rustでビルドが必要
  2. clangヘッダー不足libclang-dev を追加インストールで解決
  3. iptablesのルール順序 → REJECTより前にACCEPTルールを入れる必要がある
  4. CloudflareのProxy → certbot実行前はDNS onlyに切り替える
  5. shimmyはデフォルトでストリーミング → プロキシで stream: false を適切にハンドリングする
  6. Hy-MT2はシステムプロンプト非対応 → モデルのREADMEに明記されている。userメッセージに翻訳指示を入れる

モデルについて

Hy-MT2はTencent製の翻訳特化モデルです。汎用チャットには向いていませんが、33言語対応の翻訳精度は高いです。

プロンプト例:

Translate the following text into Japanese. Note that you should only output the translated result without any additional explanation:

{翻訳したいテキスト}

OCI Always Free + shimmy + Let's Encrypt で完全無料のLLM APIサーバーが作れました。

この記事は参考情報をAIに投げたあと加筆したものです。

スポンサーリンク

コメント