배경

추석 연휴로 본가에 내려가면서 충전기 전원을 내리고 나갔다. 홈서버(물리 macOS 호스트, 그 위에서 multipass VM 3대 — k3s-master/k3s-worker1/k3s-worker2를 돌림)가 배터리로 계속 돌다가 방전되어 하드 다운됐다. Remote-Wake-and-Downtime-Alerting.md에서 다룬 "잠들어서 Tailscale에서 사라짐" 사건과는 원인이 다르다 — 이번엔 전원 자체가 끊긴 것이라 그 문서의 caffeinate/Amphetamine 대책(잠들지 않게 하기)은 이 케이스에 적용되지 않는다.

복구 과정

1. 물리 호스트 특정

Tailscale에 k3s-master/k3s-worker1/k3s-worker2가 각자 별도 피어로 잡혀 있어서(각 VM이 자체 Tailscale 클라이언트를 가짐) 처음엔 그쪽으로 바로 SSH를 시도했으나 호스트 키 미스매치로 실패 — 이 세 피어는 Tailscale SSH(자체 데몬이 sshd 역할)를 쓰고 있어서 일반 ssh가 아니라 tailscale ssh로 붙어야 했는데, tailnet 정책(ACL)이 이 계정으로의 SSH를 막고 있어서 이 경로도 실패했다.

실제로 붙어야 하는 곳은 VM들이 올라가 있는 물리 macOS 호스트(tailscale status에서 macbookair, 100.112.104.24)였다 — ssh [email protected]로 접속.

tailscale status                     # macbookair(물리 호스트) IP 확인
ssh [email protected]           # 물리 호스트에 직접 접속

2. multipass/k3s 상태 확인

물리 호스트에서 multipass가 PATH에 없어서(비대화형 ssh 셸이라 .zprofile 미로드) 풀패스(/usr/local/bin/multipass)로 호출해야 했다.

/usr/local/bin/multipass list

결과: VM 3대 모두 Running. multipass의 launchd 데몬이 호스트 부팅 시 VM을 자동 기동하도록 이미 구성돼 있어서, 전원이 복구되고 macOS가 재부팅되자 별도 조치 없이 VM들이 스스로 다시 떴다.

multipass exec k3s-master -- sudo k3s kubectl get nodes -o wide     # 3대 모두 Ready
multipass exec k3s-master -- sudo k3s kubectl get pods -A -o wide   # 전 파드 Running, RESTARTS 1회(재부팅 시점과 일치)
multipass exec k3s-master -- sudo k3s kubectl get applications -n argocd \
  -o custom-columns=NAME:.metadata.name,SYNC:.status.sync.status,HEALTH:.status.health.status
  # target-tracking-service / threat-intel-ai-service 둘 다 Synced/Healthy

k3s, ArgoCD, 앱 파드까지 전부 스스로 복구되어 있었다 — systemd/launchd 기반 자동 기동 체인이 정전 이후 재부팅에도 그대로 먹혔다는 뜻.

3. 유일하게 스스로 복구되지 않은 것: threat-intel-ai-service의 Kafka consumer

외부 헬스체크 엔드포인트를 직접 두드려서 최종 확인하는 중 발견:

curl -s https://k3s-master.taildcdcee.ts.net/ai/health
# {"status":"ok","ai_enabled":true,"rerank_enabled":false,"qdrant_connected":true,"kafka_consumer_running":false}

kafka_consumer_running: false — Kafka 파드 자체는 Running인데, AI 서비스의 컨슈머만 안 붙어 있었다. 로그 확인:

kubectl logs -n c4i deploy/threat-intel-ai-service --since=6h | grep -iE 'kafka|error'
# ERROR:aiokafka:Unable connect to "kafka-service.c4i.svc.cluster.local:9092": [Errno 111] Connection refused

원인: 정전 후 재부팅 시 모든 파드가 동시에 재시작되면서, threat-intel-ai-service가 Kafka 브로커보다 먼저 뜨는 경쟁이 발생했다. app/kafka/consumer.py의 run_consumer()는 consumer.start()가 실패하면 재시도 없이 그냥 죽는 구조였다 — 최초 1회 연결 실패 = 그 프로세스가 살아있는 동안 영구 다운. 4시간 넘게 이 상태로 떠 있었지만 /health가 항상 200 OK를 반환하는 바디 구조라(상태값은 JSON 필드일 뿐 HTTP 코드에 반영 안 됨) 기존 livenessProbe도 이걸 못 잡았다.

즉시 복구: 수동으로 롤아웃 재시작해서 Kafka가 이미 떠 있는 지금은 정상 연결됨.