
Eco² vocabulary
- edge
- auth offload
- domain api
- task queue
- scan pipeline
- chat agent
- event bus
- cluster
- gitops
- autoscaling
- observability
- load test
- evaluation
Edge: ROUTE 53 · ALB · HTTPS 443 · internet-facing · external-dns · istio-ingressgateway · NodePort · eco2-gateway · VirtualService · x-canary · AuthorizationPolicy · ENVOY SIDECAR · kiali-serverAuth Offload: AuthorizationPolicy · action: CUSTOM · notPaths · envoyExtAuthzGrpc · failOpen: false · CheckRequest · x-user-id · BlacklistCache · BootstrapFromRedis · outbox:blacklist · blacklist.events · UsersServiceDomain API: VirtualService · x-user-id · /api/v1/scan · X-Idempotency-Key · job_id · stream_url · 202 ACCEPTED · Retry-After · ScanTask · UsersService · ImageService · ENVOY SIDECARTask Queue: outbox:blacklist · blacklist.events · RabbitmqCluster · scan.direct · reward.events · dlx · x-dead-letter-exchange · chat.process · AioPikaBroker · task_acks_late · apply_async · gevent · scan.vision · scan.rule · scan.answer · scan.reward · CELERY CHAIN · QueueLength · inspect ping · QUEUE SATURATIONScan Pipeline: ScanTask · scan.direct · reward.events · apply_async · gevent · scan.vision · scan.rule · scan.answer · scan.reward · CELERY CHAIN · RewardStep · item_class_list.yaml · situation_tags.yaml · LITE RAG · JsonRegulationRetriever · PipelineStage · waste_rag · XADD · domain: worker-ai · pipeline_duration · vision_stage_durationChat Agent: ImageService · chat.process · AioPikaBroker · create_chat_graph · route_by_intent · MULTI-INTENT FANOUT · waste_rag · recyclable_price · collection_point · image_generation · aggregator · thread_id · SyncableRedisSaver · AsyncPostgresSaver · LANGSMITH_TRACING · regenerateEvent Bus: job_id · stream_url · PipelineStage · XADD · scan:events:{shard} · XREADGROUP · XACK · XAUTOCLAIM · PendingReclaimer · STATE KV · scan:state:{job_id} · sse:events:{shard} · SSEBroadcastManager · Last-Event-ID · scan_sse_connections_active · ServiceMonitor · traceparentCluster: NodePort · kubeadm init · kubeadm join · vxlanMode · BGPConfiguration · module "vpc" · worker_ai · nodeSelector · domain: worker-ai · NoSchedule · 24-NODE CLUSTERGitOps: external-dns · x-canary · dev-root · ApplicationSet · sync-wave · selfHeal · ignoreDifferences · argocd-image-updater · update-strategy: digest · write-back-method · targetRevision · ci-services.ymlAutoscaling: ignoreDifferences · ScaledObject · QueueLength · cooldownPeriod · pollingInterval · fallback · maxReplicaCount · stabilizationWindowSeconds · HorizontalPodAutoscaler · scan_sse_connections_active · prometheus-adapterObservability: 24-NODE CLUSTER · prometheus-adapter · FLUENT BIT · Elasticsearch · Kibana · kube-prometheus-stack · AlertmanagerConfig · ServiceMonitor · JAEGER · OPENTELEMETRY · traceparent · kiali-server · LANGSMITH_TRACINGLoad Test: k6 · ramping-vus · thresholds · pipeline_duration · vision_stage_duration · poll_timeout · inspect ping · WORKER RESTART · POLLING TIMEOUT · QUEUE SATURATIONEvaluation: EvalConfig · eval_mode · eval_sample_rate · eval_cost_budget_daily_usd · EvalResult · EvalGrade · code_grader · llm_grader · CUSUM · regenerate · OpenAIBARSEvaluator
24ノードが実際にどうつながるかを、フローごとに。
kubeadmで構築したセルフマネージドのKubernetesです。構成図はREADMEの5つのレイヤーの中に24の物理ノードとその上のワークロードを配置し、実際の接続でつないでいます。フローを選ぶと、その経路だけが番号付きのステップで強調されます。
写真から廃棄物を識別して出し方を案内し、対話では場所・天気・リサイクル価格・Web検索のツールを呼び出し、画像も生成します。このチームプロジェクトでは、クラスターとデプロイの基盤から始め、ドメインのバックエンド、非同期処理、対話エージェント、検証へと担当範囲を広げました。
大会での開発から、バックエンドと運用の強化まで
サービスは運用を終えています。録画したデモと技術レポートで、動作の様子を見られます。
Scanリクエスト → リアルタイム配信
写真解析のリクエストがキューの作業になり、その進捗イベントが同じブラウザに戻ります。
ブラウザDNS参照: DNS参照Route 53
Route 53のレコードで名前を解決するだけです。リクエストはDNSを通りません。
ブラウザデータフロー: HTTPS 443AWS ALB
ALBでTLSが終端されます。
AWS ALBデータフロー: HTTP · NodePortIstio ingress Envoy
k8s-ingress-gatewayEC2インスタンスターゲットのNodePortを通じて、Istio ingress Envoyへ渡されます。
Istio ingress Envoy
k8s-ingress-gatewayデータフロー: VirtualServiceScan APIk8s-api-scanScan APIへルーティングされます。Scan APIは作業をキューに入れてすぐにjob_idを返し、結果の照会にはジョブが終わるまで202で応答します。
Scan API
k8s-api-scanデータフロー: CeleryタスクRabbitMQk8s-rabbitmqScanのタスクがRabbitMQに入ります。
RabbitMQ
k8s-rabbitmqデータフロー: consumescan-workerk8s-worker-ai · k8s-worker-ai-2AIプールのscan-worker(Celery · gevent)がそれを受け取り、Vision → Rule → Answer → Rewardを実行します。4つの段階はchainでつないだCeleryタスクであり、ノードではありません。
AIプール
k8s-worker-ai · k8s-worker-ai-2データフロー: HTTPSLLM APIscanとchatのワーカーが、OpenAIとGoogleのAPIを呼び出します。
scan-worker
k8s-worker-ai · k8s-worker-ai-2データフロー: XADD scan:eventsStreamsk8s-redis-streams進捗イベントは、4つのStreamsシャードのいずれかに書き込まれます。
Streams
k8s-redis-streamsデータフロー: XREADGROUPEvent Routerk8s-event-routerEvent Routerがコンシューマーグループとしてそれを読み、処理後にXACKします。pendingとreclaimにより、配信をat-least-onceに保ちます。
Event Router
k8s-event-routerデータフロー: SETEX stateState KVk8s-redis-streams同じRedisノード上のState KVに、最新の状態と重複処理の印を書き込みます。
Event Router
k8s-event-routerデータフロー: PUBLISHPub/Subk8s-redis-pubsubシャードPub/Subのジョブのチャンネルで、リアルタイムに配信されます。
Pub/Sub
k8s-redis-pubsubデータフロー: SUBSCRIBESSE Gatewayk8s-sse-gatewaySSE Gatewayはプロセスごとに共有のリスナー1つで購読し、接続中の各ブラウザへ振り分けて送ります。
SSE Gateway
k8s-sse-gatewayデータフロー: SSEストリームブラウザ同じALBとingressの経路で開かれた接続です。配信はat-least-onceで、重複は冪等に処理します。
State KV
k8s-redis-streamsデータフロー: 再接続時の復旧SSE Gatewayk8s-sse-gatewayLast-Event-IDで再接続すると、State KVと保持されたStreamsが取りこぼしたイベントを補います。
Chat · トークンと保存
ChatのトークンはScanと同じイベント経路をたどり、チェックポイントとメッセージは別々のワークロードが保存します。
ブラウザデータフロー: HTTPS 443AWS ALB
ALBでTLSが終端されます。
AWS ALBデータフロー: HTTP · NodePortIstio ingress Envoy
k8s-ingress-gatewayEC2インスタンスターゲットのNodePortを通じて、Istio ingress Envoyへ渡されます。
Istio ingress Envoy
k8s-ingress-gatewayデータフロー: VirtualServiceChat APIk8s-api-chatChat APIへルーティングされます。
Chat API
k8s-api-chatデータフロー: TaskiqタスクRabbitMQk8s-rabbitmqChatのタスクがRabbitMQに入ります。
RabbitMQ
k8s-rabbitmqデータフロー: consumechat-workerk8s-worker-ai · k8s-worker-ai-2chat-worker(Taskiq · asyncio)がLangGraphで意図を分類し、必要に応じてvision、選ばれたツール、aggregator、必要に応じて要約を経て回答します。リクエストごとに必要な分岐だけを実行します。
AIプール
k8s-worker-ai · k8s-worker-ai-2データフロー: HTTPSLLM APIscanとchatのワーカーが、OpenAIとGoogleのAPIを呼び出します。
chat-worker
k8s-worker-ai · k8s-worker-ai-2データフロー: XADD chat:eventsStreamsk8s-redis-streamsトークンと段階イベントは4つのStreamsシャードに書き込まれ、そこからの配信経路はScanと同じです。
Streams
k8s-redis-streamsデータフロー: XREADGROUPEvent Routerk8s-event-routerEvent Routerがコンシューマーグループとしてそれを読み、処理後にXACKします。pendingとreclaimにより、配信をat-least-onceに保ちます。
Event Router
k8s-event-routerデータフロー: PUBLISHPub/Subk8s-redis-pubsubシャードPub/Subのジョブのチャンネルで、リアルタイムに配信されます。
Pub/Sub
k8s-redis-pubsubデータフロー: SUBSCRIBESSE Gatewayk8s-sse-gatewaySSE Gatewayはプロセスごとに共有のリスナー1つで購読し、接続中の各ブラウザへ振り分けて送ります。
SSE Gateway
k8s-sse-gatewayデータフロー: SSEストリームブラウザ同じALBとingressの経路で開かれた接続です。配信はat-least-onceで、重複は冪等に処理します。
chat-worker
k8s-worker-ai · k8s-worker-ai-2データフロー: checkpointCachek8s-redis-cacheLangGraphのチェックポイントは、Redis Cacheに保持されます。
Cache
k8s-redis-cacheデータフロー: チェックポイントの読み取りcheckpoint-syncerk8s-worker-ai · k8s-worker-ai-2checkpoint-syncerがRedisからチェックポイントを読みます。
checkpoint-syncer
k8s-worker-ai · k8s-worker-ai-2データフロー: PostgreSQLへの同期PostgreSQLk8s-postgresqlAsyncPostgresSaverがPostgreSQLへ非同期に保存します。
Streams
k8s-redis-streamsデータフロー: chat-persistence grouppersistence consumerk8s-api-chatChatノード上のpersistence consumerが、doneイベントをコンシューマーグループとして読みます。
persistence consumer
k8s-api-chatデータフロー: メッセージの保存PostgreSQLk8s-postgresqlチャットのメッセージをPostgreSQLに保存します。checkpoint-syncerとは別のワークロードです。
入口 · 認証
クラスターの入口からドメインAPIに届くまでの同期経路です。ポリシー対象のパスだけが先に認証を確認し、ノード間のPod通信はCalico VXLANです。
ブラウザDNS参照: DNS参照Route 53
Route 53のレコードで名前を解決するだけです。リクエストはDNSを通りません。
ブラウザデータフロー: HTTPS 443AWS ALB
ALBでTLSが終端されます。
AWS ALBデータフロー: HTTP · NodePortIstio ingress Envoy
k8s-ingress-gatewayEC2インスタンスターゲットのNodePortを通じて、Istio ingress Envoyへ渡されます。
Istio ingress Envoy
k8s-ingress-gatewayデータフロー: ext-authz · gRPC 50051ext-authzk8s-api-authCUSTOMポリシーの対象パスだけが、先にGoのext-authzに問い合わせます(タイムアウト2秒、failOpen false)。ログイン、health、docsなどのパスは除外されます。
ext-authz
k8s-api-authデータフロー: ブラックリストの照会blacklistk8s-redis-authトークンをRedisのブラックリストと照合します。
Istio ingress Envoy
k8s-ingress-gatewayデータフロー: VirtualServiceChat APIk8s-api-chatChat APIへルーティングされます。
Chat API
k8s-api-chatデータフロー: gRPCimages APIk8s-api-imageREADMEに記されたchat → imagesの同期呼び出しです。
auth API
k8s-api-authデータフロー: gRPCusers APIk8s-api-usersREADMEに記されたauth → usersの同期呼び出しです。
storageプール · 非同期保存
認証のブラックリストとキャラクター所有権は、RabbitMQから作業を受け取るstorageプールのワーカーが保存します。
blacklist
k8s-redis-authデータフロー: outbox:blacklistauth-relayk8s-worker-storage · k8s-worker-storage-2auth-relay(READMEのauth-Bus)がRedisのoutboxを読みます。
auth-relay
k8s-worker-storage · k8s-worker-storage-2データフロー: blacklist.eventsRabbitMQk8s-rabbitmqRabbitMQへ再発行します。再試行とDLQがあります。
RabbitMQ
k8s-rabbitmqデータフロー: blacklist.eventsauth-workerk8s-worker-storage · k8s-worker-storage-2auth-workerがブラックリストのイベントを受け取ります。
auth-worker
k8s-worker-storage · k8s-worker-storage-2データフロー: ブラックリストの保存blacklistk8s-redis-authRedisのブラックリストに保存されます。
RabbitMQ
k8s-rabbitmqデータフロー: Celery batchusers-workerk8s-worker-storage · k8s-worker-storage-2users-workerが、キャラクター所有権の処理をまとめて受け取ります。
users-worker
k8s-worker-storage · k8s-worker-storage-2データフロー: UPSERTPostgreSQLk8s-postgresqlPostgreSQLに反映されます。
構成 · スケーリング · 観測
Platform Layerはリクエストが通過するステップではなく、他のレイヤーをデプロイ・構成・スケール・観測します。ノードはTerraformで作成したEC2で、AnsibleでkubeadmとCalicoを導入しています。CIはTerraformを検証するだけで、自動でapplyすることはありません。
GitHub Actions · Docker Hub構成 · スケール: イメージ · マニフェストのリビジョンArgo CD image updater
k8s-masterArgo CDがApp-of-AppsとSync Wavesでクラスター構成をそろえます。control-planeへの配置を確認できたのは、image updaterだけです。
ALB Controller · ExternalDNS
k8s-master構成 · スケール: Ingress → ALB · DNSレコードAWSマネージドALB ControllerとExternalDNSがAWS側のリソースを構成します。リクエストが通る経路ではありません。
Istiod
k8s-master構成 · スケール: xDS設定Istio ingress Envoyk8s-ingress-gatewayIstiodがGateway、VirtualService、EnvoyFilterの設定を、xDSでingress Envoyと各APIのEnvoyサイドカーに配布します。
KEDA
k8s-monitoring構成 · スケール: Pod数AIプールk8s-worker-ai · k8s-worker-ai-2KEDAはワーカーのPod数を調整します。ノード数は変わりません。
KEDA
k8s-monitoring構成 · スケール: Pod数storageプールk8s-worker-storage · k8s-worker-storage-2storageプールのワーカーPodも、同じ方法でスケールします。
Fluent Bit観測: ログElasticsearch
k8s-loggingFluent Bitは全ノードで動くDaemonSetで、ログをElasticsearchへ送ります。
AIプール
k8s-worker-ai · k8s-worker-ai-2観測: LLM実行のトレースLangSmithLLM呼び出しのトレースは、外部SaaSのLangSmithへ送られます。
すべての接続を同じ太さで表示します。フローを選ぶと、その経路だけを番号付きで強調します。
- 実線 · リクエスト、作業、イベント、書き込みが流れる方向
- 点線 · DNS参照。リクエストは通過しない
- 破線 · 構成またはスケール。データ経路ではない
- 長い一点鎖線 · ログと実行トレース
- 太い黒線 · 選んだフロー。番号は順序
- ボックス · 物理ノード。見出しにホスト名
- 内側のチップ · そのノードで動くワークロード
- 破線の枠 · 2ノードのスケジューリングプール
- 破線ボックス · クラスターの外。ノード数に含めない
- オレンジの枠 · モデルが判断する場所
- Control plane1ノード
- Domain API8ノード
- AIプール2ノード
- storageプール2ノード
- Data5ノード
- Platform6ノード
READMEの5つのレイヤー(帯)と物理グループ(各ノードの見出しの色)は、同じ24ノードを異なる方法でまとめています。State KVはStreamsノードを共有しているため別に数えず、Route 53、ALB、クラスター外のサービスはノードではありません。プールはスケジューリングの範囲であり、すべてのPodが両方のノードで動くという意味ではありません。
24ノード
24 = 1 control plane + 8 API + 4 worker + 5 data + 6 platform
1Control plane
role: control-planekube-apiserver · scheduler · controller-manager · etcdと、プラットフォームのコントローラー。
- k8s-master
8Domain API
domain: <サービス>ドメインごとに1ノード。内部呼び出しはgRPCです。
- k8s-api-auth
- k8s-api-users
- k8s-api-scan
- k8s-api-character
- k8s-api-location
- k8s-api-image
- k8s-api-chat
- k8s-api-info
4Execution worker
2ノードずつのスケジューリングプールが2つです。KEDAはPod数を変え、ノード数は変わりません。
storageプール
domain: worker-storage- k8s-worker-storage
- k8s-worker-storage-2
AIプール
domain: worker-ai- k8s-worker-ai
- k8s-worker-ai-2
5Data
Redisは用途別に4つのノードに分けています。PostgreSQLのストレージはEBS CSI · gp3です。
- k8s-postgresql
- k8s-redis-auth
- k8s-redis-streams
- k8s-redis-cache
- k8s-redis-pubsub
6Platform
メッセージング、可観測性、ingress、リアルタイム配信は、それぞれ専用のノードに置いています。
- k8s-rabbitmq
- k8s-monitoring
- k8s-logging
- k8s-ingress-gateway
- k8s-sse-gateway
- k8s-event-router
- 全ノード
- Fluent Bit · DaemonSet
- Calico
設定されていたモデル · プロバイダー別
サービスは終了しているため、これらは現在選べるモデルではなく、リポジトリで設定されていたモデルです。役割ごとに別々に記載しており、実行を確認した一覧ではありません。
OpenAI
Chatの既定プロバイダー
Chat
openai/gpt-5.2既定
既定のモデルはgpt-5.2で、リクエストでモデルが指定された場合はそちらを使います。
Scan
gpt-5.2既定gpt-5.2-progpt-5.1gpt-5gpt-5-progpt-5-mini
13モデルの対応表にないモデルは拒否されます。
画像の対応表
gpt-image-1.5
Responsesのオーケストレーションモデルであるgpt-5.2とは別です。画像生成は既定でオフです。
評価
gpt-4o-mini既定
会話評価に使うLLM graderで、Chatモデルとは別です。
Google
ChatでGoogleを選んだ場合
Chat
google/gemini-3-pro-previewgoogle/gemini-3-flash-preview
Googleを選ぶと、gemini-3-flash-previewが既定です。
Scan
gemini-3-pro-previewgemini-3-flash-previewgemini-2.5-progemini-2.5-flashgemini-2.5-flash-litegemini-2.0-flashgemini-2.0-flash-lite
画像の対応表
gemini-3-pro-image-preview