Bot プログラミングスキル 環境構築・インフラ 開発ログ

🛠️開発記録#259(2025/7/5)ビルド地獄からの解放:Recorder → NATS連携を最速でデバッグする実践ノウハウ

1. はじめに――“ビルド地獄”とは何か

「ちょっと修正して試すだけなのに、
docker compose builduplogs数分待ちその間に集中が切れる――。
仮想通貨 Bot のようにリアルタイム性が命のシステムでは、このタイムロスこそが開発速度を最も殺すボトルネックです。本記事では、Recorder → NATS という定番のストリーム基盤を題材に、再ビルド/再起動ゼロ で即デバッグできる環境を構築し、「ビルド地獄」を終わらせるまでの実録手順を共有します。


2. アーキテクチャ概要:Recorder ⇨ NATS ⇨ Analyzer

flowchart LR
  subgraph Runtime
    R[Recorder(トレード/板データ収集)]
    N[NATS(メッセージバス)]
    A[Analyzer(集計・検証)]
  end
  R -- publish --> N
  N -- subscribe --> A
  click R "#logger"
  click N "#natsbox"
  • Recorder:bybit 等から WebSocket でマーケットデータを取得し、NATS に Publish
  • NATS:低レイテンシなメッセージブローカー(JetStream 使用可)
  • Analyzer:Parquet へ落として集計/可視化

今回は “Recorder ↔︎ NATS” の接続確認にフォーカスします。


3. ロガー統一で見えた真実

# logging_setup.py
import logging, os
LOG_FMT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
logging.basicConfig(level=os.getenv("LOG_LEVEL", "DEBUG"), format=LOG_FMT)

# nats_publisher.py(修正抜粋)
logger = logging.getLogger("recorder")  # ← ここを __name__ から統一
...
async def publish_trade(msg: bytes):
    await nc.publish("mmbot.trades", msg)
    await nc.flush()
    logger.debug("📤 Published trade: %s bytes", len(msg))
  • DEBUG 一元化で「どこが詰まったか」を即発見
  • await nc.flush() を挟むことで、Publish が実際に送信されたタイミングをログで保証

4. Compose Watch で“保存→即反映”を実現

# compose.override.yml
services:
  mmbot-recorder:
    develop:
      watch:
        - action: sync+restart   # 変更を同期しプロセスのみ再起動
          path: ./src

起動は detach せずログウインドウを見守るスタイルが快適:

docker compose up --watch mmbot-recorder

修正→保存すると 1〜2 秒で Recorder が再起動し、新コードで NATS へ Publish が始まるのを確認できます。もう buildrestart も不要。


5. NATS 接続エラーの犯人は lambda

初期実装ではコールバックを lambda で渡していて、次のようなエラーが発生。

❌ Failed to connect to NATS: nats: callbacks must be coroutine functions

修正後:

async def on_disconnect():
    logger.warning("NATS disconnected")

async def on_reconnect():
    logger.info("NATS re-connected")

nc = await nats.connect(
    "nats://nats:4222",
    disconnected_cb=on_disconnect,
    reconnected_cb=on_reconnect,
)
  • コールバックは async def で定義
  • 再接続時の挙動がログで追えるため、ネットワーク瞬断も可視化

6. nats-boxで秒速疎通テスト

# Recorder コンテナと同じネットワーク名を確認
docker network ls
# 例: mmbot_default ネットワークに join してサブスクライブ
docker run --rm -it --network mmbot_default natsio/nats-box \
  nats sub -s nats://nats:4222 "mmbot.>"

Recorder 側で Publish が走ると、nats-box が即受信してターミナルに表示。
“Publish 成功”=“ネットワーク到達成功” を3秒で保証できます。


7. 再ビルドゼロ開発を CI にも落とし込む

# tests/test_nats_e2e.py
from testcontainers.nats import NatsContainer
import pytest, asyncio, nats

@pytest.mark.asyncio
async def test_publish():
    with NatsContainer() as nats_url:
        nc = await nats.connect(nats_url.get_connection_url())
        await nc.publish("health.ok", b"ping")
        await nc.flush()
  • Testcontainers-Python10 秒未満 の統合テスト
  • GitHub Actions では services に docker:dind を立て、pytest -q するだけ

8. さらなる加速:Tilt・Prometheus・Slack Alerts

目的ツール一言メリット
マルチサービス同時ホットリロードTilt live_update(sync, restart)Recorder/Analyzer/Bot をワンコマンドで監視
NATS & Recorder の可視化nats-prometheus-exporter + Grafanamsgs_in/sec, pending, subs をグラフ化
障害検知を即通知Alertmanager → Slack“Publish 0件 5分” でチャンネル通知

これらを組み込めば、開発〜運用がワンストップで滑らかに。


9. まとめと次の一手

  1. ロガー統一+Compose Watch で “保存→秒で検証” ループ完成
  2. nats-box で手元から即疎通テスト、Testcontainers で CI にも移植
  3. Tilt / 監視基盤を追加して “動く” から “壊れない” へフェーズアップ

学びのリマインド

  • 依存を図にしてから実装すると、詰まりの因果を最短で切り分けられる
  • “まず見える化” が問題発見の最速ルート(ロガー統一が鍵)
  • ビルドを無くす思考の連続性を守る開発が速く・楽しくなる

Appendix:完全 Compose / Tiltfile 雛形

docker-compose.yml(抜粋)

version: "3.9"
services:
  nats:
    image: nats:2.10
    ports: ["4222:4222"]
  mmbot-recorder:
    build: .
    command: python -m mmbot.recorder
    environment:
      - LOG_LEVEL=DEBUG
    depends_on:
      - nats

compose.override.yml(develop.watch)

services:
  mmbot-recorder:
    develop:
      watch:
        - action: sync+restart
          path: ./src

Tiltfile(最小構成)

docker_build("mmbot-recorder", ".", live_update=[
    sync("./src", "/app/src"),
    restart_process(),
])
k8s_yaml("k8s/recorder-deployment.yaml")


これでようやく ビルド地獄を卒業
さらなる高速開発と堅牢運用へ!

付録:“ビルドを無くす” 開発フローの落とし穴とその回避策

潜在リスク何が起きるか実戦的な回避策
本番イメージとの乖離ホットリロードは“コードと依存だけ”を差し替えるため、Dockerfile で固定していた OS/ライブラリが徐々にズレる。
結果:本番だけ再現するバグや依存衝突。
- 日次完全ビルド+テスト CI を回し、差分を検知。
- 週 1 程度で「本番と同じ手順でビルド→E2E テスト」。
ネイティブ拡張の反映漏れpip install がイメージ内でしか走らないケースでは、C 拡張/バイナリがホットリロードに載らない。- 依存追加時だけ 軽量ターゲットビルド (--target dev) を実行。
- CI で poetry export 差分を検知したら自動ビルド。
長時間稼働のメモリリーク検知遅れプロセスは再起動してもコンテナは生き続けるため、累積リークに気付きにくい。- docker stats 監視+Prometheus アラート(RSS 増大閾値)。
- 1〜2 日に一度は docker compose down && up でフル再起動。
バインドマウントの I/O ペナルティ特に macOS では大量ファイル同期でレイテンシ増。- :cached / :delegated オプションを付与。
- Mutagendocker-sync で双方向同期。
ホットリロード対象外コードの残存入口スクリプト外のバッチ処理やサブプロセスが旧ロジックのまま動く。- 入口を一カ所にまとめ watch 対象を明示
- ログに Git commit hash を出して “実行中バージョン” を可視化。
セキュリティスキャン遅延イメージを更新しないと脆弱パッケージ検知が後手に回る。- Dependabot で脆弱性 PR を自動作成。
- “週次完全ビルド” で最新 CVE と照合。
“動くけどコミット忘れ”事故コンテナ内だけ修正して git に push し忘れる。- pre-commituntracked ファイルがあると失敗させる。
- Tilt / Compose の live_updaterun("git status --porcelain") を仕込んで警告。

結論

  • ビルドを無くす ≠ ビルドを捨てる
    日常開発をホットリロードで高速化しつつ、定期フルビルド自動スキャン で品質を担保する “ハイブリッド運用” がベストプラクティス。
  • 落とし穴の多くは 時間軸(長時間稼働・週次セキュリティ)と 境界外(ネイティブ依存・本番環境)に潜む。そこだけ定期タスクで補完すれば、開発スピードと安定運用を両立できる。

これらの注意点を押さえておけば、「ビルド地獄」から解放されても、品質を落とさない開発フロー を維持できます。

-Bot, プログラミングスキル, 環境構築・インフラ, 開発ログ