트러블슈팅
트러블슈팅 (Troubleshooting)
이 페이지에서는 쿠버네티스 클러스터 배포에서 CloudNativePG를 트러블슈팅하는 방법에 대한 몇 가지 기본 정보를 찾을 수 있어요.
:::tip[Hint]
쿠버네티스 관리자라면 kubectl Cheat Sheet 페이지를 북마크해 두세요!
:::
출처: 문서
본문
시작하기 전에
쿠버네티스 환경
트러블슈팅 활동에서 차이를 만들 수 있는 것은 기본 쿠버네티스 시스템에 대한 명확한 정보를 제공하는 거예요.
다음을 알고 있는지 확인하세요:
- 사용 중인 쿠버네티스 배포판과 버전
- PostgreSQL이 실행 중인 노드의 사양
- 프로덕션에 들어가기 전에 수행한 스토리지 클래스와 벤치마크를 포함한 실제 스토리지에 대해 할 수 있는 한 많이
- 클러스터에서 사용 중인 관련 쿠버네티스 애플리케이션 (즉 Prometheus, Grafana, Istio, Certmanager, ...)
- 연속 백업의 상황, 특히 제자리에 있고 올바르게 동작하는지: 아니라면 잠재적으로 파괴적인 작업을 수행하기 전에 반드시 비상 백업을 수행하세요
유용한 유틸리티
필수 kubectl 유틸리티 위에 트러블슈팅을 위해 다음 플러그인/유틸리티를 시스템에 사용할 수 있기를 권장해요:
kubectl용cnpg플러그인jq, 가볍고 유연한 커맨드라인 JSON 프로세서grep, 하나 이상의 입력 파일에서 지정된 패턴과 일치하는 줄을 검색해요. 대부분의 *nix 배포판에서 이미 사용 가능해요. Windows OS를 사용한다면grep의 대안으로findstr를 사용하거나 직접wsl을 사용해 선호하는 *nix 배포판을 설치하고 위에 언급된 도구들을 사용할 수 있어요.
첫 단계 (First steps)
클러스터나 설치에 대한 개요를 빠르게 얻으려면 kubectl 플러그인이 주요 도구예요:
- status 하위명령은 클러스터의 개요를 제공해요
- report 하위명령은 클러스터와 오퍼레이터 배포의 매니페스트를 제공해요.
--logs옵션으로 로그도 포함할 수 있어요. 플러그인으로 생성된 report는 전체 클러스터 매니페스트를 포함할 거예요.
플러그인은 패키지를 통해 에어갭(air-gapped) 시스템에도 설치할 수 있어요. 완전한 지침은 플러그인 문서를 참조하세요.
백업이 있나요?
플러그인으로 클러스터 매니페스트를 얻은 후 백업이 설정되어 있고 동작하는지 확인해야 해요.
트러블슈팅 작업을 진행하기 전에 백업에 대한 조사 결과에 따라 비상 백업을 수행하는 것이 좋을 수 있어요. 지침은 다음 섹션을 참조하세요.
정기적 백업을 유지하지 않고 프로덕션 데이터베이스를 운영하는 것은 극도로 위험해요.
비상 백업 (Emergency backup)
어떤 비상 상황에서는 메인 app 데이터베이스의 비상 논리 백업을 가져와야 할 수 있어요.
:::info[Important]
아래의 지침은 비상 상황에서만 실행해야 하며 임시 백업 파일은 조직에서 유효한 데이터 보호 정책 하에 보관해야 해요. 덤프 파일은 실제로 kubectl 명령을 실행하는 클라이언트 머신에 저장되므로, 모든 보호 조치가 마련되어 있고 백업 파일을 저장할 충분한 공간이 있는지 확인하세요.
:::
다음 예시는 cluster-example Postgres 클러스터의 app 데이터베이스 논리 백업을 cluster-example-1 Pod에서 가져오는 방법을 보여줘요:
kubectl exec cluster-example-1 -c postgres \
-- pg_dump -Fc -d app > app.dump
:::note 환경에서 사용한 객체 이름을 제공함으로써 위 명령을 자신의 클러스터 백업에 쉽게 적용할 수 있어요. :::
위 명령은 커스텀 형식으로 pg_dump 명령을 실행하며, 이것은 PostgreSQL에서 논리 백업을 가져오는 가장 다재다능한 방법이에요.
다음 단계는 데이터베이스를 복원하는 거예요. 방금 초기화된 새 PostgreSQL 클러스터에서 작업하고 있다고 가정해요 (app 데이터베이스가 비어 있음).
다음 예시는 프라이머리(new-cluster-example-1 pod)에 연결해 new-cluster-example Postgres 클러스터의 app 데이터베이스에 위 논리 백업을 복원하는 방법을 보여줘요:
kubectl exec -i new-cluster-example-1 -c postgres \
-- pg_restore --no-owner --role=app -d app --verbose < app.dump
:::info[Important]
이 섹션의 예시는 우리의 권장 사항대로 덤프하고 복원할 다른 전역 객체(데이터베이스와 역할)가 없다고 가정해요. 여러 역할이 있다면 pg_dumpall -g로 백업을 가져와 새 클러스터에서 수동으로 복원했는지 확인하세요. 여러 데이터베이스가 있다면 올바른 소유권을 할당하면서 한 번에 하나의 데이터베이스씩 위 작업을 반복해야 해요. PostgreSQL에 익숙하지 않다면 전문 지원 회사의 지도 하에 이러한 중요 작업을 수행할 것을 조언해요.
:::
위 단계는 언젠가 cnpg 플러그인에 통합될 수 있어요.
로그 (Logs)
CloudNativePG가 생성하고 관리하는 모든 리소스는 JSON 형식을 사용해 쿠버네티스 규칙에 따라 표준 출력에 로그를 기록해요.
로그는 일반적으로 CloudNativePG의 로그를 포함해 인프라 수준에서 처리되지만, 트러블슈팅 중에는 커맨드라인 인터페이스에서 직접 로그에 접근하는 것이 중요해요. 이를 위한 세 가지 주요 옵션이 있어요:
kubectl logs명령으로 특정 리소스의 로그를 가져오고, 더 나은 가독성을 위해jq를 적용해요.- CloudNativePG 전용 로깅에
kubectl cnpg logs명령을 사용해요. - stern 같은 전문 오픈소스 도구를 활용해요. 이 도구는 여러 리소스의 로그를(예:
cnpg.io/clusterName레이블을 선택해 PostgreSQL 클러스터의 모든 pod) 집계하고, 로그 항목을 필터링하며, 출력 형식을 커스터마이즈하는 등의 기능을 해요.
:::note 다음 섹션들은 CloudNativePG를 트러블슈팅할 때 다양한 리소스의 로그를 검색하는 방법의 예시를 제공해요. :::
오퍼레이터 정보 (Operator information)
기본적으로 CloudNativePG 오퍼레이터는 쿠버네티스의 cnpg-system 네임스페이스에 Deployment로 설치돼요 (자세한 내용은 "Details about the deployment" 섹션 참조).
다음을 실행해 오퍼레이터 Pod 목록을 얻을 수 있어요:
kubectl get pods -n cnpg-system
:::note
정상적인 상황에서는 cnpg-controller-manager-로 시작하는 이름으로 식별되는, 오퍼레이터가 실행 중인 Pod 하나를 가져야 해요. 고가용성을 위해 오퍼레이터를 설정했다면 더 많은 항목을 가져야 해요. 이 Pod들은 cnpg-controller-manager라는 deployment가 관리해요.
:::
<POD> Pod에서 실행 중인 오퍼레이터에 대한 관련 정보를 다음과 같이 수집하세요:
kubectl describe pod -n cnpg-system <POD>
그런 다음 같은 Pod의 로그를 다음과 같이 가져오세요:
kubectl logs -n cnpg-system <POD>
오퍼레이터에 대한 더 많은 정보 수집
CloudNativePG 오퍼레이터 Deployment의 모든 Pod에서 로그를 다음과 같이 가져오세요 (멀티 오퍼레이터 배포인 경우):
kubectl logs -n cnpg-system \
deployment/cnpg-controller-manager --all-containers=true
:::tip
위 명령에 -f 플래그를 추가해 실시간으로 로그를 따라갈 수 있어요.
:::
로그를 JSON 파일로 저장하려면:
kubectl logs -n cnpg-system \
deployment/cnpg-controller-manager --all-containers=true | \
jq -r . > cnpg_logs.json
kubectl-cnpg 플러그인으로 CloudNativePG 오퍼레이터 버전을 얻으세요:
kubectl-cnpg status <CLUSTER>
출력:
Cluster in healthy state
Name: cluster-example
Namespace: default
System ID: 7044925089871458324
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:18.6-system-trixie
Primary instance: cluster-example-1
Instances: 3
Ready instances: 3
Current Write LSN: 0/5000000 (Timeline: 1 - WAL File: 000000010000000000000004)
Continuous Backup status
Not configured
Streaming Replication status
Name Sent LSN Write LSN Flush LSN Replay LSN Write Lag Flush Lag Replay Lag State Sync State Sync Priority
---- -------- --------- --------- ---------- --------- --------- ---------- ----- ---------- -------------
cluster-example-2 0/5000000 0/5000000 0/5000000 0/5000000 00:00:00 00:00:00 00:00:00 streaming async 0
cluster-example-3 0/5000000 0/5000000 0/5000000 0/5000000 00:00:00.10033 00:00:00.10033 00:00:00.10033 streaming async 0
Instances status
Name Database Size Current LSN Replication role Status QoS Manager Version
---- ------------- ----------- ---------------- ------ --- ---------------
cluster-example-1 33 MB 0/5000000 Primary OK BestEffort 1.12.0
cluster-example-2 33 MB 0/5000000 Standby (async) OK BestEffort 1.12.0
cluster-example-3 33 MB 0/5000060 Standby (async) OK BestEffort 1.12.0
클러스터 정보 (Cluster information)
NAMESPACE 네임스페이스에서 <CLUSTER> 클러스터의 상태를 다음과 같이 확인할 수 있어요:
kubectl get cluster -n <NAMESPACE> <CLUSTER>
출력:
NAME AGE INSTANCES READY STATUS PRIMARY
<CLUSTER> 10d4h3m 3 3 Cluster in healthy state <CLUSTER>-1
위 예시는 3개의 인스턴스를 가진 정상 PostgreSQL 클러스터를 보고하며, 모두 ready 상태이고 <CLUSTER>-1이 프라이머리예요.
비정상 조건의 경우 Cluster 리소스의 매니페스트를 가져와 더 자세히 알 수 있어요:
kubectl get cluster -o yaml -n <NAMESPACE> <CLUSTER>
수집할 또 다른 중요한 명령은 cnpg 플러그인이 제공하는 status 명령이에요:
kubectl cnpg status -n <NAMESPACE> <CLUSTER>
:::tip
--verbose 옵션을 추가해 더 많은 정보를 출력할 수 있어요.
:::
PostgreSQL 컨테이너 이미지 버전을 얻으세요:
kubectl describe cluster <CLUSTER_NAME> -n <NAMESPACE> | grep "Image Name"
출력:
Image Name: ghcr.io/cloudnative-pg/postgresql:18.6-system-trixie
:::note
같은 정보를 얻기 위해 kubectl-cnpg status -n <NAMESPACE> <CLUSTER_NAME>도 사용할 수 있어요.
:::
Pod 정보 (Pod information)
주어진 PostgreSQL 클러스터에 속한 인스턴스 목록을 다음과 같이 검색할 수 있어요:
kubectl get pod -l cnpg.io/cluster=<CLUSTER> -L role -n <NAMESPACE>
출력:
NAME READY STATUS RESTARTS AGE ROLE
<CLUSTER>-1 1/1 Running 0 10d4h5m primary
<CLUSTER>-2 1/1 Running 0 10d4h4m replica
<CLUSTER>-3 1/1 Running 0 10d4h4m replica
Pod가 어떻게/실패했는지 다음과 같이 확인할 수 있어요:
kubectl get pod -n <NAMESPACE> -o yaml <CLUSTER>-<N>
주어진 PostgreSQL 인스턴스의 모든 로그를 다음과 같이 얻을 수 있어요:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N>
검색을 PostgreSQL 프로세스로만 제한하고 싶다면 다음과 같이 실행할 수 있어요:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N> | \
jq 'select(.logger=="postgres") | .record.message'
다음 예시는 타임스탬프도 추가해요:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N> | \
jq -r 'select(.logger=="postgres") | [.ts, .record.message] | @csv'
타임스탬프가 Unix Epoch 시간으로 표시된다면 사용자 친화적인 형식으로 변환할 수 있어요:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N> | \
jq -r 'select(.logger=="postgres") | [(.ts|strflocaltime("%Y-%m-%dT%H:%M:%S %Z")), .record.message] | @csv'
PostgreSQL Pod에 대한 추가 정보 수집 및 필터링
크래시한 특정 Pod의 로그 확인:
kubectl logs -n <NAMESPACE> --previous <CLUSTER>-<N>
특정 PostgreSQL Pod에서 FATAL 오류 얻기:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N> | \
jq -r '.record | select(.error_severity == "FATAL")'
출력:
{
"log_time": "2021-11-08 14:07:44.520 UTC",
"user_name": "streaming_replica",
"process_id": "68",
"connection_from": "10.244.0.10:60616",
"session_id": "61892f30.44",
"session_line_num": "1",
"command_tag": "startup",
"session_start_time": "2021-11-08 14:07:44 UTC",
"virtual_transaction_id": "3/75",
"transaction_id": "0",
"error_severity": "FATAL",
"sql_state_code": "28000",
"message": "role \"streaming_replica\" does not exist",
"backend_type": "walsender"
}
특정 Pod 로그에서 PostgreSQL DB 오류 메시지를 필터링:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N> | jq -r '.err | select(. != null)'
출력:
dial unix /controller/run/.s.PGSQL.5432: connect: no such file or directory
특정 Pod에서 err 단어와 일치하는 메시지 얻기:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N> | jq -r '.msg' | grep "err"
출력:
2021-11-08 14:07:39.610 UTC [15] LOG: ending log output to stderr
특정 Pod의 PostgreSQL 프로세스에서 모든 로그 얻기:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N> | \
jq -r '. | select(.logger == "postgres") | select(.msg != "record") | .msg'
출력:
2021-11-08 14:07:52.591 UTC [16] LOG: redirecting log output to logging collector process
2021-11-08 14:07:52.591 UTC [16] HINT: Future log output will appear in directory "/controller/log".
2021-11-08 14:07:52.591 UTC [16] LOG: ending log output to stderr
2021-11-08 14:07:52.591 UTC [16] HINT: Future log output will go to log destination "csvlog".
필드와 값으로 필터링한 Pod 로그를 |로 연결해 다음과 같이 얻으세요:
kubectl logs -n <NAMESPACE> <CLUSTER>-<N> | \
jq -r '[.level, .ts, .logger, .msg] | join(" | ")'
출력:
info | 1636380469.5728037 | wal-archive | Backup not configured, skip WAL archiving
info | 1636383566.0664876 | postgres | record
백업 정보 (Backup information)
명명된 클러스터에 대해 생성된 백업을 다음과 같이 나열할 수 있어요:
kubectl get backup -l cnpg.io/cluster=<CLUSTER>
스토리지 정보 (Storage information)
때때로 조사나 트러블슈팅 중에 더 많은 맥락을 얻기 위해 클러스터가 사용하는 StorageClass를 다시 확인하는 것이 유용해요. 다음과 같이요:
STORAGECLASS=$(kubectl get pvc <POD> -o jsonpath='{.spec.storageClassName}')
kubectl get storageclasses $STORAGECLASS -o yaml
여기서 클러스터 Pod 중 하나에서 StorageClass를 가져오는데, 종종 클러스터가 기본 StorageClass로 생성되기 때문이에요.
노드 정보 (Node information)
쿠버네티스 노드는 궁극적으로 PostgreSQL Pod가 실행될 곳이에요. 그것에 대해 할 수 있는 한 많이 아는 것이 전략적으로 중요해요.
쿠버네티스 클러스터의 노드 목록을 다음과 같이 얻을 수 있어요:
# look at the worker nodes and their status
kubectl get nodes -o wide
추가로 주어진 클러스터의 Pod가 실행 중인 노드 목록을 다음과 같이 수집할 수 있어요:
kubectl get pod -l cnpg.io/cluster=<CLUSTER> \
-L role -n <NAMESPACE> -o wide
후자는 Pod가 어디에 분산되어 있는지 이해하는 데 중요해요. affinity/anti-affinity 규칙 및/또는 tolerations를 사용 중이라면 매우 유용해요.
조건 (Conditions)
여기처럼 많은 네이티브 쿠버네티스 객체와 마찬가지로, Cluster도 status.conditions를 노출해요. 이것은 전체 클러스터 건강 상태에 의존하는 대신 특정 이벤트가 발생하기를 '기다릴' 수 있게 해줘요. 현재 사용 가능한 조건은 다음과 같아요:
- LastBackupSucceeded
- ContinuousArchiving
- Ready
LastBackupSucceeded는 최신 백업의 상태를 보고해요. True로 설정되면 마지막 백업이 올바르게 수행된 것이고, 그렇지 않으면 False로 설정돼요.
ContinuousArchiving은 WAL 아카이빙의 상태를 보고해요. True로 설정되면 마지막 WAL 아카이브 프로세스가 올바르게 종료된 것이고, 그렇지 않으면 False로 설정돼요.
Ready는 클러스터가 사용자가 지정한 인스턴스 수를 갖고 프라이머리 인스턴스가 준비되었을 때 True예요. 이 조건은 스크립트에서 클러스터가 생성되기를 기다리는 데 사용할 수 있어요.
특정 조건을 기다리는 방법
- 백업:
$ kubectl wait --for=condition=LastBackupSucceeded cluster/<CLUSTER-NAME> -n <NAMESPACE>
- ContinuousArchiving:
$ kubectl wait --for=condition=ContinuousArchiving cluster/<CLUSTER-NAME> -n <NAMESPACE>
- Ready (클러스터가 준비되었는지):
$ kubectl wait --for=condition=Ready cluster/<CLUSTER-NAME> -n <NAMESPACE>
다음은 실패 조건을 포함한 cluster.status의 발췌문이에요:
$ kubectl get cluster/<cluster-name> -o yaml
.
.
.
status:
conditions:
- message: 'unexpected failure invoking barman-cloud-wal-archive: exit status
2'
reason: ContinuousArchivingFailing
status: "False"
type: ContinuousArchiving
- message: exit status 2
reason: LastBackupFailed
status: "False"
type: LastBackupSucceeded
- message: Cluster Is Not Ready
reason: ClusterIsNotReady
status: "False"
type: Ready
네트워킹 (Networking)
CloudNativePG는 기본적인 네트워킹과 연결을 요구해요. networking 섹션에서 더 많은 정보를 찾을 수 있어요.
기존 환경에 CloudNativePG를 설치한다면 네트워크 정책이나 클러스터용으로 특별히 만들어진 다른 네트워크 구성이 있을 수 있으며, 이는 오퍼레이터와 클러스터 Pod 사이 및/또는 Pod 사이의 필요한 연결에 영향을 줄 수 있어요.
기존 네트워크 정책을 다음 명령으로 찾아볼 수 있어요:
kubectl get networkpolicies
쿠버네티스 네트워크 관리자가 설정한 여러 네트워크 정책이 있을 수 있어요.
$ kubectl get networkpolicies
NAME POD-SELECTOR AGE
allow-prometheus cnpg.io/cluster=cluster-example 47m
default-deny-ingress <none> 57m
PostgreSQL 코어 덤프 (PostgreSQL core dumps)
드물지만 PostgreSQL은 때때로 크래시하여 PGDATA 폴더에 코어 덤프를 생성할 수 있어요. 그런 경우 대개 PostgreSQL의 버그이며 (아마 이미 해결되었을 가능성이 높아요 — 그래서 항상 최신 마이너 버전의 PostgreSQL을 실행하는 것이 중요해요).
CloudNativePG는 cnpg.io/coredumpFilter 주석을 통해 코어 덤프에 무엇을 포함할지 제어할 수 있게 해줘요.
:::info CloudNativePG가 제공하는 표준 주석에 대한 더 자세한 내용은 "Labels and annotations"를 참조하세요. :::
기본적으로 cnpg.io/coredumpFilter는 공유 메모리 세그먼트를 덤프에서 제외하기 위해 0x31로 설정되어 있는데, 이것이 대부분의 경우 가장 안전한 접근 방식이기 때문이에요.
:::info 코어 덤프 필터를 제어하는 비트마스크 설정 방법에 대한 자세한 내용은 Linux Kernel 문서의 "Core dump filtering settings" 섹션을 참조하세요. :::
:::info[Important] 이 설정은 Pod 시작 시에만 적용되며, 주석 변경이 인스턴스의 자동 롤아웃을 트리거하지 않는다는 점에 주의하세요. :::
직접 코어 덤프를 검사하는 데 참여하지 않을 수도 있지만, Postgres 전문가가 살펴볼 수 있도록 제공해야 할 수 있어요. 먼저 다음 명령으로 PGDATA 디렉토리에 코어 덤프가 있는지 확인하세요 (Postgres 인스턴스가 실행 중인 올바른 Pod에 대해 실행하세요):
kubectl exec -ti POD -c postgres \
-- find /var/lib/postgresql/data/pgdata -name 'core.*'
정상적인 상황에서는 빈 집합을 반환해야 해요. 예를 들어 코어 덤프 파일이 있다고 가정해 보세요:
/var/lib/postgresql/data/pgdata/core.14177
디스크 공간이 충분한지 확인한 후 kubectl cp로 코어 덤프를 다음과 같이 머신에 수집할 수 있어요:
kubectl cp POD:/var/lib/postgresql/data/pgdata/core.14177 core.14177
이제 파일이 생겼어요. 코어 덤프를 제거해 서버의 공간을 확보하세요.
프로파일 데이터 시각화 및 분석
CloudNativePG는 pprof와 통합해 두 수준에서 프로파일링 데이터를 수집하고 분석해요:
- 오퍼레이터 수준 – 오퍼레이터 배포에
--pprof-server=true옵션을 추가해 활성화 ("Operator configuration" 참조). - Postgres 클러스터 수준 –
Cluster리소스에alpha.cnpg.io/enableInstancePprof주석을 추가해 활성화 (아래 설명).
alpha.cnpg.io/enableInstancePprof 주석이 "true"로 설정되면 각 인스턴스 Pod는 인스턴스 매니저가 제공하는 Go pprof HTTP 서버를 노출해요.
- 서버는 Pod 내부의
0.0.0.0:6060에서 수신해요. pprof라는 컨테이너 포트(6060/TCP)가 Pod 사양에 자동으로 추가돼요.
주석을 제거하거나 "false"로 설정해 언제든지 pprof를 비활성화할 수 있어요. 오퍼레이터는 pprof 포트와 플래그를 제거하기 위해 변경을 자동 롤아웃해요.
:::info[Important]
pprof 서버는 포트 6060에서 일반 HTTP만 서빙해요.
:::
예시
주석을 추가해 클러스터에서 pprof를 활성화해요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example
annotations:
alpha.cnpg.io/enableInstancePprof: "true"
spec:
instances: 3
# ...
이 주석을 변경하면 인스턴스 Pod 사양이 업데이트되고(포트 6060과 해당 플래그 추가) 롤링 업데이트가 트리거돼요.
:::warning
아래 예시는 로컬 테스트용으로만 kubectl port-forward를 사용해요. 이것은 프로덕션에서 기능을 노출하는 의도된 방법이 아니에요. pprof를 민감한 디버깅 인터페이스로 취급하고 절대 공개적으로 노출하지 마세요. 원격으로 접근해야 한다면 적절한 네트워크 정책과 접근 제어로 보호하세요.
:::
포트 포워딩으로 pprof 엔드포인트에 접근하세요:
kubectl port-forward -n <namespace> pod/<instance-pod> 6060
curl -sS http://localhost:6060/debug/pprof/
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
브라우저에서 http://localhost:6060/debug/pprof/로 pprof에 접근할 수도 있어요.
트러블슈팅
먼저 클러스터에 alpha.cnpg.io/enableInstancePprof: "true" 주석이 설정되어 있는지 확인하세요.
다음으로 인스턴스 매니저 명령이 --pprof-server 플래그를 포함하고 포트 6060/TCP가 노출되었는지 확인하세요. 다음을 실행해 확인할 수 있어요:
kubectl -n <namespace> describe pod <instance-pod>
그런 다음 출력의 Command와 Ports 섹션을 검토하세요.
마지막으로 포트 포워딩을 사용하지 않는다면 NetworkPolicies가 포트 6060/TCP에 대한 접근을 허용하는지 확인하세요.
몇 가지 알려진 문제 (Some known issues)
스토리지가 가득 참
스토리지가 가득 차면 PostgreSQL Pod는 새 데이터를 쓸 수 없게 되고, WAL 세그먼트를 포함한 디스크가 가득 차면 PostgreSQL은 종료돼요.
로그에서 디스크가 가득 찼다는 메시지가 보이면 영향을 받는 PVC의 크기를 늘려야 해요. PVC를 편집하고 spec.resources.requests.storage 필드를 변경해서 이렇게 할 수 있어요. 그 후 Cluster 리소스도 새 크기로 업데이트해 같은 변경을 모든 Pod에 적용해야 해요. 문서의 "Volume expansion" 섹션을 참조하세요.
WAL 세그먼트 공간이 고갈되면 Pod는 크래시 루프를 돌게 되고 클러스터 상태는 Not enough disk space를 보고해요. PVC에서 그리고 Cluster 리소스에서 크기를 늘리면 문제가 해결돼요. "Disk Full Failure" 섹션도 참조하세요.
Pod가 Pending 상태에 갇힘
Cluster의 인스턴스가 Pending 단계에 갇혀 있다면 Pod의 Events 섹션을 확인해 그 뒤의 이유를 파악해야 해요:
kubectl describe pod -n <NAMESPACE> <POD>
가능한 원인 중 일부는 다음과 같아요:
nodeSelector와 일치하는 노드가 없음- Tolerations가 노드의 taints와 일치하도록 올바르게 구성되지 않음
- 사용 가능한 노드가 전혀 없음: 이것은
cluster-autoscaler가 어떤 한계에 부딪히거나 일시적인 문제가 있는 것과 관련될 수도 있음
이 경우 네임스페이스의 이벤트를 확인하는 것도 유용할 수 있어요:
kubectl get events -n <NAMESPACE>
# list events in chronological order
kubectl get events -n <NAMESPACE> --sort-by=.metadata.creationTimestamp
백업이 구성되지 않았을 때 레플리카가 동기화에서 벗어남
때때로 레플리카는 유지보수 이유로 잠시 꺼질 수 있어요 (쿠버네티스 노드가 drain될 때를 생각해 보세요). 클러스터에 백업이 구성되어 있지 않으면, 레플리카가 다시 올라올 때 "The postgresql section"에서 언급한 WAL 관리 정책에 따라 이미 재활용되어 더 이상 프라이머리에 없는 WAL 파일을 요구할 수 있고, 동기화에서 벗어날 수 있어요.
마찬가지로 pg_rewind가 이전 프라이머리에 더 이상 없는 WAL 파일을 요구할 때 pg_rewind: error: could not open file을 보고할 수 있어요.
이런 경우 Pod는 더 이상 ready가 될 수 없으며, PVC를 삭제해 오퍼레이터가 레플리카를 재구축하도록 해야 해요.
동적으로 프로비저닝된 Persistent Volume에 의존하고 PV 자체를 삭제하는 데 자신이 있다면 다음과 같이 할 수 있어요:
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
클러스터가 Creating new replica에 갇힘
Pod 로그에 관련 문제가 표시되지 않는데 클러스터가 "Creating a new replica"에 갇혀 있어요. 이것은 다음 문제인 연결성(connectivity)과 관련이 있는 것으로 밝혀졌어요. 네트워킹 문제는 상태 열에 다음과 같이 반영돼요:
Instance Status Extraction Error: HTTP communication issue
설치된 Network Policy로 인한 네트워킹 손상
네트워킹 섹션에서 지적했듯이 로컬 네트워크 정책이 필요한 연결 중 일부를 막을 수 있어요.
연결성이 손상되었다는 징후는 오퍼레이터 로그에 다음과 같은 메시지가 있는 것입니다:
"Cannot extract Pod status", […snipped…] "Get \"http://<pod IP>:8000/pg/status\": dial tcp <pod IP>:8000: i/o timeout"
네트워크 정책을 나열하고 연결을 제한하는 정책이 있는지 찾아보세요.
$ kubectl get networkpolicies
NAME POD-SELECTOR AGE
allow-prometheus cnpg.io/cluster=cluster-example 47m
default-deny-ingress <none> 57m
예를 들어 위 목록에서 default-deny-ingress가 유력한 원인으로 보여요. 그것을 자세히 들여다볼 수 있어요:
$ kubectl get networkpolicies default-deny-ingress -o yaml
<…snipped…>
spec:
podSelector: {}
policyTypes:
- Ingress
networking 페이지에서 오퍼레이터가 크로스 네임스페이스로 클러스터 Pod에 연결할 수 있게 명시적으로 허용하는 NetworkPolicy를 만들도록 커스터마이즈할 수 있는 네트워크 정책 파일을 찾을 수 있어요.
데이터 디렉토리 부트스트래핑 중 오류
Cluster의 bootstrap init 컨테이너가 "Bus error (core dumped) child process exited with exit code 135"로 크래시한다면, 아마 Cluster의 hugepages 설정을 고쳐야 해요.
그 이유는 cgroup v1에서 hugepages 지원이 불완전하기 때문이며 v2에서 고쳐져야 해요. 더 많은 정보는 PostgreSQL BUG #17757: Not honoring huge_pages setting during initdb causes DB crash in Kubernetes를 확인하세요.
hugepages가 활성화되어 있는지 확인하려면 쿠버네티스 노드에서 grep HugePages /proc/meminfo를 실행하고 hugepages가 있는지, 크기와 비어 있는 수를 확인하세요.
hugepages가 있다면 각 PostgreSQL Pod가 사용할 수 있어야 하는 hugepages 메모리 양을 구성해야 해요.
예를 들어:
postgresql:
parameters:
shared_buffers: "128MB"
resources:
requests:
memory: "512Mi"
limits:
hugepages-2Mi: "512Mi"
클러스터의 모든 Pod를 스케줄링하기에 충분한 hugepages 메모리가 있어야 한다는 점을 기억하세요 (위 예시에서 Pod당 최소 512MiB가 비어 있어야 함).
Bootstrap init 컨테이너가 running 상태에서 멈춤
클러스터의 인스턴스 Pod가 "error while waiting for the API server to be reachable" 메시지와 함께 bootstrap init 컨테이너가 Running 상태일 때 멈춘다면, 쿠버네티스 API 서버와의 통신을 막는 네트워크 문제가 있을 가능성이 커요. bootstrap init 컨테이너는 오퍼레이터의 대부분의 구성 요소처럼 쿠버네티스 API에 접근해야 해요. 네트워킹을 확인하세요.
또 다른 가능한 원인은 사이드카 주입(sidecar injection)이 구성된 경우예요. Istio 같은 사이드카는 시작 중에 네트워크를 일시적으로 사용할 수 없게 만들 수 있어요. 사이드카 주입이 활성화되어 있다면 주입을 비활성화하고 재시도하세요.
장애 조치 후 레플리카가 재연결하는 데 2분 이상 걸림
프라이머리 인스턴스가 실패하면 오퍼레이터는 가장 발전된 스탠바이를 프라이머리 역할로 승격해요. 그러면 다른 스탠바이 인스턴스는 복제를 위해 -rw 서비스에 재연결을 시도해요. 하지만 이 재연결 과정에서 kube-proxy가 라우팅 정보를 아직 업데이트하지 않았을 수 있어요. 결과적으로 스탠바이 인스턴스가 보낸 초기 SYN 패킷이 의도한 목적지에 도달하지 못할 수 있어요.
네트워크가 패킷을 거부하는 대신 조용히 버리도록 구성되어 있다면, 스탠바이 인스턴스는 응답을 받지 못하고 지수 백오프 기간 후에 연결을 재시도해요. Linux 시스템에서 tcp_syn_retries 커널 파라미터의 기본값은 6이므로, 시스템은 포기하기 전에 약 127초 동안 연결 설정을 시도할 거예요. 이 길어진 재시도 기간은 재연결 과정을 크게 지연시킬 수 있어요. 더 자세한 내용은 tcp_syn_retries 문서를 참조하세요.
오퍼레이터 구성에서 STANDBY_TCP_USER_TIMEOUT을 설정해 이 문제를 해결할 수 있어요. 이렇게 하면 지정된 타임아웃 내에 초기 SYN 패킷이 승인되지 않으면 스탠바이 인스턴스가 TCP 연결을 닫아 더 빠르게 재연결을 재시도할 수 있게 해줘요.