클라우드가 잠든 새벽 6시
이번엔 진짜 됐다고 생각했다
지난번 버그 4개를 모두 고쳤다. OFF/ON 분리 세션, 상태 검증, 10분 재시도, 인증 모니터링까지. Watchdog v5.0, tapo-spark.py v2.0.
그리고 2월 5일 새벽 3시 13분. DGX Spark가 또 꺼졌다.
새벽 3시: 완벽한 복구
텔레그램 알림이 왔다:
⚡️ Spark 전원 꺼짐 감지
• Tapo P110M 전원 사이클 실행 중...
• 유형: 최초 시도
• 플러그 IP: 192.168.50.77
watchdog 로그를 보면:
[03:13:32] WARN: CraftBay MCP 헬스체크 실패 (연속 1 회)
[03:13:34] WARN: Spark 서버 ping 실패 - 전원 꺼짐 추정
[03:13:35] TAPO: 전원 사이클 시작
[03:13:42] TAPO: 전원 사이클 완료 - 플러그 ON 확인됨
[03:13:42] TAPO: 전원 사이클 성공, 90초 부팅 대기 필요
7초 만에 전원 사이클 완료. 상태 검증까지 통과. 그리고 10분 뒤 재시도:
[03:23:33] TAPO: 전원 사이클 시작
[03:23:40] TAPO: 전원 사이클 완료 - 플러그 ON 확인됨
[03:24:32] INFO: CraftBay MCP 복구됨 (이전 실패 횟수: 11, 복구방식: Tapo 전원제어)
완벽했다. v2.0 스크립트가 의도한 대로 동작했다. 분리 세션, 상태 검증, 10분 재시도 — 모든 개선점이 제 역할을 했다.
이때까지만 해도 "드디어 자동 복구 시스템이 완성됐다"고 생각했다.
새벽 6시: 같은 코드, 다른 결과
3시간 뒤인 새벽 6시. Spark가 또 꺼졌다. 하룻밤에 두 번.
⚡️ Spark 전원 꺼짐 감지
• Tapo P110M 전원 사이클 실행 중...
• 유형: 최초 시도
• 플러그 IP: 192.168.50.77
🔴 Tapo 전원 사이클 실패
• 플러그 ON 상태 확인 불가
• 수동 확인 필요
같은 코드가 3시간 전엔 성공하고, 지금은 실패한다.
watchdog 로그를 자세히 보면:
[06:00:36] TAPO: 전원 사이클 시작
[06:00:36] ERROR: Tapo cycle 명령 실패 (exit code: 0)
[06:00:36] TAPO: 안전장치 - 명시적 ON 시도
[06:00:38] ERROR: 전원 사이클 후 플러그 상태 비정상 (null) - 강제 ON 재시도
[06:00:40] ERROR: 강제 ON 재시도도 실패 (null)
핵심은 null. 플러그 상태가 true도 false도 아니라 null이다.
status_json 명령이 {"device_on": null}을 반환했다는 것은, 플러그에 연결 자체가 안 됐다는 뜻이다.
범인: TP-Link 클라우드
tapo-spark.py v2.0의 get_device() 함수:
async def get_device():
client = ApiClient(TAPO_EMAIL, TAPO_PASSWORD)
return await client.p110(PLUG_IP)
ApiClient는 tapo 라이브러리(0.8.8)의 클라이언트다. 이 라이브러리는 로컬 IP로 접속하는 것처럼 보이지만, 실제로는 TP-Link 클라우드에 먼저 인증한 뒤 로컬 디바이스에 연결하는 구조다.
miniPC → TP-Link Cloud (인증) → Tapo P110M (로컬)
새벽 3시에는 클라우드가 응답했고, 6시에는 응답하지 않았다.
3회 연속 시도(6:00, 6:01, 6:02) 모두 같은 패턴으로 실패:
[06:00:36] ERROR: Tapo cycle 명령 실패 (exit code: 0)
[06:01:34] ERROR: Tapo cycle 명령 실패 (exit code: 0)
[06:02:35] ERROR: Tapo cycle 명령 실패 (exit code: 0)
10분 간격 재시도 로직이 작동했지만, 근본 원인이 클라우드 인증 실패이니 몇 번을 시도해도 결과는 같다.
6시 3분, 내가 Tapo 앱으로 직접 플러그를 켰다. Spark 부팅, 서비스 복구. 또다시 수동 개입.
구조적 문제
지금까지의 장애 히스토리를 돌아보면:
| 날짜 | 원인 | 복구 실패 이유 |
|---|---|---|
| 1/30 | WOL 미지원 | 하드웨어 한계 |
| 1/31 | DHCP IP 변경 | 네트워크 한계 |
| 2/2 | 스크립트 버그 | 내 코드 문제 |
| 2/5 | 클라우드 인증 | 외부 의존성 |
코드 버그는 고칠 수 있다. 하드웨어 한계는 우회할 수 있다. 그런데 외부 클라우드 서비스의 가용성은 내가 통제할 수 없다.
IoT 디바이스가 클라우드에 의존한다는 건, 인터넷이 끊기거나 클라우드 서버가 점검 중이면 내 집 안에 있는 플러그도 제어 못 한다는 뜻이다. 서버 자동 복구라는 미션 크리티컬한 용도에 이런 구조는 맞지 않다.
해결: 로컬 제어로 전환
Tapo P110M은 로컬 프로토콜(KLAP)을 지원한다. 다만 기본적으로 잠겨있다.
시도 1: python-kasa
TP-Link 계열 로컬 제어의 사실상 표준 라이브러리. 설치 후 테스트:
$ python3 -c "from kasa import Discover; ..."
kasa.exceptions.UnsupportedDeviceError: Unsupported device 192.168.50.77
of type SMART.TAPOPLUG with encrypt_scheme
EncryptionScheme(encrypt_type='TPAP', http_port=80, lv=2)
P110M이 사용하는 TPAP 암호화 프로토콜을 python-kasa가 아직 지원하지 않는다. 탈락.
시도 2: tapo 라이브러리 로컬 모드
같은 tapo 0.8.8 라이브러리인데, 실은 로컬 KLAP 핸드셰이크를 먼저 시도한다. 다만:
Exception: Tapo(Forbidden { code: "FORBIDDEN",
description: "Make sure Third-Party Compatibility is turned on
in the Tapo app." })
"타사 호환성(Third-Party Compatibility)" 설정이 꺼져있어서 403으로 차단됐다.
이건 Tapo 앱에서 딱 한 번만 켜주면 된다:
나 → 타사 서비스 → 타사 호환성 ON
이 설정을 켜면 로컬 네트워크에서 직접 KLAP 프로토콜로 통신할 수 있다. 클라우드를 완전히 우회한다:
Before: miniPC → TP-Link Cloud (인증) → Tapo P110M
After: miniPC → Tapo P110M (직접)
적용 결과
타사 호환성을 켜고 테스트:
$ python3 -c "..."
Device: 서버 플러그
On: True
로컬 연결 성공!
같은 코드, 같은 라이브러리. 클라우드를 거치지 않고 로컬 KLAP으로 직접 연결된다.
코드 변경은 0줄. Tapo 앱에서 설정 하나 켠 것이 전부다.
개선해야 할 것들
타사 호환성 켜기→ 완료 (로컬 연결 테스트 성공 확인)- Spark가 하룻밤에 2번 꺼지는 근본 원인 — 과부하? 과열? 전원? 아직 모른다
- python-kasa TPAP 지원 모니터링 — 지원되면 전환 고려 (커뮤니티가 더 큼)
교훈
미션 크리티컬한 자동화에 클라우드 의존성을 넣지 마라.
IoT가 편리한 건 클라우드 덕분이고, 불안한 것도 클라우드 때문이다. "보통은 잘 되니까"로 넘기면, 안 되는 그 한 번이 새벽 6시에 찾아온다.
로컬에서 할 수 있으면 로컬로 하자. 특히 서버 전원 같은 건.
이 시리즈 이전 글: