Google Cloud 上に Langfuse v3 本番環境をセルフホスティングしてみた
はじめに
こんにちは。
クラウドエース株式会社 第一開発部の髙木です。
ここ数年のうちにすさまじい勢いで大規模言語モデル(以下、LLM) が発展しており、様々な LLM サービスが提供されるようになりました。
一方で、LLM の生成の監視やプロンプトの管理については、まだ世の中に浸透していないと考えています。
本記事ではそのような LLM の監視・管理に特化したアプリケーションである Langfuse の新バージョンのデプロイ手順を紹介します。
対象読者
以下の対象読者を想定しています。
- Langfuse v3 を本番環境でデプロイしたい方
- Cloud SQL + Cloud Run 構成の Langfuse v2 アプリケーションを v3 にアップデートしたい方
- Google Cloud でサービスをデプロイした経験がある方
Langfuse v3 はシステム構成が複雑なため、1 箇所設定を間違えるとデプロイできないことがあります。
そのため、Cloud Run や Google Compute Engine (以下 GCE) でエラーが出た際に、ログを見て自力で解決できるのが望ましいです。
また、本記事では細かい設定の説明は省いています。ネットワークや Google Cloud の知識がある程度ないと置いていかれることが予想されます。
一方、Langfuse v2 は構成が簡素なので、初心者でもとっかかりやすいです。
「 Langfuse について」の章を読んで少しでも興味が湧いた方は、ぜひ弊社の遠矢の記事をご覧ください。
Langfuse について
Langfuse とは
Langfuse は、LLM アプリケーションのデバッグ、分析、反復を行うのに役立つエンジニアリング プラットフォームです。
Langfuse は以下の機能を提供します。
- データの追跡と分析
a. プロンプトと LLM の応答を追跡
b. ユーザーセッションの記録
c. コストと利用状況の監視 - デバッグ機能
a. 詳細なトレース機能
b. プロンプトのバージョン管理
c. エラーの検出と分析 - 評価機能
a. LLM の出力品質の評価
b. パフォーマンス指標の測定
トレースの例
Langfuse v3 の特徴
2024 年 12 月 6 日に Langfuse v3 がリリースされました。
Langfuse の新バージョンを導入することで、スケーラビリティ、パフォーマンスの向上などが見込めるようになりました。
これは主にシステムの構成を変更したことで実現しています。
Langfuse v2 では、アプリケーションが 1 つ、ストレージが 1 つというシンプルな構成でした。ストレージは PostgreSQL を使用します。
Langfuse v3 では、アプリケーションが 2 つ、ストレージが 4 つの構成になっています。ストレージは PostgreSQL に加え、ClickHouse、Redis、Blob Storage を使用します。4種類のストレージを使い分けることで、大量のイベントを高い信頼性で処理できるようになります。
Google Cloud での構成
実際に Google Cloud でセルフホスティングする際の構成を説明します。
Langfuse v3 を Google Cloud に導入する方法として、最初に思いつくのは Google Kubernetes Engine(以下、GKE) を使用したセルフホスティングです。GKE でセルフホスティングする方法は GAO 株式会社の遠矢がこちらにまとめております。
SaaS 版 ClickHouse の ClickHouse Cloud は垂直スケーリングします。記事執筆時点で垂直スケーリングする Google Cloud のコンピューティングプロダクトは GKE しかないため、GKE を使用したセルフホスティングは Langfuse の推奨構成になると考えています。
しかし、GKE の費用は割高であるため、v3 へのアップデートを躊躇している方もいらっしゃると思います。
そこで、私は GKE を使用せずに、セルフホスティングできる構成を考えました。
以下は、本記事で紹介する構成のアーキテクチャ図です。
各コンポーネントに対する Google Cloud プロダクトの選定理由を記載します。
コンポーネント | Google Cloud プロダクト | 選定理由 |
---|---|---|
Langfuse Web | Cloud Run | サーバーレスコンテナサービス 自動スケーリング |
PostgreSQL | Cloud SQL | フルマネージドデータベースサービス |
Langfuse Worker | GCE | 高パフォーマンス 仮想マシン 固定の内部 IP アドレス |
Redis | Memorystore for Redis | フルマネージドキャッシュサービス |
ClickHouse | Managed Instance Group (以下、MIG) |
スケーリング可能 |
Blob Storage | Google Cloud Storage (以下、GCS) |
マネージドストレージサービス 可用性・耐久性 |
Langfuse v2 のセルフホスティング
アーキテクチャ図を見比べるとわかる通り、Langfuse v2 環境を構築したのちに v3 環境を構築できます。
そのため、まずは Langfuse v2 のセルフホスティングをします。
Langfuse v2 は、弊社の遠矢が執筆した以下の記事の通りに構築します。
サブネット名は test-subnet にしています。
ただし、以下の項目を変更します。
DATABASE_URL
の文字列を変更する。
まず、CloudSQL の内部 IP アドレスをメモしておきます。
シークレットに登録する DATABASE_URL
の文字列を以下のように変更します。
postgresql://postgres:PASSWORD@INTERNAL_IP/DB_NAME
それぞれの値は次のように設定します。
項目 | 値 |
---|---|
PASSWORD | db のパスワード |
INTERNAL_IP | 内部 IP アドレス |
DB_NAME | 任意で設定したデータベース名 |
2
Docker のタグはCloud Run の [コンテナイメージ] を docker.io/langfuse/langfuse:2
にします。
現在 v3 環境ですので、latest
を選ぶとデプロイ失敗します。
IAM の設定
以下のリンクから Langfuse v2 で作成したサービスアカウントに追加の権限を与えます。
以下のロールを追加します。
ロール | 追加理由 |
---|---|
ログ書き込み | Langfuse Worker のログの確認のため |
ファイアウォール ルールの設定
以下のリンクからファイアウォール ルールを作成します。
- Langfuse Web のファイアウォール ルール
項目 | 値 |
---|---|
名前 | allow-langfuse |
ネットワーク | test-vpc |
ターゲットタグ | langfuse-worker |
送信元 IPv4 範囲 | test-subnet の IPv4 範囲 |
プロトコルとポート | tcp: 3030 |
- Clickhouse のファイアウォール ルール
項目 | 値 |
---|---|
名前 | allow-clickhouse |
ネットワーク | test-vpc |
ターゲットタグ | clickhouse |
送信元 IPv4 範囲 | test-subnet の IPv4 範囲 |
プロトコルとポート | tcp: 8123,9000 |
- ヘルスチェックのファイアウォール ルール
項目 | 値 |
---|---|
名前 | allow-health-check |
ネットワーク | test-vpc |
ターゲットタグ | clickhouse |
送信元 IPv4 範囲 | 35.191.0.0/16, 130.211.0.0/22 |
プロトコルとポート | tcp: 8123 |
Memorystore for Redis のセットアップ
以下のリンクから Memorystore for Redis のインスタンスを立ち上げます。
公式ドキュメントによると、10 万イベント/分あたりの容量は 1 GB です。
容量は適宜調整します。
項目 | 値 |
---|---|
ティア | 基本 |
容量 | 1GB |
リージョン | asia-northeast1 |
ネットワーク | test-vpc |
接続 | プライベート サービス アクセス |
Set an IP range name | test-vpc-ip-range |
AUTH を有効にする | オン |
転送中の暗号化を有効にする | オフ |
以下のように構成を追加します。
以下の値をメモしておきます。後ほど Secret Manager に登録します。
値 | 記載箇所 |
---|---|
プライマリ エンドポイント | [接続] |
AUTH 文字列 | [セキュリティ] |
Blob Storage の準備
以下のリンクから GCS バケットを作成します。
項目 | 値 |
---|---|
ロケーションタイプ | Region |
リージョン | asia-northeast1 |
次に HMAC キーを発行します。
GCS の [設定] から [相互運用性] へいき、HMAC キーを作成します。
通常はサービスアカウントのアクセスキーを使用します(参考リンク)。
以下の値をメモしておきます。
値 | 記載箇所 |
---|---|
GCS バケット名 | [バケット] |
HMAC アクセスキー | [設定] -> [相互運用] -> [HMAC キー] |
HMAC シークレット | [設定] -> [相互運用] -> [HMAC キー] |
Cloud NAT のセットアップ
GCE でデプロイした langfuse-worker と GCS バケットを接続するために、Cloud NAT を構成します。
以下のリンクから Cloud NAT ゲートウェイを作成します。
項目 | 値 |
---|---|
NAT タイプ | 公開 |
ネットワーク | test-vpc |
リージョン | asia-northeast1 |
Cloud NAT マッピング | VM インスタンス、GKE ノード、サーバーレス |
MIG のセットアップ
インスタンステンプレートの作成
以下のリンクからインスタンステンプレートを作成します。
項目 | 値 |
---|---|
リージョン | asia-northeast1 |
マシンタイプ | e2-medium |
ブートディスク | Container Optimized OS |
サービスアカウント | v2 で作成したサービスアカウント |
ネットワークタグ | clickhouse |
ネットワーク | test-vpc |
サブネット | test-subnet |
外部 IPv4 アドレス | なし |
また、起動スクリプトとして以下のコードを使用しています。
このコードでは /home でコンテナの起動とログの設置をしています。
#!/bin/bash
sudo mkdir -p /home/ch_data /home/ch_logs
cd /home
# Dockerの設定ファイルを作成
cat <<EOF > /etc/docker/daemon.json
{
"live-restore": true,
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
# Dockerサービスをリロード
systemctl reload docker
# Clickhouseコンテナの起動
docker run --rm --name clickhouse-server \
-v $(realpath ./ch_data):/var/lib/clickhouse/ \
-v $(realpath ./ch_logs):/var/log/clickhouse-server/ \
-e CLICKHOUSE_USER=clickhouse \
-e CLICKHOUSE_PASSWORD=clickhouse \
-d --ulimit nofile=262144:262144 \
-p 8123:8123 \
-p 9000:9000 \
clickhouse/clickhouse-server
この起動スクリプト内の docker に設定する環境変数は変更できます。
以下の値をメモしておきます。
項目 | 値 |
---|---|
CLICKHOUSE_USER |
ClickHouse ユーザー名 (上記コードでは"clickhouse") |
CLICKHOUSE_PASSWORD |
ClickHouse パスワード (上記コードでは"clickhouse") |
MIG の作成
以下のリンクから MIG を作成します。
項目 | 値 |
---|---|
インスタンス数 | 3 |
場所 | マルチゾーン |
ステートフル構成 | ディスクと内部 IP をステートフルに |
ポートマッピング | http->8123, clickhouse->9000 |
インスタンス数などの構成は ClickHouse Cloud に合わせています。詳しくは ClickHouse のスケーリングをご覧ください。
Load Balancer のセットアップ
以下のリンクから内部アプリケーション Load Balancer を作成します。
項目 | 値 |
---|---|
ロードバランサのタイプ | ネットワーク ロードバランサ(TCP / UDP / SSL) |
プロキシまたはパススルー | パススルー ロードバランサ |
インターネット接続または内部 | 内部 |
リージョン | asia-northeast1 |
ネットワーク | test-vpc |
バックエンド サービス
項目 | 値 |
---|---|
バックエンドタイプ | インスタンスグループ |
プロトコル | TCP |
インスタンス グループ | 作成した MIG |
ヘルスチェックのプロトコル | tcp |
ヘルスチェックのポート | 8123 |
フロントエンド サービス
項目 | 値 |
---|---|
サブネットワーク | test-subnet |
ポート | 複数 |
ポート番号 | 8123, 9000 |
以下の値をメモしておきます。
値 | 記載箇所 |
---|---|
Load Balancer の IP アドレス | Load Balancer のフロントエンド |
Secret Manager の追加
Langfuse v3 で追加された環境変数をシークレットに登録します。
名前 | シークレットの値 |
---|---|
ENCRYPTION_KEY |
openssl rand -hex 32 で生成した値 |
CLICKHOUSE_URL |
http://LB_IP:8123 |
CLICKHOUSE_MIGRATION_URL |
clickhouse://LB_IP:9000 |
CLICKHOUSE_USER |
CLICKHOUSE ユーザー名 |
CLICKHOUSE_PASSWORD |
CLICKHOUSE パスワード |
LANGFUSE_S3_EVENT_UPLOAD_BUCKET |
GCS バケット名 |
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID |
HMAC アクセスキー |
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY |
HMAC シークレット |
REDIS_CONNECTION_STRING |
redis://default:REDIS_AUTH@REDIS_HOST:6379 |
ただし、CLICKHOUSE_URL
とCLICKHOUSE_MIGRATION_URL
、REDIS_CONNECTION_STRING
の値は次のように設定します。
項目 | 値 |
---|---|
LB_IP |
Load Balancer の IP アドレス |
REDIS_HOST |
Memorystore のプライマリエンドポイント |
REDIS_AUTH |
Memorystore の AUTH 文字列 |
環境変数の詳細は公式ドキュメントを参照してください。
Langfuse v3 のセルフホスティング
langfuse web のデプロイ
以下のリンクから Cloud Run へ行きます。
Langfuse v2 のサービスをクリックし、[新しいディビジョンの編集とデプロイ] をクリックします。
- コンテナイメージの URL を
docker.io/langfuse/langfuse:3
にします。 - 環境変数の設定では以下のように設定します。
- シークレットは Secret Manager で設定した値を全て参照します。
ここまで完了したら、[デプロイ]をクリックします。
デプロイが失敗した場合、まずは環境変数とシークレットが間違えてないか確認すると良いです。
langfuse worker のデプロイ
以下のリンクから Langfuse worker をデプロイします。
項目 | 値 |
---|---|
リージョン | asia-northeast1 |
マシンタイプ | e2-medium |
ブートディスク | Container Optimized OS |
ネットワークタグ | langfuse-worker |
ネットワーク | test-vpc |
サブネット | test-subnet |
外部 IPv4 アドレス | なし |
サービスアカウント | v2 で作成したサービスアカウント |
アクセス スコープ | すべての Cloud API に完全アクセス権を許可 |
Logging を有効にする | true |
また、起動スクリプトとして以下のコードを使用しています。
#!/bin/bash
# 環境変数ファイルのディレクトリを作成
mkdir -p /var/run/containers
PROJECT_ID= #プロジェクトIDを入力
ACCESS_TOKEN="$(curl -s -H 'Metadata-Flavor: Google' \
'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token' \
| jq -r '.access_token')"
echo $ACCESS_TOKEN
SECRET_NAME=("DATABASE_URL" "SALT" "ENCRYPTION_KEY" "CLICKHOUSE_MIGRATION_URL" "CLICKHOUSE_URL" "CLICKHOUSE_USER" "CLICKHOUSE_PASSWORD" "REDIS_CONNECTION_STRING" "LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID" "LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY" "LANGFUSE_S3_EVENT_UPLOAD_BUCKET")
for SECRET in "${SECRET_NAME[@]}"
do
VALUE=$(curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"https://secretmanager.googleapis.com/v1/projects/$PROJECT_ID/secrets/$SECRET/versions/latest:access" \
| jq -r '.payload.data' | base64 --decode)
echo "$SECRET:$VALUE"
echo "$SECRET=$VALUE" >> /var/run/containers/env.list
done
# 固定値の環境変数を追加
cat >> /var/run/containers/env.list << EOF
CLICKHOUSE_CLUSTER_ENABLED=false
LANGFUSE_S3_EVENT_UPLOAD_REGION=auto
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=https://storage.googleapis.com
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true
LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
EOF
# ファイルのパーミッションを設定
chmod 600 /var/run/containers/env.list
# Langfuse Workerコンテナの起動
docker run --rm --name langfuse-worker \
--env-file /var/run/containers/env.list \
-p 3030:3030 \
-a STDOUT \
langfuse/langfuse-worker:3
PROJECT_ID= #プロジェクトIDを入力
のところにプロジェクトIDを入力してください。
VM を作成したら Cloud Logging でログを見ます。
startup-script:
でログを検索し、 Secret manager で設定した値がログに出ているか確認します。
その他にエラーが出た場合、はエラー内容に応じて対処します。
Redis connection error: ERR unknown command 'client'
のエラーは無視して良いです。(Memorystore for Redis には client コマンドがないためこのようなエラーが出るようです。)
動作確認
Langfuse の初期設定
Langfuse の初期設定を v2 で完了させている場合はスキップしても構いません。
- Cloud Run の URL から Langfuse にアクセスします。
- サインアップ & サインインします。
- 組織を作成します。
- プロジェクトを作成します。
API キーの取得
API キーの取得を v2 で完了させている場合はスキップしても構いません。
[Settings]→[API Keys]→[Create new API keys] の順にクリックします。
以下の値をメモします。
- Secret Key
- Public Key
- Host
トレースを作成
トレースは通常 LLM のモデルに回答をリクエストする際に発生しますが、今回は Langfuse に対して直接 HTTP リクエストを送る検証をします。
ターミナルで以下のコマンドを実行します。
ただし、Secret Key
, Public Key
, Host
には先ほどメモした値を入力します。
curl -X POST "{Host}/api/public/ingestion" \
-u "{Public Key}:{Secret Key}" \
-H "Content-Type: application/json" \
-d '{
"batch": [
{
"type": "trace-create",
"id": "'$(uuidgen | tr "[:upper:]" "[:lower:]")'",
"timestamp": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'",
"metadata": null,
"body": {
"id": "'$(uuidgen | tr "[:upper:]" "[:lower:]")'",
"name": "string",
"timestamp": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'",
"public": false
}
}
],
"metadata": null
}'
ステータス201のレスポンスが返ってきたら成功です。
トレースの確認
Langfuse の画面に先ほど作成されたトレースが表示されているか確認します。
表示されない場合、Cloud Logging でエラーを確認し、解決します。
※トレースの表示までの流れは以下のようになっています。デバッグに活用ください。
おわりに
最後までお読みいただき、ありがとうございます。
本記事の構成でセルフホスティングを実施した場合、GKE を使用する場合と比較してコストを大幅に抑えることができます。ただし、ClickHouse で自動スケーリングができない点には注意しなければなりません。
これらのことから、以下のユースケースに適しています。
- 使用量の増加や減少が緩やかで、アラートで手動スケーリングをする運用で問題ないケース
- Langfuse の使用量を予測でき、スケジュールされた Cloud Functions などを使用してスケーリングできるケース
- PoC や開発目的で、Langfuse v3 を小規模で開始したいケース
ClickHouse の部分については、必要に応じて GKE や ClickHouse Cloud への移行も可能で、柔軟な拡張性を備えています。
インフラ構築の知見を深めながらの実装であったため、完成までに時間を要しましたが、得られた知見は非常に貴重なものとなりました。本記事が、Langfuse v3 のセルフホスティングを検討されている方々の参考になれば幸いです。
Discussion