호라이즌앱스 로고
HORIZON
APPS
← 기술 노트

SSH가 죽어도 들어가는 길 — 시리얼 콘솔, 셀프호스트의 마지막 보루

임균영

복원력 5계층을 다 갖춰도 빈틈이 하나 있습니다. 그 계층들이 전부 네트워크가 살아있어야 작동한다는 점입니다. 프로세스 매니저·워치독·외부 데드맨 — 모두 SSH나 핑이 닿아야 의미가 있습니다.

그런데 진짜 최악의 장애(부팅 실패·커널 패닉·네트워크 완전 다운)에선 SSH도 핑도 데드맨도 다 같이 죽습니다. "죽었는데 들여다볼 방법조차 없는" 상태. 이때 유일하게 살아있는 채널이 시리얼 콘솔(UART) 입니다. 네트워크와 무관한 out-of-band 접근 — 복원력의 마지막 보루(L5)입니다.

단일보드(SBC)나 미니 서버를 무인으로 돌린다면, 시리얼 콘솔은 "보험(insurance)" 입니다. 평소엔 안 쓰지만, 그날이 오면 이게 없으면 보드를 뽑아서 책상으로 가져와야 합니다.


준비물

항목설명
USB-TTL 어댑터CP2102 / CP2102N / FTDI 등. 보드 콘솔 baud를 낼 수 있어야 함(아래 함정 참고)
점퍼 케이블보드 핀·모듈 핀이 보통 둘 다 수(male)라 암-암(female-female)
3.3V 로직대부분의 SBC는 3.3V. 어댑터를 3.3V로 설정(5V면 핀이 탑니다)

★ 가장 큰 함정 — "40핀 GPIO ≠ 콘솔"

라즈베리파이는 40핀 헤더의 8·10번이 UART 콘솔이라, 많은 가이드가 그걸 기준으로 합니다. 하지만 다른 SBC는 40핀이 콘솔이 아닌 경우가 많습니다.

실제로 한 RK3588 보드에서 40핀 8·10번에 완벽히 교차 연결했는데 신호가 0바이트였습니다. 확인해 보니:

gpio readall →  핀 8 = GPIO1_A1 (일반 GPIO),  핀 10 = GPIO1_A0 (일반 GPIO)
/proc/cmdline → console=ttyS2,1500000   ← 콘솔은 ttyS2(전용 디버그 포트)

40핀엔 콘솔 UART가 아예 없었습니다. 콘솔은 전원부 근처에 별도로 솟아 있는 전용 디버그 3핀(실크에 GND/TX/RX 인쇄)이었습니다.

교훈: 40핀 헤더부터 의심하지 말고, 보드의 "전용 디버그 포트"를 찾으세요. 보통 전원 입력 근처나 보드 가장자리에 UART/DEBUG 또는 GND TX RX 실크가 찍힌 작은 3핀입니다. 콘솔이 어느 디바이스인지는 cat /proc/cmdlineconsole=로 확인합니다.


연결

모듈 GND  → 보드 GND
모듈 TXD  → 보드 RX   (교차!)
모듈 RXD  → 보드 TX   (교차!)
(모듈 VCC / 5V / 3V3 는 연결 안 함 — 보드는 자체 전원)
  • TX↔RX 교차가 핵심. 안 되면 십중팔구 이게 뒤집힌 것.
  • GND 직결 필수.
  • VCC(빨강)는 절대 연결 금지 — 보드를 태우는 유일한 실수.
  • 핀이 볼록 솟아 있으면(핀헤더) 점퍼선 바로 꽂으면 됩니다(납땜 불필요). 빈 스루홀(구멍)만이면 핀헤더를 납땜.

⚠️ baud 함정

보드 콘솔의 baud는 제각각입니다. 라즈베리파이는 115200이지만, 일부 RK3588 보드는 1500000(1.5M) 입니다(console=ttyS2,1500000).

  • 구형 CP2102는 데이터시트상 최대 ~1Mbps라 1.5M에서 글자가 깨질 수 있습니다.
  • CP2102N(신형)·FTDI는 3Mbps까지 내므로 1.5M도 정상입니다.

글자가 깨지면(�· 같은 거): ① CP2102N/FTDI로 바꾸거나 ② 커널 cmdline의 console baud를 115200으로 낮춥니다(단 u-boot 초기 로그는 여전히 고속).


PC 설정 (Windows)

  1. Silicon Labs CP210x VCP 드라이버 설치 → 장치관리자에 COMx 생성
    • 안 잡히면 pnputil /add-driver silabser.inf /install(관리자)
  2. 터미널(PuTTY 등): Serial · COMx · 보드 baud · 8-N-1 · Flow control None
  3. 연결 순서: 보드 끄고 → 3선 연결 → 터미널 열어 대기 → 보드 켜기 → 부팅 로그가 처음부터 흐름

성공하면 login: 프롬프트가 뜨고, 거기서 로그인하면 — 네트워크 없이도 셸입니다.


자동 로깅 = 진짜 인슈어런스

수동으로 터미널을 여는 것도 좋지만, 상시 로거가 더 강력합니다. PC가 시리얼 포트를 백그라운드로 열어 부팅 로그·커널 패닉·OOM 메시지를 파일로 캡처해두면 — 봇이 SSH도 안 되게 죽었을 때 그 로그가 유일한 원인 단서가 됩니다.

핵심 코드(.NET SerialPort 예):

$port = New-Object System.IO.Ports.SerialPort("COM3", 1500000, "None", 8, "One")
$port.Open()
while ($true) { $line = $port.ReadLine(); "$(Get-Date -f 'HH:mm:ss') $line" | Out-File -Append log.txt }

로거가 포트를 점유하므로, 인터랙티브로 접속할 땐 로거를 먼저 닫습니다(한 포트는 한 번에 하나).


한 걸음 더: 수동 로깅에서 능동 복구로

여기까지는 시리얼을 "읽기"로만 썼습니다. 그런데 시리얼은 양방향 — 읽는 만큼 쓸 수도(TX) 있습니다. 즉 PC가 시리얼로 복구 명령을 직접 주입할 수 있습니다. SSH가 죽어 네트워크가 끊긴 그 순간에도요.

능동 워치독

상시 로거를 한 단계 올려, PC가 주기적으로 헬스체크(ping)하고 죽으면 스스로 대응하게 만듭니다:

헬스체크(ping)
  ├ 정상 → 시리얼 로그만 캡처(패닉/OOM 패턴 감시)
  └ 응답 없음(N회 연속) → 시리얼로 복구 명령 주입 + 알림

핵심은 네트워크와 무관한 복구 경로라는 점입니다. SSH는 네트워크가 살아야 하지만, 시리얼은 케이블만 꽂혀 있으면 OS가 살아있는 한 닿습니다. "네트워크 스택만 죽고 OS는 멀쩡한" 장애에서 — 시리얼로 들어가 systemctl restart <네트워크> 한 줄이면 복구됩니다.

비번 없이 들어가기 — getty autologin

복구를 자동화하려면 시리얼 로그인이 걸림돌입니다. 비번을 PC에 저장하는 건 찜찜하고, 자동 타이핑은 타이밍이 불안정하죠. 더 깔끔한 방법은 시리얼 콘솔 getty를 자동 로그인으로 두는 것입니다(systemd drop-in):

# /etc/systemd/system/serial-getty@<tty>.service.d/autologin.conf
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --keep-baud <baud> %I

이러면 시리얼 셸이 항상 로그인된 채 상주 → PC는 비번 없이 명령만 보내면 됩니다. 트레이드오프는 "시리얼 포트에 물리 접근하면 비번 없이 root"라는 점 — 하지만 시리얼 케이블은 보통 내 PC에만 물려 있고 케이스 안에 있으니, SSH(여전히 키 인증)와 분리된 이 채널의 위험은 제한적입니다. 원본 유닛을 건드리지 않는 drop-in이라, 되돌릴 땐 파일 하나만 지우면 됩니다.

하드웨어 워치독과 역할 분담

시리얼 능동 복구가 만능은 아닙니다. 커널이 통째로 멈추면 시리얼 명령도 안 먹습니다. 그건 하드웨어 워치독의 영역입니다. 대부분의 SBC에는 워치독 타이머가 있고, systemd가 이걸 켜면(커널/systemd가 주기적으로 핑) 멈췄을 때 자동 리부팅합니다:

# /etc/systemd/system.conf
RuntimeWatchdogSec=20s    # systemd가 주기적으로 /dev/watchdog 핑; 멈추면 HW 리부팅

⚠️ 칩이 견디는 max timeout보다 작게 설정하세요(wdctl로 확인). 너무 짧으면 정상 부하에도 오작동 리부팅합니다. (예: DesignWare dw_wdt는 44초가 한도라 20초로.)

정리하면 층이 갈립니다: 커널 hang은 하드웨어 워치독이, 네트워크만 죽은 경우는 시리얼이 — 서로 다른 빈틈을 메웁니다. 둘은 경쟁이 아니라 보완입니다.

알림은 '독립 채널'로

복구를 시도했으면 알려야 합니다. 그런데 죽은 서비스를 통해 알릴 수는 없습니다 — 봇이 죽었는데 그 봇으로 알림을 보낼 순 없죠. 그래서 알림은 반드시 장애 대상과 독립된 채널(별도 메신저 봇, 외부 푸시 등)로 보내야 "죽었다"는 사실이 닿습니다. 이게 의외로 자주 빠지는 함정입니다.


함정 모음

함정해법
40핀 8/10이 콘솔일 거라 가정gpio readall·/proc/cmdline 확인. 전용 디버그 포트 찾기
TX/RX 교차 반대보드 TX→모듈 RX. 0바이트면 뒤집어 보기
baud 안 맞음console=의 값으로. 깨지면 어댑터(CP2102N/FTDI)나 cmdline 조정
VCC 연결절대 금지(보드 손상). GND·TX·RX 3선만
5V 로직3.3V로 설정(SBC는 3.3V)

정리

복원력의 L0~L4가 네트워크 안에서 작동한다면, 시리얼 콘솔(L5)은 네트워크 밖에서 작동합니다. 평소엔 쓸 일이 없지만, "부팅이 안 되는데 SSH도 안 되는" 그날 — 이게 있으면 자리에서 진단하고, 없으면 보드를 뽑아야 합니다. 무인 서버를 운영한다면, 한 번 연결해두는 값이 충분합니다.


개인 셀프호스트 서버(ARM 단일보드) 운영에서 시리얼 콘솔을 인슈어런스로 구축한 기록입니다.

카톡 상담