0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【イチから学ぶ! 初めての Azure と生成 AI】Azure App Service / Container Apps を用いた生成AIアプリケーション開発 - サイドカーコンテナ連携編

Posted at

はじめに

本記事は、【イチから学ぶ! 初めての Azure と生成 AI】セミナーのセッション『AI アプリのクラウド運用入門 ~App Service と Container Apps の選び方と管理のポイント』の解説記事です。

Azure を活用した生成 AI アプリケーション開発の際に役立つポイントをご紹介します。実装時に直面しがちなハマりどころやその解決策を、全 5 回に分けてお届けします。

本記事では、SLM を題材として、Azure で稼働するアプリケーションをサイドカーコンテナとどのように連携するかについて、一例をご紹介します。

本記事でご紹介するサンプルアプリケーションのソースコードは、以下のリポジトリで公開しています。

SLM をサイドカーコンテナで稼働させる方法

ファインチューニングしたモデルをサイドカーコンテナで実行し、アプリケーションと連携する手法は、生成 AI アプリケーションの開発で注目されるユースケースの一つです。
これにより、以下の利点が得られます。

  • コスト削減
  • モデル精度の最適化
  • 独立したコンテナとしてのスケーリング

また、監視・ログ等のコンテナアプリケーションをサイドカーに配置し、メインのコンテナアプリケーションと疎結合に構成することもユースケースの一つです。

image.png

セミナーでは、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 それぞれで稼働します。

image.png

image.png

コード例

具体的なコードは以下の通りです。

sidecar_service.py
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()
controller.py
@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 で動的に生成する形としています。

common_container_image_build_push.yml
      - 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 イメージをビルドする形としています。

Dockerfile
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、クライアントからのリクエスト・サーバーからのレスポンスをそのまま添付します。

controller.py
@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")

client
POST https://xxxxxxxxxx.xxxxxxxxxx.canadaeast.azurecontainerapps.io/slm
Content-Type: application/json

{
  "prompt": "こんにちは。"
}
phi-3からのレスポンス
{
  "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 の可能性を広げるための情報を発信していきます。

ぜひ、皆様も試行錯誤の中で、最適なシステム運用方法を見つけていただければと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?