feat: Fleet/Collector/엣지 배포 관련 누적 작업 일괄 커밋
Build and Push Images / build-and-push (push) Has been cancelled

이전 세션들에서 작업된 아래 범위를 모두 포함:

Fleet 서브시스템 (src/fleet/)
- fleetDeviceService / fleetCommandService / fleetDeploymentService / fleetReleaseService
- fleetMetricsService, fleetScriptService, fleetEdgeConfigService
- Edge 디바이스 관리, 커맨드 발행, 배포/릴리스, 스크립트 동기화

Collector 확장
- centralMqttForwarder / centralForwarderConfigService
- equipmentStateService, pythonHookRunner, scriptCache
- Modbus/OPC-UA/S7/XGT 프로토콜 클라이언트
- targetDbIntrospection (저장 DB 조회)

Routes / API
- automationDashboardRoutes, centralForwarderRoutes, equipmentStateRoutes

DB
- importEdgeConfig (Python cached config → Pipeline DB)
- seedDataSources (external_db_connections 초기 시드)

엣지 배포 리소스
- docker/edge/Dockerfile.backend.prod, Dockerfile.frontend.prod
- docker/edge/docker-compose.edge.yml

프론트엔드
- admin/automaticMng (centralForwarder, dashboard, equipmentState)
- admin/fleet (commands, devices, deployments, releases, scripts, alerts)
- admin/pipeline-device 개선 (저장 DB 드롭다운, 태그 매핑 등)
- ExternalDbConnectionModal, ScriptsManagerDialog 등 신규 컴포넌트
- lib/api: automationDashboard, centralForwarder, equipmentState, fleet

docs/
- EDGE_SERVER_STRUCTURE, FLEET_COMPLETE, FLEET_EDGE_INTEGRATION, FLEET_HOOK_INTEGRATION

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chpark
2026-04-23 20:00:06 +09:00
parent 01625d9efd
commit 4c1dc4082e
77 changed files with 14639 additions and 205 deletions
+2 -2
View File
@@ -3,9 +3,9 @@ FROM node:20-bookworm-slim
WORKDIR /app
# 시스템 패키지 설치 (curl: 헬스 체크용)
# 시스템 패키지 설치 (curl: 헬스 체크용, python3: Fleet Hook dry-run 용)
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl ca-certificates curl \
&& apt-get install -y --no-install-recommends openssl ca-certificates curl python3 \
&& rm -rf /var/lib/apt/lists/*
# package.json 복사 및 의존성 설치 (개발 의존성 포함)
@@ -9,6 +9,8 @@ services:
- ../../backend-node/.env
ports:
- "8080:8080"
- "1883:1883" # MQTT TCP (내장 브로커)
- "8083:8083" # MQTT WebSocket
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
+8 -5
View File
@@ -10,14 +10,17 @@ services:
environment:
- NEXT_PUBLIC_API_URL=http://localhost:8080/api
- SERVER_API_URL=http://pipeline-backend:8080
- NODE_OPTIONS=--max-old-space-size=8192
- NODE_OPTIONS=--max-old-space-size=6144
- NEXT_TELEMETRY_DISABLED=1
- WATCHPACK_POLLING=true
- WATCHPACK_POLLING_INTERVAL=3000
# volumes:
# - ../../frontend:/app # 소스 마운트 (Docker for Mac에서 컴파일 느림 → 비활성화)
# - /app/node_modules
# - /app/.next
mem_limit: 8g
mem_reservation: 3g
mem_swappiness: 0
volumes:
- ../../frontend:/app:delegated
- /app/node_modules
- /app/.next
networks:
- pipeline-network
restart: unless-stopped
+25
View File
@@ -0,0 +1,25 @@
# ============================================================
# Pipeline Edge 환경변수 예제 (이 파일을 .env로 복사 후 채우세요)
# ============================================================
# ─── DB 연결 ─────────────────────────────────────────
# 옵션 A: IDC 중앙 PostgreSQL 사용 (간단, 네트워크 의존)
DATABASE_URL=postgresql://vexplor_pipeline_user:pipline0909!!@211.115.91.170:11141/vexplor_pipeline
# 옵션 B: 엣지 로컬 PostgreSQL 쓰려면 같은 compose에 postgres 서비스 추가 후:
# DATABASE_URL=postgresql://pipeline:password@postgres:5432/pipeline
# ─── 보안 (반드시 바꿀 것) ───────────────────────────
JWT_SECRET=change-me-to-strong-random-secret-at-least-32-chars
PASSWORD_ENCRYPTION_KEY=change-me-32-byte-hex-key-for-aes-256
# ─── 엣지 식별 ───────────────────────────────────────
# 고객사 코드
COMPANY_CODE=spifox
# 엣지 UUID (스피폭스 예: aff81fbf-9b4c-43e0-9395-566bf47c3f9c)
EDGE_ID=aff81fbf-9b4c-43e0-9395-566bf47c3f9c
# ─── Pipeline 이미지 (Harbor 경로) ───────────────────
PIPELINE_IMAGE=harbor.wace.me/vexplor_fleet/pipeline-backend:latest
PIPELINE_FRONT_IMAGE=harbor.wace.me/vexplor_fleet/pipeline-front:latest
+58
View File
@@ -0,0 +1,58 @@
# ============================================================
# Pipeline Backend — 엣지 배포용 프로덕션 이미지
#
# Python 훅 실행기용 python3 포함.
# ts-node 대신 dist/app.js 실행 (프로덕션).
# ============================================================
FROM node:20-bookworm-slim AS builder
WORKDIR /app
# 시스템 패키지
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl ca-certificates curl python3 \
&& rm -rf /var/lib/apt/lists/*
# 의존성 설치 (devDependencies 포함 — tsc 빌드 필요)
COPY package*.json ./
RUN npm ci --prefer-offline --no-audit
# 소스 복사 + 빌드
COPY tsconfig.json ./
COPY src ./src
COPY db ./db
RUN npx tsc --outDir dist
# ── Runtime 스테이지 (작은 이미지) ──────────────────
FROM node:20-bookworm-slim
WORKDIR /app
# Python3 + 필수 런타임만
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl ca-certificates curl python3 \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
# Production 의존성만
COPY package*.json ./
RUN npm ci --omit=dev --prefer-offline --no-audit \
&& npm cache clean --force
# 빌드 결과물 복사
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/db ./db
# 스토리지 폴더
RUN mkdir -p /app/storage /app/uploads \
&& chown -R node:node /app
USER node
EXPOSE 8080 1883 8083
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -fsS http://localhost:8080/health || exit 1
CMD ["node", "dist/app.js"]
+36
View File
@@ -0,0 +1,36 @@
# Pipeline Frontend — 엣지 배포용 프로덕션 이미지 (next build + next start)
FROM node:20-bookworm-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --prefer-offline --no-audit
COPY . .
# 프로덕션 빌드
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# ── Runtime 스테이지 ───────────────────────────────
FROM node:20-bookworm-slim
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
COPY package*.json ./
RUN npm ci --omit=dev --prefer-offline --no-audit \
&& npm cache clean --force
# 빌드 결과물
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.* ./
EXPOSE 3000
CMD ["npx", "next", "start", "-p", "3000"]
+181
View File
@@ -0,0 +1,181 @@
# Pipeline Edge Deployment
스피폭스 등 고객사 엣지 서버에 Pipeline을 올려 기존 Python data-collector + Kafka + forwarder를 **완전 대체**합니다.
## 기존 vs 신규 구조
```
[기존]
PLC → Python data-collector → 로컬 Kafka → kafka-to-central-mqtt → IDC EMQX → TimescaleDB
[신규 — Pipeline 단일 서비스]
PLC → Pipeline (XGT/Modbus/OPC UA/S7 직접 수집 + Python 훅 실행) → IDC EMQX → TimescaleDB
```
Pipeline이 다음 역할을 모두 수행:
- 장비 폴링 (XGT/Modbus/OPC UA/S7)
- Python 훅 실행 (transform/filter/derived_tags, `python3` 서브프로세스)
- 로컬 현재값 스냅샷 (`equipment_current_state`)
- IDC MQTT 포워딩 (`dt/v1/data/{company_id}/{edge_id}`)
- 재시도 큐 (`central_mqtt_forwarder_retry_queue`)
- 모든 것을 UI에서 관리
## 1. 이미지 빌드 & 푸시 (최초 1회, 로컬에서)
```bash
cd /Users/chpark/workspace/vexplor_Pipeline
# 백엔드 프로덕션 이미지
docker build \
-f docker/edge/Dockerfile.backend.prod \
-t harbor.wace.me/vexplor_fleet/pipeline-backend:latest \
./backend-node
docker push harbor.wace.me/vexplor_fleet/pipeline-backend:latest
# (선택) 프론트엔드 이미지 — 엣지에서 UI 직접 띄우려면
docker build \
-f docker/dev/frontend.Dockerfile \
-t harbor.wace.me/vexplor_fleet/pipeline-front:latest \
./frontend
docker push harbor.wace.me/vexplor_fleet/pipeline-front:latest
```
## 2. 엣지 서버 준비 (스피폭스 `112.168.212.142`)
> ⚠️ **병행 운영 모드**
> 기존 Python data-collector / fleet-agent / kafka-to-central-mqtt는 **절대 중지하지 않고** 그대로 둡니다.
> Pipeline은 옆에서 별도로 기동해 "연결/수집/포워딩이 잘 되는지"만 검증합니다.
> 안정성 확인 후 사용자가 판단해서 기존 컨테이너 중지 여부 결정.
```bash
ssh wace@112.168.212.142
# Harbor 로그인
docker login harbor.wace.me
# Pipeline 전용 디렉토리 (기존 data-collector와 분리)
mkdir -p /home/wace/pipeline-edge
cd /home/wace/pipeline-edge
```
### 포트 충돌 확인 (기존 컨테이너와 겹치지 않는지)
```bash
# 기존 스피폭스 엣지의 포트 사용 현황 확인
docker ps --format 'table {{.Names}}\t{{.Ports}}' | grep -E '8080|1883|8083|9771'
```
만약 겹치면 Pipeline 쪽 포트를 바꿔 기동 (compose에서 `ports:` 좌측 값만 수정).
## 3. compose + env 배치
`docker-compose.edge.yml``.env.example`를 엣지에 업로드 후 `.env.example``.env`로 복사하고 값 설정:
```bash
# 로컬 → 엣지로 scp
scp docker/edge/docker-compose.edge.yml wace@112.168.212.142:/home/wace/pipeline-edge/
scp docker/edge/.env.example wace@112.168.212.142:/home/wace/pipeline-edge/.env
# 엣지에서 .env 편집
ssh wace@112.168.212.142
cd /home/wace/pipeline-edge
vi .env # DATABASE_URL, JWT_SECRET, PASSWORD_ENCRYPTION_KEY, EDGE_ID 등 입력
```
## 4. 기동
```bash
docker compose -f docker-compose.edge.yml up -d
# 프론트 UI도 같이 띄우려면:
docker compose -f docker-compose.edge.yml --profile with-ui up -d
# Watchtower 자동 업데이트까지:
docker compose -f docker-compose.edge.yml --profile watchtower up -d
```
## 5. 검증
```bash
# 헬스체크
curl http://localhost:8080/health
# 부팅 로그 확인
docker logs pipeline-backend --tail 100 | grep -iE 'collector|forwarder|script'
# 기대 출력:
# ✅ 중앙 MQTT 포워더 + 장비 현재값 테이블 생성 완료
# ✅ 프로토콜 CHECK 제약 확장 완료
# 🔌 장비 수집기 자동 시작: N개 연결
# [CentralForwarder] 연결됨: mqtt://211.115.91.170:31883
```
- 이후 웹에서 `http://<엣지IP>:9771`로 UI 접근 (또는 중앙 Pipeline UI에서 같은 DB 공유 시 공통 사용).
- **장비 통신** 페이지에서 PLC 연결 활성화 / 비활성화 가능
- **Python 훅** `/admin/fleet/scripts`에서 편집 → 연결에 체크박스로 붙임 → 다음 폴링부터 자동 반영
## 6. 롤백 / 정리
기존 Python data-collector는 그대로 돌고 있으므로 **Pipeline만 내리면** 원상 복구됩니다.
```bash
# Pipeline만 중지 (기존 data-collector는 영향 없음)
cd /home/wace/pipeline-edge
docker compose -f docker-compose.edge.yml down
```
## 병행 운영 중 주의사항 — **중복 IDC 전송 방지**
기존 `kafka-to-central-mqtt` forwarder가 돌고 있는 상태에서 Pipeline 포워더까지 켜면 **같은 데이터가 IDC에 두 번 들어갑니다** (동일 `edge_id`/`company_id` + 동일 토픽).
### 해결책 (택 1)
**A. Pipeline 포워더는 켜지 말기 (추천 — 연결 검증만 먼저)**
- `/admin/automaticMng/centralForwarder` 에서 포워더 설정 **비활성**(`is_enabled='N'`) 유지
- Pipeline은 수집/UI 테스트만, IDC 전송은 기존 forwarder가 계속 담당
**B. 테스트용 edge_id 사용**
- `.env``EDGE_ID=spifox-pipeline-test` 같은 식별자
- IDC TimescaleDB에서 이 edge_id만 별도로 보면서 수집값 검증
- 검증 끝나면 실 edge_id로 변경 + 기존 forwarder 중지
**C. 기존 포워더 중지 (완전 대체 시점)**
```bash
docker stop kafka-to-central-mqtt
# 이제 Pipeline 포워더 활성화
```
## 주요 환경변수
| 변수 | 설명 | 필수 |
|---|---|---|
| `DATABASE_URL` | PostgreSQL 접속 URL | ✅ |
| `JWT_SECRET` | JWT 서명 키 (32+ 글자) | ✅ |
| `PASSWORD_ENCRYPTION_KEY` | AES-256 키 (32바이트 hex) | ✅ |
| `ENABLE_AUTO_COLLECTOR` | 부팅 시 모든 활성 연결 자동 폴링 (엣지=true) | 엣지용 |
| `COMPANY_CODE` | 고객사 식별 (예: spifox) | ✅ |
| `EDGE_ID` | 엣지 UUID | ✅ |
## 트러블슈팅
### Python 훅 실행 에러
```bash
docker exec pipeline-backend python3 --version # 3.11+이어야 함
```
### IDC MQTT 미연결
```bash
docker exec pipeline-backend node -e '
const mqtt=require("mqtt");
const c=mqtt.connect("mqtt://211.115.91.170:31883",{username:"ingestion",password:"ingestion_secret_prod"});
c.on("connect",()=>{console.log("OK"); c.end();});
c.on("error",e=>console.log("ERR",e.message));
'
```
### PLC 미연결
```bash
docker exec pipeline-backend sh -c 'timeout 3 bash -c "cat < /dev/tcp/192.168.101.50/2004" && echo OK || echo FAIL'
```
+107
View File
@@ -0,0 +1,107 @@
# ============================================================
# Pipeline Edge 배포 Compose
#
# 목적: 스피폭스 등 고객사 엣지 서버에 Pipeline을 올려
# 기존 Python data-collector + Kafka + forwarder를 완전 대체
#
# 실행:
# cd /home/wace/pipeline-edge
# docker compose -f docker-compose.edge.yml up -d
#
# 전제:
# - .env 파일에 DATABASE_URL, PASSWORD_ENCRYPTION_KEY, JWT_SECRET 설정
# - Harbor 레지스트리 로그인 완료 (docker login harbor.wace.me)
# - 엣지에서 PLC(예: 192.168.101.50:2004) 도달 가능
# - 엣지에서 IDC EMQX (211.115.91.170:31883) 도달 가능
# ============================================================
services:
pipeline-backend:
image: ${PIPELINE_IMAGE:-harbor.wace.me/vexplor_fleet/pipeline-backend:latest}
container_name: pipeline-backend
restart: always
ports:
- "8080:8080" # REST API + Admin UI
- "1883:1883" # 내장 MQTT (로컬 용, 선택)
- "8083:8083" # MQTT WebSocket (선택)
environment:
# ─── 핵심 ─────────────────────────────────────────
- NODE_ENV=production
- PORT=8080
# ─── DB 연결 (IDC 원격 또는 로컬 Postgres) ──────────
- DATABASE_URL=${DATABASE_URL}
# ─── 보안 ─────────────────────────────────────────
- JWT_SECRET=${JWT_SECRET}
- PASSWORD_ENCRYPTION_KEY=${PASSWORD_ENCRYPTION_KEY}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
# ─── 장비 수집기 자동 시작 ────────────────────────
# 엣지에선 반드시 true — 부팅 시 DB의 모든 활성 연결 폴링 시작
- ENABLE_AUTO_COLLECTOR=true
# ─── 회사/엣지 식별 ──────────────────────────────
- COMPANY_CODE=${COMPANY_CODE:-spifox}
- EDGE_ID=${EDGE_ID}
volumes:
# 영속 데이터 (업로드, 로그 등)
- pipeline-data:/app/storage
- pipeline-uploads:/app/uploads
networks:
- pipeline-network
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
# ─── 프론트엔드 (선택) ──────────────────────────────
# 엣지에서 직접 UI 접근하고 싶으면 켜기. 보통은 중앙 Pipeline UI 사용.
pipeline-front:
image: ${PIPELINE_FRONT_IMAGE:-harbor.wace.me/vexplor_fleet/pipeline-front:latest}
container_name: pipeline-front
restart: always
ports:
- "9771:3000"
environment:
- NEXT_PUBLIC_API_URL=http://localhost:8080/api
- SERVER_API_URL=http://pipeline-backend:8080
- NODE_OPTIONS=--max-old-space-size=2048
networks:
- pipeline-network
labels:
- "com.centurylinklabs.watchtower.enable=true"
profiles: ["with-ui"] # docker compose --profile with-ui up 로 선택 기동
# ─── Watchtower (자동 업데이트) ──────────────────────
# 기존 스피폭스 엣지와 동일한 패턴: Harbor 폴링 + 라벨 기반
watchtower:
image: nickfedor/watchtower:latest
container_name: watchtower
restart: always
environment:
- WATCHTOWER_POLL_INTERVAL=300
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_LABEL_ENABLE=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ~/.docker/config.json:/config.json:ro
command: --interval 300
labels:
- "com.centurylinklabs.watchtower.enable=false"
profiles: ["watchtower"]
networks:
pipeline-network:
driver: bridge
name: pipeline-network
volumes:
pipeline-data:
name: pipeline-data
pipeline-uploads:
name: pipeline-uploads