Skip to content

component image snapshot pipeline

JJong-03 edited this page Jun 17, 2026 · 3 revisions

컴포넌트 - Image Snapshot Pipeline

Factory A의 AI event snapshot 원본은 presigned URL로 S3에 직접 업로드하고, IoT Core에는 이미지 binary가 아니라 S3 참조 metadata만 전달한다.


책임과 경계

Safe-Edge AI
  -> worker2 hostPath snapshot 생성
  -> snapshot-uploader polling
  -> SnapshotPresigner API
  -> S3 presigned PUT (image binary)
  -> outbox image_snapshot metadata
  -> edge-iot-publisher
  -> AWS IoT Core MQTT (metadata only)
  -> IoT Rule
      -> S3 raw metadata
      -> Lambda Data Processor
          -> S3 processed metadata
          -> DynamoDB latest image reference

이미지 binary는 AWS IoT Core, MQTT message, raw metadata JSON, processed metadata JSON, DynamoDB를 통과하지 않는다. Binary 전송 경로는 snapshot-uploader -> presigned S3 PUT뿐이다.

image_snapshotfactory_state payload의 선택 필드가 아니라 별도 canonical source type이다. 현재 publisher와 Data Processor가 허용하는 source type은 다음과 같다.

factory_state
infra_state
image_snapshot

로컬 Snapshot 생성

safe-edge-integrated-ai가 AI event 이미지를 worker2의 node-local hostPath에 만든다.

Safe-Edge Pod path: /app/snapshots
Host path:          /var/lib/safe-edge/snapshots
파일 예:            260608114226_event_FIRE.jpg

파일명 앞의 YYMMDDhhmmss를 UTC source_timestamp로 해석한다. 형식이 맞지 않으면 파일 mtime을 사용한다. _event_ 뒤 문자열은 대문자 event_type이 되며, 파싱할 수 없으면 UNKNOWN이다.

지원 확장자와 content type:

확장자 content type
.jpg, .jpeg image/jpeg
.png image/png

Polling과 Uploader State

snapshot-uploader는 filesystem event가 아니라 polling으로 hostPath root의 파일을 확인한다.

scan dir:      /var/lib/safe-edge/snapshots
scan interval: 10초
state file:    /var/lib/aegis/outbox/.snapshot-uploader-state.json

Scan 결과는 mtime과 파일명으로 정렬한다. 후보마다 크기, mtime, SHA-256, content type, event type, source timestamp를 계산한다. 기본 최대 크기는 5 MiB이며 초과 파일은 건너뛴다.

State entry에는 로컬 절대 경로를 key로 두고 다음 값을 기록한다.

{
  "/var/lib/safe-edge/snapshots/example.jpg": {
    "size_bytes": 60345,
    "mtime_ns": 1780882955270612976,
    "sha256": "<sha256>",
    "s3_key": "image_snapshot/factory_id=factory-a/...",
    "uploaded_at": "2026-06-08T00:45:00Z"
  }
}

같은 path의 size_bytesmtime_ns가 state와 같으면 재업로드하지 않는다. 둘 중 하나가 달라지면 새 후보로 처리한다.

State가 없으면 기존 hostPath 파일도 최초 scan에서 backlog로 업로드한다. State JSON이 손상되면 timestamp가 붙은 .bad 파일로 격리하고 빈 state로 계속한다. S3 PUT과 outbox 기록은 끝났지만 state 기록에 실패한 경우, 같은 message ID의 outbox metadata에서 size, SHA-256, S3 key가 일치하면 재업로드 없이 state를 복구한다.

Uploader는 snapshot hostPath를 read-only로 mount하며 원본 파일을 삭제하지 않는다. 로컬 보존과 삭제는 Safe-Edge cleanup/purge 정책의 책임이다.


Presigner API와 S3 PUT

Uploader는 이미지 bytes가 아닌 upload 요청 metadata를 API Gateway의 POST /image-snapshot/presign으로 보낸다.

{
  "factory_id": "factory-a",
  "node_id": "worker2",
  "filename": "260608114226_event_FIRE.jpg",
  "content_type": "image/jpeg",
  "size_bytes": 60345,
  "sha256": "<sha256>",
  "source_timestamp": "2026-06-08T11:42:26Z",
  "event_type": "FIRE"
}

SnapshotPresigner Lambda의 검증 기준:

  • factory_idALLOWED_FACTORY_IDS에 포함되어야 한다. 현재 Factory A MVP 허용 값은 factory-a다.
  • filename은 path separator와 ..가 없는 basename이어야 한다.
  • content type은 image/jpeg, image/png만 허용한다.
  • size는 1 byte 이상, 기본 5 MiB 이하여야 한다.
  • SHA-256은 64자리 hex 문자열이어야 한다.
  • source timestamp는 timezone이 있는 ISO 8601이어야 한다.

Lambda가 S3 key를 생성하고 기본 300초 유효한 presigned PUT URL과 필수 Content-Type header를 반환한다. Edge가 임의 S3 key를 지정하지 않는다. Lambda IAM 권한도 data bucket의 image_snapshot/*에 대한 s3:PutObject로 제한한다.

Uploader는 반환 URL에 image bytes를 HTTP PUT한다. Pi에는 이 작업을 위한 장기 AWS access key를 저장하지 않는다.


Outbox와 MQTT Metadata

S3 PUT 성공 후 uploader가 canonical envelope를 outbox에 atomic write한다.

/var/lib/aegis/outbox/tmp/<temporary>.tmp
  -> fsync
  -> /var/lib/aegis/outbox/{message_id}.json

핵심 필드:

{
  "source_type": "image_snapshot",
  "source_timestamp": "2026-06-08T11:42:26Z",
  "payload": {
    "event_type": "FIRE",
    "content_type": "image/jpeg",
    "size_bytes": 60345,
    "sha256": "<sha256>",
    "s3_bucket": "aegis-bucket-data",
    "s3_key": "image_snapshot/factory_id=factory-a/yyyy=2026/mm=06/dd=08/hh=11/260608114226_event_FIRE.jpg",
    "local_path": "/var/lib/safe-edge/snapshots/260608114226_event_FIRE.jpg",
    "upload_status": "uploaded"
  }
}

edge-iot-publisher가 publish 직전에 published_atdata_plane_instance_id를 갱신하고 다음 topic으로 QoS 1 publish ack 완료 후 outbox metadata를 삭제한다.

aegis/factory-a/image_snapshot

성공하면 outbox JSON을 삭제하고, MQTT/TLS 실패 시 그대로 두어 재시도한다. Invalid JSON이나 schema 오류는 outbox/quarantine/으로 이동한다.


저장 경로 구분

세 경로는 데이터 성격과 생성 주체가 다르다.

구분 S3 경로 내용 생성 주체
Snapshot original image_snapshot/factory_id={factory_id}/yyyy={YYYY}/mm={MM}/dd={DD}/hh={HH}/{filename} JPEG/PNG binary presigned S3 PUT
Raw metadata raw/{factory_id}/image_snapshot/yyyy={YYYY}/mm={MM}/dd={DD}/{message_id}.json IoT Core가 받은 canonical envelope IoT Rule
Processed metadata processed/{factory_id}/image_snapshot/yyyy={YYYY}/mm={MM}/dd={DD}/hh={HH}/{message_id}.json 정규화된 S3 참조 metadata Data Processor

Raw metadata 경로에는 현재 hh partition이 없고, processed metadata에는 있다.

Data Processor는 payload를 정규화한 뒤 공장별 LATEST item에 최신 이미지 참조를 갱신한다.

pk = FACTORY#factory-a
sk = LATEST

latest_image_snapshot:
  event_type
  source_timestamp
  processed_at
  s3_bucket
  s3_key
  content_type
  size_bytes
  sha256
  message_id

last_image_snapshot_at

DynamoDB에는 binary나 presigned URL을 저장하지 않는다.


대시보드 조회와 표시

위 단계까지는 원본 binary가 S3 image_snapshot/ prefix에, 참조 metadata가 processed/와 DynamoDB LATEST에 남는다. 본사 관제자는 이 객체를 직접 보지 않고 Dashboard를 통해 조회한다. 표시 경로는 다음과 같다.

Dashboard Web (/image-snapshots, /chat)
  -> Dashboard Backend (FastAPI)
      -> RBAC: System 권한 확인
      -> S3 list_objects_v2 (image_snapshot/ prefix)
      -> S3 presigned GET URL 생성 (object별)
  -> 브라우저 <img>가 presigned URL로 원본을 직접 GET

이미지 binary는 Backend를 통과하지 않는다. Backend는 S3 객체 목록과 짧은 만료의 presigned GET URL만 생성하고, 실제 download는 브라우저가 presigned URL로 S3에서 직접 받는다. S3 객체는 여전히 non-public이며, public-read ACL을 쓰지 않는다.

Backend 조회 경로

routers/image_snapshots.py의 두 endpoint가 services/s3.py를 호출한다.

Endpoint 동작 S3 호출
GET /image-snapshots/range?factory_id= picker용 가용 범위(가장 이른/늦은 partition, 객체 수) image_snapshot/factory_id=.../ prefix 전체 list
GET /image-snapshots?factory_id=&start=&end=&limit= 시간 범위 내 스냅샷 목록 + presigned GET URL 시간별 hh= prefix list
  • 두 endpoint 모두 require_system_access(principal)System 권한을 강제한다. 공장 단위 권한이 아니라 시스템 조회 권한이 경계다.
  • start/end는 ISO 8601 local time이며 start < end, 미래 종료 보정 등은 Frontend가 선검증하고 Backend가 재검증(400 Invalid time range)한다.
  • 조회는 시간 범위가 겹치는 yyyy=/mm=/dd=/hh= partition prefix만 list하여 비용을 제한한다. 객체가 limit(기본 120, 최대 300)에 도달하면 조기 종료한다.
  • S3 지연/실패는 504로 변환한다(S3UnavailableError).

각 객체에 대해 Backend가 만드는 응답 항목:

{
  "factory_id": "factory-a",
  "s3_key": "image_snapshot/factory_id=factory-a/yyyy=2026/mm=06/dd=08/hh=11/260608114226_event_FIRE.jpg",
  "filename": "260608114226_event_FIRE.jpg",
  "url": "<presigned GET URL, 기본 900초 만료>",
  "last_modified": "2026-06-08T11:42:30+00:00",
  "size_bytes": 60345,
  "detection_type": "FIRE"
}

detection_type은 파일명 _event_<TYPE> 패턴에서 파생한다(예: FIRE). DynamoDB나 metadata를 다시 읽지 않고 S3 key만으로 분류 라벨을 만든다.

Image Snapshots 화면 (/image-snapshots)

apps/dashboard-webImageSnapshotsPage가 갤러리를 그린다.

  • System 권한 전용: /auth/me.can_view_system이 false이면 화면 진입 시 "시스템 조회 권한이 필요합니다" empty-state를 표시하고 조회를 시도하지 않는다(Backend 403이 최종 경계).
  • 공장 selector + 시작/종료 시각 picker로 범위를 고른다. 진입 시 GET /image-snapshots/range로 S3 가용 시작 시각을 받아 picker 하한을 맞추고, 미래·역전 범위를 막는다.
  • 범위가 바뀌면 GET /image-snapshots를 다시 호출해 카드 그리드를 갱신한다. 각 카드는 presigned URL을 <img loading="lazy">로 표시하고, 클릭 시 새 탭에서 원본을 연다. 카드 메타에는 탐지 종류(detection_type), 수정 시각, 크기를 보여준다.
  • 상태 처리: 로딩 spinner, S3 지연/오류 재시도, 403 권한 거부, 빈 범위(해당 시간대 스냅샷 없음) empty-state를 구분한다.

챗봇 증빙 이미지 연계

AI 채팅에서 "사진/이미지/스냅샷/증빙" 류 키워드가 있으면, Backend가 같은 s3.list_image_snapshots를 질문 시각 범위(또는 탐지 spike 시각 ±10분)에 대해 호출해 답변에 **증빙 이미지(image_ref)**를 함께 싣는다. 이 경로도 require_system_access를 거치므로, 채팅이 RBAC 우회 통로가 되지 않는다. 자세한 동작은 AI 채팅 어시스턴트를 따른다.


배치와 MVP 제약

Factory A 현재 배치는 다음과 같다.

safe-edge-integrated-ai: worker2 snapshot hostPath 사용
snapshot-uploader:       worker2 단일 Deployment
edge-iot-publisher:      worker2 배치
outbox:                  Longhorn RWO PVC

Snapshot hostPath는 node-local이므로 worker1에서 worker2의 기존 파일을 볼 수 없다. 또한 uploader와 publisher가 공유하는 outbox가 Longhorn RWO이므로 현재 MVP는 worker2의 단일 writer/publisher 배치를 전제로 한다.

Worker2 장애 시 AI workload 자체가 worker1에서 재기동될 수 있어도, worker1 snapshot upload failover와 worker2 backlog 인계는 구현 범위가 아니다. Worker별 uploader, state 분리, outbox 다중 writer, 중복 방지까지 포함한 failover 설계가 추가로 필요하다.


보안

  • S3 original object를 public-read로 만들지 않는다. Presigner와 uploader 어느 쪽도 ACL을 지정하지 않는다.
  • Raspberry Pi에 장기 AWS access key를 저장하지 않는다.
  • Presigned URL은 짧은 만료 시간과 제한된 object key에만 사용한다.
  • 실제 token, certificate, IoT endpoint secret은 repo와 위키에 기록하지 않는다.
  • 현재 PRESIGN_SHARED_TOKEN 기본값과 운영 적용값은 빈 문자열이라 bearer token 인증이 비활성화된 상태다. 운영 전 token 설정 또는 더 강한 API 인증으로 보강해야 한다.

관련 문서

Aegis-Pi Wiki

· 대표 문서 목록은 홈의 문서 탐색 표 참조

시작하기

요구사항

핵심 개념

아키텍처

컴포넌트 (Edge → Cloud → Dashboard)

Dashboard & 운영

시나리오 · 사례 · 참조

Clone this wiki locally