장애 대응 런북

실제 운영에서 발생할 가능성이 높은 장애 시나리오별 대응 절차입니다. 각 항목은 증상 → 확인 → 원인 분류 → 조치 → 재발 방지 순으로 정리되어 있습니다.

장애 대응 공통 전제:

  • 모든 서비스는 Kubernetes(NHN NKS) 위에서 돌아갑니다. 로그는 kubectl logs <POD> 로 확인합니다.

  • 네임스페이스 맵 (gitops 기준):

    네임스페이스구성 요소
    cone-watcherAPI(prd-cone-watcher-api), Worker(prd-cone-watcher-worker), Scheduler(prd-cone-watcher-scheduler), Admin(prd-cone-watcher-admin), Dashboard(prd-cone-watcher-dashboard), Ingress, mongo-express(ExternalName)
    mongodbPercona Server for MongoDB (mongodb-rs-0/1/2), mongo-express 실체
    valkeyValkey(Redis 호환) helm release
  • 파드 라벨: 애플리케이션 파드는 app.kubernetes.io/component=<api|worker|scheduler|admin|dashboard> 로 셀렉트합니다 (레거시 app=<name> 라벨은 붙어있지 않음).

  • 파드 이름은 prd-cone-watcher-api-*, prd-cone-watcher-worker-*, prd-cone-watcher-scheduler-* 패턴입니다. (prefix는 base cone-watcher- + overlay prd- 가 합쳐진 결과)

  • 시간대는 컨테이너 기본인 UTC 기준입니다. 한국 시각은 +9시간.

  • Dramatiq 워커는 자동 재시도가 0회로 설정되어 있습니다 (common/worker/brokers.py:17 Retries(max_retries=0)). config.DRAMATIQ_MAX_RETRIES=3 은 미사용 상수이니 혼동 주의. 실패는 곧바로 워커 파드 stdout 로그에 스택트레이스를 남기고 끝납니다. 재실행은 수동입니다.

  • worker_task 컬렉션은 현재 미사용 레거시입니다. AWS 시절 포크 유산이며 WorkerTaskController 가 어디서도 호출되지 않아 문서가 쌓이지 않습니다. 태스크 실행 여부는 kubectl logs + error_log + 결과물(nhn_invoice 등) 확인 조합으로 판단합니다.

A. 청구서 생성 실패

관련 작업: 스케줄러 nhn_invoice_task (매월 5일 00:00 KST)

A.1 증상

  • 매월 5~6일에 "청구서가 안 보인다"는 고객사 제보.
  • 대시보드에서 특정 조직만 청구서가 누락됨.
  • Unissued 상태로 넘어가지 않고 Origin 만 쌓임.

A.2 확인

# 1. 워커 파드 최근 로그 — 실행 시작/실패 스택트레이스가 여기만 남습니다
kubectl logs -n cone-watcher -l app.kubernetes.io/component=worker --tail=500 | grep -iE 'invoice|error|traceback'

# 2. 배치 시각(매월 5일 00:00 KST = 4일 15:00 UTC) 전후 로그만 집중적으로
kubectl logs -n cone-watcher -l app.kubernetes.io/component=worker --since=24h | grep -i 'nhn_invoice_task'
// mongosh — 결과물 기준으로 배치가 어디까지 돌았는지 역추적
// 해당 월의 상태별 청구서 개수
db.nhn_invoice.aggregate([
  { $match: { date: '2026-04' } },
  { $group: { _id: { company_id: '$company_id', status: '$status' }, n: { $sum: 1 } } }
]);

// 최근 NHN API 에러 (배치 실패의 가장 흔한 원인)
db.error_log.find({
  source: 'nhn_api',
  created_at: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
}).sort({ created_at: -1 }).limit(20);

A.3 원인 분류

원인확인 포인트
NHN API 호출 실패error_log.source == 'nhn_api' 다수. B 섹션으로 이동.
계약 미등록/만료해당 조직의 contract 문서 존재 여부, 기간
계정 연동 만료nhn_accountcloud_type, 키 자격증명 유효성
프로젝트 목록 미동기화update_nhn_project_list_task 최근 실행 여부
워커 OOM/타임아웃파드 재시작 이력 (kubectl describe pod), AgeLimit 1시간 초과

A.4 조치

특정 조직만 재실행

  1. 해당 조직의 월별 청구서를 삭제(DB 접근 가이드 §5.2 참조).
  2. nhn_invoice_task.send(company_id='...', organization_id='...', month='YYYY-MM') 로 재생성.

전체 회사 재실행

  1. nhn_invoice 에서 해당 월 상태 분포 확인(§A.2 의 aggregate 쿼리).
  2. Origin 만 있고 Unissued 복사가 실패한 경우: 다음 호출로 복사 단계부터 다시 실행됩니다.
    nhn_invoice_task.send(month='YYYY-MM')
  3. 고객에게 이미 발행된 청구서가 있다면 섣불리 재실행하지 말고 단건 씩 처리.

워커 파드 재기동

kubectl -n cone-watcher rollout restart deployment/prd-cone-watcher-worker

A.5 재발 방지

  • 매월 5~6일 배치 성공 확인 을 체크리스트로 운영합니다. worker_task 가 비어있으므로 결과물 + 로그 두 가지로 검증:
    # (1) 배치 시각(UTC 4일 15:00 = KST 5일 00:00) 전후 워커 로그에 완료 라인이 있는지
    kubectl logs -n cone-watcher -l app.kubernetes.io/component=worker --since=36h \
      | grep -iE 'nhn_invoice_task|completed|finished|error'
    // (2) 대상 월에 실제로 생성된 청구서 수 — 회사별·상태별 분포
    db.nhn_invoice.aggregate([
      { $match: { date: '2026-04' } },
      { $group: { _id: { company_id: '$company_id', status: '$status' }, n: { $sum: 1 } } }
    ]);
  • 특정 고객사에서 반복 실패한다면 해당 nhn_account 자격증명을 우선 점검합니다(§B).

B. NHN API 연동 오류

관련 모듈: common/client/nhn/reseller.py

B.1 증상

  • 특정 NHN 계정 하나만 사용량이 수집되지 않음.
  • 프로젝트 목록 동기화가 계속 실패.
  • 청구서 Origin 생성이 안 됨.

B.2 확인

// 최근 NHN API 에러 로그
db.error_log.find({ source: 'nhn_api' })
  .sort({ created_at: -1 }).limit(30);

// 특정 계정 기준
db.error_log.find({
  source: 'nhn_api',
  'context.account_uid': '<ACCOUNT_UID>'
}).sort({ created_at: -1 });

// 해당 계정의 cloud_type / email
db.nhn_account.findOne({ uid: '<ACCOUNT_UID>' });

워커 로그에서 호출 경로와 응답 코드를 직접 확인할 수도 있습니다.

kubectl logs -n cone-watcher -l app.kubernetes.io/component=worker --tail=1000 | grep -i 'nhn\|reseller'

B.3 원인 분류

증상/메시지원인조치 방향
401 Unauthorized, Invalid signatureHMAC 서명 실패 — 시스템 키 불일치.envNHN__HMAC_SECRET / NHN__ACCESS_KEY / NHN__SECRET_KEY (또는 NHN_GOV__*) 확인
403 Forbidden리셀러 권한 회수 / 대상 조직 권한 없음NHN 측에 리셀러 권한 재확인 요청
404 Not Found on projects이메일/조직 구조 변경get_projects_by_email 요청의 email 값 확인
타임아웃 (60s)NHN 측 지연 또는 네트워크재호출 — 일시적이면 수동 재실행
NHN API request failed: ... 로그네트워크/DNS/ingress egress 문제클러스터 → 외부 통신 가능 여부 점검

B.4 조치

  1. 자격증명 문제: prd-env 시크릿 값 교체 후 워커·스케줄러·API 파드 재기동.
    kubectl -n cone-watcher rollout restart \
      deploy/prd-cone-watcher-worker \
      deploy/prd-cone-watcher-scheduler \
      deploy/prd-cone-watcher-api
  2. 일시적 장애: 수동 재실행.
    update_nhn_project_list_task.send()       # 전체 동기화
    nhn_invoice_task.send(month='YYYY-MM')    # 청구서 재생성
  3. 공공 환경 계정: nhn_account.cloud_type == 'GOV' 이면 NHN_GOV__* 키로 호출합니다. 키 쌍이 섞이지 않았는지 확인.

B.5 재발 방지

  • NHN API 키에 만료일이 있으면 만료 30일 전 알람 을 별도 캘린더에 등록.
  • 주요 실패 URL은 error_log.context 에 남으므로, 주 1회 상위 에러 패턴을 확인하는 운영 루틴을 둡니다.

C. 워커 / 스케줄러 정체

관련 컴포넌트: Dramatiq (Worker), APScheduler (Scheduler), Redis(Valkey) 브로커

C.1 증상

  • 스케줄 시각이 지났는데 작업이 실행되지 않음.
  • 작업을 enqueue 했는데 워커 파드 로그에 아무 실행 흔적이 없음 (소비되지 않은 것).
  • Origin 청구서는 생성됐는데 Unissued 로 넘어가지 않고 멈춤.

C.2 확인

# 스케줄러 파드 로그 — cron 시각에 send 로그가 찍히는지
kubectl logs -n cone-watcher -l app.kubernetes.io/component=scheduler --tail=200

# 워커 파드 상태
kubectl get pods -n cone-watcher -l app.kubernetes.io/component=worker
kubectl logs -n cone-watcher -l app.kubernetes.io/component=worker --tail=200

# Valkey(Redis) 파드 상태 — 별도 네임스페이스
kubectl get pods -n valkey

큐에 쌓인 메시지를 확인하려면 Valkey에 직접 붙어서:

# Valkey Service FQDN: valkey.valkey.svc.cluster.local:6379
kubectl -n valkey port-forward svc/valkey 6379:6379

# 비밀번호는 helm/valkey/overlay.yaml 의 aclUsers.default.password (운영 자격증명은 시크릿 참조)
redis-cli -a <REDIS_PASSWORD>
> KEYS hk-prd:*
> LLEN hk-prd:HK-COMMON

참고: 여기서 hk-prdK8s 네임스페이스가 아니라 Redis key prefix 입니다. 애플리케이션은 APP_ENV 값으로 hk-{env} 를 만들어 모든 dramatiq 관련 키 앞에 붙입니다(config/__init__.py:redis_namespace). K8s 네임스페이스는 항상 valkey 입니다. 큐 이름은 HK-COMMON 상수(config.COMMON_QUEUE_NAME).

C.3 원인 분류

원인확인
워커 파드 다운kubectl get pods, CrashLoopBackOff / Pending 상태
스케줄러 타임존 오해스케줄러는 UTC 로 동작. 크론 값을 KST로 오해하지 않았는지
Redis 연결 실패워커 로그에 Failed to initialize redis broker
메시지 만료AgeLimit(1시간) — enqueue 후 1시간 안에 소비되지 않으면 버려집니다
동일 작업 중복 실행Dramatiq는 중복 enqueue를 막지 않음. 긴 작업은 락 없이 두 번 돌 수 있음

C.4 조치

큐가 계속 쌓일 때 (워커가 느린 경우)

  1. 워커 파드 수 증설.
    kubectl -n cone-watcher scale deploy/prd-cone-watcher-worker --replicas=<N>
  2. 긴 작업(예: 청구서 배치)은 company_id 단위로 쪼개 수동 enqueue.

스케줄러가 발사하지 않을 때

  1. 스케줄러 파드 로그에서 예외 스택 확인.
  2. .env.scheduler 의 cron 표현식이 UTC 기준임을 확인 (한국시각 09:00 → UTC 00:00).
  3. 파드 재기동.
    kubectl -n cone-watcher rollout restart deploy/prd-cone-watcher-scheduler

워커가 멈춘 것처럼 보일 때

  1. AgeLimit 초과로 조용히 드롭됐을 가능성 — 워커 로그에 실행 시작 라인이 전혀 없으면 이 케이스.
  2. 수동 재enqueue. 큐가 비어있는지 먼저 확인(LLEN hk-prd:HK-COMMON).

C.5 재발 방지

  • 스케줄 시각 전후 15분 내 워커 파드 로그에 해당 태스크 실행 라인이 찍히는지 정기 점검 (kubectl logs -l app.kubernetes.io/component=worker --since=30m).
  • 워커 파드 메모리 상한이 충분한지 확인 (청구서 배치는 데이터 양에 따라 순간 메모리가 큽니다).
  • Redis가 영속(persistence) 설정인지 확인 — 재기동 시 큐가 날아가면 스케줄 손실로 이어집니다.

D. DB / 서버 이상

대상: MongoDB (PSMDB), 애플리케이션 파드

D.1 증상

  • API 응답이 전면적으로 느려지거나 500 에러.
  • 특정 API만 타임아웃 (주로 통계/목록 조회).
  • 디스크 사용량 알람.

D.2 확인

# 파드 상태 — 세 네임스페이스 모두 확인 필요
kubectl get pods -n cone-watcher
kubectl get pods -n mongodb
kubectl get pods -n valkey

# 리소스 사용량 (metrics-server 필요)
kubectl top pods -n cone-watcher
kubectl top pods -n mongodb
kubectl top nodes

# API 로그에서 DB 에러
kubectl logs -n cone-watcher -l app.kubernetes.io/component=api --tail=500 | grep -i 'mongo\|timeout\|connection'

MongoDB에 직접 붙어서:

// 현재 실행 중인 쿼리
db.currentOp({ active: true, secs_running: { $gt: 2 } });

// 컬렉션별 디스크 사용량
db.stats();
db.nhn_invoice.stats({ scale: 1024 * 1024 });  // MB 단위

// 느린 쿼리 프로파일링 (주의: 운영 중 켜면 부하 증가)
db.setProfilingLevel(1, { slowms: 200 });
db.system.profile.find().sort({ ts: -1 }).limit(20);
db.setProfilingLevel(0);  // 반드시 끄기

D.3 원인 분류

원인증상확인 방법
MongoDB 연결 풀 고갈API 전반 느림, serverSelectionTimeoutMS 초과API 로그 다수 timeout, db.currentOp 다수 대기
인덱스 누락특정 API만 느림.explain('executionStats')COLLSCAN
큰 컬렉션 (예: activity_log)DB stats 성장db.collection.stats()
디스크 Full모든 쓰기 실패kubectl describe pod 의 디스크 압박 이벤트, 노드 디스크 사용량
OOM파드 재시작 루프kubectl describe podOOMKilled

D.4 조치

연결 고갈

  • API 파드 재기동으로 풀을 리셋. 임시 조치이며 근본 원인은 쿼리 튜닝.
    kubectl -n cone-watcher rollout restart deploy/prd-cone-watcher-api
  • 연결 풀 크기는 클라이언트 설정: minPoolSize=10, maxPoolSize=100 (common/client/database/mongo.py).

인덱스 이슈

  • common/database/<collection>_db.pyadditional_indexes 에 필요한 인덱스가 있는지 확인.
  • 애플리케이션 시작 시 자동으로 인덱스를 생성하므로, 신규 인덱스를 넣은 코드 배포 후에는 최초 기동 시 생성 시간이 길 수 있습니다.

디스크 관리

  • error_log 는 TTL 90일, invoice_deliveries 는 365일로 자동 정리.
  • activity_log 는 TTL이 없으므로 장기적으로 가장 빨리 자람. 필요 시 기간 잘라 deleteMany 또는 아카이브.

디스크가 Full이면

  1. 쓰기 중단을 먼저 확인: API/워커가 쓰기 실패로 에러를 뱉고 있다면 우선 긴급 공간 확보.
  2. PVC 확장이 가능하면 StorageClass의 allowVolumeExpansion=true 를 이용해 확장.
  3. 임시로 오래된 로그 컬렉션 정리:
    db.activity_log.deleteMany({ created_at: { $lt: new Date('2025-01-01') } });
  4. 여유를 확보한 뒤, 근본적인 아카이브 전략 수립 (§D.5).

OOM

  • 워커 파드의 resources.limits.memory 확인.
  • 대용량 조직/월은 청구서 배치를 조직 단위로 쪼개 호출.

D.5 재발 방지

  • 주간 점검: db.stats(), 컬렉션별 stats(), 노드 디스크 사용률.
  • 월간 점검: 쿼리 프로파일링을 짧게 켰다가 끄고, COLLSCAN 이 잡히는 API를 정리.
  • 분기 점검: activity_log 등 TTL이 없는 컬렉션 증가 추이. 필요 시 아카이브 / 삭제 정책 수립.

공통: 수동 태스크 실행 방법

권장 — scripts/enqueue_task.py

scripts/enqueue_task.py 가 dry-run, 인자 검증, 중복 실행 경고를 포함한 래퍼입니다. 자세한 사전 전제(Valkey 포트포워딩 · APP_ENV · REDIS_*)는 scripts/README.md 참조.

# 가능한 태스크 목록
python -m scripts.enqueue_task --list

# Dry-run
python -m scripts.enqueue_task nhn_invoice --month 2026-04 --company <COMPANY_UID>

# 실제 enqueue
python -m scripts.enqueue_task nhn_invoice --month 2026-04 --company <COMPANY_UID> --execute

워커 파드 내부에서 실행하면 포트포워딩·환경변수 세팅이 불필요합니다.

WORKER_POD=$(kubectl -n cone-watcher get pod -l app.kubernetes.io/component=worker -o jsonpath='{.items[0].metadata.name}')
kubectl -n cone-watcher exec -it "$WORKER_POD" -- python -m scripts.enqueue_task nhn_invoice --month 2026-04 --execute

대안 — 워커 쉘 직접 접속

스크립트가 다루지 않는 태스크이거나 복잡한 조합이 필요할 때만 사용.

WORKER_POD=$(kubectl -n cone-watcher get pod -l app.kubernetes.io/component=worker -o jsonpath='{.items[0].metadata.name}')
kubectl -n cone-watcher exec -it "$WORKER_POD" -- python
from worker import (
    nhn_invoice_task,
    update_nhn_project_list_task,
    contract_expiration_task,
    auto_suspend_expired_organizations,
    partner_usage_task,
)

# 예: 특정 회사의 2026-04 청구서 재생성
nhn_invoice_task.send(company_id='<COMPANY_UID>', month='2026-04')

주의: 같은 월·회사에 이미 작업이 돌고 있다면 중복 실행이 됩니다. 재실행 전 워커 파드 로그에서 해당 태스크가 아직 진행 중인지(kubectl logs -n cone-watcher -l app.kubernetes.io/component=worker --since=1h | grep <TASK_NAME>) 반드시 확인하세요. enqueue_task.py 는 dry-run 단계에서 이 확인 커맨드를 안내합니다.


에스컬레이션 순서

  1. 운영 담당자 1차 대응 (이 문서 범위).
  2. 30분 내 해결되지 않으면 백엔드 개발 담당자 호출.
  3. NHN Cloud 측 문제(키 만료, API 장애)로 확정되면 NHN 리셀러 창구로 이관.
  4. 데이터 손상·유실 가능성이 있으면 즉시 쓰기 작업 중단백업 및 복구 의 복구 절차로.