클러스터 설치
대규모 워크로드를 위해 PlantPulse 를 마스터 + 워커 노드 클러스터 로 구성하는 방법입니다. 단일 노드로 시작했다가 처리량이 한계에 도달했을 때 점진적으로 확장할 수 있습니다.
다중 노드 구성 두 가지 — 목적이 다릅니다
구성 목적 문서 ① MASTER / WORKER 클러스터 인프라(Cassandra·Kafka·Spark)를 수평 확장 — 같은 계층을 여러 노드로 분산 이 문서 ② DATALAKE / APP 2노드 분리 저장/처리 계층과 콘솔/배치 계층을 서로 다른 박스로 분리(수직 분리) — 노드별 메모리 절감·부하 격리 2노드 분리 설치 처리량이 부족해 인프라를 늘리려면 ①, 한 박스의 메모리가 부족하거나 배치 부하를 콘솔과 격리하려면 ② 를 사용합니다. 둘은 배타적이지 않으며
PP_MODE(클러스터링)와PP_TIER(계층 게이트)는 직교합니다.
언제 클러스터로 확장하나요?
| 환경 규모 | 권장 구성 | 비고 |
|---|---|---|
| 태그 ~5,000 | 단일 마스터 | 클러스터 불필요 |
| 태그 5,000 ~ 50,000 | 마스터 + 1~2 워커 | 분석 / 메시징 분산 |
| 태그 50,000+ | 마스터 + 3+ 워커 | 본격 분산 운영 |
클러스터 토폴로지
하드웨어 요구사항
각 워커 노드 사양 권장:
| 구분 | 최소 | 표준 | 대규모 |
|---|---|---|---|
| CPU | 16 vCPU | 32 vCPU | 48 vCPU |
| 메모리 | 64 GB | 128 GB | 200+ GB |
| 데이터 디스크 | 200 GB NVMe | 1 TB NVMe | 4 TB NVMe |
| 네트워크 | 1 Gbps | 10 Gbps | 10 Gbps |
노드 간 네트워크: 클러스터 노드들 간에는 10 Gbps 이상 권장. Cassandra repair, Kafka replication, Spark shuffle 트래픽이 큽니다.
사전 요건
마스터 노드와 모든 워커 노드에서 다음이 완료되어야 합니다.
- 시스템 요구사항 충족
- 호스트명 / 고정 IP / DNS 설정
- 시간 동기화 (chronyd) — 노드 간 시간차 100ms 이하 필수
- OS 튜닝 (limits / sysctl / swap off)
- 노드 간 사설망 통신 가능 (RFC 1918 또는 Tailscale)
- 클러스터 내부 포트 허용 (Cassandra 7000/7001/9042, Kafka 9092/9093, Spark 7077/8081 등)
/etc/hosts 설정 (모든 노드 동일)
# /etc/hosts — 마스터 / 워커 모든 노드에 동일하게
192.168.0.41 plantpulse-master plantpulse-master.local
192.168.0.101 plantpulse-worker-1 plantpulse-worker-1.local
192.168.0.102 plantpulse-worker-2 plantpulse-worker-2.local
192.168.0.103 plantpulse-worker-3 plantpulse-worker-3.local
Docker 클러스터 (권장 경로)
Docker/원라인 설치 환경에서는 워커를 compose 오버레이로 띄웁니다. 마스터 노드에 기본 스택이 떠 있는 상태에서 bin/worker-add.sh 가 워커를 하나씩 추가합니다. 시크릿·인증서는 아래 절차대로 사이드카 파일로 노드 간 복사하므로 값을 손으로 옮겨 적을 필요가 없습니다.
워커 목록의 정본 — compose/workers.roster
어떤 워커가 존재하는가의 정본은 compose/workers.roster 파일 하나입니다. 한 줄에 <id> <ip> 형식이며, 단일 노드 설치라면 비어 있는 것이 정상입니다.
# compose/workers.roster
1 10.99.0.101
| 항목 | 뜻 |
|---|---|
id | PP_WORKER_ID. 컨테이너 이름(plantpulse-worker-<id>)과 볼륨 이름(pw-<id>-*)의 접미사 |
ip | pp-net(10.99.0.0/24) 위의 고정 주소. .1 은 게이트웨이, .100 은 데이터레이크이므로 .101 부터 사용합니다 |
compose/docker-compose.worker.yml 은 이 파일에서 생성되고(bin/gen-worker-compose.sh), bin/env.sh 도 같은 파일에서 워커 IP 와 노드 목록을 유도합니다. 그래서 compose·운영 스크립트·시크릿 회전 가드가 서로 다른 워커 집합을 보는 일이 생기지 않습니다.
docker-compose.worker.yml 은 생성물입니다. 손으로 고치면 CI 의 gen-worker-compose.sh --check 가 실패합니다. 워커를 늘리고 줄이는 것은 roster 에 줄을 넣고 빼는 것이 아니라 아래의 worker-add.sh / worker-decommission.sh + worker-remove.sh 로 합니다 — 워커는 Cassandra 토큰 레인지와 PostgreSQL 복제 슬롯을 들고 있어서 목록만 고쳐서는 만들어지지도 없어지지도 않습니다.
워커 관련 환경 변수 (bin/env.sh)
| 변수 | 기본값 | 설명 |
|---|---|---|
DOCKER_PW_NAME | plantpulse-worker | 워커 컨테이너 이름 접두어 (plantpulse-worker-1 …) |
DOCKER_PW_MEMORY | DOCKER_DATALAKE_MEMORY 와 같은 값 (80G, 호스트가 작으면 RAM 의 90%) | 워커 컨테이너 메모리 상한 |
DOCKER_PW_IP_<n> · PP_WORKER_NODES | roster 에서 유도 | 손으로 적지 않습니다 |
워커는 데이터레이크 마스터와 같은 이미지를 쓰므로(2026-08-31 통합) 그 이미지에 구워진 JVM 크기도 그대로 따릅니다 — Cassandra 하나가 -Xms16G/-Xmx16G 를 요구합니다. 호스트 크기에 비례해 잡는 방식은 틀렸습니다. 필요한 양은 호스트가 아니라 서비스 구성이 정합니다.
2026-08-31 실측: 옛 12g 기본값의 워커가 Cassandra 가 링에 닿기 전에 OOMKilled 되었고, 컨테이너는 OOMKilled=true 로 죽었는데 조인은 일어나지 않은 채 health: starting 을 유지했습니다.
워커 추가
cd /opt/kopens/plantpulse-platform-docker
bin/worker-add.sh # 빈 id·빈 주소 자동 선택
bin/worker-add.sh 3 # id 지정
bin/worker-add.sh 3 10.99.0.103 # id·주소 지정
worker-add.sh 가 순서대로 수행합니다.
- roster 에 등록 (이미 있는 id 는 «추가» 가 아니므로 거부합니다)
- 오버레이 compose 재생성
- 볼륨 생성 → 이미지 pull →
up -d - 링 합류 확인 — 마스터의
nodetool로 직접 검증
워커는 Cassandra 토큰 레인지, Spark 워커 등록, PostgreSQL 복제 슬롯, Redis 복제 링크를 함께 들고 있습니다. 컨테이너 자체의 준비 검사는 «마스터에 닿는가»만 보기 때문에, 조인에 실패한 워커도 성공한 워커와 똑같이 정상으로 보고합니다.
2026-08-31 실측: Cassandra 가 OOMKilled 된 워커가 링은 1노드인 채로 health: starting 을 유지했습니다. 그래서 worker-add.sh 는 링 자체를 물어봅니다.
워커 운영
cd /opt/kopens/plantpulse-platform-docker
# 진입 (워커는 기본 스택의 서비스가 아니라 shell.sh 로는 잡히지 않습니다)
docker exec -ti plantpulse-worker-3 /bin/bash
# 정지 — compose 동사 그대로
docker compose -f compose/docker-compose.yml -f compose/docker-compose.worker.yml \
--profile worker-3 stop plantpulse-worker-3
# 이미지 갱신 (이미 도는 워커. 추가 시점의 pull 은 worker-add.sh 안에 들어 있습니다)
docker compose -f compose/docker-compose.yml -f compose/docker-compose.worker.yml \
--profile worker-3 pull plantpulse-worker-3
docker compose -f compose/docker-compose.yml -f compose/docker-compose.worker.yml \
--profile worker-3 up -d plantpulse-worker-3
워커 제거 — 반드시 이 순서
bin/worker-decommission.sh 3 # 데이터 이관 + 링 이탈 확인 + 복제 슬롯 정리
bin/worker-remove.sh # 그 다음에 컨테이너 제거
docker rm 은 제거가 아니라 「유기」 입니다Cassandra 는 그 노드의 토큰 레인지와 host id 를 DN 으로 붙들고 있습니다. RF=3 이면 QUORUM 이 그대로 성립하므로 아무 경보도 뜨지 않습니다. 그리고 마스터는 그 워커의 PostgreSQL 물리 복제 슬롯을 계속 들고 있어 디스크가 찰 때까지 WAL 을 고정합니다.
worker-remove.sh 는 컨테이너가 돌고 있으면 거부하고 worker-decommission.sh 를 먼저 부르라고 안내합니다.
worker-run.sh · worker-stop.sh · worker-update.sh · worker-bash.sh 네 개는 삭제되었습니다. 손으로 관리하던 워커 서비스 목록을 전제로 만들어진 것들이라, roster 생성 방식으로 바꾸면서 이름만 바꾼 게 아니라 없앴습니다. 대체는 각각 worker-add.sh(pull 포함) · compose 동사 · compose pull + up -d · docker exec 입니다.
시크릿 사이드카 복사 (손 복사 금지)
마스터에서 생성된 서비스 시크릿은 /etc/kopens/plantpulse-platform.env 사이드카 파일에 있습니다. 이 파일을 워커 노드로 복사하면 워커가 동일한 자격증명으로 조인합니다 — 비밀번호를 하나씩 옮겨 적지 마세요(2노드 분리 문서의 조인 번들과 동일 패턴).
# 마스터 노드에서 각 워커로
scp /etc/kopens/plantpulse-platform.env root@<worker-ip>:/etc/kopens/
공유 CA 복사 → 자동 seed
노드 간 TLS 신뢰는 공유 클러스터 CA 로 성립합니다. 마스터의 /etc/kopens/ca/ 를 워커로 복사하면 컨테이너 기동 시 pp-security 볼륨에 자동 seed 되어 인증서를 손수 배치할 필요가 없습니다.
# 마스터 노드에서 각 워커로 (컨테이너 경로 기준 자동 seed)
scp -r /etc/kopens/ca root@<worker-ip>:/etc/kopens/
platform.node.env는 노드별 정체성 파일이므로 복사하지 마세요. 복사 대상은 위 두 항목(plantpulse-platform.env,ca/)뿐입니다.
클러스터 검증 (Docker 환경)
# Cassandra 링 — 모든 노드가 UN (Up Normal) 이어야 합니다
docker exec plantpulse-datalake \
/opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin/pd node status
# Spark — 워커가 ALIVE 인지
# 브라우저에서 http://<마스터IP>:4440/ 의 Workers 탭
네이티브(바이너리) 클러스터 절차
아래는 바이너리 설치로 구축된 기존 시스템을 위해 남겨 둔 절차입니다. 현행 출하본은 위의 Docker Compose 경로 하나이므로, 신규 구축에는 사용하지 말아 주세요.
1. 마스터 노드 설치
마스터 노드는 단일 노드 설치 절차와 동일합니다.
1.1 마스터 설치
# 마스터 노드에서
mkdir -p /opt/kopens
cd /opt/kopens
tar -xzvf plantpulse-platform-2026.05.tgz
cd /opt/kopens/plantpulse-platform/tools
./setup.sh
# 노드 모드 입력: MASTER
# HOST IP: 192.168.0.41
# SERVICE IP: 192.168.0.41 (외부 노출 IP)
1.2 env.local.sh 조정 (클러스터 옵션)
env.sh는 배포로 덮이는 SSOT(기본값 정본)이므로 직접 수정하지 마세요. 머신 고유값은 같은 디렉토리의env.local.sh에 오버라이드로 작성합니다(env.sh가 맨 위에서 먼저 source 하므로 항상 우선 적용). 자세한 원리는 바이너리 설치 §4 참고.
vi /opt/kopens/plantpulse-platform/plantpulse-startup/env.local.sh
# 마스터 노드
export PP_MODE=MASTER
export PP_HOST_IP=192.168.0.41
export PP_SERVICE_IP=192.168.0.41
export PP_MASTER_IP=192.168.0.41
export PP_PUBLIC_IP=192.168.0.41
# 클러스터 자원
export PP_CLUSTER_CORES=64 # 마스터 + 워커 코어 합 (Spark 사용)
export PP_CLUSTER_MEMORY_BY_CORE=2G
# TLS SAN 에 모든 노드 IP / 도메인 포함
export PP_TLS_SAN_IPS="192.168.0.41,192.168.0.101,192.168.0.102,192.168.0.103,127.0.0.1"
export PP_TLS_SAN_DNS="plantpulse-master,plantpulse-worker-1,plantpulse-worker-2,plantpulse-worker-3,localhost"
export PP_TLS_NODE_NAMES="master worker-1 worker-2 worker-3"
1.3 마스터 시작
cd /opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin
./configure.sh
./prepare-ssl.sh
./start-daemon.sh
./status.sh
2. 워커 노드 추가
2.1 워커 설치
각 워커 노드에서 동일한 절차로 설치합니다.
# 워커 노드 (예: 192.168.0.101) 에서
mkdir -p /opt/kopens
cd /opt/kopens
tar -xzvf plantpulse-platform-2026.05.tgz
cd /opt/kopens/plantpulse-platform/tools
./setup.sh
# 노드 모드 입력: WORKER
# HOST IP: 192.168.0.101
# SERVICE IP: 192.168.0.101
# MASTER IP: 192.168.0.41
2.2 워커 env.local.sh 조정 (노드 고유값만)
노드별 IP 등 머신 고유값만 env.local.sh 에 작성합니다(env.sh 직접 수정 ✗).
vi /opt/kopens/plantpulse-platform/plantpulse-startup/env.local.sh
# 워커 노드
export PP_MODE=WORKER
export PP_HOST_IP=192.168.0.101 # 이 워커의 IP
export PP_SERVICE_IP=192.168.0.101
export PP_MASTER_IP=192.168.0.41 # 마스터의 IP
export PP_PUBLIC_IP=192.168.0.101
비밀번호/키스토어 비밀번호는 여기에 손으로 옮겨 적지 않습니다 — 아래 시크릿 사이드카로 노드 간 동기화합니다.
2.3 시크릿 사이드카 복사
서비스 시크릿은 마스터의 /etc/kopens/plantpulse-platform.env 사이드카 파일에 있습니다. 이 파일을 워커로 복사하면 워커가 동일 자격증명으로 조인합니다(2노드 분리 문서의 조인 번들과 동일 패턴). 비밀번호를 하나씩 옮겨 적지 마세요.
# 마스터에서 워커로 (env.sh 보다 먼저 source 되는 사이드카)
scp /etc/kopens/plantpulse-platform.env root@192.168.0.101:/etc/kopens/
2.4 공유 CA 복사 → 자동 seed
노드 간 TLS 신뢰는 공유 클러스터 CA 로 성립합니다. 마스터의 /etc/kopens/ca/ 를 워커로 복사하면 기동 시 컨테이너 경로 기준으로 자동 seed 되어 인증서를 손수 배치할 필요가 없습니다.
# 마스터에서 워커로 (공유 CA — 크로스노드 TLS 신뢰)
scp -r /etc/kopens/ca root@192.168.0.101:/etc/kopens/
platform.node.env는 노드별 정체성 파일이므로 복사하지 마세요.
2.5 워커 시작
cd /opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin
./configure.sh
./start-daemon.sh
./status.sh
워커 노드의 status.sh 출력에서 다음이 RUNNING 이면 정상입니다.
- Cassandra (:9042)
- Kafka (:9092)
- Spark Worker (:8081)
참고: 워커 노드는 server / cep / batch 등 애플리케이션 모듈은 실행하지 않습니다. 인프라 / 분산 처리 컴포넌트만 클러스터링됩니다.
3. 클러스터 검증
3.1 Cassandra 클러스터
# 마스터에서
cd /opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin
./pd node status
기대 출력 (예시):
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
-- Address Load Tokens Owns Host ID Rack
UN 192.168.0.41 45.2 GiB 256 ? <uuid> rack1
UN 192.168.0.101 44.8 GiB 256 ? <uuid> rack1
UN 192.168.0.102 45.5 GiB 256 ? <uuid> rack1
UN (Up Normal) 이 모든 노드에 표시되면 정상입니다.
3.2 Kafka 클러스터
cd /opt/kopens/plantpulse-platform/plantpulse-messaging/kafka/bin
./kafka-broker-api-versions.sh --bootstrap-server 192.168.0.41:9092 | head
브로커가 3개로 표시되면 정상입니다.
3.3 Spark 클러스터
브라우저에서 마스터의 Spark UI 접속:
http://192.168.0.41:4440/
Workers 탭에서 모든 워커가 ALIVE 상태인지 확인합니다.
4. 클러스터 운영
워커 추가 (동적 확장)
이미 운영 중인 클러스터에 워커 노드를 추가하는 절차:
워커 제거
# 1. 워커를 안전하게 비우기 (Cassandra)
ssh root@192.168.0.103 \
/opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin/pd node drain
# 2. 마스터에서 노드 제거
cd /opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin
./pd node remove
# 호스트 ID 입력
# 3. 워커 정지 후 제거
ssh root@192.168.0.103 \
/opt/kopens/plantpulse-platform/plantpulse-startup/stop.sh
노드 교체
장애 노드를 새 노드로 교체할 때는 관리자: 노드 교체 절차를 참고해 주세요. 핵심은 같은 IP / 호스트명을 유지하면서 새 노드를 부팅 후 bootstrap 모드로 데이터를 끌어오는 것입니다.
5. 클러스터 백업
마스터 노드의 plantpulse-backup 이 전체 클러스터를 책임집니다.
cd /opt/kopens/plantpulse-platform/plantpulse-backup/bin
# Cassandra: 모든 노드에서 snapshot 수집
./backup.sh --cassandra
# PostgreSQL: 마스터에서만
./backup.sh --postgres
백업 및 복구 참고.
자주 발생하는 문제
| 증상 | 원인 | 조치 |
|---|---|---|
| 워커 join 안 됨 | seed 노드 미설정 | cassandra.yaml 의 seeds 에 마스터 IP 포함 확인 |
| Cassandra 토큰 불균형 | 부적절한 join | pd node cleanup + 토큰 재할당 |
| Kafka under-replicated | 브로커 다운 | kafka-topics.sh --describe + 복제 인자 점검 |
| Spark Worker 등록 안 됨 | 방화벽 7077 차단 | 사설망 7077, 8081 양방향 허용 |
| 인증서 Mismatch | keystore 동기화 누락 | 마스터에서 prepare-ssl.sh 재실행 + 워커 재배포 |
| 시간 불일치 | NTP 미설정 | chronyc tracking 확인, 100ms 이하 유지 |
클러스터 제거
# 각 워커에서
ssh root@<WORKER_IP> /opt/kopens/plantpulse-platform/plantpulse-startup/stop.sh
ssh root@<WORKER_IP> rm -rf /opt/kopens/plantpulse-platform
# 마스터에서
cd /opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin
./stop.sh
rm -rf /opt/kopens/plantpulse-platform
# 데이터 디스크는 별도 정책으로 관리 (필요 시 백업 후 삭제)