본문 바로가기
WIKI 기술 지식 베이스

모니터링

원문 보기 위키 갱신

모니터링 (Monitoring)

CloudNativePG 클러스터를 Prometheus와 Grafana로 모니터링하는 방법을 알려 드릴게요. 오퍼레이터가 각 PostgreSQL 인스턴스에 대해 포트 9187의 metrics 엔드포인트로 노출하는 메트릭, 사전 정의된 메트릭 세트, 사용자 정의 쿼리, 그리고 오퍼레이터 자체 모니터링까지 정리해요.

출처: 문서

본문

:::info[Important] Prometheus와 Grafana 설치는 이 프로젝트의 범위 밖이에요. 시스템에 올바르게 설치되어 있다고 가정해요. 그러나 실험을 위해 Quickstart의 4부에서 지침을 제공해요. :::

인스턴스 모니터링 (Monitoring Instances)

각 PostgreSQL 인스턴스에 대해 오퍼레이터는 포트 9187의 HTTP 또는 HTTPS를 통해, 이름이 metrics인 Prometheus용 메트릭 exporter를 제공해요. 오퍼레이터는 사전 정의된 메트릭 세트와 함께, 하나 이상의 ConfigMap 또는 Secret 리소스를 통해 추가 쿼리를 정의할 수 있는 매우 구성 가능하고 커스터마이즈 가능한 시스템을 제공해요 (자세한 내용은 아래 "사용자 정의 메트릭" 섹션 참고).

:::info[Important] CloudNativePG는 기본적으로 cnpg-default-monitoring이라는 ConfigMap에 사전 정의된 메트릭 세트를 설치해요. :::

:::info 내보내진 메트릭은 아래 "내보내진 메트릭을 확인하는 방법" 섹션의 지침을 따라 검사할 수 있어요. :::

PostgreSQL에서 수행되는 모든 모니터링 쿼리는:

  • 원자적(atomic)이에요 (쿼리당 하나의 읽기 전용 트랜잭션)
  • cnpg_metrics_exporter 역할(pg_monitor의 멤버)로 실행돼요
  • application_name을 cnpg_metrics_exporter로 설정해 실행돼요

연결은 파드 로컬 Unix 소켓의 피어(peer) 인증을 사용해요. session_user는 절대 슈퍼유저가 아니므로 모니터링 세션은 RESET ROLE 또는 RESET SESSION AUTHORIZATION을 통해 권한을 상승시킬 수 없어요. 커스텀 쿼리가 요구하는 테이블 레벨 권한과 pg_monitor 외에 cnpg_metrics_exporter에 추가 권한이나 역할 멤버십을 부여하지 마세요: 어떤 추가 멤버십도 상속을 통해 스크레이프 세션으로 흘러들어가 이 속성을 약화시켜요.

pg_monitor 역할에 대한 자세한 내용은 PostgreSQL 문서의 "Predefined Roles(사전 정의된 역할)" 섹션을 참고해 주세요.

쿼리는 기본적으로 Cluster 리소스의 지정된 bootstrap 방법에 따라 정의된 메인 데이터베이스에 대해 실행돼요. 논리는 다음과 같아요:

  • initdb 사용: 쿼리는 기본적으로 initdb.database에 지정된 데이터베이스에 대해 실행되며, 지정되지 않으면 app에 대해 실행돼요
  • recovery 사용: 쿼리는 기본적으로 recovery.database에 지정된 데이터베이스에 대해 실행되며, 지정되지 않으면 postgres에 대해 실행돼요
  • pg_basebackup 사용: 쿼리는 기본적으로 pg_basebackup.database에 지정된 데이터베이스에 대해 실행되며, 지정되지 않으면 postgres에 대해 실행돼요

기본 데이터베이스는 특정 사용자 정의 메트릭에 대해 target_databases 옵션에 하나 이상의 데이터베이스 목록을 지정해 언제든 재정의할 수 있어요.

:::note[Prometheus/Grafana] CloudNativePG와 Prometheus, Grafana의 통합을 평가하는 데 관심이 있다면 quickstart의 4부에서 빠른 설정 가이드를 찾을 수 있어요 :::

출력 캐싱 (Output caching)

기본적으로 모니터링 쿼리의 출력은 30초 동안 캐시돼요. 이는 리소스 효율성을 높이고 prometheus 엔드포인트가 스크레이프될 때마다 PostgreSQL이 모니터링 쿼리를 실행하지 않도록 하기 위한 것이에요.

캐시 자체는 cache_hits, cache_misses, last_update_timestamp 메트릭으로 관찰할 수 있어요.

cluster.spec.monitoring.metricsQueriesTTL을 0으로 설정하면 캐시가 비활성화되며, 그 경우 메트릭은 모든 메트릭 엔드포인트 스크레이프에서 실행돼요.

Prometheus operator로 모니터링 (Monitoring with the Prometheus operator)

Prometheus Operator의 PodMonitor 리소스를 사용해 특정 PostgreSQL 클러스터를 모니터링할 수 있어요.

권장되는 접근 방식은 각 CloudNativePG 클러스터에 대한 PodMonitor를 수동으로 생성하고 관리하는 것이에요. 이 방법은 모니터링 구성과 수명 주기에 대한 완전한 제어를 제공해요.

PodMonitor 생성 (Creating a PodMonitor)

클러스터를 모니터링하려면 다음과 같이 PodMonitor 리소스를 정의하세요. Prometheus Operator가 PodMonitor 리소스를 찾도록 구성된 것과 같은 네임스페이스에 배포해야 해요.

apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: cluster-example
spec:
  selector:
    matchLabels:
      cnpg.io/cluster: cluster-example
  podMetricsEndpoints:
  - port: metrics

:::info[Important Configuration Details] - metadata.name: PodMonitor에 고유한 이름을 지정하세요. - spec.namespaceSelector: PostgreSQL 클러스터가 실행되는 네임스페이스를 지정하려면 사용하세요. - spec.selector.matchLabels: PostgreSQL 인스턴스를 올바르게 대상화하려면 cnpg.io/cluster: <cluster-name> 라벨을 사용해야 해요. :::

자동 PodMonitor 생성의 폐기 (Deprecation of Automatic PodMonitor Creation)

:::warning[Feature Deprecation Notice] Cluster 리소스의 .spec.monitoring.enablePodMonitor 필드는 이제 deprecated이며 향후 오퍼레이터 버전에서 제거될 예정이에요. :::

현재 이 기능을 사용 중이라면 .spec.monitoring.enablePodMonitor를 제거하거나 false로 설정하고 위에서 설명한 대로 클러스터용 PodMonitor 리소스를 수동으로 만들 것을 강력히 권장해요. 이 변경은 모니터링 구성의 완전한 소유권을 보장하며, 오퍼레이터가 이를 관리하거나 덮어쓰는 것을 방지해요.

메트릭 포트에 TLS 활성화 (Enabling TLS on the Metrics Port)

메트릭 포트에서 TLS 통신을 활성화하려면 .spec.monitoring.tls.enabled 설정을 true로 구성하세요. 이 설정은 메트릭 exporter가 PostgreSQL이 포트 5432의 통신을 보호하는 데 사용하는 것과 동일한 서버 인증서를 사용하도록 보장해요.

:::info[Important] .spec.monitoring.tls.enabled 설정을 변경하면 클러스터의 롤링 재시작을 트리거해요. :::

PodMonitor가 오퍼레이터에 의해 관리되는 경우(.spec.monitoring.enablePodMonitor가 true로 설정됨), TLS를 통해 메트릭에 접근하는 데 필요한 구성을 자동으로 포함할 거예요.

TLS를 통해 메트릭을 읽기에 적합한 PodMonitor를 수동으로 배포하려면 다음과 같이 정의하고 필요에 따라 조정하세요:

apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: cluster-example
spec:
  selector:
    matchLabels:
      "cnpg.io/cluster": cluster-example
  podMetricsEndpoints:
  - port: metrics
    scheme: https
    tlsConfig:
      ca:
        secret:
          name: cluster-example-ca
          key: ca.crt
      serverName: cluster-example-rw

:::info[Important] 위 예시를 고유한 이름, 올바른 클러스터 네임스페이스와 라벨(예: cluster-example)로 수정해야 합니다. :::

:::info[Important] 메트릭 엔드포인트의 serverName 필드는 서버 인증서에 정의된 이름 중 하나와 일치해야 해요. 기본 인증서를 사용 중이라면 serverName 값은 <cluster-name>-rw 형식이어야 해요. :::

사전 정의된 메트릭 세트 (Predefined set of metrics)

모든 PostgreSQL 인스턴스 exporter는 기본적으로 두 가지 주요 범주로 분류할 수 있는 사전 정의된 메트릭 세트를 노출해요:

  • PostgreSQL 관련 메트릭, cnpg_collector_*로 시작하며 다음을 포함:

    • WAL 파일 수와 디스크의 총 크기
    • 아카이브 상태 폴더의 .ready 및 .done 파일 수
    • 요청된 최소 및 최대 동기 레플리카 수, 예상 값과 실제 관찰된 값
    • 인스턴스를 수용하는 고유 노드 수
    • 마지막 실패 및 마지막 사용 가능한 백업을 나타내는 타임스탬프, 그리고 클러스터의 첫 번째 복구 가능 지점
    • replica 클러스터 모드가 활성화 또는 비활성화되었는지 나타내는 플래그
    • 수동 스위치오버가 필요한지 나타내는 플래그
    • 펜싱(fencing)이 활성화 또는 비활성화되었는지 나타내는 플래그
  • Go 런타임 관련 메트릭, go_*로 시작

다음은 인스턴스의 localhost:9187/metrics 엔드포인트가 반환하는 메트릭 샘플이에요. 보시다시피 Prometheus 형식은 자가 문서화(self-documenting)돼요:

# HELP cnpg_collector_collection_duration_seconds Collection time duration in seconds
# TYPE cnpg_collector_collection_duration_seconds gauge
cnpg_collector_collection_duration_seconds{collector="Collect.up"} 0.0031393

# HELP cnpg_collector_collections_total Total number of times PostgreSQL was accessed for metrics.
# TYPE cnpg_collector_collections_total counter
cnpg_collector_collections_total 2

# HELP cnpg_collector_fencing_on 1 if the instance is fenced, 0 otherwise
# TYPE cnpg_collector_fencing_on gauge
cnpg_collector_fencing_on 0

# HELP cnpg_collector_nodes_used NodesUsed represents the count of distinct nodes accommodating the instances. A value of '-1' suggests that the metric is not available. A value of '1' suggests that all instances are hosted on a single node, implying the absence of High Availability (HA). Ideally this value should match the number of instances in the cluster.
# TYPE cnpg_collector_nodes_used gauge
cnpg_collector_nodes_used 3

# HELP cnpg_collector_last_collection_error 1 if the last collection ended with error, 0 otherwise.
# TYPE cnpg_collector_last_collection_error gauge
cnpg_collector_last_collection_error 0

# HELP cnpg_collector_manual_switchover_required 1 if a manual switchover is required, 0 otherwise
# TYPE cnpg_collector_manual_switchover_required gauge
cnpg_collector_manual_switchover_required 0

# HELP cnpg_collector_pg_wal Total size in bytes of WAL segments in the '/var/lib/postgresql/data/pgdata/pg_wal' directory  computed as (wal_segment_size * count)
# TYPE cnpg_collector_pg_wal gauge
cnpg_collector_pg_wal{value="count"} 9
cnpg_collector_pg_wal{value="slots_max"} NaN
cnpg_collector_pg_wal{value="keep"} 32
cnpg_collector_pg_wal{value="max"} 64
cnpg_collector_pg_wal{value="min"} 5
cnpg_collector_pg_wal{value="size"} 1.50994944e+08
cnpg_collector_pg_wal{value="volume_max"} 128
cnpg_collector_pg_wal{value="volume_size"} 2.147483648e+09

# HELP cnpg_collector_pg_wal_archive_status Number of WAL segments in the '/var/lib/postgresql/data/pgdata/pg_wal/archive_status' directory (ready, done)
# TYPE cnpg_collector_pg_wal_archive_status gauge
cnpg_collector_pg_wal_archive_status{value="done"} 6
cnpg_collector_pg_wal_archive_status{value="ready"} 0

# HELP cnpg_collector_replica_mode 1 if the cluster is in replica mode, 0 otherwise
# TYPE cnpg_collector_replica_mode gauge
cnpg_collector_replica_mode 0

# HELP cnpg_collector_sync_replicas Number of requested synchronous replicas (synchronous_standby_names)
# TYPE cnpg_collector_sync_replicas gauge
cnpg_collector_sync_replicas{value="expected"} 0
cnpg_collector_sync_replicas{value="max"} 0
cnpg_collector_sync_replicas{value="min"} 0
cnpg_collector_sync_replicas{value="observed"} 0

# HELP cnpg_collector_up 1 if PostgreSQL is up, 0 otherwise.
# TYPE cnpg_collector_up gauge
cnpg_collector_up{cluster="cluster-example"} 1

# HELP cnpg_collector_postgres_version Postgres version
# TYPE cnpg_collector_postgres_version gauge
cnpg_collector_postgres_version{cluster="cluster-example",full="18.6"} 18.6

# HELP cnpg_collector_last_failed_backup_timestamp The last failed backup as a unix timestamp (Deprecated)
# TYPE cnpg_collector_last_failed_backup_timestamp gauge
cnpg_collector_last_failed_backup_timestamp 0

# HELP cnpg_collector_last_available_backup_timestamp The last available backup as a unix timestamp (Deprecated)
# TYPE cnpg_collector_last_available_backup_timestamp gauge
cnpg_collector_last_available_backup_timestamp 1.63238406e+09

# HELP cnpg_collector_first_recoverability_point The first point of recoverability for the cluster as a unix timestamp (Deprecated)
# TYPE cnpg_collector_first_recoverability_point gauge
cnpg_collector_first_recoverability_point 1.63238406e+09

# HELP cnpg_collector_lo_pages Estimated number of pages in the pg_largeobject table
# TYPE cnpg_collector_lo_pages gauge
cnpg_collector_lo_pages{datname="app"} 0
cnpg_collector_lo_pages{datname="postgres"} 78

# HELP cnpg_collector_wal_buffers_full Number of times WAL data was written to disk because WAL buffers became full. Only available on PG 14+
# TYPE cnpg_collector_wal_buffers_full gauge
cnpg_collector_wal_buffers_full{stats_reset="2023-06-19T10:51:27.473259Z"} 6472

# HELP cnpg_collector_wal_bytes Total amount of WAL generated in bytes. Only available on PG 14+
# TYPE cnpg_collector_wal_bytes gauge
cnpg_collector_wal_bytes{stats_reset="2023-06-19T10:51:27.473259Z"} 1.0035147e+07

# HELP cnpg_collector_wal_fpi Total number of WAL full page images generated. Only available on PG 14+
# TYPE cnpg_collector_wal_fpi gauge
cnpg_collector_wal_fpi{stats_reset="2023-06-19T10:51:27.473259Z"} 1474

# HELP cnpg_collector_wal_records Total number of WAL records generated. Only available on PG 14+
# TYPE cnpg_collector_wal_records gauge
cnpg_collector_wal_records{stats_reset="2023-06-19T10:51:27.473259Z"} 26178

# HELP cnpg_collector_wal_sync Number of times WAL files were synced to disk via issue_xlog_fsync request (if fsync is on and wal_sync_method is either fdatasync, fsync or fsync_writethrough, otherwise zero). Only available on PG 14+
# TYPE cnpg_collector_wal_sync gauge
cnpg_collector_wal_sync{stats_reset="2023-06-19T10:51:27.473259Z"} 37

# HELP cnpg_collector_wal_sync_time Total amount of time spent syncing WAL files to disk via issue_xlog_fsync request, in milliseconds (if track_wal_io_timing is enabled, fsync is on, and wal_sync_method is either fdatasync, fsync or fsync_writethrough, otherwise zero). Only available on PG 14+
# TYPE cnpg_collector_wal_sync_time gauge
cnpg_collector_wal_sync_time{stats_reset="2023-06-19T10:51:27.473259Z"} 0

# HELP cnpg_collector_wal_write Number of times WAL buffers were written out to disk via XLogWrite request. Only available on PG 14+
# TYPE cnpg_collector_wal_write gauge
cnpg_collector_wal_write{stats_reset="2023-06-19T10:51:27.473259Z"} 7243

# HELP cnpg_collector_wal_write_time Total amount of time spent writing WAL buffers to disk via XLogWrite request, in milliseconds (if track_wal_io_timing is enabled, otherwise zero). This includes the sync time when wal_sync_method is either open_datasync or open_sync. Only available on PG 14+
# TYPE cnpg_collector_wal_write_time gauge
cnpg_collector_wal_write_time{stats_reset="2023-06-19T10:51:27.473259Z"} 0

# HELP cnpg_last_error 1 if the last collection ended with error, 0 otherwise.
# TYPE cnpg_last_error gauge
cnpg_last_error 0

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 5.01e-05
go_gc_duration_seconds{quantile="0.25"} 7.27e-05
go_gc_duration_seconds{quantile="0.5"} 0.0001748
go_gc_duration_seconds{quantile="0.75"} 0.0002959
go_gc_duration_seconds{quantile="1"} 0.0012776
go_gc_duration_seconds_sum 0.0035741
go_gc_duration_seconds_count 13

# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 25

# HELP go_info Information about the Go environment.
# TYPE go_info gauge
go_info{version="go1.20.5"} 1

# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
# TYPE go_memstats_alloc_bytes gauge
go_memstats_alloc_bytes 4.493744e+06

# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_total counter
go_memstats_alloc_bytes_total 2.1698216e+07

# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.
# TYPE go_memstats_buck_hash_sys_bytes gauge
go_memstats_buck_hash_sys_bytes 1.456234e+06

# HELP go_memstats_frees_total Total number of frees.
# TYPE go_memstats_frees_total counter
go_memstats_frees_total 172118

# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started.
# TYPE go_memstats_gc_cpu_fraction gauge
go_memstats_gc_cpu_fraction 1.0749468700447189e-05

# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata.
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 5.530048e+06

# HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use.
# TYPE go_memstats_heap_alloc_bytes gauge
go_memstats_heap_alloc_bytes 4.493744e+06

# HELP go_memstats_heap_idle_bytes Number of heap bytes waiting to be used.
# TYPE go_memstats_heap_idle_bytes gauge
go_memstats_heap_idle_bytes 5.8236928e+07

# HELP go_memstats_heap_inuse_bytes Number of heap bytes that are in use.
# TYPE go_memstats_heap_inuse_bytes gauge
go_memstats_heap_inuse_bytes 7.528448e+06

# HELP go_memstats_heap_objects Number of allocated objects.
# TYPE go_memstats_heap_objects gauge
go_memstats_heap_objects 26306

# HELP go_memstats_heap_released_bytes Number of heap bytes released to OS.
# TYPE go_memstats_heap_released_bytes gauge
go_memstats_heap_released_bytes 5.7401344e+07

# HELP go_memstats_heap_sys_bytes Number of heap bytes obtained from system.
# TYPE go_memstats_heap_sys_bytes gauge
go_memstats_heap_sys_bytes 6.5765376e+07

# HELP go_memstats_last_gc_time_seconds Number of seconds since 1970 of last garbage collection.
# TYPE go_memstats_last_gc_time_seconds gauge
go_memstats_last_gc_time_seconds 1.6311727586032727e+09

# HELP go_memstats_lookups_total Total number of pointer lookups.
# TYPE go_memstats_lookups_total counter
go_memstats_lookups_total 0

# HELP go_memstats_mallocs_total Total number of mallocs.
# TYPE go_memstats_mallocs_total counter
go_memstats_mallocs_total 198424

# HELP go_memstats_mcache_inuse_bytes Number of bytes in use by mcache structures.
# TYPE go_memstats_mcache_inuse_bytes gauge
go_memstats_mcache_inuse_bytes 14400

# HELP go_memstats_mcache_sys_bytes Number of bytes used for mcache structures obtained from system.
# TYPE go_memstats_mcache_sys_bytes gauge
go_memstats_mcache_sys_bytes 16384

# HELP go_memstats_mspan_inuse_bytes Number of bytes in use by mspan structures.
# TYPE go_memstats_mspan_inuse_bytes gauge
go_memstats_mspan_inuse_bytes 191896

# HELP go_memstats_mspan_sys_bytes Number of bytes used for mspan structures obtained from system.
# TYPE go_memstats_mspan_sys_bytes gauge
go_memstats_mspan_sys_bytes 212992

# HELP go_memstats_next_gc_bytes Number of heap bytes when next garbage collection will take place.
# TYPE go_memstats_next_gc_bytes gauge
go_memstats_next_gc_bytes 8.689632e+06

# HELP go_memstats_other_sys_bytes Number of bytes used for other system allocations.
# TYPE go_memstats_other_sys_bytes gauge
go_memstats_other_sys_bytes 2.566622e+06

# HELP go_memstats_stack_inuse_bytes Number of bytes in use by the stack allocator.
# TYPE go_memstats_stack_inuse_bytes gauge
go_memstats_stack_inuse_bytes 1.343488e+06

# HELP go_memstats_stack_sys_bytes Number of bytes obtained from system for stack allocator.
# TYPE go_memstats_stack_sys_bytes gauge
go_memstats_stack_sys_bytes 1.343488e+06

# HELP go_memstats_sys_bytes Number of bytes obtained from system.
# TYPE go_memstats_sys_bytes gauge
go_memstats_sys_bytes 7.6891144e+07

# HELP go_threads Number of OS threads created.
# TYPE go_threads gauge
go_threads 18

:::note cnpg_collector_postgres_version은 PostgreSQL의 Major.Minor 버전을 포함하는 GaugeVec 메트릭이에요. 완전한 시맨틱 버전 Major.Minor.Patch는 full이라는 라벨 필드 중 하나에서 찾을 수 있어요. :::

:::warning cnpg_collector_last_failed_backup_timestamp, cnpg_collector_last_available_backup_timestamp, cnpg_collector_first_recoverability_point 메트릭은 버전 1.26부터 deprecated됐어요. 이 메트릭들은 in-core Barman Cloud(deprecated) 및 볼륨 스냅샷 같은 네이티브 백업 솔루션에서 계속 작동해요. 이 경우 cnpg_collector_first_recoverability_point와 cnpg_collector_last_available_backup_timestamp는 첫 번째 백업이 객체 스토어에 완료될 때까지 0으로 유지된다는 점을 유의하세요. 이는 WAL 아카이빙과는 별개예요. :::

사용자 정의 메트릭 (User defined metrics)

이 기능은 현재 beta 상태이며 형식은 PostgreSQL Prometheus Exporter의 queries.yaml 파일(릴리스 0.12)에서 영감을 받았어요.

커스텀 메트릭은 Cluster 정의의 .spec.monitoring.customQueriesConfigMap 또는 customQueriesSecret 섹션 아래에서 생성된 Configmap/Secret을 참조해 사용자가 정의할 수 있어요:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-example
  namespace: test
spec:
  instances: 3

  storage:
    size: 1Gi

  monitoring:
    customQueriesConfigMap:
      - name: example-monitoring
        key: custom-queries

customQueriesConfigMap/customQueriesSecret 섹션은 커스텀 쿼리가 정의된 키를 지정하는 ConfigMap/Secret 참조 목록을 포함해요. 참조되는 리소스가 Cluster 리소스와 같은 네임스페이스에 생성되어야 한다는 점에 주의하세요.

:::note ConfigMap과 Secrets가 인스턴스에 의해 자동으로 리로드되기를 원한다면 cnpg.io/reload 키의 라벨을 추가할 수 있고, 그렇지 않으면 kubectl cnpg reload 하위 명령을 사용해 인스턴스를 리로드해야 해요. :::

:::info[Important] 사용자 정의 메트릭이 이미 존재하는 메트릭을 덮어쓸 때 인스턴스 매니저는 Query with the same name already found. Overwriting the existing one. 메시지를 포함한 json 경고 로그와 덮어쓴 쿼리 이름을 담은 queryName 키를 출력해요. :::

커스텀 쿼리 권한과 안전 (Custom query privileges and safety)

:::warning 커스텀 쿼리는 pg_monitor를 상속하는 cnpg_metrics_exporter 역할로 실행돼요. pg_monitor 범위 내의 쿼리(카탈로그 읽기, pg_stat_* 뷰, 구성 파라미터)는 수정 없이 작동해요. 사용자 소유 테이블이나 슈퍼유저 전용 카탈로그(예: pg_authid, pg_subscription)를 읽는 쿼리는 명시적 권한이 필요해요. 테이블을 읽으려면 그 스키마에 대한 USAGE도 필요해요:

```sql
GRANT USAGE ON SCHEMA myschema TO cnpg_metrics_exporter;
GRANT SELECT ON TABLE myschema.mytable TO cnpg_metrics_exporter;
```

`target_databases`의 모든 데이터베이스가 `cnpg_metrics_exporter`가 `CONNECT`할 수 있게 허용해야 해요. 어떤 데이터베이스에서 `PUBLIC`의 `CONNECT`를 철회한 클러스터라면 그 역할에 명시적으로 부여하세요:

```sql
GRANT CONNECT ON DATABASE domainapp TO cnpg_metrics_exporter;
```

`"*"` 와일드카드보다 신뢰할 수 있는 데이터베이스의 명시적 목록(예: `target_databases: ["domainapp"]`)을 선호하세요. 와일드카드는 역할이 연결할 수 있는 모든 데이터베이스를 스크레이프하고 나머지는 조용히 건너뛰므로, 명시적 목록은 누락된 권한을 더 쉽게 알아차리게 해줘요. `"*"`는 쿼리가 전체 클러스터에 걸쳐 데이터베이스별 메트릭을 수집하려는 경우에만 사용하세요.

사용자 소유 객체에 의한 `search_path` 섀도잉을 방지하려면 카탈로그 참조를 스키마 한정(`pg_catalog.now()`, `pg_catalog.current_database()`)으로 하세요.

커스텀 모니터링 쿼리는 데이터베이스나 역할에 구성된 `search_path`와 무관하게 `search_path`가 `pg_catalog, public, pg_temp`로 고정된 트랜잭션 안에서 실행돼요. 따라서 다른 사용자 정의 스키마의 객체에 대한 비한정 참조는 해석에 실패할 거예요: 쿼리가 `search_path`에 의존하지 않도록 스키마 한정(예: `myschema.mytable`)을 하세요.

:::

사용자 정의 메트릭 예시 (Example of a user defined metric)

여기 위 Cluster 예시가 참조하는, 단일 커스텀 쿼리를 포함한 ConfigMap의 예시를 볼 수 있어요:

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-monitoring
  namespace: test
  labels:
    cnpg.io/reload: ""
data:
  custom-queries: |
    pg_replication:
      query: "SELECT CASE WHEN NOT pg_catalog.pg_is_in_recovery()
              THEN 0
              ELSE GREATEST (0,
                EXTRACT(EPOCH FROM (pg_catalog.now() OPERATOR(pg_catalog.-) pg_catalog.pg_last_xact_replay_timestamp())))
              END AS lag,
              pg_catalog.pg_is_in_recovery() AS in_recovery,
              EXISTS (TABLE pg_catalog.pg_stat_wal_receiver) AS is_wal_receiver_up,
              (SELECT pg_catalog.count(*) FROM pg_catalog.pg_stat_replication) AS streaming_replicas"

      metrics:
        - lag:
            usage: "GAUGE"
            description: "Replication lag behind primary in seconds"
        - in_recovery:
            usage: "GAUGE"
            description: "Whether the instance is in recovery"
        - is_wal_receiver_up:
            usage: "GAUGE"
            description: "Whether the instance wal_receiver is up"
        - streaming_replicas:
            usage: "GAUGE"
            description: "Number of streaming replicas connected to the instance"

기본 모니터링 쿼리 목록은 CloudNativePG 배포에 이미 설치된 default-monitoring.yaml 파일에서 찾을 수 있어요 ("기본 메트릭 세트" 참고).

조건자 쿼리를 가진 사용자 정의 메트릭 예시 (Example of a user defined metric with predicate query)

predicate_query 옵션을 사용하면 지정된 조건에서만 query를 실행해 메트릭을 수집할 수 있어요. 이를 위해 사용자는 단일 boolean 열을 가진 최대 한 행을 반환하는 조건자 쿼리(predicate query)를 제공해야 해요.

조건자 쿼리는 기본 쿼리와 같은 트랜잭션에서, 같은 데이터베이스에 대해 실행돼요.

some_query: |
  predicate_query: |
    SELECT 
      some_bool as predicate 
    FROM some_table
  query: |
    SELECT
     pg_catalog.count(*) as rows
    FROM some_table
  metrics:
    - rows:
        usage: "GAUGE"
        description: "number of rows"

여러 데이터베이스에서 실행되는 사용자 정의 메트릭 예시 (Example of a user defined metric running on multiple databases)

target_databases 옵션이 둘 이상의 데이터베이스를 나열하면 메트릭은 각각에서 수집돼요.

target_databases 목록에 shell 유사 패턴(즉 *, ? 또는 [] 포함)을 지정하면 특정 쿼리에 대해 데이터베이스 자동 발견(auto-discovery)을 활성화할 수 있어요. 제공되면 오퍼레이터는 SELECT datname FROM pg_catalog.pg_database WHERE datallowconn AND NOT datistemplate AND pg_catalog.has_database_privilege(datname, 'CONNECT') 실행 결과로 반환되고 path.Match() 규칙에 따라 패턴과 일치하는 모든 데이터베이스를 추가해 대상 데이터베이스 목록을 확장해요. cnpg_metrics_exporter에 CONNECT 권한이 없는 데이터베이스는 조용히 건너뛰어요. PUBLIC 접근이 철회된 데이터베이스를 스크레이프하려면 CONNECT를 명시적으로 부여하세요 (위 "커스텀 쿼리 권한과 안전" 참고).

:::note * 문자는 yaml에서 특별한 의미를 가지므로, target_databases 값에 그러한 패턴이 포함될 때는 "*"로 따옴표 처리해야 해요. :::

반환된 라벨에 항상 데이터베이스 이름을 포함하는 것이 좋아요. 예를 들어 다음 예시처럼 pg_catalog.current_database() 함수를 사용하세요:

some_query: |
  query: |
    SELECT
     pg_catalog.current_database() as datname,
     pg_catalog.count(*) as rows
    FROM some_table
  metrics:
    - datname:
        usage: "LABEL"
        description: "Name of current database"
    - rows:
        usage: "GAUGE"
        description: "number of rows"
  target_databases:
    - albert
    - bb
    - freddie

이렇게 하면 다음 메트릭이 노출돼요:

cnpg_some_query_rows{datname="albert"} 2
cnpg_some_query_rows{datname="bb"} 5
cnpg_some_query_rows{datname="freddie"} 10

다음은 자동 발견이 활성화되고 template1 데이터베이스에서도 실행되는 쿼리 예시(그렇지 않으면 앞서 말한 쿼리가 반환하지 않음)예요:

some_query: |
  query: |
    SELECT
     pg_catalog.current_database() as datname,
     pg_catalog.count(*) as rows
    FROM some_table
  metrics:
    - datname:
        usage: "LABEL"
        description: "Name of current database"
    - rows:
        usage: "GAUGE"
        description: "number of rows"
  target_databases:
    - "*"
    - "template1"

위 예시는 (데이터베이스가 존재한다면) 다음 메트릭을 생성해요:

cnpg_some_query_rows{datname="albert"} 2
cnpg_some_query_rows{datname="bb"} 5
cnpg_some_query_rows{datname="freddie"} 10
cnpg_some_query_rows{datname="template1"} 7
cnpg_some_query_rows{datname="postgres"} 42

사용자 정의 메트릭의 구조 (Structure of a user defined metric)

모든 커스텀 쿼리는 다음 기본 구조를 가져요:

<MetricName>:
      query: "<SQLQuery>"
      metrics:
        - <ColumnName>:
            usage: "<MetricType>"
            description: "<MetricDescription>"

사용 가능한 모든 필드에 대한 간단한 설명은 다음과 같아요:

  • <MetricName>: Prometheus 메트릭의 이름
    • name: 정의되면 <MetricName>을 재정의
    • query: 메트릭을 생성하기 위해 대상 데이터베이스에서 실행할 SQL 쿼리
    • primary: 쿼리를 primary 인스턴스에서만 실행할지 여부
    • master: primary와 동일 (Prometheus PostgreSQL exporter의 구문과의 호환용 - deprecated)
    • runonserver: 쿼리를 실행해야 하는 PostgreSQL 버전을 제한하는 시맨틱 버전 범위 (예: ">=11.0.0" 또는 ">=12.0.0 <=15.0.0")
    • target_databases: query를 실행할 데이터베이스 목록, 또는 자동 발견을 활성화하는 shell 유사 패턴. 제공되면 기본 데이터베이스를 재정의
    • predicate_query: 대상 데이터베이스에서 실행할, 최대 한 행과 하나의 boolean 열을 반환하는 SQL 쿼리. 시스템이 조건자를 평가하고 true이면 query를 실행
    • metrics: 다음으로 정의된 모든 내보내진 열 목록을 포함하는 섹션:
      • <ColumnName>: 쿼리가 반환한 열의 이름
        • name: 정의되면 메트릭에서 열의 ColumnName을 재정의
        • usage: 아래에 설명된 값 중 하나
        • description: 메트릭의 설명
        • metrics_mapping: usage가 MAPPEDMETRIC으로 설정될 때의 선택적 열 매핑

usage의 가능한 값은 다음과 같아요:

Column Usage Label Description (설명)
DISCARD 이 열은 무시해야 함
LABEL 이 열을 라벨로 사용
COUNTER 이 열을 카운터로 사용
GAUGE 이 열을 게이지로 사용
MAPPEDMETRIC 제공된 텍스트 값 매핑과 함께 이 열을 사용
DURATION 이 열을 텍스트 기간(밀리초)으로 사용
HISTOGRAM 이 열을 히스토그램으로 사용

자세한 내용은 Prometheus 문서의 "Metric Types" 페이지를 방문해 주세요.

사용자 정의 메트릭의 출력 (Output of a user defined metric)

커스텀 정의 메트릭은 Prometheus exporter 엔드포인트(:9187/metrics)에서 다음 형식으로 반환돼요:

cnpg_<MetricName>_<ColumnName>{<LabelColumnName>=<LabelColumnValue> ... } <ColumnValue>

:::note LabelColumnName은 usage가 LABEL로 설정된 메트릭과 그 Value예요 :::

위 pg_replication 예시를 고려하면, exporter 엔드포인트는 호출 시 다음 출력을 반환해요:

# HELP cnpg_pg_replication_in_recovery Whether the instance is in recovery
# TYPE cnpg_pg_replication_in_recovery gauge
cnpg_pg_replication_in_recovery 0
# HELP cnpg_pg_replication_lag Replication lag behind primary in seconds
# TYPE cnpg_pg_replication_lag gauge
cnpg_pg_replication_lag 0
# HELP cnpg_pg_replication_streaming_replicas Number of streaming replicas connected to the instance
# TYPE cnpg_pg_replication_streaming_replicas gauge
cnpg_pg_replication_streaming_replicas 2
# HELP cnpg_pg_replication_is_wal_receiver_up Whether the instance wal_receiver is up
# TYPE cnpg_pg_replication_is_wal_receiver_up gauge
cnpg_pg_replication_is_wal_receiver_up 0

기본 메트릭 세트 (Default set of metrics)

오퍼레이터는 오퍼레이터 네임스페이스 안의 ConfigMap 또는 Secret에 정의된 모니터링 쿼리 세트를 클러스터에 자동으로 주입하도록 구성할 수 있어요. "오퍼레이터 구성"에서 MONITORING_QUERIES_CONFIGMAP 또는 MONITORING_QUERIES_SECRET 키를 각각 ConfigMap 또는 Secret의 이름으로 설정해야 해요. 그러면 오퍼레이터는 queries 키의 내용을 사용해요.

queries 내용의 어떤 변경 사항도 그것을 사용하는 모든 배포된 클러스터에 즉시 반영돼요.

오퍼레이터 설치 매니페스트는 모든 클러스터가 사용할 cnpg-default-monitoring이라는 사전 정의된 ConfigMap과 함께 제공돼요. MONITORING_QUERIES_CONFIGMAP은 오퍼레이터 구성에서 기본적으로 cnpg-default-monitoring으로 설정돼요.

기본 메트릭 세트를 비활성화하려면:

  • 오퍼레이터 레벨에서 비활성화: 오퍼레이터 ConfigMap에서 MONITORING_QUERIES_CONFIGMAP/MONITORING_QUERIES_SECRET 키를 ""(빈 문자열)로 설정. 오퍼레이터 ConfigMap 변경은 오퍼레이터 재시작이 필요해요.
  • 특정 클러스터에 대해 비활성화: 클러스터에서 .spec.monitoring.disableDefaultQueries를 true로 설정.

:::info[Important] MONITORING_QUERIES_CONFIGMAP/MONITORING_QUERIES_SECRET을 통해 지정된 ConfigMap 또는 Secret은 항상 cnpg-default-monitoring이라는 고정 이름으로 클러스터 네임스페이스에 복사돼요. 따라서 기본 메트릭을 사용하려면 클러스터 네임스페이스에 이 이름의 ConfigMap을 만들지 말아야 해요. :::

Prometheus Postgres exporter와의 차이점 (Differences with the Prometheus Postgres exporter)

CloudNativePG는 PostgreSQL Prometheus Exporter에서 영감을 얻었지만 몇 가지 차이점이 있어요. 특히 cache_seconds 필드는 CloudNativePG의 exporter에서 구현되지 않아요.

메트릭 exporter 역할 수동 생성 (Manually creating the metrics exporter role)

오퍼레이터는 리컨실리에이션 중에 primary에 cnpg_metrics_exporter PostgreSQL 역할을 만들고, 이후 스트리밍 리플리케이션을 통해 스탠바이와 replica 클러스터로 전파해요.

역할이 누락된 경우(replica 클러스터가 primary보다 먼저 업그레이드됨, 역할 이전의 백업에서 복원, 우발적 제거), 리플리케이션 체인의 쓰기 가능한 primary(replica 클러스터의 지정된 primary가 아닌 소스 primary)에서 슈퍼유저로 다시 생성하세요:

CREATE ROLE cnpg_metrics_exporter WITH LOGIN NOSUPERUSER NOCREATEDB
    NOCREATEROLE NOREPLICATION NOBYPASSRLS INHERIT;
GRANT pg_monitor TO cnpg_metrics_exporter;

커스텀 모니터링 쿼리가 pg_monitor 범위 밖의 객체에 접근해야 한다면 필요한 권한을 명시적으로 부여하세요. 테이블에 대한 SELECT는 그 스키마에 대한 USAGE도 필요해요:

GRANT USAGE ON SCHEMA myschema TO cnpg_metrics_exporter;
GRANT SELECT ON TABLE myschema.mytable TO cnpg_metrics_exporter;

CloudNativePG 오퍼레이터 모니터링 (Monitoring the CloudNativePG operator)

오퍼레이터는 내부적으로 포트 8080의 metrics라는 이름으로 HTTP를 통해 Prometheus 메트릭을 노출해요.

:::info 내보내진 메트릭은 아래 "내보내진 메트릭을 확인하는 방법" 섹션의 지침을 따라 검사할 수 있어요. :::

현재 오퍼레이터는 기본 kubebuilder 메트릭을 노출해요. 자세한 내용은 kubebuilder 문서를 참고해 주세요.

Prometheus로 오퍼레이터 모니터링 (Monitoring the operator with Prometheus)

오퍼레이터는 다음과 같이 오퍼레이터 파드(들)를 가리키는 PodMonitor를 정의해 Prometheus Operator로 모니터링할 수 있어요 (오퍼레이터와 같은 네임스페이스에 적용됨):

kubectl -n cnpg-system apply -f - <<EOF
---
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: cnpg-controller-manager
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: cloudnative-pg
  podMetricsEndpoints:
    - port: metrics
EOF

오퍼레이터 메트릭에 TLS 활성화 (Enabling TLS for operator metrics)

기본적으로 오퍼레이터는 포트 8080에서 HTTP로 메트릭을 노출해요. 이 엔드포인트를 TLS로 보호하려면 다음 단계를 따르세요:

  1. TLS 인증서(tls.crt)와 개인 키(tls.key)를 포함한 Kubernetes Secret을 생성하세요.
  2. Secret을 오퍼레이터 파드에 마운트하세요.
  3. METRICS_CERT_DIR 환경 변수를 인증서가 마운트된 디렉토리를 가리키도록 설정하세요.

Secret 정의 예시:

apiVersion: v1
kind: Secret
metadata:
  name: cnpg-metrics-cert
  namespace: cnpg-system
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-certificate>
  tls.key: <base64-encoded-key>

다음으로 오퍼레이터 디플로이먼트를 업데이트해 시크릿을 마운트하고 환경 변수를 구성하세요:

spec:
  template:
    spec:
      containers:
      - name: manager
        env:
        - name: METRICS_CERT_DIR
          value: /run/secrets/cnpg.io/metrics
        volumeMounts:
        - mountPath: /run/secrets/cnpg.io/metrics
          name: metrics-certificates
          readOnly: true
      volumes:
      - name: metrics-certificates
        secret:
          secretName: cnpg-metrics-cert
          defaultMode: 420

:::note METRICS_CERT_DIR가 설정되면 오퍼레이터는 메트릭 서버에 대해 자동으로 TLS를 활성화해요. PodMonitor 구성을 https 스킴을 사용하도록 업데이트해야 해요. :::

TLS가 활성화된 PodMonitor 구성 예시:

apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: cnpg-controller-manager
  namespace: cnpg-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: cloudnative-pg
  podMetricsEndpoints:
    - port: metrics
      scheme: https
      tlsConfig:
        insecureSkipVerify: true  # or configure proper CA validation

내보내진 메트릭을 확인하는 방법 (How to inspect the exported metrics)

이 섹션에서는 특정 PostgreSQL 인스턴스 매니저(primary 또는 replica) 또는 오퍼레이터가 내보내는 메트릭을 확인하는 기본 지침을 제공해요.

:::note 아래 예시에서는 기본 네임스페이스에서 작업하고 오퍼레이터가 cnpg-system 네임스페이스에 설치되어 있다고 가정해요. 사용 사례에 맞게 조정해 주세요. :::

포트 포워딩 사용 (Using port forwarding)

메트릭을 확인하는 가장 간단한 방법은 관련 파드의 메트릭 포트를 포트 포워딩하는 것이에요.

예를 들어 cluster-example의 -1 인스턴스에서 메트릭을 확인하려면 9187 포트를 포트 포워딩해요:

kubectl port-forward cluster-example-1 9187:9187

포트 포워딩이 활성화되면 메트릭은 구성에 따라 HTTP 또는 HTTPS를 사용해 웹 브라우저에서 localhost:9187/metrics 주소로 쉽게 확인할 수 있어요.

오퍼레이터 파드도 포트 8080에서 메트릭을 내보내요. 인스턴스와 마찬가지로, 오퍼레이터 네임스페이스에 있는 오퍼레이터 파드를 포트 포워딩해요:

kubectl -n cnpg-system port-forward pod/<CONTROLLER-MANAGER-POD> 8080:8080

포트 포워딩이 활성화되면 메트릭은 브라우저에서 localhost:8080/metrics로 쉽게 볼 수 있어요.

curl 사용 (Using curl)

다음 명령으로 curl 파드를 생성하세요:

kubectl apply -f - <<EOF
---
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
  - name: curl
    image: curlimages/curl:8.22.0
    command: ['sleep', '3600']
EOF

인스턴스가 내보내는 메트릭을 확인하려면 대상 파드의 포트 9187에 연결해야 해요. 파드의 IP 주소를 알아야 하며, kubectl get pod -o wide를 실행해 쉽게 찾을 수 있어요. 다음 일반 명령은 원하는 파드에서 curl을 실행해요:

kubectl exec -ti curl -- curl -s <pod_ip>:9187/metrics

예를 들어 PostgreSQL 클러스터가 cluster-example이고 클러스터의 첫 번째 파드가 내보내는 메트릭을 검색하려면 다음 명령으로 그 파드의 IP를 프로그래밍 방식으로 얻을 수 있어요:

POD_IP=$(kubectl get pod cluster-example-1 --template '{{.status.podIP}}')

그런 다음 실행하세요:

kubectl exec -ti curl -- curl -s ${POD_IP}:9187/metrics

TLS 메트릭을 활성화했다면 대신 다음을 실행하세요:

kubectl exec -ti curl -- curl -sk https://${POD_IP}:9187/metrics

오퍼레이터의 메트릭에 접근하려면 오퍼레이터가 실행되는 파드를 가리키고 TCP 포트 8080을 대상으로 사용해야 해요.

메트릭 확인이 끝나면 curl 파드를 삭제하는 것을 잊지 마세요:

kubectl delete -f curl.yaml

보조 리소스 (Auxiliary resources)

:::info[Important] 이 리소스들은 설명과 실험을 위해 제공되며 프로덕션 시스템에 대한 어떤 종류의 권장 사항도 나타내지 않아요 :::

doc/src/samples/monitoring/ 디렉토리에서 관측성을 위한 일련의 샘플 파일을 찾을 수 있어요. 맥락은 quickstart의 4부 섹션을 참고해 주세요:

  • kube-stack-config.yaml: kube-stack helm 차트 설치용 구성 파일. Prometheus가 모든 PodMonitor 리소스를 듣도록 보장해요.
  • prometheusrule.yaml: CloudNativePG용 알림이 있는 PrometheusRule. 주의: 이 파일은 알림 서비스와의 상호운용을 포함하지 않아요. Prometheus 문서를 참고해 주세요.
  • podmonitor.yaml: CloudNativePG 오퍼레이터 배포용 PodMonitor.

또한 alerts.yaml 파일에 Prometheus 알림 규칙의 "raw" 소스를 제공해요.

CloudNativePG 클러스터와 오퍼레이터용 Grafana 대시보드는 전용 리포지토리 cloudnative-pg/grafana-dashboards에 대시보드 JSON 구성으로 보관돼요: grafana-dashboard.json. 이 파일은 다운로드해 Grafana로 가져올 수 있어요 (메뉴: Dashboard > New > Import).

kube-prometheus-stack에서 사용 가능한 설정에 대한 일반 참조는 helm show values prometheus-community/kube-prometheus-stack을 실행할 수 있어요. 자세한 내용은 kube-prometheus-stack 페이지를 참고해 주세요.