Tapo 전원 사이클의 함정
아침에 맞이한 텔레그램 알림
2026년 1월 31일 아침. 텔레그램에 익숙한 알림이 와 있었다.
⚡️ Spark 전원 꺼짐 감지
• Tapo P110M 전원 사이클 실행 중...
• 플러그 IP: 192.168.50.77
어제 구축한 Tapo 자동 복구 시스템이 작동했다! 이번엔 진짜 살아났겠지?
8시 20분. CraftBay MCP는 여전히 응답이 없었다.
316번의 실패
Spark Recovery MCP(miniPC)를 통해 상태를 확인했다.
[2026-01-31 08:20:34] WARN: CraftBay MCP 헬스체크 실패 (연속 313 회)
[2026-01-31 08:20:34] WARN: Spark 서버 ping 실패 - 전원 꺼짐 추정
[2026-01-31 08:20:34] INFO: 전원 사이클 이미 실행됨, 부팅 대기 중...
313번 연속 실패. 그런데 "전원 사이클 이미 실행됨"이라고 한다.
state.json을 확인해보니:
{
"craftbay": {
"failures": 316,
"power_cycled": true,
"last_power_cycle": "2026-01-31T03:08:31+09:00"
}
}
새벽 3시 8분에 전원 사이클을 실행했고, 이후 계속 부팅 대기 중이었다. 5시간 넘게.
범인은 플러그 상태
Tapo 플러그 상태를 직접 확인했다:
$ python3 tapo-spark.py status
장치명: 서버 플러그
전원상태: OFF
OFF?!
전원 사이클은 OFF → 5초 대기 → ON 순서로 실행되어야 하는데, 플러그가 OFF에서 멈춰있었다.
진짜 원인: DHCP IP 변경
복구 후 원인을 분석해보니, 전날 있었던 인터넷 장애가 문제였다.
- 인터넷 장애 발생
- 모뎀과 공유기 리셋
- DHCP가 각 장비에 새 IP 할당
- Tapo 플러그 IP가 변경됨
- 복구 스크립트는 하드코딩된 기존 IP로 접속 시도
- 연결 실패 → 플러그 OFF 상태로 방치
스크립트를 다시 보자:
PLUG_IP = "192.168.50.77" # 하드코딩된 IP
async def get_device():
client = ApiClient(TAPO_EMAIL, TAPO_PASSWORD)
return await client.p110(PLUG_IP) # 이 IP가 맞아야 함
공유기 리셋 후 Tapo 플러그가 다른 IP를 받았다면, 이 스크립트는 엉뚱한 IP에 명령을 보내거나 연결 자체가 실패했을 것이다.
원격으로 전원 복구
Claude.ai에서 Spark Recovery MCP를 통해 직접 전원을 켰다:
$ python3 tapo-spark.py on
Tapo P110M: 전원 ON
$ python3 tapo-spark.py status
장치명: 서버 플러그
전원상태: ON
이제 Spark 부팅을 기다린다.
30초... 1분... 2분...
$ ping -c 3 192.168.50.10
From 192.168.50.196 icmp_seq=1 Destination Host Unreachable
여전히 응답이 없다. 그런데 3분쯤 지나니 부팅이 완료됐다.
Welcome to NVIDIA DGX Spark Version 7.3.1 (GNU/Linux 6.14.0-1013-nvidia aarch64)
System information as of Sat Jan 31 08:28:28 AM KST 2026
System load: 1.45 Temperature: 82.9 C
Usage of /: 70.5% of 3.67TB Processes: 737
Memory usage: 69% Users logged in: 0
DGX Spark 부팅 시간이 생각보다 길었을 뿐. 원격 복구 성공!
근본적인 해결: DHCP 예약
같은 일이 반복되지 않도록, 공유기에서 DHCP 예약(IP 고정)을 설정했다.
| 장비 | MAC | 고정 IP |
|---|---|---|
| H200-Hub | 3C:6A:D2:CB:D5:D4 | 192.168.50.8 |
| spark-home | 4C:BB:47:29:B1:81 | 192.168.50.150 |
| DESKTOP-HOME | 9C:6B:00:8D:95:70 | 192.168.50.102 |
| miniPC | 68:1D:EF:4E:2C:AD | 192.168.50.196 |
| Tapo P110M | 98:03:8E:D3:24:19 | 192.168.50.77 |
특히 Tapo 스마트 플러그의 IP를 고정한 것이 핵심이다. 이제 공유기를 리셋해도 플러그 IP는 항상 192.168.50.77이 된다.
개선해야 할 것들
이번 장애에서 드러난 문제점:
1. 하드코딩된 IP
PLUG_IP = "192.168.50.77" # 이게 바뀌면 끝
개선안: DHCP 예약으로 IP 고정 (완료)
2. 전원 사이클 스크립트에 예외 처리 없음
# 현재 코드 - 에러 시 플러그가 OFF로 남을 수 있음
await device.off()
await asyncio.sleep(delay)
await device.on() # 여기서 실패하면?
개선안:
try:
await device.off()
await asyncio.sleep(delay)
await device.on()
except Exception as e:
# 실패 시 무조건 ON 재시도
try:
await device.on()
except:
pass
raise
3. 전원 상태 확인 로직 없음
Watchdog이 "전원 사이클 이미 실행됨"만 보고 무한 대기했다. 플러그가 실제로 ON인지 확인하는 로직이 필요하다.
4. 복구 타임아웃 없음
새벽 3시에 전원 사이클 후 5시간 동안 그냥 기다렸다. 일정 시간 후 재시도하는 로직이 필요하다.
교훈
-
DHCP는 믿을 게 못 된다 - 네트워크 장비 리셋하면 IP가 바뀐다. 서버와 IoT 장비는 반드시 IP 고정.
-
하드코딩은 재앙의 씨앗 - 설정 파일로 분리하거나, 최소한 DHCP 예약으로 IP를 고정해야 한다.
-
외부 의존성은 불안정하다 - Tapo는 클라우드 인증이 필요하고, 네트워크 상태에 민감하다. 폴백 로직 필수.
-
"실행했다" ≠ "성공했다" - 실제 상태를 확인해야 한다. 플러그가 OFF인지 ON인지.
이제 진짜 안 죽겠지. 아마도.
추신: 결국 원격으로 해결해서 다행이다. 주말 아침에 서버 전원 누르러 집에 가는 건 좀 그렇잖아.