AIDC 랙 밀도 계산기 개발기
NVIDIA Rubin HGX 기반 16MW AI 데이터센터 설계 도구를 Kubernetes 위에서 만들어가는 과정
목차
- 프로젝트 개요
- 시스템 아키텍처
- 168 Rack / 16MW 구성으로 재설계
- 랙 타입별 구성 탭 추가
- NVIDIA Rubin HGX 기반 최적 아키텍처
- Network 랙 중앙 배치 알고리즘
- 트러블슈팅
- 기술적 결정 모음
1. 프로젝트 개요
AI 데이터센터(AIDC) 구축을 위해 랙 밀도 계산기를 웹 애플리케이션으로 만들었다.
IT 전력·랙 수·PUE 등 파라미터를 입력하면:
- 랙 밀도, 서버 수, 냉각 부하를 자동 계산
- Canvas 기반 배치 구성도를 실시간 렌더링
- 설정을 MySQL에 저장하고 불러오기 가능
최종적으로 168 Rack, 16MW IT Load, GPU 7,200개 규모의 NVIDIA Rubin 기반 클러스터 설계를 목표로 했다.
화면 구성
| 탭 | 설명 |
|---|---|
| 랙 밀도 계산 | PUE, 랙 구성, 전력 파라미터 입력 및 결과 확인 |
| 배치 구성도 | Canvas 기반 랙 레이아웃 시각화 (줌/드래그/클릭) |
| 랙 타입 구성 | 랙 타입별 서버 스펙 상세 설정 |
| 저장 이력 | MySQL에 저장된 설정 목록 및 불러오기 |
2. 시스템 아키텍처
브라우저
└── Nginx (NodePort 30080)
├── / → 정적 파일 (HTML/CSS/JS)
└── /api/ → FastAPI (port 8000)
└── MySQL (port 3306)
기술 스택
| 레이어 | 기술 |
|---|---|
| 인프라 | Kubernetes (namespace: aidc) |
| 웹 서버 | Nginx Alpine |
| 백엔드 | FastAPI + SQLAlchemy (Python) |
| DB | MySQL 8.0 |
| 프론트엔드 | Vanilla JS (no framework), Canvas API |
Kubernetes 배포 방식: ConfigMap 마운트
이 프로젝트의 핵심 배포 전략은 Docker 이미지를 빌드하지 않는 것이다. 코드 파일을 ConfigMap으로 올려 런타임에 마운트한다.
# nginx deployment 中
volumeMounts:
- name: web-html
mountPath: /usr/share/nginx/html
- name: web-css
mountPath: /usr/share/nginx/html/css
- name: web-js
mountPath: /usr/share/nginx/html/js
volumes:
- name: web-js
configMap:
name: web-js-cm
덕분에 코드 수정 후 redeploy.sh 한 번이면 반영된다.
# redeploy.sh 핵심 흐름
kubectl create configmap web-js-cm \
--from-file=web/js/app.js \
--from-file=web/js/calculator.js \
--from-file=web/js/layout.js \
--from-file=web/js/racktype.js \
--dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart deployment/nginx deployment/api -n aidc
kubectl rollout status deployment/nginx -n aidc --timeout=60s
장점: 이미지 빌드·푸시 시간 없이 수십 초 만에 반영
단점: 파일이 많아지면 ConfigMap 관리가 번거로워짐
3. 168 Rack / 16MW 구성으로 재설계
4가지 랙 타입
처음에는 단일 랙 타입으로 시작했지만, 실제 데이터센터 설계에 맞게 4가지로 분류했다.
| 타입 | 수량 | 랙당 전력 | 특징 |
|---|---|---|---|
| GPU | 150개 | 96 kW | HGX Rubin NVL8, Liquid Cooling |
| Storage | 8개 | 21 kW | NVMe JBOF |
| Network | 5개 | 45 kW | Spectrum-X SN5600 |
| 기타 | 5개 | 12 kW | 관리/모니터링 서버 |
| 합계 | 168개 | — | IT Load ≈ 14.85 MW |
IT Load 계산 방식 변경
초기에는 총 IT 전력을 사용자가 직접 입력했다. 이를 자동 계산으로 전환했다.
# 백엔드 calculator.py
it_load_mw = round(
(gpu_count * gpu_kw +
sto_count * sto_kw +
net_count * net_kw +
oth_count * oth_kw) / 1000,
3
)
total_power_mw = round(it_load_mw * pue, 3)
cooling_load_mw = round(total_power_mw - it_load_mw, 3)
IT Load = 전체 랙 전력 합산 / 1000 → PUE를 곱하면 총 설비 전력.
Canvas 배치도: 수직 아일 패턴
COLD | col | HOT | col | COLD | col | HOT | ...
짝수 열 앞: Cold Aisle (냉각 통로)
홀수 열 앞: Hot Aisle (열 통로)
섹션(SEC_ROWS=7)마다 구역을 구분해 렌더링.
4. 랙 타입별 구성 탭 추가
설계 목적
랙당 전력(kW)을 직접 입력하는 대신, 서버 스펙에서 자동 계산되도록 했다.
서버당 소비전력 (W) × 서버 개수 + 기타 소비전력 (W)
─────────────────────────────────────────────────── = 랙 소비전력 (kW)
1000
UI 구성
4개 타입 카드를 2×2 그리드로 배치. 각 카드의 입력 항목:
| 항목 | 설명 |
|---|---|
| 서버 크기 (U) | 서버 1대가 차지하는 U 수 |
| 서버 종류 | 모델명 텍스트 입력 |
| 서버당 소비전력 (W) | 노드 1대의 소비전력 |
| 기타 소비전력 (W) | ToR 스위치, OOB 등 |
| 노드당 GPU 수 | GPU 총계 자동 계산용 |
| 서버 개수 | 실제 설치 대수 |
→ 전력 소비량 (kW) 자동 계산 후 계산기 탭 슬라이더에 실시간 반영
데이터 흐름
랙 타입 구성 탭 입력
↓
racktype.js: updateCard()
↓ kw.toFixed(1) 값을 push
calculator 탭 슬라이더 (#gpu-rack-kw 등)
↓
window.triggerCalculate()
↓
POST /api/calculate/
↓
결과 카드 + 배치 구성도 업데이트
localStorage 버전 관리
설정값은 localStorage에 저장해 새로고침 후에도 유지된다. 기본값 변경 시 구버전 캐시와 충돌하므로 키 버전 번호로 관리한다.
// 버전 올리면 구버전 localStorage는 무시되고 HTML 기본값 사용
const LS_RACKTYPE_KEY = 'aidc-racktype-config-v3';
const LS_KEY = 'aidc-calc-inputs-v3';
5. NVIDIA Rubin HGX 기반 최적 아키텍처
GPU 랙 스펙 (HGX Rubin NVL8)
| 항목 | 값 | 근거 |
|---|---|---|
| 서버 폼팩터 | 8U | HGX 표준 섀시 |
| GPU / 노드 | 8개 | Rubin Ultra NVL8 구성 |
| GPU TDP | ~1,200W | Rubin 아키텍처 추정 |
| 노드 총 소비전력 | 16,000W | GPU×1,200W + CPU/DRAM/NVLink 오버헤드 ~6,400W |
| 노드 수 / 랙 | 6대 | 48U 점유 (4U: ToR/케이블) |
| 랙 소비전력 | 96 kW | 6 × 16,000W |
→ 150 GPU 랙 × 96kW = 14.4MW
→ 총 GPU: 150 × 6 × 8 = 7,200개
인프라 랙 현실적 스펙
실제 장비 데이터시트 기반으로 보정했다. 초기 추정치가 크게 과대 계상되어 있었다.
Storage (NVMe JBOF)
| 항목 | 값 |
|---|---|
| 모델 | 2U NVMe JBOF (Supermicro SSG 계열) |
| 구성 | 24× NVMe SSD U.2 (7.68TB) |
| CPU | 2× AMD EPYC (저전력) |
| 소비전력 | 800W / 서버 (SSD 144W + CPU 300W + 기타) |
| 설치 대수 | 24대 (48U) |
| 기타 | ToR 스위치 2,000W |
| 랙 소비전력 | (24 × 800 + 2,000) / 1,000 = 21.2 kW |
SSD는 Flash 특성상 HDD 대비 전력이 매우 낮다 (약 6W/ea).
Network (NVIDIA Spectrum-X SN5600)
| 항목 | 값 |
|---|---|
| 모델 | Spectrum-X SN5600 (1U) |
| 포트 | 64× 400GbE QSFP112 |
| 소비전력 | 1,200W / 스위치 (데이터시트 기준) |
| 설치 대수 | 36대 (36U) |
| 기타 | 패치패널/관리 오버헤드 2,000W |
| 랙 소비전력 | (36 × 1,200 + 2,000) / 1,000 = 45.2 kW |
비교: 초기 추정치 80kW → 실제 45kW (44% 과대 추정)
기타 (관리/모니터링 서버)
| 항목 | 값 |
|---|---|
| 모델 | 2U EPYC 서버 (Dell R750 계열) |
| 소비전력 | 500W / 서버 (관리 부하 기준 ~30-50% 이용률) |
| 설치 대수 | 20대 (40U) |
| 기타 | OOB 스위치, KVM 2,000W |
| 랙 소비전력 | (20 × 500 + 2,000) / 1,000 = 12 kW |
최종 구성 요약
GPU 150랙 × 96.0kW = 14,400kW (97.0%)
STO 8랙 × 21.2kW = 170kW ( 1.1%)
NET 5랙 × 45.2kW = 226kW ( 1.5%)
ETC 5랙 × 12.0kW = 60kW ( 0.4%)
─────────────────────────────────────
14,856kW ≈ 14.85MW IT Load
× PUE 1.25 = 18.57MW 총 설비 전력
GPU가 IT Load의 97%를 차지 — 실제 AI 슈퍼컴퓨터의 전형적인 전력 분포다.


6. Network 랙 중앙 배치 알고리즘
문제
기존 배치 순서: GPU → Storage → Network → Other
→ Network 랙이 항상 GPU 블록 뒤에 배치됨
→ 실제 데이터센터에서 코어 스위치는 컴퓨트 플로어 중앙에 위치해야 케이블 길이를 최소화할 수 있음
알고리즘
# GPU 블록 중앙에 Network 랙 삽입 위치 계산
gpu_mid = gpu_count // 2
if cols > 0 and gpu_count >= cols * 2:
# 행(row) 경계에 맞춰 정렬 → 가로 띠(band) 형태
net_insert = (gpu_mid // cols) * cols
else:
# GPU가 2행 미만이면 단순 절반 위치
net_insert = gpu_mid
net_insert = max(0, min(net_insert, gpu_count))
type_sequence = (
[("gpu", ...)] * net_insert + # GPU 앞 블록
[("network", ...)] * net_count + # Network (중앙)
[("gpu", ...)] * (gpu_count - net_insert) + # GPU 뒤 블록
[("storage", ...)] * sto_count +
[("other", ...)] * oth_count
)
핵심: GPU 수의 절반 위치를 찾고, 그보다 작은 가장 가까운 행 시작점에 맞춤.
행 경계에 맞추면 Network 랙이 가로 띠 형태로 GPU 구역을 관통해 시각적으로 명확하다.
검증 (다양한 GPU 수)
GPU=150, NET=5, cols=12 → row 6 of 13 (앞 72 / 뒤 78)
GPU=100, NET=5, cols=12 → row 4 of 9 (앞 48 / 뒤 52)
GPU= 50, NET=5, cols=12 → row 2 of 5 (앞 24 / 뒤 26)
GPU=200, NET=8, cols=12 → row 8 of 18 (앞 96 / 뒤 104)
GPU 수가 바뀌어도 항상 중앙 행에 배치된다.
실제 배치 결과 (GPU 150, cols 12)
Row 0: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 1: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 2: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 3: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 4: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 5: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 6: [N][N][N][N][N][G][G][G][G][G][G][G] ← Network 중앙 배치
Row 7: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 8: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 9: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 10: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 11: [G][G][G][G][G][G][G][G][G][G][G][G]
Row 12: [G][G][G][G][G][G][G][G][G][G][G][S]
Row 13: [S][S][S][S][S][S][S][O][O][O][O][O]

7. 트러블슈팅
AttributeError: 'SaveConfigRequest' has no attribute 'total_racks'
증상: 저장 버튼 클릭 시 HTTP 500
원인: 리팩토링 과정에서 CalculateRequest에서 total_racks 필드를 제거했는데, save_config 핸들러에서 req.total_racks를 그대로 참조하고 있었음.
# 잘못된 코드
config = models.RackConfig(
total_racks=req.total_racks, # ← req에는 이 필드가 없음
...
)
# 수정 후
config = models.RackConfig(
total_racks=result["total_racks"], # ← run_calculation() 결과에서 가져옴
...
)
교훈: CalculateRequest(입력)와 CalculateResult(계산 결과)를 명확히 구분해야 한다. 입력에서 파생되는 값은 항상 result에서 가져와야 한다.

IT Load가 고정값으로 보이는 문제
증상: 랙 수량을 바꿔도 IT Load 카드 값이 변하지 않음
원인: 초기 설계에서 total_it_power_mw를 사용자 입력으로 받았음. 랙 구성을 바꿔도 이 입력값은 별도로 바뀌지 않아서 계산 결과에 반영이 안 됨.
해결: CalculateRequest에서 total_it_power_mw 필드를 완전히 제거하고 백엔드에서 계산하도록 전환.
기타 랙 수량이 설정 불가한 문제
증상: 기타 랙 수량 슬라이더가 없음
원인: 초기에 기타 = 총 랙 수 - (GPU + Storage + Network)로 자동 계산했음
해결: 4가지 타입 모두 독립적인 슬라이더를 두고, 총 합계를 별도 표시로 분리.
8. 기술적 결정 모음
ConfigMap 기반 배포를 선택한 이유
CI/CD 파이프라인 없이 단독으로 운영하는 개발 환경에서 이미지 빌드·레지스트리 푸시 사이클은 오버헤드가 크다. ConfigMap 마운트 방식은:
- 이미지 빌드 불필요
kubectl apply+rollout restart로 수십 초 이내 반영- 코드가 소스 파일 그대로 유지되어 가독성 유지
단, 파일 수가 늘어나면 ConfigMap 관리가 복잡해지므로 규모가 커지면 이미지 빌드 방식으로 전환하는 것이 맞다.
DB 스키마 마이그레이션 없이 테이블명 변경
Alembic을 쓰지 않는 단순한 환경에서 컬럼 추가/변경이 필요할 때는 테이블명 자체를 변경하는 방식을 사용했다.
# models.py
class RackConfig(Base):
__tablename__ = "rack_configs_v2" # 이전: rack_configs
Base.metadata.create_all()이 startup 시 자동 실행되어 새 테이블을 생성한다. 구버전 데이터는 보존되되, 앱은 새 테이블을 바라본다.
슬라이더 min 값 조정
kW 슬라이더 하한을 10 → 1로 낮춰 Storage(21kW), 기타(12kW) 같은 낮은 값도 슬라이더로 조작 가능하게 했다. 아울러 step을 0.1로 설정해 소수점 kW도 number input으로 입력 가능하게 했다.
프론트엔드 JS 파일 분리 전략
| 파일 | 역할 |
|---|---|
app.js | 공통 유틸, 탭 전환, 저장 이력 |
calculator.js | 슬라이더 동기화, 계산 API 호출, UI 업데이트 |
layout.js | Canvas 렌더러 (IIFE 패턴, 클로저로 상태 캡슐화) |
racktype.js | 랙 타입별 스펙 입력 → kW 자동 계산 → calculator 탭에 push |
racktype.js는 마지막에 로드되어 window.triggerCalculate가 이미 등록된 상태에서 초기화된다.
마치며
AI 데이터센터 설계를 위한 계산 도구를 만들면서 실제 장비 스펙을 조사하다 보니 흥미로운 사실을 발견했다.
GPU가 IT Load의 97%를 차지한다.
NVIDIA Rubin HGX 기반 7,200 GPU 클러스터에서:
- Storage: 1.1% (NVMe SSD는 Flash라 전력이 생각보다 훨씬 낮다)
- Network: 1.5% (Spectrum-X 스위치 하나가 1,200W)
- 관리: 0.4% (경부하 운영)
즉 이 규모의 AI 데이터센터는 GPU 전력 = 데이터센터 전력이라고 봐도 무방하다. 나머지 인프라는 오차 범위 수준이다.
작성일: 2026-06-25
스택: FastAPI · SQLAlchemy · MySQL · Nginx · Kubernetes · Vanilla JS · Canvas API