【イチから学ぶ! 初めての Azure と生成 AI】Azure App Service / Container Apps を用いた生成AIアプリケーション開発 - ファイルアップロード編

# はじめに

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

本記事は、App Service (Web App for Containers) / Container Apps それぞれを使ってファイルアップロードを行う方式について解説します。

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

https://github.com/kohei3110/azure-101-day

# シナリオ概説

生成 AI を活用し、アップロードしたファイルを分析するシナリオは、生成 AI 時代における重要なユースケースの一つです。

Azure OpenAI Assistants API や Azure AI Agent Service を利用すれば、アップロードしたファイルを生成 AI に分析させる機能を簡単に実現できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/9cd3aae6-2b69-1e77-3848-4c6302081116.png)

しかし、こうした機能を実現する上で避けて通れない課題の一つが、「どのようにファイルをアップロードするか」という点です。

# ファイルアップロードにおける考慮事項

クライアントからファイルをアップロードする際には、以下のような方法が考えられます。

## 1. SAS を利用した直接アップロード

クライアントが Azure Storage の Shared Access Signature (SAS) を使用してファイルを直接アップロードする方法です。
サーバーを経由しないため、帯域やリソースを最小限に抑えつつ、高いパフォーマンスを実現できます。特に、SPA（Single Page Application）からのアップロードには最適です。

## 2. サーバーを経由したアップロード

クライアント側で SAS の使用が難しい場合や、セキュリティ上の懸念がある場合には、サーバーを経由するアップロードが選択肢となります。この場合、以下の構成が考えられます：

- ストリーム形式でサーバー経由で直接ストレージに送る方法
- サーバーに一時的にファイルを保存し、後でストレージに転送する方法

ただし、サーバーを経由することで、帯域やリソース消費が増加する可能性がある点に注意が必要です。

本記事では、特に **サーバーを経由してファイルをアップロードする方法** に焦点を当て、App Service (Web App for Containers) および Container Apps を利用した具体的な手法をご紹介します。

# アップロード方式詳細

それでは、App Service (Web App for Containers) および Container Apps を利用してファイルをアップロードする方式を解説していきます。

## App Service (Web App for Containers)

Linux ベースの Web App for Containers では、コンテナの `/home` ディレクトリが永続ボリュームとしてマウントされるため、ここに配置したファイルは再起動やスケールアウト時にも保持されます。
しかし、その他のディレクトリはコンテナのイメージに基づくためエフェメラル（揮発性）となります。

Web App for Containers でファイルをアップロードする場合、２つの方式があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/d996b4cf-4e68-4619-a74d-7f1fa7a12f6e.png)

１. カスタム コンテナー ファイル システムの `/home` ディレクトリを使用する方式

ローカルフォルダに保存する構成と同様のため、アップロードされたファイルをコンテナ内に一時保存する場合、保存先を `/home` にすることである程度の永続性は確保できますが、App Service プランに含まれる記憶領域のクォータに影響します。

複数インスタンス間での共有については、設定の注意が必要です。
環境変数 `WEBSITES_ENABLE_APP_SERVICE_STORAGE` を `true` にすることで、スケールされたインスタンス間で /home ディレクトリを共有可能となります。
既定では `false` となっています。

https://learn.microsoft.com/ja-jp/azure/app-service/reference-app-settings?tabs=kudu%2Cdotnet#custom-containers

２. マウント済み Azure ストレージパスを使用する方式

ユーザがアップロードしたファイルの長期管理や大規模な運用を行う場合、Azure Files などの外部ストレージサービスと連携するのが望ましいです。これにより、ファイルの冗長性やスケールアウト時の一貫性が向上します。


## Container Apps

Container Apps はコンテナ上でアプリケーションを実行するため、コンテナ内のファイルシステムは一時的（エフェメラル）です。

- 再起動やスケールアウト時の注意

コンテナが再起動したり、インスタンスが増減した場合、ローカルに保存したファイルは保持されません。

- 永続性の確保

ユーザがアップロードしたファイルを長期間保持する必要がある場合、 Blob Storage や Azure Files といった外部の永続ストレージサービスを利用することが推奨されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/b73cae34-4073-46ea-863b-3d969c07c3fb.png)

## サンプル実装

今回は FastAPI で実装しています。なお、Web App for Containers、Container Apps どちらも同じ実装でアプリケーションを展開できます。コンテナの「可搬性」という強みが生きていますね。

https://github.com/kohei3110/azure-101-day/tree/main/app/backend/api/src

以下は、`/files` ディレクトリにデータをアップロードする例です。

```python:controller.py
@router.post("/files")
@inject
async def upload_files(
    file: UploadFile = File(...),                   
    file_upload_service: FileUploadService = Depends(
        Provide[Container.file_upload_service]
    )
):
    """Upload a file to the data directory."""
    try:
        with tracer.start_as_current_span("upload_files"):
            filename = file_upload_service.upload_file(file, "files")
            return {"filename": filename}
    except Exception as e:
        logging.error(e)
        raise HTTPException(status_code=500, detail="Failed to upload file")
```

```python:file_upload_service.py
import logging
import shutil
from pathlib import Path
from fastapi import UploadFile

class FileUploadService:
    def __init__(self, base_dir: Path):
        self.base_dir = base_dir

    def upload_file(self, file: UploadFile, sub_dir: str) -> str:
        target_dir: Path = self.base_dir / sub_dir
        if not target_dir.exists():
            target_dir.mkdir(parents=True, exist_ok=True)
        save_path: Path = target_dir / file.filename
        try:
            with open(save_path, "wb") as buffer:
                shutil.copyfileobj(file.file, buffer)
        except Exception as e:
            logging.error(e)
            raise Exception(e)
        return file.filename
```

# ローカル保存ファイルの取り扱い上の注意点

コンテナ環境に限らず、アプリケーションを介してファイルをアップロードする際は、ローカル保存したファイルには以下のような留意点があります。

## ストレージ容量の制約

ローカルの一時領域には使用可能なストレージ容量に限りがあります。
大量のファイルアップロードや大容量ファイルの場合、容量オーバーによるアプリケーションの不具合が発生する恐れがあります。
定期的なファイルのクリーンアップや自動削除の仕組みを導入することで、容量の管理を徹底する必要があります。

## セキュリティ対策

ローカルに保存する場合でも、アップロードされるファイルの種類やサイズ、拡張子の検証を実施し、不正なファイルの保存を防ぐことが重要です。

## 一時保存用途としての利用

ローカルストレージは、一時的なファイルの保存や処理中の一時ファイルとして利用することを前提に設計することが望ましいです。

永続性や共有性が求められるデータは、専用の永続ストレージサービスに移すなど、役割を明確に分けることでシステム全体の信頼性が向上します。

https://github.com/kohei3110/azure-101-day/blob/main/app/backend/api/src/startup.py

例えば、アプリケーションのバックグラウンドタスクを使って、ローカルストレージ上のファイルを削除する場合、以下のように書くことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/c6573117-1224-4b19-8202-59a6d412810e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/14e9c013-c61a-4d43-800d-3791cb8caaf7.png)


```python:startup.py
import os
import logging
from pathlib import Path
from fastapi import FastAPI
from apscheduler.schedulers.background import BackgroundScheduler

from controller import router as api_router
from tracing.tracing import tracer
from di.containers import Container

logging.basicConfig(
    level=logging.DEBUG
)

DATA_DIR = Path(os.getenv("DATA_DIR", "/data"))

def delete_files():
    """Delete all files in the data directory."""
    with tracer.start_as_current_span("delete_files"):
        for file in DATA_DIR.iterdir():
            if file.is_file():
                print(f"Deleting {file}")
                os.remove(file)

scheduler = BackgroundScheduler()
scheduler.add_job(delete_files, 'interval', seconds=30)
scheduler.start()

def create_app() -> FastAPI:
    container = Container()
    container.wire(modules=["controller"])

    app = FastAPI()
    app.include_router(api_router)

    return app
```

# おわりに

本記事では、Azure App Service (Web App for Containers) と Container Apps を利用したファイルアップロードの実装方法および、ローカルに保存したファイルの取り扱いに関する注意点について解説しました。

コンテナ環境特有のエフェメラルなファイルシステムやストレージ容量の制約、さらにはセキュリティ対策の重要性について理解いただけたかと思います。特に、アップロードしたファイルの永続性を確保するためには、適切な永続ストレージ（例：Azure Blob Storage や Azure Files）との連携が不可欠である点を押さえておく必要があります。

また、サンプルコードを通して FastAPI を用いた実装例や、バックグラウンドタスクによるファイルの自動クリーンアップ方法についてもご紹介しました。これにより、開発環境における一時ファイル管理の重要性と、実際の運用に役立つ手法を具体的に把握いただけたのではないでしょうか。

生成 AI を活用したアプリケーション開発は今後ますます高度化していく中で、効率的かつ安全なファイル管理が鍵となります。この記事が、皆様のプロジェクトでの課題解決の一助となれば幸いです。