| 역할 | 백엔드 개발 / 시뮬레이터 개발 / EMS 연동 테스트 |
| 담당 파트 | • ESS·Solar·Diesel·Load 시뮬레이터 설계 및 구현 • Docker 기반 동적 컨테이너 관리 시스템(simulator-manager) 개발 • 시뮬레이터 → MQTT → EMS 전체 데이터 파이프라인 연동 및 검증 |
| Backend | Python, FastAPI, MQTT (paho-mqtt), Redis Stream |
| Frontend | Vue.js Python |
| AI | OpenAI Codex, Anthropic Claude (설계 교차 검증 및 코드 생성) |
| Database & Messaging | PostgreSQL, Redis, MQTT Broker (Mosquitto) |
| Infrastructure | Docker, Docker SDK, GitLab CI/CD, AWS EC2 (5대), Nginx |
| 핵심 아이디어 | 이미지는 고정하고, 설정 파일을 볼륨으로 주입해 서로 다른 스펙의 시뮬레이터 인스턴스를 동적으로 생성 |
| 구현 의도 | 웹 UI에서 엣지를 생성하면 미리 빌드된 시뮬레이터 이미지를 재사용하면서도, 각 컨테이너가 고유한 전력 용량·MQTT 주소·디바이스 설정을 갖도록 설계 |
| 기대 효과 | 이미지 빌드 없이 설정 파일만 바꿔 다양한 ESS·Solar·Diesel·Load 시뮬레이터를 빠르게 생성·삭제할 수 있는 테스트 환경 확보 |
solar-simulator:latest ─┐
ess-simulator:latest ─┤ (미리 빌드된 이미지)
diesel-simulator:latest ─┤
load-simulator:latest ─┘def create_edge(body: dict) -> dict:
edge_id = body.get("edge_id", "").strip()
edge_type = body.get("edge_type", "").strip()
# 1. 설정 디렉토리 생성
d = _edge_dir(edge_id) # edges/{edge_id}/
d.mkdir(parents=True)
# 2. 메타 정보 저장
(d / "edge_info.json").write_text(json.dumps(info, indent=2))
# 3. 시뮬레이터 설정값 저장 (power_limit_kw, capacity_kwh 등)
with (d / "devices.yaml").open("w") as f:
yaml.dump(cfg, f, default_flow_style=False, allow_unicode=True)
# 4. 컨테이너 실행
_start_container(edge_id, edge_type)
return {"edge_id": edge_id, "status": "created"}def _start_container(edge_id: str, edge_type: str) -> None:
image = EDGE_TYPE_IMAGES[edge_type] # "ess-simulator:latest"
host_path = str(Path(HOST_EDGES_PATH) / edge_id)
# 핵심: 호스트의 설정 디렉토리 -> 컨테이너 /app/config 로 마운트
volumes = {host_path: {"bind": "/app/config", "mode": "rw"}}
_docker_client.containers.run(
image=image,
name=edge_id, # 컨테이너 이름 = edge_id (고유 식별자)
detach=True,
volumes=volumes, # 설정 주입
network=REAL_NETWORK_NAME,
restart_policy={"Name": "always"},
environment={
"MQTT_USER": os.environ.get("MQTT_USER", ""),
"MQTT_PASSWORD": os.environ.get("MQTT_PASSWORD", ""),
"EDGE_ID": edge_id,
},
extra_hosts={"host.docker.internal": "host-gateway"},
)호스트 파일시스템 컨테이너 내부
edges/
ess-edge-01/
edge_info.json -- 볼륨 마운트 --> /app/config/edge_info.json
devices.yaml -- 볼륨 마운트 --> /app/config/devices.yaml
시뮬레이터 시작 시 여기서 설정 읽음
solar-edge-01/
devices.yaml -- 볼륨 마운트 --> /app/config/devices.yaml| 🛑 문제 : | 삭제된 장치 데이터가 /resources API에 계속 노출 |
| 로컬에서 시뮬레이터 컨테이너를 삭제했음에도 EMS /resources API에 해당 디바이스가 계속 노출되어, 실제 운영 중이지 않은 장치가 EMS 제어 로직에 포함될 수 있었음 | |
| 🔍 원인 : | Redis TTL 미설정과 MQTT Retained 메시지 잔존 |
| Redis 상태 키가 영구 저장되고, MQTT broker의 retained 메시지가 ingestion 서비스 재시작 시 다시 수신되어 삭제된 상태 키가 재생성되는 구조였음 | |
| 🛠️ 해결 : | Redis TTL 적용 및 엣지 삭제 시 MQTT retained 메시지 자동 클리어 |
| state-processor의 Redis 상태 저장 로직에 30초 TTL을 적용하고, simulator-manager의 엣지 삭제 로직에서 빈 payload와 retain=True로 retained 메시지를 삭제하도록 구현 |
| 🛑 문제 : | 로컬 시뮬레이터가 동작 중인데 /resources API가 빈 배열 반환 |
| 시뮬레이터가 MQTT로 데이터를 정상 전송하고 있었지만 EMS API에서는 실시간 리소스 데이터가 조회되지 않았음 | |
| 🔍 원인 : | Redis Stream Consumer Group 소실 |
| MQTT, ingestion, Redis Stream까지는 정상 동작했으나, Redis Stream을 DEL 명령으로 삭제하면서 Consumer Group도 함께 사라져 state-processor가 xreadgroup으로 데이터를 읽지 못하는 상태였음 | |
| 🛠️ 해결 : | Consumer Group 재생성 및 state-processor 재시작 |
| XGROUP CREATE mg:sensor:data state-group 0 MKSTREAM으로 Consumer Group을 재생성하고, state-processor를 재시작해 내부 |
| 구분 | 상세 내용 |
|---|---|
| 🛑 문제 : | 코드 변경 없이 동일 커밋을 push해도 CI/CD 파이프라인 반복 실패 |
| GitLab CI/CD가 Docker 빌드 단계에서 계속 실패했으나, 코드 변경 사항만으로는 원인을 찾기 어려웠음 | |
| 🔍 원인 : | EC2 디스크 용량 100% 사용 |
| EC2에 직접 접속해 df -h로 확인한 결과 /dev/xvda1 디스크가 30GB 중 30GB를 사용 중이었음 | |
| 🛠️ 해결 : | 불필요한 Docker 이미지 정리 |
| SDD + AI Agent 교차 검증 | API 스펙과 데이터 구조를 마크다운 문서로 먼저 정의한 뒤 AI Agent에게 넘기면 팀 전체가 일관된 코드 스타일을 유지할 수 있음을 배움.OpenAI Codex와 Claude를 교차 검증에 활용하며 서로 다른 관점에서 엣지 케이스와 설계 결함을 발견할 수 있었음. |
| 분산 시스템 데이터 생명주기 관리 | 데이터를 어떻게 만드는지만큼 언제, 어떻게 지우는지가 중요하다는 것을 체감함.Redis TTL과 MQTT retained 클리어를 조합해 stale 데이터 문제를 근본적으로 해결함. |
| 멀티 EC2 환경 E2E 디버깅 | 분산된 서비스 간 장애는 양 끝에서 안쪽으로 좁혀가는 단계별 추적이 효율적임을 배움.로그가 여러 서버에 분산된 환경에서 중앙 집중형 모니터링의 필요성을 실감함. |
| 협업에서 문서와 말의 병행 | 문서는 합의된 기준점으로, 말은 복잡한 개념 전달과 빠른 의사결정 수단으로 역할을 분리할 수 있음을 배움.개발 결과물의 가치는 팀이 활용할 수 있을 때 완성된다는 점을 체감함. |
| 데이터 파이프라인 구축 | 시뮬레이터(ESS·Solar·Diesel·Load) → MQTT → ingestion → state-processor → EMS 전체 데이터 파이프라인 구축 및 정상화 |
| 실시간 연동 완료 | 시뮬레이터 0.1초 간격(10Hz) telemetry 발행 → ingestion 1초 단위 집계 → EMS 실시간 제어 판단까지 end-to-end 연동 완료 |
| Stale 데이터 문제 해결 | Redis TTL + MQTT retained 클리어 조합으로 stale 데이터 문제 해결, 시뮬레이터 삭제 후 30초 이내 자동 만료 |
| 동적 시뮬레이터 환경 구축 | Docker 볼륨 주입 패턴으로 다양한 설정의 시뮬레이터를 동적으로 생성·삭제하는 로컬 테스트 환경 구축 |
| ✨ 결과 : | 삭제된 시뮬레이터 데이터의 자동 만료 및 재복원 문제 해결 |
| 시뮬레이터 삭제 후 30초 이내 /resources API에서 자동 제거되며, ingestion 재시작 시 삭제된 데이터가 복원되는 문제를 해소 |
| ✨ 결과 : | 파이프라인 전 구간 정상화 |
| 시뮬레이터 → MQTT → ingestion → Redis Stream → state-processor → Redis 상태 키 → API 흐름이 복구되어 /resources API에서 실시간 시뮬레이터 데이터 반환 확인 |
| ✨ 결과 : | 동일 코드로 CI/CD 정상 통과 |
| CI/CD 실패를 코드 문제로만 단정하지 않고 서버 디스크, 메모리, 네트워크 등 인프라 상태까지 확인해야 한다는 점을 확인 |