plantpulse-server (웹 콘솔 + IIoT 엔진)
역할
플랫폼의 얼굴이자 두뇌에 해당하는 핵심 모듈입니다. 운영자가 보는 웹 콘솔, 외부 시스템이 호출하는 REST API, 그리고 산업 데이터를 실제로 처리하는 IIoT 엔진이 모두 이 한 모듈 안에서 동작합니다.
| 항목 | 값 |
|---|---|
| 모듈명 | plantpulse-server |
| 컨테이너 | plantpulse-server-web (자기 이미지, 자기 컨테이너) |
| 컨테이너 안 경로 | /opt/kopens/plantpulse-platform/plantpulse-server/ |
| 사용자 접속 | 80 / 443 — plantpulse-proxy 가 받아 이 컨테이너로 넘깁니다 |
| 관리 콘솔 | 7443 (HTTPS, plantpulse-datalake 가 publish) |
| 호스트 포트 | 없음 — 프록시 뒤에서만 동작합니다 |
| 메모리 한계 | DOCKER_SERVER_MEMORY 기본 16g / 힙 DOCKER_SERVER_HEAP 기본 12g |
| 런타임 | Apache Tomcat 9 + Java 21 |
| 처리량 | 초당 40,000건 메시지 (36스레드 파이프라인) |
사용자 트래픽은 항상 plantpulse-proxy(80/443)를 거칩니다. 그래서 백엔드가 옮겨가거나 이름이 바뀌어도 사용자·설비 설정을 건드릴 필요가 없습니다. 컨테이너 상태는 bin/status.sh, 로그는 bin/logs.sh plantpulse-server-web 으로 봅니다.
책임 영역
디렉토리 구조
plantpulse-server/
├── config/ # 서버 관리 설정 (외부화 — 배포해도 유지)
│ ├── plantpulse-engine.properties # IIoT 엔진 튜닝
│ ├── plantpulse-storage.properties # DB 연결
│ ├── plantpulse-mq.properties # 메시징 연결
│ ├── plantpulse-mail.properties # SMTP
│ ├── plantpulse-ai.properties # AI 게이트웨이
│ └── log4j2.xml # 로깅 설정 (자가 시딩 — 최초 기동 시 생성)
├── bin/
│ ├── start.sh
│ ├── stop.sh
│ └── log-viewer.sh
├── logs/
│ └── system.log
├── path/ # 경로 매핑
└── server/ # 내장 Tomcat
├── conf/
│ ├── server.xml # 커넥터 / 포트
│ ├── context.xml # 컨텍스트 / 데이터소스
│ ├── catalina.properties
│ ├── logging.properties
│ ├── tomcat-users.xml
│ ├── web.xml
│ └── Catalina/localhost/
│ └── ROOT.xml # 컨텍스트 설정 (sessionCookieName 등)
└── webapps/
├── ROOT.war # 웹 애플리케이션 (WAR 통배포)
└── ROOT/ # Tomcat 이 unpack 한 디렉토리 (수정 금지)
배포 방식: 웹앱은
server/webapps/ROOT.war교체 방식으로 배포합니다 (WAR 통배포, 전환 진행 중). 기존app/plantpulse-server-webexploded 디렉토리 방식은 폐기 예정이며,server.xml의 ContextdocBase오버라이드는 제거되었습니다.ROOT/내부 파일을 직접 수정하면 다음 배포 때 사라지므로, 설정 변경은 항상config/에서 합니다.
주요 설정 파일
설정 파일은 모듈의 config/ 디렉토리에 있으며, 서버는 -Dpp.conf.dir → ${catalina.base}/../config → 클래스패스(fallback) 순으로 탐색합니다. 외부 설정이 로드되면 기동 로그에 Properties loaded from external conf: 가 출력됩니다.
application.properties 는 제거되었습니다 (2026.06). 테마 / 홈페이지 등 콘솔 동작 설정은 콘솔의 시스템 > 설정 관리 (
mm_config테이블)에서 관리하고,alarm.duplicate.check.minutes는plantpulse-engine.properties로 이관되었습니다.
plantpulse-engine.properties
IIoT 엔진 파이프라인 튜닝.
| 항목 | 기본값 | 설명 |
|---|---|---|
engine.pipeline.threads | 36 | 병렬 처리 스레드 수 |
engine.pipeline.ratelimit | 40000 | 초당 최대 메시지 |
engine.pipeline.batch.size | 1000 | 배치 묶음 크기 |
engine.pipeline.queue.size | 1200000 | 큐 용량 |
engine.dedup.enabled | true | 중복 제거 활성화 |
plantpulse-storage.properties
데이터 저장소 연결.
# Cassandra
cassandra.host=${PP_CASSANDRA_HOST}
cassandra.port=${PP_CASSANDRA_PORT}
cassandra.keyspace=${PP_KEYSPACE}
cassandra.user=${PP_CASSANDRA_USER}
cassandra.password=${PP_CASSANDRA_PASSWORD}
# PostgreSQL (Spring DataSource)
postgres.url=jdbc:postgresql://${PP_POSTGRES_HOST}:${PP_POSTGRES_PORT}/${PP_DB_NAME}
postgres.user=${PP_PG_USER}
postgres.password=${PP_PG_PASSWORD}
# Valkey
redis.host=${PP_REDIS_HOST}
redis.port=${PP_REDIS_PORT}
redis.password=${PP_REDIS_PASSWORD}
이 앱은 설정을 렌더링하지 않습니다. 컨테이너로 갈라진 뒤 웹 서버는 데이터레이크의 설정 렌더러 없이 뜨며, 값을 세 곳에서 읽습니다 — ① 운영자가 둔 외부 파일 → ② WAR 에 내장된 기본값 → ③ compose 가 넘긴 환경변수. 셋 중 환경변수가 내장 기본값을 덮습니다.
그래서 앱이 보는 값의 정의처는
compose/docker-compose.yml하나뿐입니다. 데이터레이크 쪽에만 있고 compose 에 없는 이름은 이 앱에 닿지 않습니다 — 데이터레이크에서만 식별자를 바꾼 사이트에서 앱 다섯이 옛 이름을 계속 쓴 사고가 그것입니다(2026-08-29).웹앱 내부(
server/webapps/ROOT/)의 파일은 수정하지 마세요 — 배포 시 사라집니다.
server/conf/server.xml
Tomcat 커넥터 설정. 포트 변경 / TLS / 압축 / 스레드 풀.
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
maxThreads="500"
acceptCount="200"
URIEncoding="UTF-8"
compression="on"
compressibleMimeType="text/html,text/css,application/json,application/javascript" />
<Connector port="443" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
SSLEnabled="true"
maxThreads="500"
sslEnabledProtocols="TLSv1.3,TLSv1.2">
<SSLHostConfig>
<Certificate certificateKeystoreFile="/var/security/plantpulse/server.jks"
certificateKeystorePassword="${PP_TLS_KEYSTORE_PASSWORD}" />
</SSLHostConfig>
</Connector>
운영 명령
호스트에서 — 컨테이너 단위
컨테이너를 다시 띄우는 것이 곧 이 모듈의 재시작입니다. 인프라와 다른 앱은 영향을 받지 않습니다.
cd /opt/kopens/plantpulse-platform-docker
# 서버만 재시작
docker compose -f compose/docker-compose.yml restart plantpulse-server-web
# 상태 · 로그
bin/status.sh
bin/logs.sh plantpulse-server-web -n 200
컨테이너 안으로 들어가기
cd /opt/kopens/plantpulse-platform-docker/bin
./shell.sh plantpulse-server-web
restart-server.sh 로는 재시작되지 않습니다그 스크립트는 각 앱의 자기 컨테이너 안에서 그 모듈의 런처를 부르는 얇은 위임입니다. 데이터레이크 컨테이너에는 웹 서버 실행 파일이 없으므로 «모듈이 없다» 는 오류로 끝납니다. 위의 컨테이너 단위 재시작을 사용해 주세요.
핫 리로드 (재시작 없이 설정 반영)
일부 설정은 콘솔의 시스템 > 설정 관리 메뉴에서 핫 리로드가 가능합니다. config/ 의 프로퍼티 파일이나 server.xml 등 부팅 시점 설정은 재시작이 필요합니다.
로그
| 로그 | 경로 | 내용 |
|---|---|---|
| 메인 로그 | logs/system.log | 애플리케이션 로직, 에러 |
| Tomcat catalina | server/logs/catalina.out | Tomcat 표준 출력 |
| 액세스 로그 | server/logs/localhost_access_log.YYYY-MM-DD.txt | HTTP 요청 로그 |
| 엔진 로그 | logs/engine.log | IIoT 엔진 처리 상세 |
| 슬로우 쿼리 | logs/slow.log | 200ms 이상 쿼리 |
로그 레벨 변경 — 로깅 설정(log4j2.xml)은 모듈 config/ 로 외부화되어 있습니다 (자가 시딩: 최초 기동 시 WAR 기본값이 config/log4j2.xml 로 복사됨, 적용 시 기동 로그에 Logging reconfigured from external conf: 출력):
# 웹 서버 컨테이너 안에서
cd /opt/kopens/plantpulse-platform-docker/bin
./shell.sh plantpulse-server-web
vi /opt/kopens/plantpulse-platform/plantpulse-server/config/log4j2.xml
# <Logger name="plantpulse" level="DEBUG"/> 등 수정 후 exit
# 호스트에서 그 컨테이너만 재시작
cd /opt/kopens/plantpulse-platform-docker
docker compose -f compose/docker-compose.yml restart plantpulse-server-web
외부 파일이므로 WAR 재배포에도 영구 보존됩니다. 웹앱 내부(
server/webapps/ROOT/WEB-INF/classes/log4j2.xml)는 수정하지 마세요 — 배포 시 사라집니다.
성능 튜닝
JVM 힙
# server/bin/setenv.sh (없으면 생성)
export CATALINA_OPTS="-Xms16g -Xmx32g \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/plantpulse/heap/ \
-Xlog:gc*:file=/var/log/plantpulse/gc.log:time,uptime:filecount=10,filesize=100M"
Tomcat 스레드 풀
server.xml 의 Connector 에서 maxThreads / acceptCount 조정. 대규모 동시 접속 환경에서는 다음을 권장합니다.
| 동시 접속 | maxThreads | acceptCount |
|---|---|---|
| ~ 100 | 200 | 100 |
| ~ 500 | 500 | 200 |
| 500+ | 1000 | 500 |
IIoT 엔진 처리량
plantpulse-engine.properties 의 engine.pipeline.threads 를 호스트 코어 수의 70~80% 로 설정. 메시지 적체 시 engine.pipeline.queue.capacity 도 함께 상향.
자세한 튜닝은 성능 튜닝 페이지를 참고해 주세요.
헬스 체크
# 표준 헬스체크 (익명, readiness — 엔진이 전 단계 기동을 완료(RUNNING)했을 때만 UP. 배포 자동검증 / 모니터링용)
curl -fsS http://127.0.0.1/api/health
# 준비 완료: 200 {"status":"UP","service":"plantpulse-server-web","ts":1765500000000,"checks":{"engine":"RUNNING"}}
# 기동 중: 503 {"status":"STARTING",...} / 기동 실패·중지: 503 {"status":"DEGRADED",...}
# 콘솔 ping
curl -fsS http://127.0.0.1/api/v5/ping
# 인증 필요 헬스체크
curl -fsS -u admin:admin123! http://127.0.0.1/api/v5/health
# WebSocket 핸드셰이크 확인
curl -i -N \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
-H "Sec-WebSocket-Version: 13" \
http://127.0.0.1:8000/ws
자주 발생하는 문제
| 증상 | 원인 | 조치 |
|---|---|---|
OutOfMemoryError | 힙 부족 | setenv.sh 에서 -Xmx 상향 + GC 로그 분석 |
Too many open files | 파일 디스크립터 한계 | /etc/security/limits.conf 의 nofile 65535+ |
| 콘솔 응답 느림 | DB 슬로우 쿼리 | PostgreSQL slow query 로그, Valkey 캐시 적중률 확인 |
| 로그인 무한 루프 | 세션 / 쿠키 문제 | 브라우저 캐시 삭제, server/conf/Catalina/localhost/ROOT.xml 의 sessionCookieName 확인 |
| 502 / 504 (리버스 프록시) | 백엔드 응답 시간 초과 | nginx proxy_read_timeout 상향 |
| WebSocket 끊김 | 방화벽 idle timeout | 방화벽 timeout > 60s, tomcat.websocket.session.timeout 조정 |
상세 진단은 문제 해결 페이지를 참고해 주세요.
보안 체크리스트
- 기본 admin 비밀번호 변경 (
admin / admin123!) -
tomcat-users.xml의 manager 계정 제거 또는 강한 비밀번호 -
server.xml의shutdown port (7000)외부 접근 차단 - HTTPS 인증서 적용 (
prepare-ssl.sh결과 또는 외부 CA) - X-Frame-Options / CSP / HSTS 헤더 설정 확인
- API Bearer Token 운영 vault 에 저장
-
config/프로퍼티 파일의 디버그 옵션 비활성화 및 권한 제한 (chmod 600)
자세한 보안 설정은 보안 설정 페이지를 참고해 주세요.