Skip to content

release: 옛 serve 가 살아남아 낡은 빌드를 서빙하던 문제 수정 - #171

Merged
dldnsgkr merged 2 commits into
mainfrom
develop
Aug 25, 2026
Merged

release: 옛 serve 가 살아남아 낡은 빌드를 서빙하던 문제 수정#171
dldnsgkr merged 2 commits into
mainfrom
develop

Conversation

@dldnsgkr

Copy link
Copy Markdown
Collaborator

develop → main. #170 하나뿐이고 마이그레이션 변경은 없다.

무엇을 고치나

프리뷰를 다시 띄울 때 옛 serve 를 죽이는 pkill실제 프로세스를 한 번도 잡지 못했다. 컨테이너 안 cmdline 에 리터럴 npx serve 가 없고, 대신 그 pkill 을 실행하는 sh -c 자신의 cmdline 에 패턴 문자열이 있어 스스로에게 SIGTERM 을 보냈다.

컨테이너 실측(2026-08-25, node:20-alpine, 운영과 같은 cap-drop/pids/memory):

pkill -f 'npx serve'   → exec 종료코드 143,  살아남은 serve 2개
pkill -f '[s]erve -s'  → exec 종료코드 0,    남은 serve 0개

새 serve 로그:  Accepting connections at http://localhost:46113   ← 랜덤 포트로 밀려남
포트 3000 응답: <h1>OLD-DIST</h1>                                  ← 게이트웨이가 보는 포트

옛 serve 가 포트 3000 을 쥐고 있으면 새 serve 는 EADDRINUSE 로 죽지 않고 조용히 옮겨 붙는다. 게이트웨이는 3000 만 프록시하므로 옛 serve 가 붙들고 있는 옛 디렉터리가 계속 응답한다. 빌드 산출물 경로가 바뀔 때(distbuild) 낡은 화면이 남는다 — 같은 경로면 serve 가 요청마다 디스크를 읽어 최신이 나오므로, 증상은 경로가 달라질 때만 발현된다.

이미 들어가 있던 리스닝 확인이 오히려 이것을 가렸다. 옛 serve 가 3000 에서 응답하니 probe 가 통과해 "준비 완료"가 된다.

함께 고친 것 — 빌드 실패가 조용히 성공으로 넘어가던 문제

DockerContainerService.exec 이 종료 코드를 읽지 않아 프리뷰 빌드의 set -o pipefail 이 무의미했다. 빌드가 깨져도 호출자는 성공으로 알았고, 컨테이너를 재사용하는 프리뷰에서는 이전 빌드의 dist 가 남아 있어 실패한 빌드가 옛 화면으로 성공처럼 보였다.

exec 의 의미는 바꾸지 않았다 — 호출부 48곳 중 상당수가 실패해도 되는 명령이라, 종료 코드를 함께 돌려주는 execWithExitCode 를 따로 두고 빌드에서만 쓴다.

검증

  • 마이그레이션 없음
  • 테스트 4개 추가. pkill 테스트는 패턴을 정규식으로 컴파일해 실측 cmdline 을 잡는지 + 자기 자신은 안 잡는지를 본다(옛 패턴으로 되돌리면 둘 다 깨진다)
  • ./gradlew test 전체 통과, CI 통과, dev 배포 성공(f83e762) 및 기동 확인(200)

Closes #115

🤖 Generated with Claude Code

dldnsgkr and others added 2 commits August 25, 2026 18:08
pkill 패턴이 실제 프로세스를 한 번도 잡지 못했다. 컨테이너 안 cmdline 은

  npm exec serve -s /workspace/app/dist -l 3000
  node /root/.npm/_npx/<hash>/node_modules/.bin/serve -s /workspace/app/dist -l 3000

이라 리터럴 'npx serve' 가 없다. 대신 그 pkill 을 실행하는 sh -c 자신의 cmdline 에는
패턴 문자열이 들어 있어 스스로에게 SIGTERM 을 보냈다.

2026-08-25 컨테이너 실측(node:20-alpine, 운영과 같은 cap-drop/pids/memory):

  pkill -f 'npx serve'   → exec 종료코드 143, 살아남은 serve 2개
  pkill -f '[s]erve -s'  → exec 종료코드 0,   남은 serve 0개

옛 serve 가 포트 3000 을 쥐고 있으면 새 serve 는 EADDRINUSE 로 죽지 않고 랜덤 포트로
조용히 옮겨 붙는다("Accepting connections at http://localhost:46113"). 게이트웨이는
3000 만 프록시하므로 옛 serve 가, 즉 그 serve 가 붙들고 있는 옛 디렉터리가 계속
응답한다. 빌드 산출물 경로가 바뀌면 낡은 화면이 그대로 보인다 — 같은 디렉터리면
serve 가 요청마다 디스크를 읽어 최신이 나오므로, 이 증상은 경로가 바뀔 때만 터진다.

이미 들어가 있는 리스닝 확인이 오히려 이것을 가렸다. 옛 serve 가 3000 에서 응답하니
probe 가 통과해 "준비 완료"가 된다.

함께 고친 것 — exec 이 종료 코드를 읽지 않던 문제.

DockerContainerService.exec 은 stdout·stderr 만 모으고 종료 코드를 확인하지 않았다.
그래서 프리뷰 빌드의 set -o pipefail 이 아무 의미가 없었고, 빌드가 깨져도 호출자는
성공으로 알았다. 컨테이너를 재사용하는 프리뷰에서는 이전 빌드의 dist 가 남아 있어
detectBuildOutputDir 이 그것을 잡았고, 실패한 빌드가 옛 화면으로 성공처럼 보였다.

exec 의 의미는 바꾸지 않았다. 호출부가 48곳인데 상당수는 실패해도 되는 명령이라
(없는 파일 cat, 이미 있는 remote 를 git remote add, [ -d ... ] 존재 확인) 전부
예외로 바꾸면 멀쩡한 흐름이 깨진다. 종료 코드를 함께 돌려주는 execWithExitCode 를
따로 두고, 실패가 반드시 전달돼야 하는 빌드에서만 쓴다. 호출자는 이미 예외를 받아
세션을 FAILED 로 닫는다.

Closes #115

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(preview): 옛 serve 가 살아남아 낡은 빌드를 서빙하던 문제
@dldnsgkr
dldnsgkr merged commit ef5d460 into main Aug 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Preview] serve 프로세스 종료 실패로 낡은 빌드가 계속 서빙됨 (pkill 패턴 미매치)

1 participant