P4 컷오버계획 — 크라우니 게이트웨이 한선씨 무중단 포트 재바인딩
레거시 JS 게이트웨이(node bin/cli.js start, :8080 HTTP / :8443 HTTPS)를
한선씨 게이트웨이(:8080 평문 + stunnel :8443 TLS 종단)로 무중단 전환한다.
1. 전략 결정 (확정): 단계 순서교환 (Staged Handover)
SO_REUSEPORT 미사용. 단계 순서교환을 채택한다.
1.1 SO_REUSEPORT 를 쓰지 않는 근거
- macOS 는
SO_REUSEPORT(0x0200)를 정의한다 (/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/socket.h:137).
- 그러나 양쪽 리스너가 모두
SO_REUSEPORT를 설정해야 포트를 공유할 수 있다.
bin/cli.js start) 이고, Node 의 net/http2 서버는
기본적으로 SO_REUSEADDR 만 설정하고 SO_REUSEPORT 는 설정하지 않는다.
→ 한선씨가 SO_REUSEPORT 로 8080 을 bind 해도 incumbent 가 REUSEPORT 미설정이라 EADDRINUSE.
- 설령 REUSEPORT 가 양쪽에 켜져 있어도, 커널이 두 리스너에 연결을 로드밸런싱한다.
- 현재 라이브 리스너 확인:
lsof -nP -iTCP:8080 -iTCP:8443 -sTCP:LISTEN
→ node 59361 *:8080 (LISTEN) / *:8443 (LISTEN) # PPID 35477 = infra watchdog
1.2 단계 순서교환의 무중단 보장
- 그림자(:8081 한선씨 평문, :8444 stunnel) 에서 헬스 통과를 먼저 확인한다 (라이브 무접촉).
- 레거시를 종료한 직후 즉시 한선씨를 8080 에 rebind 한다.
TCP_LISTEN 은 SO_REUSEADDR 를 설정하므로(crownyc.c:10528),
레거시 TIME_WAIT 잔여 상관없이 즉시 bind 성공.
- 단절 구간은 "레거시 종료 ~ 한선씨 bind 완료" = 약 1~2초 (sleep 1 + bind).
2. Watchdog 처리 (가장 중요 — 충돌 회피)
2.1 위협 구조
라이브 게이트웨이를 자동 재기동하는 주체는 crowny-infra watchdog (PID 35477) 이다. (node /Users/ef/crowny-infra/bin/cli.js watchdog, PPID=1, launchd KeepAlive)crowny-stack.yaml 의 crowny-gateway:command: /opt/homebrew/bin/node bin/cli.js startport: 8080, health: http://127.0.0.1:9100/healthlib/watchdog.js)::9100/health 프로브 (interval=10000)._restart() 발동._restart() 는 lsof -ti :8080 → SIGTERM, 2초 후 SIGKILL → node bin/cli.js start 재기동.2.2 처리 방안 (채택): SIGSTOP → 감시대상 교체 → SIGCONT
- 컷오버 직전 watchdog 를 SIGSTOP 으로 동결한다(종료 아님, 프로세스 트리 보존):
kill -STOP 35477
STOP 상태에서는 setInterval 타이머가 진행되지 않아 프로브·재기동이 모두 멈춘다.
30초 카운트다운 자체가 동결되므로 안전.
- 컷오버 성공 확인 후
crowny-stack.yaml의crowny-gateway를 교체:
command: cd /Users/ef/crowny-gateway && ./crownyc run 한선게이트웨이/게이트웨이라이브.toau
(+ stunnel 한선게이트웨이/stunnel-live.conf)
- health : http://127.0.0.1:8080/ ← 임시. :9100 admin 은 한선씨에 아직 미구현.
(후속 과제: 한선씨 게이트웨이에 :9100/health admin 엔드포인트 구현 → 그때 health 원복)
- watchdog 재개:
kill -CONT 35477
이제 watchdog 는 한선씨 게이트웨이를 :8080 루트로 감시하고, 죽으면 한선씨를 되살린다.2.3 대안 (미채택, 참고)
- A. health 프로브만 :8080 으로 바꾸고 STOP 생략: watchdog 가 30초 창 안에 8080 다운을
- B. 한선씨에 :9100/health 구현 후 stack.yaml 무변경: 이상적이지만 :9100 구현은 별도 과제.
- 게이트웨이 자체
scripts/watchdog.sh는gateway:9150(CGWCS 관제 포트)만 감시하고
3. 라이브 런북 (운영자 수동 트리거 — 단계별 명령)
드라이런: cd /Users/ef/CrownyOS/crownyc && ./hanseonc_high /Users/ef/crowny-gateway/한선게이트웨이/컷오버.한선 > /tmp/컷오버.toau 2>/dev/null && ./crownyc run /tmp/컷오버.toau
아래는 실제 적용. 순서 엄수. 각 단계 검증 실패 시 즉시 §4 롤백.bash# ── 사전 빌드 (1회) ──
cd /Users/ef/CrownyOS/crownyc
./hanseonc_high /Users/ef/crowny-gateway/한선게이트웨이/게이트웨이라이브.한선 \
> /Users/ef/crowny-gateway/한선게이트웨이/게이트웨이라이브.toau 2>/dev/null
# ── 단계0: 사전체크 (라이브 무접촉) ──
curl -sf -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8081/ # 그림자 한선씨 (200 기대)
curl -sf -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9878/ # tiomta (200)
curl -sf -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9907/ # mpti (200)
# 하나라도 실패면 컷오버 미진입.
# ── 단계1: 워치독 동결 (레거시 자동 재기동 차단) ──
kill -STOP 35477
ps -p 35477 -o pid,stat= # STAT 에 T(stopped) 확인
# ── 단계2: 레거시 종료 (bin/cli.js start 패턴만) ──
for p in $(lsof -ti :8080 2>/dev/null); do ps -p $p -o command= 2>/dev/null | grep -q 'bin/cli.js start' && kill -TERM $p 2>/dev/null; done
for p in $(lsof -ti :8443 2>/dev/null); do ps -p $p -o command= 2>/dev/null | grep -q 'bin/cli.js start' && kill -TERM $p 2>/dev/null; done
sleep 1
lsof -nP -iTCP:8080 -sTCP:LISTEN # 비어야 함
# ── 단계3: 한선씨 8080 재바인딩 + stunnel 8443 종단 ──
cd /Users/ef/crowny-gateway
./crownyc run 한선게이트웨이/게이트웨이라이브.toau > /tmp/한선게이트웨이-라이브.log 2>&1 &
sleep 2
stunnel 한선게이트웨이/stunnel-live.conf & # foreground=no → 데몬화
sleep 1
# ── 단계4: 승격 헬스 재확인 ──
curl -sf -o /dev/null -w 'http8080=%{http_code}\n' http://127.0.0.1:8080/
curl -sk --resolve tiomta.com:8443:127.0.0.1 https://tiomta.com:8443/ -o /dev/null -w 'tiomta8443=%{http_code}\n'
curl -sk --resolve mpti.tiomta.com:8443:127.0.0.1 https://mpti.tiomta.com:8443/ -o /dev/null -w 'mpti8443=%{http_code}\n'
# 정상코드(200/301 등)면 컷오버 확정 → 단계5. 실패면 §4 롤백.
# ── 단계5: 워치독 감시대상 교체 + 재개 ──
# crowny-stack.yaml 의 crowny-gateway 를 아래로 편집:
# command: cd /Users/ef/crowny-gateway && ./crownyc run 한선게이트웨이/게이트웨이라이브.toau
# health : http://127.0.0.1:8080/
kill -CONT 35477
ps -p 35477 -o pid,stat= # STAT 에 S/R (running) 확인
4. 롤백 (어느 단계든 Ta 시)
bash# 한선씨 라이브 인스턴스 종료
for p in $(lsof -ti :8080 2>/dev/null); do ps -p $p -o command= 2>/dev/null | grep -qE '한선게이트웨이|crownyc' && kill -TERM $p 2>/dev/null; done
# stunnel 8443 종료
for p in $(lsof -ti :8443 2>/dev/null); do ps -p $p -o command= 2>/dev/null | grep -q 'stunnel' && kill -TERM $p 2>/dev/null; done
# 레거시 재기동
cd /Users/ef/crowny-gateway && nohup /opt/homebrew/bin/node bin/cli.js start > /tmp/gw-rollback.log 2>&1 &
sleep 2
curl -sf -o /dev/null -w 'legacy9100=%{http_code}\n' http://127.0.0.1:9100/health
# stack.yaml 을 원복(편집했다면) 후 워치독 재개
kill -CONT 35477
레거시가 :8080/:8443/:9100 으로 복귀하면 운영 영향 최소화.
5. 산출 파일
| 파일 | 역할 |
|---|---|
한선게이트웨이/컷오버.한선 | 오케스트레이터 (드라이런 기본, 단계0~5 + 롤백) — 컴파일·드라이런 검증됨 |
한선게이트웨이/게이트웨이라이브.한선 | 컷오버 후 라이브 8080 엔트리 (게이트웨이메인의 8080 변형) — 컴파일 검증됨 |
한선게이트웨이/stunnel-live.conf | 라이브 8443 TLS 종단 → 8080 포워드 (SNI: ext + abti) |
한선게이트웨이/컷오버계획.md | 본 문서 — 전략·watchdog·런북·롤백 |
6. 잔여 이슈
- :9100 admin/health 한선씨 미구현 → 컷오버 후 watchdog health 를 임시로 :8080 루트로 둠.
- stunnel SNI 분기는 ext(기본) + abti(crownybus.com) 2개만. 추가 SAN별 cert 필요 도메인은
인증서선택(도메인) 산출로 stunnel conf 동적 생성(P2 연동) 후속.
게이트웨이라이브.toau는 실행 시 TCP accept 루프로 블록 → 반드시 백그라운드(&) 기동.