Operations 10 min read 고급

news.hwmoon.com 배포 중단 원인과 Agent 자가복구 체계 구축 기록

정시 기사 발행과 Static Web Apps 배포가 반복 실패한 원인을 Key Vault 만료가 아닌 self-hosted agent 런타임 불안정으로 분류하고, Node 런타임 복구, Key Vault variable group 전환, 예약 healthcheck까지 반영한 운영 기록입니다.

Azure Pipelines Agent Runtime
Node.js 24.16.0
App Build Runtime
Node.js 22.22.3
Static Web Apps CLI
2.0.9
Healthcheck Schedule
07:50-19:50 KST, every 2 hours
서버 랙과 네트워크 케이블이 보이는 데이터센터 장비
자료 이미지: Photo by Kevin Ache on Unsplash
이 글의 목차
  1. 관측된 증상
  2. Key Vault 만료가 아니라고 판단한 이유
  3. 복구 방향
  4. 1. Key Vault 조회 방식을 변경
  5. 2. 기본 checkout task를 우회
  6. 3. NodeTool task를 제거
  7. 4. SWA 배포도 retry 가능하게 변경
  8. 검증 결과
  9. 자동 복구 스케줄
  10. 동시간대 충돌 여부
  11. 운영 판단 기준
  12. 남은 리스크

이번 기록은 news.hwmoon.com 정시 기사 발행과 Static Web Apps 배포가 반복적으로 실패한 문제를 다시 정리한 운영 회고다.

겉으로는 Slack에 “article-generation 실패”, “CI 실패”, “배포 실패”가 찍혔다. 그래서 처음에는 Key Vault secret 만료나 PAT 인증기간 문제처럼 보였다. 하지만 실제 실패 로그를 run 단위로 따라가 보니 결론은 달랐다.

이번 장애의 직접 원인은 Key Vault 만료가 아니었다.
self-hosted Azure Pipelines agent의 Node/task runtime 계층이 불안정했고,
az, npm, npx, AzureKeyVault task, Checkout task wrapper가 모두 exit 139로 죽었다.

이 차이를 명확히 잡는 것이 중요했다. Key Vault만 계속 보면 토큰을 재발급해도 같은 실패가 반복된다.

관측된 증상

운영 기준 시간은 KST다.

시각PipelineRun실패 지점관측
2026-06-14 22:00article-generation#2213AzureCLI secret loadaz 실행 중 exit 139
2026-06-15 08:00article-generation#2215SWA CLI deploynpx @azure/static-web-apps-cli 실행 중 exit 139
2026-06-15 10:00article-generation#2218npm ciNode 24/npm 환경에서 반복 SIGSEGV
검증 중news-cd-static-web-app#2226Checkout taskAgent.PluginHost checkout plugin exit 139
검증 중news-cd-static-web-app#2229AzureKeyVault@2task 내장 Node24 wrapper exit 139

실패 지점이 하나가 아니었다. az만 죽은 것이 아니고, npm만 죽은 것도 아니고, Azure DevOps task wrapper 자체도 죽었다.

이 패턴은 secret 값이 틀렸을 때의 실패와 다르다. 인증 실패라면 보통 401, 403, secret not found, forbidden 같은 오류가 난다. 이번에는 process 자체가 segmentation fault로 종료됐다.

Key Vault 만료가 아니라고 판단한 이유

Key Vault나 인증기간 문제를 배제한 근거는 세 가지다.

첫째, 실패 run 중 service principal 로그인과 Key Vault 조회가 성공한 사례가 있었다. 즉 인증 경로가 항상 막힌 것은 아니었다.

둘째, PR 생성과 병합에 쓰인 Azure DevOps PAT도 정상 동작했다. article-generation run #2215는 실제로 PR을 만들고 완료했다.

셋째, 같은 agent에서 AzureKeyVault@2 task 자체가 exit 139로 죽었다. 이것은 secret value 문제가 아니라 task runtime 문제다.

그래서 결론은 다음과 같았다.

질문결론
키 인증기간 만료인가직접 원인은 아님
PAT를 무제한으로 늘려야 하나권장하지 않음
어디가 문제인가self-hosted agent의 Node/task/runtime 안정성
무엇을 해야 하나secret 의존 task를 줄이고 agent runtime을 선제 복구

PAT나 secret을 무제한으로 두면 운영 안정성이 올라가는 것이 아니라 보안 위험이 커진다. 이번 장애는 만료 시간을 늘려도 해결되지 않는 종류였다.

복구 방향

조치 방향은 “다시 실행”이 아니라 “같은 실패 계층을 밟지 않기”였다.

1. Key Vault 조회 방식을 변경

기존에는 pipeline 안에서 AzureCLI@2 또는 AzureKeyVault@2 task를 실행해 secret을 가져왔다. 하지만 이번에는 그 task wrapper 자체가 exit 139로 죽었다.

그래서 Key Vault secret을 Azure DevOps variable group으로 연결했다.

Key Vault
  -> Azure DevOps variable group
  -> pipeline variables
  -> Bash/SWA CLI env

적용한 variable group은 vg-hwmoon-global-insightlab-dev다. 이 group에 아래 secret을 연결했다.

Secret용도
app-static-web-app-deployment-tokennews Static Web Apps 배포
app-tech-static-web-app-deployment-tokentech Static Web Apps 배포
aiops-azdo-patSystem.AccessToken 실패 시 fallback
ops-slack-webhook-url운영 실패 알림
ops-slack-channel-name운영 알림 채널
app-azure-openai-api-key기사 생성
app-azure-openai-endpoint기사 생성
app-azure-openai-deployment기사 생성
app-unsplash-access-key기사 이미지 후보

이렇게 하면 pipeline 중간에 az keyvault secret show를 실행하지 않아도 된다.

2. 기본 checkout task를 우회

기본 checkout: self도 Agent.PluginHost에서 죽었다. 그래서 news 계열 파이프라인은 기본 checkout을 끄고, Bash에서 git fetch를 직접 수행하도록 바꿨다.

checkout: none
  -> git fetch with System.AccessToken
  -> PAT fallback
  -> timeout
  -> no interactive prompt

인증 순서는 System.AccessToken 우선, aiops-azdo-pat fallback이다. PAT를 첫 번째 수단으로 쓰지 않는 이유는 권한 표면을 줄이기 위해서다.

3. NodeTool task를 제거

NodeTool@0도 깨진 Node cache를 다시 사용하거나 tarball 해제 단계에서 실패했다. 그래서 Node 설치를 task에 맡기지 않고 Bash와 Python 표준 라이브러리로 직접 처리했다.

download Node tarball
  -> download SHASUMS256.txt
  -> verify sha256
  -> extract with Python tarfile
  -> prepend PATH

앱 빌드 런타임은 Node.js 22.22.3으로 고정했다. agent task wrapper 런타임은 Node.js 24.16.0으로 복구한다.

4. SWA 배포도 retry 가능하게 변경

Static Web Apps 배포는 AzureStaticWebApp@0 task 대신 SWA CLI를 명시적으로 실행하고 retry한다.

npx @azure/static-web-apps-cli@2.0.9 deploy dist
  -> retry up to 3 attempts
  -> cleanup helper containers
  -> smoke check public route

이 방식은 실패 위치가 더 잘 보인다. npm이 실패했는지, SWA CLI가 실패했는지, public route smoke check가 실패했는지 분리된다.

검증 결과

PR #498로 news 파이프라인 보강을 반영했고, main merge commit은 5c6a694다.

RunBranch결과의미
#2235PR branch성공hardened CD path 검증
#2240main성공운영 배포 성공
public checkhttps://news.hwmoon.com/kr/HTTP 200사이트 반영 확인
archive check/kr/archive/2026-06-15 기사 노출최신 기사 노출 확인

사이트에는 2026.06.15 08:00 기사인 “AI 음성에이전트와 수출통제가 바꾸는 글로벌 AI 지형”이 노출됐다.

자동 복구 스케줄

같은 계열의 문제가 다시 생기지 않게 별도 예약 파이프라인을 만들었다.

agent-runtime-healthcheck

이 pipeline은 운영 슬롯 직전에 self-hosted agent를 점검하고 복구한다.

KST목적
07:5008:00 운영 슬롯 전 점검
09:5010:00 운영 슬롯 전 점검
11:5012:00 운영 슬롯 전 점검
13:5014:00 운영 슬롯 전 점검
15:5016:00 운영 슬롯 전 점검
17:5018:00 운영 슬롯 전 점검
19:5020:00 운영 슬롯 전 점검

Azure Pipelines cron은 UTC 기준이므로 YAML은 다음과 같이 들어갔다.

schedules:
  - cron: "50 23,1,3,5,7,9,11 * * *"
    displayName: Repair and warm news deployment agent before operating slots
    branches:
      include:
        - main
    always: true

healthcheck가 수행하는 일은 다음과 같다.

  • agent Node24 runtime을 24.16.0으로 복구
  • 앱 빌드용 Node를 22.22.3으로 준비
  • Azure Repos 인증 확인
  • npm/npx 동작 확인
  • SWA CLI 2.0.9 실행 확인
  • 실패 시 Slack 운영 알림 발송

수동 검증 run #2244도 성공했다.

Agent Node24 runtime is already v24.16.0.
node v22.22.3
npm 10.9.8
Repository authentication check succeeded.
SWA CLI 2.0.9
Agent runtime healthcheck completed.

동시간대 충돌 여부

이번에도 동시간대 pipeline 대기 상황을 확인했다. cd-console-static-web-app이 먼저 실행 중이라 news CD가 queue에서 기다렸다가 실행됐다.

이것은 같은 workspace에서 동시에 파일을 덮어쓰는 충돌이라기보다, 동일 self-hosted agent pool에서 순번을 기다리는 queue 동작에 가깝다.

다만 운영상으로는 queue 대기도 발행 지연의 원인이 될 수 있다. 그래서 healthcheck를 정시 10분 전으로 옮겼다. 정시 기사 생성과 같은 순간에 agent를 잡지 않도록 하기 위해서다.

운영 판단 기준

앞으로 비슷한 알림을 받으면 먼저 실패 step을 본다.

실패 위치먼저 볼 것
AzureKeyVault@2, AzureCLI@2 task 자체가 exit 139agent task runtime
npm ci, npx, node가 SIGSEGVNode cache 또는 native runtime
401, 403, forbidden, secret not found인증/권한/Key Vault
build는 성공, deploy 실패SWA token 또는 SWA CLI
deploy 성공, 사이트 낡음route smoke, cache, dist path
pipeline notStarted 장기 대기agent busy/offline 또는 다른 pipeline 선점

가장 중요한 기준은 exit 139다. 이 값은 대체로 설정값 오류보다 runtime crash 쪽에 가깝다.

남은 리스크

자동 복구가 모든 것을 해결하지는 않는다.

리스크대응
NAS host 자체 다운Slack healthcheck 실패 알림, 수동 NAS 확인
Azure DevOps 장애재시도와 상태 확인, 외부 서비스 복구 대기
Node 공식 배포 다운로드 실패checksum retry, 다음 healthcheck에서 재시도
PAT 실제 만료System.AccessToken 우선 사용, PAT fallback 실패 시 명확한 알림
Key Vault 권한 제거variable group secret download 실패로 조기 감지

이번 조치의 핵심은 “무제한 키”가 아니다. 키를 길게 늘리는 대신, 인증과 런타임 문제를 구분하고, 런타임 계층은 운영 시간 전에 자동으로 복구하도록 만든 것이다.

정리하면 이번 복구는 단순 재실행이 아니라 운영 경로를 다시 설계한 작업이다. 실패 원인을 더 빨리 분류하고, agent 런타임을 선제 복구하고, public site까지 확인하는 루프를 만든 것이 재발 방지의 핵심이다.

시리즈

Publisher Infrastructure

전체 8편 중 8편입니다. 관련 구축 기록을 순서대로 모아 전체 맥락을 쉽게 확인할 수 있습니다.

  1. 1. Azure DNS와 Zoho Mail로 도메인 이메일 만들기
  2. 2. Synology NAS로 Azure DevOps Self-hosted Agent 운영하기
  3. 3. Pipeline은 성공했는데 Static Web Apps가 404를 냈던 이유
  4. 4. Azure Repos를 운영 원본으로 두고 GitHub를 백업으로 쓰는 전략
  5. 5. Slack 기반 AIOps로 NAS Agent 운영 자동화하기
  6. 6. news.hwmoon.com 기사 자동 발행 중단 재발 방지 기록
  7. 7. news.hwmoon.com AdSense 승인 전 준비와 운영 전환 기록
  8. 8. news.hwmoon.com 배포 중단 원인과 Agent 자가복구 체계 구축 기록

관련 글