TL;DR Ubuntu 24.04 HWE 커널
6.17.0-20-generic에서 Electron 기반 앱(Claude Desktop)의 worker thread 종료 시xas_load무한 루프로 soft lockup이 발생했다. 웹 Claude의 도움으로 이리저리 좌충우돌 후 직전 버전6.17.0-19-generic으로 GRUB 디폴트를 변경하고 문제 커널을apt-mark hold하여 해결했다.
발단 — “왜 블로그가 안 열리지?”
2026년 4월 26일 일요일 오후. 노트북을 켜고 블로그를 확인했는데, 응답이 없었다. 오직 핑만 간다. 내 블로그 서버가 죽은 모양이다. 원격으로 SSH 접속을 시도해도 무응답.
결국 4월 27일 오전에 사무실에 출근하여 직접 확인해보니 화면이 멈춰있었다. (kernel panic) 강제로 전원을 끄고 다시 켰더니 정상적으로 부팅되긴 한다. 일단 살았다.
서버 사양은 다음과 같다.
$ uname -a Linux faros-Server 6.17.0-20-generic #20~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Mar 19 01:28:37 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
- CPU: Intel Coffee Lake 계열
- iGPU: Intel UHD Graphics 630 (CoffeeLake-S GT2)
- 보드: MSI MPG Z390 GAMING EDGE AC
- OS: Ubuntu 24.04 LTS (HWE 커널 트랙)
블로그 글 발행 외에 Docker로 워드프레스 MCP 서버도 돌리고 있는, 나에게는 꽤 중요한 머신이다. 아래부터는 무엇이 문제였는지, 그리고 어떻게 해결했는지 잊지 말자는 차원에서 적은 내용이다.
1차 단서 — 최근 커널 업그레이드 이력
가장 먼저 의심한 것은 커널 업데이트였다. 자동 업그레이드로 새 커널이 들어와서 문제를 일으키는 사례는 흔하다.
# dpkg 로그에서 커널과 grub 관련 설치/업그레이드/제거 이력만 추출 # zgrep : .gz 압축된 과거 로그까지 한 번에 검색 # -E : 확장 정규식 zgrep -E "installing|upgrade|remove" /var/log/dpkg.log* | grep -E "linux-image|grub"
출력을 정리하면 이렇다.
2026-04-02 11:39:19 upgrade linux-image-generic-hwe-24.04 6.17.0-19 → 6.17.0-20 2026-04-03 06:16:58 remove linux-image-6.17.0-14-generic 2026-03-15 06:56:41 upgrade linux-image-generic-hwe-24.04 6.17.0-14 → 6.17.0-19 2026-03-13 06:42:02 upgrade linux-image-generic-hwe-24.04 6.14.0-37 → 6.17.0-14
4월 2일에 6.17.0-19에서 6.17.0-20으로 올라갔다. 그리고 약 3주 후에 사건 발생. 시간차 살인 패턴이다. 의심 정황이 짙어졌다.
2차 단서 — journalctl로 패닉 로그 확인
지금까지 journalctl을 제대로 다뤄본 적이 없었다. 마침 좋은 기회다 싶어 정리하면서 진행했다.
부팅 이력 확인
# 시스템에 기록된 모든 부팅 세션을 나열 # IDX -1, -2, -3 ... 이 과거 부팅 인덱스 (0이 현재 부팅) journalctl --list-boots
출력 일부:
IDX BOOT ID FIRST ENTRY LAST ENTRY -1 92634b66c1da43bba7b578e215a9507e Wed 2026-04-22 14:51:29 KST Sat 2026-04-25 23:30:15 KST 0 0f550bede2e14eb9be244705c17521a5 Mon 2026-04-27 09:27:16 KST ...
핵심 발견: 직전 부팅 세션(-1)이 2026-04-25 23:30:15 KST에서 갑자기 끊겨있다. 즉, 이 시각 즈음에 시스템이 응답 불능 상태에 빠진 것. 4월 27일 09:27까지 약 34시간 동안 죽어있었던 셈이다.
패닉 직전 세션의 커널 로그 추출
# -k : 커널 메시지만 (dmesg에 해당) # -b -1 : 직전 부팅 세션 # --no-pager : less 같은 페이저 없이 통째로 출력 journalctl -k -b -1 --no-pager > /tmp/panic-kernel.log
패닉/Oops 시그니처 검색
# 커널 패닉, Oops, BUG 흔적을 한꺼번에 찾는 grep
# -i : 대소문자 무시
# -B 5 -A 30 : 매칭 라인 앞뒤 5/30줄 함께 출력해 콜 스택 보존
grep -iE -B 5 -A 30 \
"kernel panic|oops|BUG:|general protection|unable to handle|Call Trace|RIP:|tainted" \
/tmp/panic-kernel.log | head -200
출력에서 결정적인 부분:
4월 24 19:09:02 faros-Server kernel: watchdog: BUG: soft lockup - CPU#5 stuck for 26s! [ThreadPoolForeg:125427] 4월 24 19:09:02 faros-Server kernel: CPU: 5 UID: 1000 PID: 125427 Comm: ThreadPoolForeg Not tainted 6.17.0-20-generic 4월 24 19:09:02 faros-Server kernel: RIP: 0010:xas_load+0x71/0x100 4월 24 19:09:02 faros-Server kernel: Call Trace: 4월 24 19:09:02 faros-Server kernel: xas_find+0x15e/0x1c0 4월 24 19:09:02 faros-Server kernel: find_get_entries+0x7c/0x320 4월 24 19:09:02 faros-Server kernel: shmem_undo_range+0x242/0x850 4월 24 19:09:02 faros-Server kernel: shmem_evict_inode+0x128/0x290 4월 24 19:09:02 faros-Server kernel: evict+0x10c/0x290 4월 24 19:09:02 faros-Server kernel: iput+0x147/0x290 4월 24 19:09:02 faros-Server kernel: __fput+0x13d/0x2d0 4월 24 19:09:02 faros-Server kernel: task_work_run+0x5d/0xa0 4월 24 19:09:02 faros-Server kernel: do_exit+0x1fa/0x480 4월 24 19:09:02 faros-Server kernel: do_group_exit+0x34/0x90 4월 24 19:09:02 faros-Server kernel: get_signal+0x832/0x840
그리고 이게 26초 → 52초 → 82초로 점점 늘어나며 같은 콜 트레이스가 반복된다. 결국 다음 단계로 진입:
4월 24 19:09:38 faros-Server kernel: rcu: INFO: rcu_preempt self-detected stall on CPU
진단 — Hard Panic이 아닌 Soft Lockup
먼저 용어를 정확히 짚자. 처음엔 “kernel panic”으로 인식했지만 엄밀히는 soft lockup이다.
| 구분 | Kernel Panic (Hard) | Soft Lockup |
|---|---|---|
| 정의 | 커널이 복구 불가 오류로 즉시 정지 | 특정 CPU 코어가 일정 시간(기본 22초) 이상 한 작업에 묶임 |
| 시스템 상태 | 즉사 | 다른 코어는 살아있을 수 있음 |
| 로그 | 못 남기고 죽는 경우 다수 | watchdog이 백트레이스 남김 |
| 자동 복구 | 불가 (재부팅 필수) | 종종 자체 풀리기도 함 |
내 경우 CPU#5가 점점 길게 묶이다가 결국 RCU stall까지 가면서 시스템이 사실상 응답 불능 상태에 빠졌다.
콜 트레이스 해석
get_signal+0x832 ← 어떤 앱이 worker thread에 종료 시그널 전송 do_exit+0x1fa ← 정상적으로 종료 진입 __fput → dput → iput ← 열어둔 파일 디스크립터 정리 (정상) shmem_evict_inode ← shmem(tmpfs/공유메모리) inode 회수 (정상) shmem_undo_range ← 페이지 인덱스 순회 (정상) xas_load (★무한루프) ← ★ 여기서 커널 XArray가 망가짐
해석하면 이렇다.
- 어떤 앱이 자식 프로세스(
ThreadPoolForeg, PID 125427)에 종료 시그널을 보냈다. - 자식이
do_exit진입 → 열려있던 파일 디스크립터들을 정리하던 중, - shmem (tmpfs/공유메모리) 기반 파일의 inode를 회수하려고 시도했다. Chromium 계열은 IPC와 V8 힙용으로 shmem을 매우 많이 쓴다.
- shmem 영역의 페이지들을 추적하는 자료구조 XArray를 순회하다가,
xas_load함수가 유효하지 않은 노드 포인터를 따라가면서 무한 루프에 빠졌다.
RIP(Instruction Pointer)이 xas_load 내의 같은 작은 영역(0x44, 0x71, 0x81)을 계속 맴도는 게 결정적인 증거다. 정상적인 XArray 순회라면 절대 이렇게 되지 않는다. 명확한 커널 버그 시그니처다.
트리거 식별 — 누가 쏜 시그널인가
ThreadPoolForeg라는 thread 이름은 Chromium 코드베이스의 ThreadPoolForegroundWorker라는 명명 규칙을 따른다. 즉 Chromium 기반 앱이 트리거다.
내 블로그 서버에서 돌리는 Chromium/Electron 기반 앱은 단 하나, Claude Desktop뿐이다. 확인해보자.
# 4월 중순 ~ 4월 27일 사이에 설치/업그레이드된 패키지 추적
zgrep -hE "^2026-04-(1[5-9]|2[0-7])" /var/log/dpkg.log* \
| grep -E " (install|upgrade|remove) " \
| sort
핵심 결과만 추리면:
2026-04-17 10:55:08 install claude-desktop:amd64 1.3109.0-1.3.31 ← 최초 설치 2026-04-18 16:33:57 upgrade claude-desktop → 1.3109.0-1.3.32 2026-04-22 10:46:35 upgrade claude-desktop → 1.3561.0-2.0.1 ← 메이저 점프 2026-04-22 14:57:20 upgrade claude-desktop → 1.3883.0-2.0.1 2026-04-22 18:35:11 install docker-ce 5:29.4.1 2026-04-24 19:09:02 ★ 첫 번째 soft lockup 발생
Claude Desktop이 4월 17일에 처음 설치되고 정확히 7일 후에 첫 lockup이 발생했다. 그리고 lockup 직전 이틀 사이에 1.3.x → 2.0.x로 메이저 버전 점프까지 있었다.
그러나 “Claude Desktop의 잘못”은 아니다
여기서 중요한 구분. 사건의 트리거가 Claude Desktop이라는 사실과, 원인이 Claude Desktop이라는 진단은 별개다.
콜 트레이스를 다시 보면, user-space 앱이 정상 종료 syscall을 했는데 커널이 무한 루프에 빠졌다. 이건 어떤 user-space 앱이 무엇을 하더라도 일어나면 안 되는 일이다. 커널이 망가질 일은 user-space가 만들어줄 수 없어야 한다는 게 OS의 기본 계약이다.
따라서:
- 표면 트리거: Claude Desktop의 worker thread 종료
- 실제 원인: 6.17.0-20 커널의 XArray/shmem 코드 경로 회귀 버그
다만 Claude Desktop이 Chromium 기반이라 shmem을 매우 집약적으로 사용하는 워크로드를 만든다(Chromium은 IPC, V8 isolate 힙, shared array buffer 등에 모두 shmem을 씀). 이 버그가 노출되기 쉬운 환경을 제공한 건 사실이다. Electron 계열이 아닌 다른 일반 데몬에서는 같은 버그가 잠복해 있었을 가능성이 높다.
추가로 우분투 커뮤니티 디스코스에서 6.17.0-20 커널의 다양한 안정성 보고가 올라와 있다. 이 커널 자체에 회귀 이슈가 있다고 봐야 한다.
처방 — 6.17.0-19로 롤백
1단계: 사용 가능한 커널 확인
# 설치돼 있는 커널 이미지 패키지 목록
dpkg -l | grep -E "^ii\s+linux-image-[0-9]" | awk '{print $2, $3}' | sort
# /boot에 실제 vmlinuz 파일 확인
ls -lh /boot/vmlinuz-*
# 현재 가동 중인 커널
uname -r
내 시스템 출력:
ii linux-image-6.17.0-19-generic 6.17.0-19.19~24.04.2 ← ★ 살아있음 ii linux-image-6.17.0-20-generic 6.17.0-20.20~24.04.1 ← 문제 커널 ii linux-image-generic-hwe-24.04 6.17.0-20.20~24.04.1 ← HWE 메타 /boot/vmlinuz-6.17.0-19-generic /boot/vmlinuz-6.17.0-20-generic
운이 좋게 6.17.0-19가 패키지·vmlinuz 모두 살아있었다. 이 커널은 3월 15일 ~ 4월 2일 동안 무탈하게 돌던 검증된 커널이다.
2단계: GRUB 메뉴 항목명 확인
# /boot/grub/grub.cfg에서 menuentry 라인만 추출
# -F\' : 작은따옴표를 필드 구분자로 지정
# print $2 : 두 번째 필드(첫 따옴표 뒤 텍스트)가 실제 메뉴명
grep -E "^\s+menuentry" /boot/grub/grub.cfg | awk -F\' '{print $2}'
출력에서 Ubuntu, with Linux 6.17.0-19-generic 항목을 확인.
3단계: /etc/default/grub 편집
# 백업 (오늘 날짜 붙여서) sudo cp /etc/default/grub /etc/default/grub.bak.$(date +%Y%m%d) # 텍스트 에디터로 편집 sudo nano /etc/default/grub
다음과 같이 수정:
># 변경 전 GRUB_DEFAULT=0 # 변경 후 — Advanced options 서브메뉴 안의 6.17.0-19 항목을 가리킴 # > 기호가 서브메뉴 진입 구분자 GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 6.17.0-19-generic"
추가로 만약을 대비해 GRUB 메뉴를 잠깐 보여주도록 설정:
GRUB_TIMEOUT_STYLE=menu GRUB_TIMEOUT=3
4단계: GRUB 재생성 후 재부팅
# /etc/default/grub의 변경사항을 /boot/grub/grub.cfg에 반영 # update-grub은 grub-mkconfig -o /boot/grub/grub.cfg의 wrapper 스크립트 sudo update-grub # 재부팅 sudo reboot
5단계: 재부팅 후 검증
# 1) 현재 가동 중인 커널 확인
uname -r
# 기대 출력: 6.17.0-19-generic
# 2) 새 boot session에서 soft lockup 흔적이 없는지 확인
journalctl -k -b 0 | grep -iE "soft lockup|xas_load|shmem_undo|BUG:" \
|| echo "깨끗합니다 ✓"
# 3) 모든 systemd 서비스 정상 기동 확인
systemctl --failed
# 기대 출력: "0 loaded units listed"
내 결과:
$ uname -r 6.17.0-19-generic $ journalctl -k -b 0 | grep -iE "soft lockup|xas_load|shmem_undo|BUG:" || echo "깨끗합니다 ✓" 깨끗합니다 ✓ $ systemctl --failed UNIT LOAD ACTIVE SUB DESCRIPTION 0 loaded units listed.
세 가지 모두 정상. 안정화 1차 완료.
6단계: 6.17.0-20 자동 재설치 차단
# apt-mark hold : 해당 패키지를 업그레이드/제거 대상에서 제외
# linux-image-generic-hwe-24.04 메타도 hold해야 의존성을 통해
# 6.17.0-20이 다시 default로 돌아오는 걸 막을 수 있다
sudo apt-mark hold \
linux-image-6.17.0-20-generic \
linux-headers-6.17.0-20-generic \
linux-modules-6.17.0-20-generic \
linux-modules-extra-6.17.0-20-generic \
linux-image-generic-hwe-24.04
# hold 적용 결과 확인
apt-mark showhold
결과:
linux-headers-6.17.0-20-generic linux-image-6.17.0-20-generic linux-image-generic-hwe-24.04 linux-modules-6.17.0-20-generic linux-modules-extra-6.17.0-20-generic
사후 운영 메모
Hold가 가져오는 부작용
linux-image-generic-hwe-24.04 메타 패키지를 hold한 효과가 가장 크다. 이 메타는 “HWE 트랙의 최신 커널을 따라가라”는 의미인데, hold함으로써 향후 6.17.0-21, 6.18, 7.0 같은 새 HWE 커널이 나와도 자동으로 끌려오지 않는다.
부작용: HWE 보안 업데이트도 같이 막힌다. 따라서 Canonical에서 회귀 수정 커널이 나오면 hold를 풀어줘야 한다.
회귀 수정 커널이 나왔는지 확인하는 방법
# apt가 보고 있는 패키지 목록을 갱신 sudo apt update # linux-image-generic-hwe-24.04 메타가 가리키고 있는 후보 버전 확인 apt-cache policy linux-image-generic-hwe-24.04
Candidate: 줄이 6.17.0-21 이상으로 바뀌면, Ubuntu 커널 패키지 changelog에서 6.17.0-20 회귀 수정 여부를 확인한 후 hold 해제.
Hold 해제 명령
sudo apt-mark unhold \
linux-image-6.17.0-20-generic \
linux-headers-6.17.0-20-generic \
linux-modules-6.17.0-20-generic \
linux-modules-extra-6.17.0-20-generic \
linux-image-generic-hwe-24.04
sudo apt upgrade
안정성 모니터링
향후 1-2주 동안 종종 다음으로 점검:
journalctl -k -b 0 --since "today" | grep -iE "soft lockup|xas_load|shmem_undo|BUG:|Tainted" \
|| echo "오늘 깨끗합니다 ✓"
uptime
회고
이번 사건의 한 줄 요약:
[원인] Ubuntu 24.04 HWE 커널 6.17.0-20-generic의
XArray/shmem 코드 경로 회귀 버그
[트리거] Claude Desktop (Electron/Chromium 기반)의 worker thread 종료 시
shmem 정리 과정에서 xas_load 무한 루프
[조치] 검증된 직전 커널 6.17.0-19로 GRUB 디폴트 변경, 문제 커널 hold
[상태] 안정화 완료, 모니터링 단계
배운 것들
journalctl --list-boots는 사후 분석의 출발점이다. 직전 부팅이 언제 끝났는지(=언제 죽었는지) 한 번에 보여준다.journalctl -k -b -1로 직전 부팅 세션의 커널 로그만 추출할 수 있다.- Soft lockup은 진짜 panic이 아니다. watchdog이 백트레이스를 남겨주므로 분석 가능하다.
- 콜 트레이스의 함수 이름들이 모든 단서다.
ThreadPoolForeg같은 thread 이름은 Chromium 코드베이스에서 직접 따왔다. apt-mark hold는 메타 패키지(linux-image-generic-hwe-24.04)에 같이 걸어야 효과적이다. 개별 커널 이미지만 hold하면 메타가 다시 끌어들인다.- HWE 트랙은 “최신 하드웨어 지원”의 대가로 “회귀 가능성”을 짊어진다. 운영 서버는 GA 트랙(이 경우 6.8)이 더 안전할 수 있다.
블로그 서버를 무사히 살려냈다. 이번 일을 통해 평소 막연했던 journalctl 사용법을 정리하게 된 건 그나마 수확이다.
조회수: 6