엥? 이거 죽는 각이었어요?
글 하나 배포했는데, 페이지가 죽었어요...
아니 그냥 글 하나 띡 올리고 배포했을 뿐인데… 사이트가 내려갔다.
뭐지? 컴파일이 깨졌나? 로컬에서 확인해봤는데 그것도 아니었다.
아니, 애초에 컴파일이 깨질 수가 없다. 내 블로그는 정적 파일로 만들어진 페이지고, 밤에 그냥 아티클 읽은 걸 정리하고 md 파일 하나를 올렸을 뿐이다.
근데, 뭐가 문제인지 접속이 안 됐다.
처음엔 프론트도어가 문제인 줄 알았다. 주주의 앱서버와도 연결 되어 있으니.. 벌벌 떨면서 주주 앱을 켰지만, 별 문제는 없었다.
프론트 도어 자체는 잘 열려있었다.
그럼 뭐가?
일단 컨테이너부터
이 사이트는 Azure Container Apps 위에 nginx + 정적 파일로 떠 있다.
Container App 들어가서 보니 — 새 revision 들이 죄다 “활성화 중”. replica 0/N. 즉 컨테이너가 떠 있긴 한데 ready 처리를 못 받고 있는 상태.
어제 자기 전에 두 번 push 한 게 떠올랐다. tag 가 v$(yyyyMMdd-HHmm) 라 같은 분에 두 번 push 하면 같은 tag 가 ACR 에 덮어쓰기 됨.
“아 이것 때문에 image 가 깨졌구나”
라고 확신했다.
그게 첫 번째 함정이었다.
그래서 다시 빌드
초 단위 tag 로 깨끗하게 다시 빌드 & 배포.
$TAG = "v$(Get-Date -Format yyyyMMddHHmmss)"
az acr build ...
az containerapp update --image ...
새 revision 생성됨. 한참 기다림.
같은 상태. ActivationFailed.
근데 신기한 게, 컨테이너 로그를 보면 nginx 가 정상 시작 떴다.
nginx/1.31.1
start worker process 29, 30, 31, 32
SIGQUIT 같은 종료 신호도 없음. 멀쩡히 살아있음. 근데 Container Apps 가 ready 처리 안 함.
image 가 깨졌으면 시작도 못 했을 텐데. image 문제 아닌 거. 그럼 뭐가…
probe 인가?
Container Apps 가 ready 처리 안 한다 = probe 가 실패한다, 가 가장 자연스러운 가설.
probe path 를 /health 로 명시 (nginx.conf 에 미리 만들어둔 endpoint).
→ 같음.
TCP probe 로 변경 (socket open 만 확인).
→ 같음.
probe 완전 제거.
→ 같음.
여기서 멘탈 나갔다.
probe 가 어떻든 같은 결과면 — 진짜 probe 문제가 아닌 거다.
시스템 로그가 처음 보여준 단서
이제까지 컨테이너 로그만 봤는데, 시스템 로그를 처음으로 진지하게 봤다.
Warning: ScaledObjectCheckFailed
Msg: "Target resource doesn't expose /scale subresource"
EventSource: KEDA
Count: 6
Normal: "Scaler external-push is built"
EventSource: KEDA
KEDA scaler 가 깨져 있음. Container Apps 가 replica 스케일링 결정에 KEDA 를 쓰는데 — 그 KEDA 가 deployment 의 /scale 을 못 만져서 replica 가 0 → 1 ready 처리 못 받는 상태.
그러면 scale rule 을 바꾸면 KEDA 가 새 scaler 만들지 않을까?
rule name 변경 → 같음. rule 완전 제거 → 같음. activeRevisionsMode 토글 → 같음. ingress disable/enable → 같음.
yaml 로 할 수 있는 모든 reset 을 다 시도. 다 무효.
새 app 을 만들어보자
같은 environment 에 완전히 새 app 을 만들었다. placeholder 로 nginx:alpine (Docker Hub 의 공식 public image).
az containerapp create --name slowflowsoft-web-v2 ... --image nginx:alpine ...
같은 환경, 새 ScaledObject, 새 identity, 새 ingress. 모든 게 새것.
새로 만들었으니까 이제는 되겠지
→ 같음. healthState None, replica 0, timeout.
이 시점에서 앞이 깜깜해졌다.
새 app 도 같은 운명이면 — 이거 app 의 문제가 아니라 environment 의 문제 라는 거다.
결정적 단서
여기서 잠깐 멈췄다.
원래 인프라 짤 때 — web 과 백엔드 API 는 RG 도 environment 도 처음부터 따로 분리해뒀다. 책임 영역 분리 + blast radius 차단 목적. 그쪽 백엔드는 옆 RG 의 옆 환경에서 돌고 있다.
그리고 그 백엔드는 — 멀쩡히 잘 돌고 있었다.
이 사실이 갑자기 의미를 갖기 시작했다.
상황을 다시 정리해보면:
Backend-RG / 환경 A → api-app 정상
Web-RG / 환경 B → web-app stuck
→ web-app-2 (새거) stuck
같은 RG / 환경 안의 모든 app 은 죽었고, 옆 RG / 환경의 app 은 멀쩡.
즉 app 단위 문제가 아니다. revision 단위 문제도 아니다. probe, image, scale rule, ingress… 다 그 위 layer 의 문제도 아니다.
이건 그냥 — Web-RG 쪽 environment 자체가 통째로 망가졌다 는 거다.
이유는 모르겠다. 어젯밤 같은 분에 두 번 push 한 race condition 이었을 수도 있고, KEDA 가 어떤 식으로 내부 상태가 꼬였을 수도 있고, 그냥 그날 Azure 가 트림한 걸 수도 있고. 확신 못 함.
확실한 건 — 그 환경 안에서는 뭘 새로 만들어도 같은 운명을 맞는다. yaml patch 로 풀릴 종류의 손상이 아니다.
그러면 답도 자연스럽게 보인다 — 옆 RG / 환경 (백엔드가 잘 돌고 있는 그쪽) 에 새 app 만 만들면, 사이트는 살아난다.
그냥 다시 만들었다
망가진 env 를 고치는 건 포기.
새 RG 를 만들고, 그 안에 새 environment 를 만들고, 거기에 새 app 을 띄웠다. 처음부터 깨끗하게 다시.
# 1. 새 RG
az group create --name <new-rg> --location koreacentral
# 2. 새 environment
az containerapp env create --name <new-env> --resource-group <new-rg>
# 3. 새 app (placeholder image 로 일단 띄움)
az containerapp create \
--name slowflowsoft-web \
--resource-group <new-rg> \
--environment <new-env> \
--image nginx:alpine \
--target-port 80 --ingress external \
--system-assigned
만들자마자 placeholder 가 HTTP 200. 새 환경은 멀쩡하다는 게 단번에 증명됐다.
이후 ACR pull 권한 부여 → registry 설정 → image 교체. (중간에 az role assignment CLI 가 MissingSubscription 에러 떨어져서 ARM REST API 로 우회한 건 덤.)
새 revision: STATUS=200, 0.06초. <!doctype html><html lang="ko"> 진짜 React 응답.
마지막으로 Azure Front Door 의 origin hostName 을 이 새 fqdn 으로 변경. originHostHeader 도 같이. 3분 propagation 후 — www.slowflowsoft.com → 200.
살아남.
진짜 원인 (추정)
확신 못 함. 다만 시나리오는 — 어젯밤 같은 분에 두 번 push 한 게 race 를 만들었고, 그게 KEDA ScaledObject reconciliation 을 깨뜨렸고, 한 번 깨진 ScaledObject 는 environment 단위로 self-heal 안 됨. 그래서 이후 모든 새 revision / 새 app 이 같은 깨진 상태를 상속.
다음부터 안 깨지게
# 분 단위 tag → 초 단위 tag (race 방지)
$TAG = "v$(Get-Date -Format yyyyMMddHHmmss)"
# 매 deploy 전 dist 삭제 (az acr tar 가 한글 폴더에서 깨짐)
Remove-Item -Recurse -Force blog/dist, subnotes/dist, dist -ErrorAction SilentlyContinue
이거 두 줄로 다음 사고는 안 일어날 거다. 라고 믿고 싶다.
마치며
이틀 동안 yaml patch 만 28번쯤 한 거 같다. 다 무효였다.
답은 결국 — 나란히 있는 다른 시스템과 비교하는 한 줄.
infra 문제 만났을 때 한 시스템만 후벼 파지 말고, 옆에 있는 멀쩡한 거랑 어디가 다른지 한 번씩 봐야겠다.
근데 이거 진짜 더는 안 겪고 싶다.
마침.