はじめに
本記事は、【イチから学ぶ! 初めての Azure と生成 AI】セミナーのセッション『AI アプリのクラウド運用入門 ~App Service と Container Apps の選び方と管理のポイント』の解説記事です。
Azure を活用した生成 AI アプリケーション開発の際に役立つポイントをご紹介します。実装時に直面しがちなハマりどころやその解決策を、全 5 回に分けてお届けします。
本記事では、SLM を題材として、Azure で稼働するアプリケーションをサイドカーコンテナとどのように連携するかについて、一例をご紹介します。
本記事でご紹介するサンプルアプリケーションのソースコードは、以下のリポジトリで公開しています。
SLM をサイドカーコンテナで稼働させる方法
ファインチューニングしたモデルをサイドカーコンテナで実行し、アプリケーションと連携する手法は、生成 AI アプリケーションの開発で注目されるユースケースの一つです。
これにより、以下の利点が得られます。
- コスト削減
- モデル精度の最適化
- 独立したコンテナとしてのスケーリング
また、監視・ログ等のコンテナアプリケーションをサイドカーに配置し、メインのコンテナアプリケーションと疎結合に構成することもユースケースの一つです。
セミナーでは、Azure App Service や Container Apps を利用し、SLM(Specialized Language Model)をや OpenTelemetry コレクターをサイドカーコンテナとして稼働させる具体的な方法について解説しました。こうした実装により、モデルを既存のアプリケーションにシームレスに統合できます。
サイドカーコンテナとは
サイドカーコンテナとは、メインのアプリケーションコンテナと同じ実行環境(例えば、同一の Pod やホスト)内で動作する補助的なコンテナです。
その主な役割は、以下の通りです。
-
補助機能の提供: ログの収集、監視、トレース、セキュリティチェックなど、アプリケーション本体の機能には直接関与しないが、運用上不可欠な機能を提供します。
-
独立性と柔軟性: メインコンテナのコードを変更せずに、新たな機能やサポートツールを追加できるため、システム全体のメンテナンス性や拡張性が向上します。
-
スケーラビリティ: 補助的な処理(例えば、AI モデルの推論や監視ログの収集)を独立したコンテナとして分離することで、必要に応じて個別にスケールアウトが可能となり、リソースの効率的な利用が実現します。
本記事のサンプルアプリケーションでは、SLM や OpenTelemetry コレクターをサイドカーコンテナとして実行することで、アプリケーション本体と密結合せずに、AI モデルの運用や監視機能を提供しています。
メインアプリケーションとサイドカーコンテナとの通信
サイドカーコンテナは、メインのアプリケーションコンテナと同一ホストまたは同一 Pod 内で稼働し、補助的な機能を提供する役割を担います。
これらのコンテナ間の通信は、システム全体の機能性と監視、ロギング、セキュリティといった補助機能の実装において有益です。
基本的な通信パターン
- localhost 経由の通信
サイドカーコンテナは、同じホスト上で実行されるため、メインコンテナは通常 localhost を介してサイドカーのサービス(例: モデル推論、ロギング、監視エージェント)にアクセスします。
例えば、メインコンテナ内のアプリケーションが、http://localhost:5001
のエンドポイントにリクエストを送ることで、サイドカーで稼働している AI モデルの推論機能にアクセスすることが可能、といった具合です。
- コンテナ内ポートの公開
サイドカーコンテナは、必要なポートを公開することで、メインコンテナが指定したポート経由でサービスを呼び出すことができます。
- 実装例
以下は、Python ベースの簡単な例です。メインアプリケーションがサイドカーコンテナに配置された推論サービスへ HTTP リクエストを送信する場合のコード例です。
import requests
def get_inference(input_data):
# サイドカーコンテナは同一ホスト上のポート5001で稼働している前提
sidecar_url = "http://localhost:5001/api/inference"
response = requests.post(sidecar_url, json={"data": input_data})
return response.json()
# アプリケーション内で推論結果を取得する例
result = get_inference({"text": "生成 AI による自然言語処理の例"})
print("Inference Result:", result)
サンプルアプリケーション
今回は、サイドカーコンテナに SLM (phi-3) 、OpenTelemetry Collector をそれぞれ配置し、メインのアプリケーションからリクエストを投げる構成としました。アプリケーションは、Web App for Containers、Container Apps それぞれで稼働します。
コード例
具体的なコードは以下の通りです。
class SidecarService:
def __init__(self):
self.url = os.getenv("SIDECAR_SLM_URL", "http://localhost:11434/api/generate")
def post_slm(self, prompt: str) -> dict:
response = requests.post(
self.url,
json={
"model": "phi3",
"prompt": prompt,
"stream": False
},
headers={"Content-Type": "application/json"}
)
return response.json()
@router.post("/slm")
@inject
def post_slm(
request_data: PromptRequest,
sidecar_service: SidecarService = Depends(
Provide[Container.sidecar_service]
)
):
try:
with tracer.start_as_current_span("post_slm") as parent:
parent.set_attributes(
{
"span_type": "GenAI",
"gen_ai.operation.name": "chat",
"gen_ai.system": "_OTHER",
"gen_ai.request.model": "phi3",
}
)
result = sidecar_service.post_slm(request_data.prompt)
return result
except Exception as e:
logging.error(e)
raise HTTPException(status_code=500, detail="Failed to generate text")
なお、OpenTelemetry Collector は、サイドカーコンテナ経由で Application Insights にログ・メトリック・トレースを送信する構成としています。
OpenTelemetry Collector サイドカーコンテナのコミット履歴を git で管理したいと考え、commit 値から定義ファイルを参照する構成としたかったため、定義ファイルは GitHub Actions で動的に生成する形としています。
- id: generate-otelcollector-config
name: Generate otel-collector-config.yaml with Application Insights Connection String
run: |
cat <<EOF > app/backend/opentelemetry/otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
http:
processors:
batch:
send_batch_max_size: 100
send_batch_size: 10
timeout: 10s
exporters:
debug:
verbosity: detailed
azuremonitor:
connection_string: ${{secrets.APPLICATIONINSIGHTS_CONNECTION_STRING}}
spaneventsenabled: true
service:
pipelines:
traces:
receivers: [otlp]
exporters: [azuremonitor, debug]
processors: [batch]
metrics:
receivers: [otlp]
exporters: [azuremonitor, debug]
processors: [batch]
logs:
receivers: [otlp]
exporters: [azuremonitor, debug]
processors: [batch]
EOF
本サンプルアプリケーションでは、GitHub Actions 内で動的に生成された上記の定義ファイルをもとに、OpenTelemetry Collector イメージをビルドする形としています。
FROM otel/opentelemetry-collector-contrib:0.117.0
COPY otel-collector-config.yaml /etc/otelcol-contrib/config.yaml
ENTRYPOINT ["/otelcol-contrib"]
CMD ["--config", "/etc/otelcol-contrib/config.yaml"]
EXPOSE 4317 55679 4318
実際にサイドカーで稼働する phi-3 に「こんにちは」と話しかけた際に仕様した REST API、クライアントからのリクエスト・サーバーからのレスポンスをそのまま添付します。
@router.post("/slm")
@inject
def post_slm(
request_data: PromptRequest,
sidecar_service: SidecarService = Depends(
Provide[Container.sidecar_service]
)
):
try:
with tracer.start_as_current_span("post_slm") as parent:
parent.set_attributes(
{
"span_type": "GenAI",
"gen_ai.operation.name": "chat",
"gen_ai.system": "_OTHER",
"gen_ai.request.model": "phi4",
}
)
result = sidecar_service.post_slm(request_data.prompt)
return result
except Exception as e:
logging.error(e)
raise HTTPException(status_code=500, detail="Failed to generate text")
POST https://xxxxxxxxxx.xxxxxxxxxx.canadaeast.azurecontainerapps.io/slm
Content-Type: application/json
{
"prompt": "こんにちは。"
}
{
"model": "phi3",
"created_at": "2025-02-25T16:06:21.191642547Z",
"response": "この指示は日本語で、非常に基本的な挨拶「こんにちは」という文を用いています。これは日常的なコミュニケーションにおける最初のステップであり、指示に直接対して答える必要があります。\n\n\nこんにちは。",
"done": true,
"done_reason": "stop",
"context": [
32010,
29871,
13,
30589,
30389,
30353,
30644,
30449,
30267,
32007,
29871,
13,
32001,
29871,
13,
30589,
30199,
31084,
30858,
30449,
30325,
30346,
30968,
30499,
30330,
31838,
31190,
30353,
31359,
30346,
30210,
30371,
233,
143,
171,
233,
142,
185,
30481,
30589,
30389,
30353,
30644,
30449,
30482,
30364,
30298,
30465,
30333,
30396,
30406,
30298,
30466,
30298,
30441,
30427,
30267,
30589,
30553,
30449,
30325,
31190,
30210,
30371,
30459,
30627,
30645,
30635,
30978,
30185,
30373,
30907,
30203,
30353,
30697,
30807,
30332,
30878,
31120,
30199,
30255,
30572,
30317,
30605,
30499,
30641,
30453,
30330,
31084,
30858,
30353,
31157,
31092,
232,
178,
193,
30326,
30466,
234,
176,
151,
30914,
30332,
31641,
30698,
30458,
30641,
30453,
30441,
30427,
30267,
13,
13,
13,
30589,
30389,
30353,
30644,
30449,
30267
],
"total_duration": 20959054035,
"load_duration": 6043453243,
"prompt_eval_count": 15,
"prompt_eval_duration": 1133000000,
"eval_count": 106,
"eval_duration": 13781000000
}
精度は置いておいて、サイドカーに配置した phi-3 と会話できていることがわかります。
おわりに
本記事では、Azure App Service や Container Apps 上で SLM(Specialized Language Model)や OpenTelemetry Collector をサイドカーコンテナとして稼働させ、メインアプリケーションと連携する手法について解説しました。
このアプローチにより、以下のメリットが得られます。
-
コスト削減:
サイドカーコンテナとして AI モデルや監視ツールを独立して実行することで、必要なリソースを効率的に利用でき、メインアプリケーションへの負荷を分散できます。 -
柔軟な運用:
メインアプリケーションのコードを変更せずに、新たな機能(推論、ログ収集、監視など)を追加できるため、システム全体のメンテナンス性と拡張性が向上します。 -
高い拡張性と信頼性:
同一ホスト内の localhost 通信により、コンポーネント間の疎結合な連携が実現され、サービス間の通信が堅牢になります。
また、本サンプルでは、GitHub Actions による動的な OpenTelemetry Collector の定義ファイル生成と、Dockerfile を用いたイメージビルドの自動化など、最新の DevOps 手法を取り入れることで、継続的な運用管理が可能なシステム設計を実現しています。
今回ご紹介した実装例は、生成 AI アプリケーションの一例に過ぎませんが、Azure 環境での柔軟なシステム構築と運用を目指す上での一手法として、皆様のプロジェクトにお役立ていただけると幸いです。
今後も、新たな技術や運用手法を取り入れながら、生成 AI の可能性を広げるための情報を発信していきます。
ぜひ、皆様も試行錯誤の中で、最適なシステム運用方法を見つけていただければと思います。