본문으로 건너뛰기

11.2 트러블슈팅

문제가 생겼을 때 무엇을 어떤 순서로 볼 것인가. CloudNativePG 트러블슈팅은 두 층을 오간다. 하나는 Kubernetes 층 — Pod가 스케줄되었는가, 볼륨이 붙었는가, 이벤트에 무슨 경고가 있는가. 다른 하나는 PostgreSQL 층 — postgres가 떴는가, 로그에 FATAL이 있는가, 복제가 따라오는가. 증상만 보고 한쪽만 파면 원인을 놓친다. 이 절은 정보를 모으는 표준 절차부터 시작해, 자주 보는 문제를 증상·원인·대응으로 정리한다.

진단 분기

    flowchart TD
  START["이상 감지"] --> ST["cnpg status 확인"]
  ST --> Q{"어느 층?"}
  Q -- "Pod Pending·미스케줄" --> K8S["Kubernetes 층<br/>describe·events"]
  Q -- "Pod는 뜸, postgres 이상" --> PG["PostgreSQL 층<br/>logs 확인"]
  Q -- "Operator 자체 이상" --> OP["cnpg-system<br/>controller 로그"]
  K8S --> FIX["원인별 대응"]
  PG --> FIX
  OP --> FIX

  classDef node fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
  class START,ST,Q,K8S,PG,OP,FIX node
  

1단계: 정보 수집

가장 먼저 클러스터 상태를 본다. 대부분의 문제는 여기서 방향이 잡힌다.

kubectl cnpg status -n <NS> <CLUSTER>

상태만으로 부족하면 Cluster 리소스와 Pod를 직접 들여다본다.

kubectl get cluster -n <NS> <CLUSTER>
kubectl describe cluster <CLUSTER> -n <NS>

# 인스턴스별 역할 확인
kubectl get pod -l cnpg.io/cluster=<CLUSTER> -L role -n <NS>

Operator 자체가 의심되면 cnpg-system 네임스페이스의 컨트롤러를 본다.

kubectl get pods -n cnpg-system
kubectl logs -n cnpg-system deployment/cnpg-controller-manager --all-containers=true

지원 요청까지 갈 상황이면 kubectl cnpg report로 자료를 한 번에 묶는다(11.1).

2단계: 로그 읽기

Pod 로그는 JSON lines다. 그냥 보면 잡음이 많으므로 jq로 PostgreSQL 메시지만 걸러 낸다.

# postgres 로거 메시지만
kubectl logs -n <NS> <CLUSTER>-<N> | \
  jq 'select(.logger=="postgres") | .record.message'

# FATAL만
kubectl logs -n <NS> <CLUSTER>-<N> | \
  jq -r '.record | select(.error_severity == "FATAL")'

# 직전 크래시 로그
kubectl logs -n <NS> --previous <CLUSTER>-<N>

--previous는 Pod가 재시작을 반복(CrashLoopBackOff)할 때 특히 중요하다. 지금 뜬 컨테이너가 아니라 죽기 직전 컨테이너의 로그를 봐야 원인이 나온다.

필수 도구 세 가지를 미리 갖춰 둔다 — cnpg 플러그인, jq(JSON 로그 파싱), grep. 그리고 문제 진단에 들어가기 전에 반드시 백업 상태를 먼저 확인한다. PVC 삭제 같은 파괴적 조치를 취하기 전에 복구 지점이 살아 있는지 아는 것이 순서다.

흔한 문제

Pod가 Pending에서 멈춤

| 증상 | Pod가 Pending으로 뜨지 않고 대기 | | 원인 | nodeSelector 불일치, toleration 누락, 노드 부족, autoscaler 한계 |

대응: 이벤트에서 스케줄 실패 사유를 읽는다.

kubectl describe pod -n <NS> <POD>
kubectl get events -n <NS> --sort-by=.metadata.creationTimestamp

FailedScheduling 메시지에 원인이 그대로 적힌다. nodeSelector·taint 설정(Part V)과 노드 여유 자원을 대조한다.

스토리지 가득 참

| 증상 | Pod가 쓰기 실패, disk-full 메시지로 crash-loop | | 원인 | PVC 용량 소진 |

대응: PVC의 spec.resources.requests.storage를 늘린 뒤, Cluster 리소스의 storage 크기도 같은 값으로 맞춘다. storage class가 volume expansion을 지원해야 한다.

replica가 동기화 안 됨 (백업 미구성)

| 증상 | 유지보수 후 replica 재시작 시 따라오지 못함 | | 원인 | primary에 이미 재활용된 WAL을 replica가 요구 |

대응: 해당 replica의 PVC를 지우면 Operator가 primary에서 새로 base backup을 받아 재구성한다.

PODNAME=<POD>
VOLNAME=$(kubectl get pv -o json | \
  jq -r '.items[]|select(.spec.claimRef.name=='\"$PODNAME\"')|.metadata.name')
kubectl delete pod/$PODNAME pvc/$PODNAME pvc/$PODNAME-wal pv/$VOLNAME
이 조치는 primary가 아니라 replica에만 한다. primary의 PVC를 지우면 데이터가 사라진다. 삭제 전 cnpg status로 대상 Pod가 replica인지 반드시 확인한다. object store 백업이 구성돼 있으면 애초에 이 문제가 잘 생기지 않는다.

Pod 간 통신 실패 (NetworkPolicy)

| 증상 | Operator 로그에 “Cannot extract Pod status” 또는 “i/o timeout” | | 원인 | NetworkPolicy가 상태 포트(보통 8000)를 막음 |

대응: NetworkPolicy를 점검해 Operator와 Pod 사이 포트를 연다.

kubectl get networkpolicies -n <NS>

“Creating new replica"에서 오래 멈추거나 “HTTP communication issue"가 보이는 것도 대개 같은 네트워크 원인이다.

hugepages 초기화 오류

| 증상 | 초기화 중 “Bus error (core dumped) … exit code 135” | | 원인 | hugepages 지원이 불완전 |

대응: 리소스에 hugepages 한계를 명시한다.

resources:
  limits:
    hugepages-2Mi: "512Mi"

failover 후 standby 재접속이 느림

| 증상 | primary 전환 뒤 standby가 2분 이상 재접속 못 함 | | 원인 | kube-proxy 경로 갱신 지연 + 커널 TCP 재시도 backoff |

대응: Operator 설정에서 STANDBY_TCP_USER_TIMEOUT을 조정해 죽은 연결을 더 빨리 끊게 한다.

백업·아카이빙 확인

백업 상태와 WAL archiving은 조건(condition)으로 확인한다.

kubectl get backup -l cnpg.io/cluster=<CLUSTER>

kubectl wait --for=condition=LastBackupSucceeded cluster/<CLUSTER> -n <NS>
kubectl wait --for=condition=ContinuousArchiving cluster/<CLUSTER> -n <NS>

세 가지 핵심 조건을 기억해 둔다.

조건의미
Ready클러스터 초기화 완료, primary 준비됨
ContinuousArchivingWAL archiving 정상 동작
LastBackupSucceeded마지막 백업 성공 여부

비상 시 논리 백업·복구

물리 백업 경로가 막힌 급박한 상황에서는 Pod 안에서 직접 pg_dump·pg_restore를 돌릴 수 있다.

# 논리 백업
kubectl exec cluster-example-1 -c postgres -- \
  pg_dump -Fc -d app > app.dump

# 새 클러스터로 복구
kubectl exec -i new-cluster-example-1 -c postgres -- \
  pg_restore --no-owner --role=app -d app --verbose < app.dump

이것은 어디까지나 임시 수단이다. 정규 복구는 Part VII의 object store·PITR 경로를 따른다.

정리

  • 진단 순서 = cnpg status → Kubernetes 층(describe·events) 또는 PostgreSQL 층(logs)
  • 로그는 JSON — jq로 postgres·FATAL만 걸러 읽고, crash-loop은 --previous
  • Pending은 스케줄링, disk-full은 PVC 확장, replica 깨짐은 PVC 재구성(primary 금지)
  • 통신 오류는 NetworkPolicy·포트 8000을 의심
  • 파괴적 조치 전 백업 상태(Ready·ContinuousArchiving·LastBackupSucceeded)부터 확인

다음 절(11.3)에서는 문제 인스턴스를 데이터 변경 없이 안전하게 들여다보는 fencing을 본다.