MENU

問い合わせ


    【YOLOで作る画像認識ツール 第10回】Dockerで動かす:設定・ログ・ヘルスチェックを運用に近い形にする

    前回の第9回では、モデルをONNXに変換して速度を比べました。今回は、ここまで作ったAPIをDocker(アプリを、動かす環境ごと「箱(コンテナ)」に詰めて配る技術)で動かせるようにします。

    開発者のパソコンでは動くのに、本番のサーバーでは動かない。この定番のトラブルを減らし、運用担当者が同じ手順で起動できるようにするための準備です。

    目次

    この回で作るもの

    • APIを動かす環境の設計図 Dockerfile
    • 起動・設定をまとめた compose.yaml(Docker Composeの設定ファイル)
    • 動いているかを確かめるヘルスチェック
    • 個人情報を残さないログの出力

    先にお伝えしたいこと:この回の確認範囲

    この連載の執筆環境では、Dockerイメージの実際のビルド(作成)と起動までは確認できていません。 外部のイメージ配布元やパッケージ配布元への接続が制限されており、ビルドの途中で止まったためです。確認できたのは次の範囲です。

    • docker compose config による、compose.yaml の書き方の検証(エラーなし)
    • ログ出力の追加と、そのテスト(pytest 33件成功)
    • Dockerを使わない起動(uvicorn)での、API・画面の動作(第4〜5回で確認済み)

    読者の環境でビルドするときは、初回にうまくいかない可能性があります。その場合は、まず docker compose build のエラーメッセージを確認してください。

    仕組み:コンテナに何を入れ、何を外に置くか

    置き場所何を置くか理由
    コンテナの中(イメージ)Python・部品・app/ のコード誰が動かしても同じ環境になる
    コンテナの外(読み取り専用で渡す)AIモデルのファイル(models/)差し替えやすく、イメージが軽くなる
    実行時の設定(環境変数)しきい値・モデルのパス作り直さずに変えられる

    AIモデルは、コードと更新のタイミングが違います。イメージに焼き込まず、外から渡す形にしておくと、モデルの入れ替えでイメージを作り直さずに済みます。

    実装

    Dockerfile

    # Dockerfile
    FROM python:3.11-slim
    
    WORKDIR /app
    
    # 依存だけ先に入れる(app を変えても、この層のキャッシュが効く)。推論だけなので CPU 版の PyTorch を使う
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu \
        # サーバーには画面がないので、画面表示用の部品(libGL など)を必要としない headless 版の OpenCV に差し替える
        && pip uninstall -y opencv-python \
        && pip install --no-cache-dir opencv-python-headless==4.13.0.92
    
    COPY app ./app
    
    # root では動かさない
    RUN useradd --create-home appuser
    USER appuser
    
    # Ultralytics の設定ファイルの置き場所(書き込める場所を指定)
    ENV YOLO_CONFIG_DIR=/tmp/ultralytics \
        YOLO_MODEL_PATH=/models/yolo26n.pt
    
    EXPOSE 8000
    CMD ["uvicorn", "app.api:app", "--host", "0.0.0.0", "--port", "8000"]

    工夫した点は次のとおりです。

    • 依存を先に入れる:コードだけを変えたときは、重い部品のインストールをやり直さず、素早く作り直せます
    • CPU版のPyTorch:推論だけなら、GPU用の巨大な部品は不要です
    • headless版のOpenCV:サーバーには画面がないため、画面表示用の部品が要らない版に差し替えます
    • rootで動かさない:万一、APIに問題があっても、コンテナの中での権限を最小限にします

    compose.yaml

    # compose.yaml
    services:
      detector:
        build: .
        ports:
          - "127.0.0.1:8000:8000"   # 自分のパソコンからだけ接続できる。社内に公開するときは認証とあわせて見直す
        volumes:
          - ./models:/models:ro     # モデルは画像に焼き込まず、外から読み取り専用で渡す(差し替えやすい)
        environment:
          YOLO_CONFIDENCE: "0.25"
        read_only: true              # コンテナ内のファイルは書き換えられない
        tmpfs:
          - /tmp                     # 一時ファイルだけは書ける
        restart: unless-stopped
        healthcheck:
          test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
          interval: 30s
          timeout: 5s
          retries: 3
          start_period: 40s
        logging:
          driver: json-file
          options:
            max-size: "10m"           # ログでディスクがいっぱいにならないよう、大きさと世代を制限する
            max-file: "3"
    • ポートは自分のパソコンにだけ公開:127.0.0.1:8000:8000 と書くと、他のパソコンからは接続できません。第4回で触れたとおり、このAPIには認証がないためです
    • read_only: true:コンテナの中のファイルを書き換えられなくします。攻撃を受けても、コードを書き換えにくくなります
    • ヘルスチェック:30秒ごとに /health を呼び、応答がなければ「異常」と記録します。起動直後はモデルの読み込みに時間がかかるため、最初の40秒は判定を待ちます
    • ログの上限:1ファイル10MB・3世代まで。ログでディスクがいっぱいになり、システムが止まるのを防ぎます
    • restart: unless-stopped:異常で止まったら、自動で再起動します

    ログを残す:ただし個人情報は残さない

    運用では、「いつ・何枚・何秒かかったか」が分かるログが欠かせません。app/api.py に、ログの出力を足しました。

    # app/api.py(追加部分)
    started = time.perf_counter()
    try:
        detections = request.app.state.detector.detect_bytes(data, conf)
    except ValueError as exc:
        logger.warning("画像を読み込めませんでした bytes=%d", len(data))
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    # ファイル名には個人情報が含まれうるので、ログには残さない(大きさ・件数・時間だけ)
    logger.info("detect bytes=%d conf=%.2f count=%d ms=%.0f", len(data), conf, len(detections),
                (time.perf_counter() - started) * 1000)

    ログに残すのは、データの大きさ・しきい値・検出件数・処理時間だけです。ファイル名は残しません。 「田中様_免許証.jpg」のように、個人名などが含まれることがあるためです。テストでも、ファイル名がログに出ないことを確認しています。

    動かす手順(読者の環境で)

    python tools/download_assets.py      # モデルを models/ に用意する(初回のみ)
    docker compose up --build            # イメージを作って起動
    curl http://127.0.0.1:8000/health    # {"status":"ok"} が返れば起動している
    docker compose ps                    # STATUS が healthy になれば、ヘルスチェックも通っている

    先ほどお伝えしたとおり、この手順は、執筆環境ではビルドまで確認できていません。

    つまずきやすい点

    • モデルが見つからない:./models にモデルのファイルが無いと、起動に失敗します。先にダウンロードが必要です
    • イメージが大きい:PyTorchなどを含むため、イメージは数GBになることがあります。ビルドと配布に時間がかかります
    • 書き込み禁止でエラー:read_only にすると、書き込みが必要な場所(設定ファイルなど)でエラーになります。書き込める場所(/tmp)を指定しています
    • CPUの割り当て:コンテナに割り当てるCPUが少ないと、遅くなります。第9回の速度は、割り当てで変わります

    発注者向けメモ:「どこで動かすか」で費用と責任が変わる

    コンテナ化は、開発者の都合というより、運用の引き継ぎやすさに関わる話です。誰が動かしても同じ環境になる、というのは、開発会社の交代や、社内の担当者への引き継ぎで効いてきます。

    発注者がやること チェックリスト

    • ☐ どこで動かすか(社内サーバー・クラウド)と、その運用を誰が行うかを決めた
    • ☐ 環境の構築手順書(または、コンテナの設計図)が、納品物に含まれていることを確認した
    • ☐ ログに、個人情報(ファイル名・画像の中身)が残らない設計か確認した
    • ☐ ログの保存期間と、確認する人を決めた
    • ☐ 異常時の連絡先と、再起動などの一次対応の手順を決めた

    開発会社への質問例

    • 「動かす環境を、他の担当者や別の開発会社が再現できる形(コンテナの設計図など)で納品してもらえますか」
    • 「ログには何が記録されますか。個人情報や画像の中身が残らないことを、どう確認しますか」
    • 「異常を検知する仕組み(ヘルスチェック・通知)は、見積もりに含まれていますか」
    • 「モデルを更新するとき、システムを止める時間はどのくらいですか」

    まとめと次回予告

    第10回では、次のことを行いました。

    • Dockerfileとcompose.yamlを作り、環境・設定・ヘルスチェック・ログの上限を整理した
    • モデルをコンテナの外から渡す形にして、差し替えやすくした
    • ログに、個人情報になりうるファイル名を残さないようにした
    • ビルドと起動は、この執筆環境では確認できていないことを明記した

    次回の第11回(最終回)では、運用時の注意点(精度の低下・ライセンス・更新)と、この連載の内容の総まとめを行います。

    この連載の記事一覧

    この記事は連載「YOLOで作る画像認識ツール」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。

    システム制作・運用・保守のお問い合わせはこちら


      よかったらシェアしてね!
      • URLをコピーしました!
      • URLをコピーしました!

      この記事を書いた人

      株式会社THIRD HERO代表取締役 朝野貴朗
      Webシステム開発を中心に、toC向けサービスサイトの運営、ツール開発などを行ってまいりました。

      目次