본문 바로가기

삶은계란 (Diary)/홈랩

01. 홈랩 안정성 확보하기

배경

홈랩이 위치하는 곳은 일반 가정집의 내 방이다. 20년 가까이 살아온 이 집에 대한 기억 중 최근 몇 년의 기억을 더듬어 보면 여름철에 심심찮게 정전이 일어나 모든 전력이 끊기기 일쑤어서 이에 대한 대비가 필요했다. 단순 서비스를 제외하더라도 DB 사용 중 전원이 나가면 복구까지 생각을 해봐야 하는 골치아픔은 덤이다.

모뎀과 공유기는 거실에 위치하고, AX2004M 공유기가 방 안에 위치한다. 서비스 서버와 DB 서버에 해당하는 맥미니도 방 안에 위치하고, 여태까지 와이파이를 사용해 동작중이다. 거실까지는 범위가 지나치게 크고 최소한 방 안까지는 안전성과 연결을 개선하고자 한다.

유선으로 전환

현재 두 대의 서버가 wifi로 동작하고 있기 때문에 보기엔 좋지만 레이턴시와 연결 안정성이 떨어지는 상황이다. 방에 들어와 있는 AX2004M의 포트가 모자란 상태기도 하거니와 서버랙을 따로 구성할 생각으로 스위칭허브를 추가하기로 결정했다.

 

EFM, ipTIME 아이피타임

이지메시, 이지메쉬, 메시와이파이, 기가 와이파이, 유무선 및 무선 인터넷 공유기, 와이파이 증폭기 및 확장기, 기업 및 가정용 나스, NAS STORAGE 등 제품소개와 펌웨어 다운로드 고객지원 제공

iptime.com

8 포트짜리 스위칭허브로 현재로서는 사용할 일이 없지만 SFP와 VLAN 등의 특수기능이 포함되어 있고 하위 모델 대비 가격차이도 크지 않아 선택했다.

정전대비

서버의 핵심중 하나는 전력이다. 정전등 전력을 확보할 수 없는 상황에서 대처할 시간을 벌기 위해 UPS(Uninterruptible Power Supply system)라는 장치가 존재한다. 쉽게 말하면 발전기나 배터리가 내장된 멀티탭인데 홈랩 규모에선 배터리 정도면 충분하다. 원래는 최상단 혹은 전력 공급 부근에 전부 하나씩 있어서 전체를 견뎌야 하지만 비용 문제로 AX2004M 제외한 전력만 담당하기로 결정했다.

장비 소비전력
M1 맥미니 ~20W (부하 시 ~39W)
M1 맥미니 ~20W (부하 시 ~39W)
스위칭허브 ~5W
  ~45W

 

 

BE550-KR - APC Back-UPS ES 550VA 220V(한국) | Schneider Electric 대한민국

Schneider Electric 대한민국. BE550-KR - APC Back-UPS ES 550VA 220V(한국).

www.se.com

처음 고려한 제품은 BE550-KR로 최대 45W에서 45분 사용 가능하다. 중고가 존재한다면 저렴하고 아주 좋을테지만 매물을 구하기 힘들고 새 제품을 구매하자니 제조사 측의 AS 대응이 만료된 상태라 메리트가 떨어졌다.

 

BE650G2-GR - APC 백업 back-UPS, 650VA/400W 바닥/벽면 장착형, 230V, CEE 7/3 Schuko 콘센트 8개, USB Type A 포트,

Schneider Electric 대한민국. BE650G2-GR - APC 백업 back-UPS, 650VA/400W 바닥/벽면 장착형, 230V, CEE 7/3 Schuko 콘센트 8개, USB Type A 포트, 사용자 교체 가능 배터리.

www.se.com

 

BE850G2-GR - APC 백업 back-UPS, 850VA/520W 바닥/벽면 장착형, 230V, CEE 7/3 Schuko 콘센트 8개, USB Type A+C 포트,

Schneider Electric 대한민국. BE850G2-GR - APC 백업 back-UPS, 850VA/520W 바닥/벽면 장착형, 230V, CEE 7/3 Schuko 콘센트 8개, USB Type A+C 포트, 사용자 교체 가능 배터리.

www.se.com

다음 고려한 제품은 BE650G2-GR, BE850G2-GR 두가지 제품으로 기존 제품이 단종되고 25년 새로 리뉴얼된 제품이다. USB 포트가 존재해 유사시 충전포트로 사용할 수 있다는 장점이 있다. 650이 550의 포지션을 맞고 있고, 850은 용량을 조금 더 확보한 제품이다. 둘의 가격차이가 많이 나지 않기 때문에 여유 있게 850을 선택했다. 

자동 종료

UPS는 결국 시간을 벌어주는 것 뿐이고, 빠르게 전력을 복구하거나 복구 전까지 응급처치를 완료해야 한다. UPS를 맥에 연결하면 macOS가 알아서 인식한다. 시스템 설정 > 에너지 절약 부분에 UPS 탭이 생기고, 여러 조건을 걸어 사용할 수 있다. 단일 서버라면 말이다. 지금의 홈랩은 이미 2대로 구성되어있고, 이 부분에서는 다른 대책이 필요하다. 이러한 다대일 UPS 구성은 NUT(Network UPS Tools)을 사용한다. UPS에 연결된 기기가 NUT 서버가 되고, 이하의 다른 기기들이 상태를 구독해 각자 알아서 종료를 판단하게 된다.

방법 호환 비고
Homebrew + NUT X IOHIDFamily 충돌
Docker + NUT X OrbStack에서 USB 접근 불가
kext 언로드 + NUT 재부팅마다 원복

다만 macOS 위에서 NUT 사용 자체가 흔한 케이스가 아니고, 위와 같은 여러 제약이 있어 다른 방법을 찾아야 한다. 근본적인 원인은 macOS가 IOHID를 점유하고 있는 것이나 정보를 가져 오는 건 ssh로도 가능하니 이쪽을 이용해 보도록 한다.

$ pmset -g batt
Now drawing from 'AC Power'
 -Back-UPS BE850G2  FW:497200G ... 99%; charging present: true

위와 같이 pmset 명령어로 상태 정보를 받아 올 수 있고, 플로우는 다음과 같다.

# Server B
ssh-keygen -t ed25519 -C "ups-shutdown" -f ~/.ssh/nut_shutdown -N ""
ssh-copy-id -i ~/.ssh/nut_shutdown.pub charming@192.168.0.240

# Server A : /etc/sudoers.d/ups-shutdown
charming ALL=(ALL) NOPASSWD: /sbin/shutdown

ssh로 종료 명령을 보내기 위해 키 인증을 설치하고, 명령어를 열었다.

#!/bin/bash

LOG=/var/log/ups-monitor.log
FLAG=/tmp/ups-on-battery
SHUTDOWN_FLAG=/tmp/ups-shutting-down

# 임계값
BATT_LEVEL_THRESHOLD=20   # 배터리 % 이하
BATT_TIME_THRESHOLD=300   # 배터리 사용 경과 시간(초) - 5분
BATT_REMAIN_THRESHOLD=5   # 잔여 시간(분) - 5분

SSH_OPTS=(-i /Users/charming/.ssh/nut_shutdown -o ConnectTimeout=10 -o StrictHostKeyChecking=no)
SERVER_A="charming@192.168.0.240"

log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> $LOG; }

shutdown_all() {
    [ -f $SHUTDOWN_FLAG ] && exit 0
    touch $SHUTDOWN_FLAG

    log "종료 시작 - Server A OS 종료 시도"
    ssh "${SSH_OPTS[@]}" $SERVER_A "sudo /sbin/shutdown -h now" || true
    log "Server A 종료 명령 전송"

    sleep 15
    log "Server B 종료"
    sudo /sbin/shutdown -h now
    exit 0
}

BATT_INFO=$(pmset -g batt)

# AC면 플래그 초기화
if echo "$BATT_INFO" | grep -q "AC Power"; then
    if [ -f $FLAG ]; then
        log "AC Power 복구"
        rm -f $FLAG $SHUTDOWN_FLAG
    fi
    exit 0
fi

# 정전 첫 감지
if [ ! -f $FLAG ]; then
    log "정전 감지 - Battery Power로 전환"
    date +%s > $FLAG
fi

BATT_LEVEL=$(echo "$BATT_INFO" | grep -oE '[0-9]+%' | tr -d '%' | head -1)
REMAIN_MIN=$(echo "$BATT_INFO" | grep -oE '[0-9]+:[0-9]+ remaining' \
             | grep -oE '[0-9]+:[0-9]+' | awk -F: '{print $1*60+$2}')

START_TIME=$(cat $FLAG)
ELAPSED=$(( $(date +%s) - START_TIME ))

log "배터리 - 레벨:${BATT_LEVEL}% 경과:${ELAPSED}초 잔여:${REMAIN_MIN}분"

# 조건 1: 배터리 레벨
if [ -n "$BATT_LEVEL" ] && [ "$BATT_LEVEL" -le $BATT_LEVEL_THRESHOLD ]; then
    log "조건 충족 - 레벨 ${BATT_LEVEL}%"; shutdown_all
fi
# 조건 2: 사용 경과 시간
if [ "$ELAPSED" -ge $BATT_TIME_THRESHOLD ]; then
    log "조건 충족 - 경과 ${ELAPSED}초"; shutdown_all
fi
# 조건 3: 잔여 시간 (0 제외)
if [ -n "$REMAIN_MIN" ] && [ "$REMAIN_MIN" -gt 0 ] && [ "$REMAIN_MIN" -le $BATT_REMAIN_THRESHOLD ]; then
    log "조건 충족 - 잔여 ${REMAIN_MIN}분"; shutdown_all
fi

macOS의 UPS 옵션을 그대로 옮긴 후

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
    <key>Label</key><string>com.martinq.ups-monitor</string>
    <key>ProgramArguments</key>
    <array><string>/usr/local/bin/ups-monitor.sh</string></array>
    <key>StartInterval</key><integer>30</integer>
    <key>RunAtLoad</key><true/>
    <key>StandardOutPath</key><string>/var/log/ups-monitor.log</string>
    <key>StandardErrorPath</key><string>/var/log/ups-monitor.log</string>
</dict></plist>

'/Library/LaunchDaemons/com.martinq.ups-monitor.plist'에 launchd를 등록해 주기적으로 체크하도록 설정한다. 또한, 해당 스크립트가 종료를 담당하게 되므로 원래의 UPS 설정은 전부 정리했다.

20:36:52 종료 시작 - Server A OS 종료 시도
20:36:52 Server A 종료 명령 전송
20:37:07 Server B 종료

 

실제 UPS의 전원을 끊으면 정상적으로 종료가 되는 걸 확인할 수 있다.

한계

  1. UPS는 안전하게 종료 되는 것까지만 책임진다.
    • 실제 전력이 복구 됐을 때 부팅까지 어떻게 해결이 되는지는 미지수다. 반이라도 해결 됐으니 다행이긴 하지만 욕심이 나는 건 어쩔 수 없다.
  2. 판단 주체가 Server B 한 곳이다.
    • NUT이었다면 Server A가 UPS 상태를 독립적으로 구독해 스스로 종료를 판단했을 텐데, 지금은 B가 SSH로 A를 꺼주는 구조다. 스위칭허브·SSH·Server B 중 하나라도 죽으면 A는 graceful 종료 신호를 못 받는다. 단일 장애점인 건 사실이다.
  3. pmset 텍스트 파싱에 의존한다.
    • 펌웨어 버전을 잔여 시간으로 오인했던 그 버그가 증거다. macOS의 pmset 출력 포맷이 바뀌면 또 깨질 수 있다.
  4. 30초 폴링 갭.
    • 최악의 경우 정전을 30초 늦게 감지한다. 런타임이 길어 실질적인 위험은 없지만 즉각 반응은 아니다.

DB 데이터 중요도가 더 올라가거나 손이 남으면, 결국 정공법은 Server A에 UPS를 하나 더 두거나 서버를 리눅스로 옮기는 쪽일 것이다. 지금은 가진 장비로 "충분히 안전한" 선에서 멈췄다.

마무리

홈랩이 하나 생겼으니 재밌는 걸 하나 해보자 한다. RPI를 사용해 HAOS를 올리고 홈 IoT를 묶어보려고 한다.

'삶은계란 (Diary) > 홈랩' 카테고리의 다른 글

00. 홈서버 분리 및 확장 계획  (0) 2026.03.24