커넥션 풀링(Connection Pooling)
CloudNativePG는 PostgreSQL용 인기 오픈소스 커넥션 풀러인 PgBouncer를 Pooler 커스텀 리소스 정의(CRD)를 통해 네이티브로 지원해요. 애플리케이션과 PostgreSQL 사이에 확장 가능한 데이터 접근 계층을 만드는 방법을 정리해 드릴게요.
출처: 문서
본문
CloudNativePG는 PostgreSQL용 가장 인기 있는 오픈소스 커넥션 풀러 중 하나인 PgBouncer를 Pooler 커스텀 리소스 정의(CRD)를 통해 네이티브로 지원해요.
간단히 말해, CloudNativePG의 pooler는 애플리케이션과 PostgreSQL 서비스(예: rw 서비스) 사이에 위치하는 PgBouncer 파드의 배포예요. 별도의 확장 가능하고 구성 가능하며 고가용성인 데이터베이스 접근 계층을 만들어요.
:::warning
CloudNativePG는 PgBouncer의 auth_dbname 기능이 필요해요. PgBouncer 컨테이너 이미지 버전 1.19 이상을 사용해야 해요.
:::
아키텍처
다음 다이어그램은 PgBouncer 기반 데이터베이스 접근 계층을 도입하면 CloudNativePG의 아키텍처가 어떻게 바뀌는지 보여줘요. 애플리케이션은 PostgreSQL 프라이머리 서비스에 직접 연결하는 대신 PgBouncer의 동등한 서비스에 연결할 수 있어요. 이 능력은 기존 연결의 재사용을 가능하게 해 더 빠른 성능과 PostgreSQL 측의 더 나은 리소스 관리를 제공해요.

빠른 시작
이 예시는 CloudNativePG가 PgBouncer pooler를 어떻게 구현하는지 보여줘요:
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: pooler-example-rw
spec:
cluster:
name: cluster-example
instances: 3
type: rw
pgbouncer:
poolMode: session
parameters:
max_client_conn: "1000"
default_pool_size: "10"
:::info[Important] pooler 이름은 같은 네임스페이스의 어떤 클러스터 이름과도 같을 수 없어요. :::
:::warning
spec.cluster 필드는 생성 후 변경할 수 없어요(immutable). pooler를 다른 Cluster에 연결하려면 기존 리소스를 업데이트하는 대신 새 Pooler 리소스를 만들세요.
:::
이 예시는 pooler-example-rw라는 Pooler 리소스를 만들어요. 이는 cluster-example이라는 Postgres Cluster 리소스와 엄격하게 연결돼요. 읽기/쓰기 서비스(rw, 따라서 cluster-example-rw)로 식별되는 프라이머리를 가리켜요.
Pooler 리소스는 Postgres 클러스터와 같은 네임스페이스에 있어야 해요. 최신 안정 버전 PgBouncer 이미지를 실행하는 3개 파드의 Kubernetes 배포로 구성되며, session 풀링 모드로 구성되고 각각 최대 1000개 연결을 허용해요. PostgreSQL을 향한 기본 풀 크기는 10개의 사용자/데이터베이스 쌍이에요.
:::info[Important]
Pooler 리소스는 PgBouncer에서 * 폴백 데이터베이스만 설정해요. 이 설정은 클라이언트에서 전달되는 연결 문자열의 모든 파라미터가 PostgreSQL 서버로 중계된다는 뜻이에요. 자세한 내용은 PgBouncer 문서의 "Section [databases]"를 참고하세요.
:::
CloudNativePG는 또한 PgBouncer와 함께 사용되는 구성 파일을 포함하는 pooler 이름과 같은 시크릿을 만들어요.
:::note[API reference]
자세한 내용은 API 참조의 PgBouncerSpec을 참고하세요.
:::
Pooler 리소스 수명 주기
Pooler 리소스는 오퍼레이터가 자동으로 관리하지 않아요. 필요할 때 수동으로 만들며, 같은 PostgreSQL 클러스터에 여러 pooler를 배포할 수 있어요.
이해해야 할 핵심은 Cluster와 Pooler 리소스의 수명 주기가 독립적이라는 거예요. 클러스터를 삭제한다고 pooler가 자동으로 제거되지 않고, pooler를 삭제한다고 클러스터에 영향이 없어요.
:::info pooler가 어떻게 동작하는지 익숙해지면 아키텍처 설계에 완전한 유연성을 갖게 돼요. pooler 없이 클러스터를 실행하거나, 단일 pooler가 있는 클러스터, 또는 여러 pooler가 있는 클러스터(예: 애플리케이션당 하나)를 실행할 수 있어요. :::
:::info[Important] 오퍼레이터 자체가 업그레이드되면 pooler 파드도 롤링 업그레이드를 겪어요. 이는 pooler 파드 안의 인스턴스 매니저가 일관되게 업그레이드되도록 보장해요. :::
보안
모든 PgBouncer pooler는 풀의 클라이언트(애플리케이션) 측과 서버(PostgreSQL) 측 모두에서 TLS 연결을 통한 전송 중 암호화에 대한 CloudNativePG 지원과 투명하게 통합돼요.
컨테이너는 pgbouncer 시스템 사용자로 실행되며, pgbouncer 관리 데이터베이스에 대한 접근은 peer 인증을 통한 로컬 연결로만 허용돼요.
인증서
기본적으로 PgBouncer pooler는 PostgreSQL 클러스터와 같은 인증서를 재사용해요. PostgreSQL 서버에 연결하고 클라이언트 패스워드 인증에 사용되는 auth_query를 실행하는 데 TLS 클라이언트 인증서 인증에 의존해요. ("인증" 참고)
자체 시크릿을 제공하면 내장 통합이 비활성화돼요. 그 시점부터 인증 관리를 완전히 제어(그리고 책임)하게 돼요. 지원되는 시크릿 형식은:
- Basic Auth
- TLS
- Opaque
Opaque 시크릿의 경우 Pooler 리소스는 다음 키를 기대해요:
tls.crttls.key
실질적으로 이는 Opaque 시크릿을 같은 구조에서 시작하는 TLS 시크릿처럼 취급할 수 있다는 뜻이에요.
인증
기본 인증 방법
기본적으로 CloudNativePG는 PostgreSQL 데이터베이스에 연결하는 PgBouncer 클라이언트에 대해 패스워드 기반 인증을 네이티브로 지원해요.
이 내장 메커니즘은 버전 1.19에서 도입된 PgBouncer의 auth_dbname을 auth_user와 auth_query 옵션과 함께 활용해요.
:::info[Important] 자체 인증서 시크릿을 제공하면 내장 통합이 비활성화돼요. 이 경우 PgBouncer 인증을 구성하고 관리하는 것은 전적으로 사용자 책임이에요. :::
내장 통합은 다음 작업을 수행해요:
- PostgreSQL 서버에
cnpg_pooler_pgbouncer라는 전용 사용자 만듦 postgres데이터베이스에 조회 함수를 만들고cnpg_pooler_pgbouncer에 실행 권한을 부여함 (PoLA 원칙에 따라)- 이 사용자에 대한 TLS 인증서 발급
- PgBouncer가
cnpg_pooler_pgbouncer를auth_user로,postgres를auth_dbname으로 사용하도록 구성 - PgBouncer가 발급된 TLS 인증서를 사용해 PostgreSQL에 대해
cnpg_pooler_pgbouncer를 인증하도록 구성 - 클러스터와 연결된 pooler가 없으면 위의 모든 것을 자동으로 정리
SQL 지침
내장 통합의 일부로 CloudNativePG는 정합성 조정 중에 일련의 SQL 문을 자동으로 실행해요. 이 문들은 인스턴스 매니저가 postgres 사용자를 사용해 postgres 데이터베이스에 대해 실행해요.
역할 생성:
CREATE ROLE cnpg_pooler_pgbouncer WITH LOGIN;
postgres 데이터베이스에 대한 접근 권한 부여:
GRANT CONNECT ON DATABASE postgres TO cnpg_pooler_pgbouncer;
패스워드 검증용 조회 함수 생성. 이 함수는 postgres 데이터베이스에 SECURITY DEFINER 권한으로 생성되며 PgBouncer의 auth_query 옵션에서 사용돼요. 함수 소유자로 실행되므로 함수 본문이 호출자 또는 테넌트가 제어하는 search_path를 통해 연산자나 오브젝트를 해석할 수 없도록 search_path가 pg_catalog, pg_temp로 고정돼요:
CREATE OR REPLACE FUNCTION public.user_search(uname TEXT)
RETURNS TABLE (usename name, passwd text)
LANGUAGE sql SECURITY DEFINER
SET search_path = pg_catalog, pg_temp AS
'SELECT usename, passwd FROM pg_catalog.pg_shadow WHERE usename=$1;';
:::note
이전 버전의 CloudNativePG로 생성된 클러스터는 고정된 search_path 없이 user_search 함수를 가지고 있어요. 클러스터가 업그레이드될 때 오퍼레이터가 정합성 조정 중 SET search_path 절로 함수를 자동으로 다시 만들어요.
:::
조회 함수에 대한 권한 제한 및 부여:
REVOKE ALL ON FUNCTION public.user_search(text)
FROM public;
GRANT EXECUTE ON FUNCTION public.user_search(text)
TO cnpg_pooler_pgbouncer;
커스텀 인증 방법
자체 인증서 시크릿을 제공하면 내장 통합이 비활성화돼요.
이것은 인증 프로세스를 직접 관리할 수 있는 유연성 — 그리고 책임 — 을 줘요. 위의 지침을 따라 기본 설정과 유사한 동작을 복제할 수 있어요.
PgBouncer가 시크릿에서 파생된 사용자와 다른 사용자로 인증해야 한다면 auth_user 파라미터로 오버라이드할 수 있어요. (PgBouncer 구성 옵션 참고)
파드 템플릿
Pooler 리소스는 template 섹션을 통해 기본 파드를 커스터마이즈할 수 있게 해줘요. 이는 스케줄링 제약, 커스텀 보안 컨텍스트, 리소스 오버라이드 같은 고급 구성을 위해 Kubernetes PodSpec에 대한 전체 접근을 제공해요.
지원되는 필드의 전체 목록은 PoolerSpec API 참조를 참고하세요.
핵심 요구사항
-
pgbouncer컨테이너 이름: 컨테이너 설정(이미지나 리소스 같은)을 오버라이드할 때 컨테이너의 이름 반드시pgbouncer로 설정해야 해요. 오퍼레이터는 PgBouncer 프로세스를 관리하기 위해 이 특정 이름을 찾아요. -
필수
containers필드:template은 표준 KubernetesPodSpec스키마를 따르므로containers필드는 필수예요. -
컨테이너 레벨 설정을 수정하지 않는다면 빈 배열로 설정해야 해요:
containers: []. -
containers필드가 없으면 API 서버가ValidationError를 던져요.
예시
파드 안티-친화성으로 고가용성
이 구성은 podAntiAffinity를 사용해 PgBouncer 파드가 서로 다른 노드에 분산되도록 보장하며, 단일 노드 장애가 전체 풀을 중단시키는 것을 방지해요.
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: pooler-example-rw
spec:
cluster:
name: cluster-example
instances: 3
type: rw
template:
metadata:
labels:
app: pooler
spec:
containers: []
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pooler
topologyKey: "kubernetes.io/hostname"
리소스 한도
template 섹션에 pgbouncer라는 이름의 컨테이너를 추가해 리소스 요청(request)과 한도(limit)를 정의할 수 있어요:
# ...
template:
metadata:
# ...
spec:
containers:
# This name MUST be "pgbouncer"
- name: pgbouncer
resources:
requests:
cpu: "0.1"
memory: 100Mi
limits:
cpu: "0.5"
memory: 500Mi
PgBouncer 이미지
기본적으로 CloudNativePG는 오퍼레이터가 빌드된 최신 안정 PgBouncer 이미지를 배포해요. 그 기본값을 세 가지 방식으로 오버라이드할 수 있어요. 둘 이상이 설정되면 소스는 위에서 아래로 평가되며, 아래 목록의 첫 번째 일치가 사용돼요. 일치가 없으면 오퍼레이터의 내장 기본값이 적용돼요:
spec.template.spec.containers안의pgbouncer컨테이너에 설정된 명시적 이미지 (이스케이프 해치 — 아래 파드-템플릿 오버라이드 참고).spec.pgbouncer.image—Pooler에 직접 설정된 이미지 참조.spec.pgbouncer.imageCatalogRef—ImageCatalog또는ClusterImageCatalog의 항목 참조.
spec.pgbouncer.image와 spec.pgbouncer.imageCatalogRef는 상호 배타적이며, 최대 하나만 설정해요.
:::warning[Policy gating]
실행할 수 있는 PgBouncer 이미지를 제한하는 허용 정책(admission policy)을 강제한다면, 그 정책은 세 가지 이미지 소스 모두를 게이트해야 해요: spec.pgbouncer.image, spec.pgbouncer.imageCatalogRef, 그리고 spec.template.spec.containers 안의 pgbouncer 컨테이너의 image 필드. 처음 두 개만 다루는 정책은 파드-템플릿 오버라이드를 보호되지 않은 이스케이프 해치로 남겨 둬요. 같은 고려 사항이 Cluster.spec.imageName과 Cluster.spec.imageCatalogRef에도 적용돼요.
:::
명시적 이미지 설정
spec.pgbouncer.image를 사용해 특정 PgBouncer 버전을 고정하거나 프라이빗 레지스트리에서 가져올 수 있어요:
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: pooler-example-rw
spec:
cluster:
name: cluster-example
instances: 3
type: rw
pgbouncer:
poolMode: session
image: ghcr.io/cloudnative-pg/pgbouncer:1.25.1
이미지 카탈로그 사용
Pooler 리소스는 Cluster 리소스와 같은 패턴(Image Catalog 참고)으로 ImageCatalog 또는 ClusterImageCatalog를 통해 PgBouncer 컨테이너 이미지를 중앙에서 관리할 수 있어요. 카탈로그 항목은 카탈로그의 componentImages 목록에 정의된 key로 선택돼요.
카탈로그 항목을 spec.pgbouncer.imageCatalogRef로 참조하세요:
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: pooler-example-rw
spec:
cluster:
name: cluster-example
instances: 3
type: rw
pgbouncer:
poolMode: session
imageCatalogRef:
apiGroup: postgresql.cnpg.io
kind: ImageCatalog
name: my-catalog
key: pgbouncer
대신 클러스터 전체 카탈로그를 사용하려면 kind: ClusterImageCatalog로 설정하고 name을 해당 리소스에 연결하세요. 나머지 spec은 동일해요.
카탈로그 항목이 업데이트되면 오퍼레이터는 이를 참조하는 모든 pooler를 자동으로 정합성 조정하고 Pooler spec의 변경 없이 새 이미지를 롤아웃해요.
파드-템플릿 오버라이드
파드 템플릿도 pgbouncer 컨테이너에 image를 담을 수 있으며, 이 경우 다른 모든 소스(포함 spec.pgbouncer.image와 spec.pgbouncer.imageCatalogRef)를 오버라이드해요. 이를 이스케이프 해치로 취급하세요. 다른 컨테이너 레벨 설정(리소스, 환경, 보안 컨텍스트)을 커스터마이즈해야 할 때만 사용하고, 같은 위치에 이미지를 고정하고 싶을 때만 사용하세요. 일상적인 이미지 변경에는 spec.pgbouncer.image 또는 이미지 카탈로그를 선호하세요. 해당 필드는 검증되고, 상호 배타적이며, PgBouncer 이미지를 게이트하는 허용 정책에 보이기 때문이에요(더 넓은 템플릿 메커니즘은 파드 템플릿 참고):
# ...
template:
spec:
containers:
- name: pgbouncer
image: my-pgbouncer:latest
해석된 이미지 모니터링
오퍼레이터는 해석된 이미지를 status.image에 저장하고 결과를 status.phase에 반영해요. 이 값은 active, paused, inactive, failed 중 하나예요. failed인 경우 status.phaseReason이 원인을 설명해요(예: 카탈로그나 키가 존재하지 않는 경우). 다음으로 현재 상태를 검사할 수 있어요:
kubectl get pooler pooler-example-rw -o jsonpath='{.status.image}'
:::note[API reference]
자세한 내용은 API 참조의 PgBouncerSpec을 참고하세요.
:::
서비스 템플릿
때때로 pooler에 다른 레이블, 어노테이션, 또는 다른 Service 유형이 필요할 수 있어요. serviceTemplate 필드를 사용해 이를 달성할 수 있어요:
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: pooler-example-rw
spec:
cluster:
name: cluster-example
instances: 3
type: rw
serviceTemplate:
metadata:
labels:
app: pooler
spec:
type: LoadBalancer
pgbouncer:
poolMode: session
parameters:
max_client_conn: "1000"
default_pool_size: "10"
오퍼레이터는 기본적으로 다음 데이터가 있는 ServicePort를 추가해요:
ports:
- name: pgbouncer
port: 5432
protocol: TCP
targetPort: pgbouncer
:::warning
이름이 pgbouncer이거나 포트 5432인 ServicePort를 지정하면 기본 ServicePort가 추가되지 않아요. Kubernetes에서는 같은 name 또는 port를 가진 ServicePort 항목이 허용되지 않아 오류가 발생하기 때문이에요.
:::
고가용성 (HA)
Kubernetes의 Deployment 덕분에 pooler를 단일 인스턴스나 여러 파드에서 실행하도록 구성할 수 있어요. 노출된 서비스는 클라이언트가 PgBouncer를 실행하는 사용 가능한 파드에 무작위로 분산되도록 보장하며, PgBouncer는 기본 서버(rw 서비스를 사용하는 경우) 또는 서버들(ro 서비스를 여러 복제본과 사용하는 경우)에 대한 연결을 관리하고 재사용해요.
:::warning 인프라가 여러 가용 영역에 걸쳐 있고 영역 간 지연 시간이 높다면 네트워크 홉을 주의하세요. 예를 들어 애플리케이션은 영역 2에서 실행되고, 영역 3의 PgBouncer에 연결하며, 영역 1의 PostgreSQL 프라이머리를 가리키는 경우를 고려하세요. :::
PgBouncer 구성 옵션
오퍼레이터는 PgBouncer의 구성 옵션 대부분을 관리하며 사용자가 그 중 일부만 수정할 수 있게 해줘요.
:::warning 오퍼레이터는 이러한 설정을 검증 없이 PgBouncer에 직접 전달해요. 구성 오류나 크래시 루프를 방지하려면 각 파라미터가 특정 PgBouncer 이미지 버전에서 지원되는지 확인하세요. :::
커스터마이즈할 수 있는 PgBouncer 옵션은 다음과 같아요 (각 파라미터에 대한 PgBouncer 문서 링크 포함). 별도 언급이 없으면 기본값은 PgBouncer가 직접 설정하는 값이에요.
-
auth_type: 기본값hba는 Pooler 자체의.spec.pgbouncer.pg_hba목록에서 각 클라이언트의 인증 방법을 해석해요. (Cluster의.spec.postgresql.pg_hba와 혼동하지 마세요. 후자는 PostgreSQL 자체의 클라이언트 인증을 관장해요.) -
auth_user: PgBouncer가 인증 쿼리를 실행하기 위해 연결하는 사용자를 오버라이드해요. 기본적으로 인증 쿼리 시크릿에서 파생돼요(basic-auth 시크릿의username키, TLS 시크릿의 인증서 common name). 설정하는 것은 커스텀authQuerySecret이 있는 설정을 위한 것으로, 내장 통합이 기본cnpg_pooler_pgbouncer사용자를 위해 모든 것을 프로비저닝하기 때문이에요. basic-auth 시크릿에서는 사용자 이름만 바뀌어요. PgBouncer는 여전히 시크릿의 패스워드 필드를 사용해 자체 auth_query 연결을 인증하므로, 그 필드에는 오버라이드된auth_user역할의 실제 PostgreSQL 패스워드가 포함되어야 해요. 원래 사용자 이름의 패스워드가 아니에요. 빈 값은 무시돼요.auth_user를 커스터마이즈한 후에는 해당 사용자가 PostgreSQL에 존재하고auth_query를 실행하는 데 필요한 권한이 있는지 확인하세요. ("인증" 참고)인증 쿼리 시크릿이 인증서 기반이면
auth_user를 오버라이드해도 PgBouncer가 auth 쿼리 중 요청하는 역할만 바뀌고, PgBouncer가 제시하는 인증서는 바뀌지 않아요. 오퍼레이터의 내장pg_hba/pg_ident규칙은 리터럴cnpg_pooler_pgbouncer사용자만 일치시키므로, 커스텀auth_user는 자체pg_hba규칙과 인증서의 common name을 새 역할에 매핑하는 일치하는pg_ident항목이Cluster에 필요해요. (pg_hba및pg_ident섹션 참고) 예시:postgresql: pg_ident: - poolermap pooler-client-cn pgbouncer pg_hba: - hostssl all pgbouncer all cert map=poolermap이 매핑이 없으면
auth_user가 고정 규칙이 인식하는 인증서 신원과 더 이상 일치하지 않을 때 auth_query 연결이 인증에 실패해요.PgBouncer는
auth_type이 해석한 클라이언트 측 방법이 패스워드 기반(md5/scram)일 때만auth_query/auth_user를 참조해요. Pooler 자체pg_hba에peer,cert, 또는trust규칙이 있으면 해당 클라이언트에는 오버라이드가 효과가 없어요. -
client_tls13_ciphers(1.25+) -
ignore_startup_parameters:extra_float_digits,options에 추가돼야 함 - CloudNativePG에 필요 -
log_stats: 아래 "Monitoring" 섹션에서 설명하는 Prometheus export로 통계가 이미 수집되므로 기본적으로 비활성화(0) -
server_tls13_ciphers(1.25+)
PgBouncer 구성의 커스터마이즈는 .spec.pgbouncer.parameters 맵에 선언적으로 작성돼요.
오퍼레이터는 pooler 사양의 변경에 반응하며, 모든 PgBouncer 인스턴스가 서비스를 방해하지 않고 업데이트된 구성을 리로드해요.
:::warning 모든 PgBouncer 파드는 사양의 파라미터와 일치하는 동일한 구성을 가져요. 이 파라미터의 실수는 전체 pooler의 운영성을 방해할 수 있어요. 오퍼레이터는 어떤 옵션의 값도 검증하지 않아요. :::
모니터링
Pooler의 PgBouncer 구현은 기본 Prometheus exporter와 함께 제공돼요. 다음을 실행해 cnpg_pgbouncer_ 접두사가 붙은 여러 메트릭을 사용할 수 있게 해줘요:
SHOW LISTS(접두사:cnpg_pgbouncer_lists)SHOW POOLS(접두사:cnpg_pgbouncer_pools)SHOW STATS(접두사:cnpg_pgbouncer_stats)
CloudNativePG 인스턴스처럼 exporter는 PgBouncer를 실행하는 각 파드의 포트 9127에서 실행되며 Go 런타임 관련 메트릭(go_* 접두사)도 제공해요.
:::info
PgBouncer를 실행하는 파드에서 내보낸 메트릭을 검사할 수 있어요. 지침은 How to inspect the exported metrics를 참고하세요. 올바른 IP와 9127 포트를 사용하세요.
:::
이 예시는 cnpg_pgbouncer 메트릭의 출력을 보여줘요:
# HELP cnpg_pgbouncer_collection_duration_seconds Collection time duration in seconds
# TYPE cnpg_pgbouncer_collection_duration_seconds gauge
cnpg_pgbouncer_collection_duration_seconds{collector="Collect.up"} 0.002338805
# HELP cnpg_pgbouncer_collection_errors_total Total errors occurred accessing PostgreSQL for metrics.
# TYPE cnpg_pgbouncer_collection_errors_total counter
# HELP cnpg_pgbouncer_collections_total Total number of times PostgreSQL was accessed for metrics.
# TYPE cnpg_pgbouncer_collections_total counter
cnpg_pgbouncer_collections_total 5
# HELP cnpg_pgbouncer_last_collection_error 1 if the last collection ended with error, 0 otherwise.
# TYPE cnpg_pgbouncer_last_collection_error gauge
cnpg_pgbouncer_last_collection_error 0
# HELP cnpg_pgbouncer_lists_databases Count of databases.
# TYPE cnpg_pgbouncer_lists_databases gauge
cnpg_pgbouncer_lists_databases 1
# HELP cnpg_pgbouncer_lists_dns_names Count of DNS names in the cache.
# TYPE cnpg_pgbouncer_lists_dns_names gauge
cnpg_pgbouncer_lists_dns_names 0
# HELP cnpg_pgbouncer_lists_dns_pending Not used.
# TYPE cnpg_pgbouncer_lists_dns_pending gauge
cnpg_pgbouncer_lists_dns_pending 0
# HELP cnpg_pgbouncer_lists_dns_queries Count of in-flight DNS queries.
# TYPE cnpg_pgbouncer_lists_dns_queries gauge
cnpg_pgbouncer_lists_dns_queries 0
# HELP cnpg_pgbouncer_lists_dns_zones Count of DNS zones in the cache.
# TYPE cnpg_pgbouncer_lists_dns_zones gauge
cnpg_pgbouncer_lists_dns_zones 0
# HELP cnpg_pgbouncer_lists_free_clients Count of free clients.
# TYPE cnpg_pgbouncer_lists_free_clients gauge
cnpg_pgbouncer_lists_free_clients 49
# HELP cnpg_pgbouncer_lists_free_servers Count of free servers.
# TYPE cnpg_pgbouncer_lists_free_servers gauge
cnpg_pgbouncer_lists_free_servers 0
# HELP cnpg_pgbouncer_lists_login_clients Count of clients in login state.
# TYPE cnpg_pgbouncer_lists_login_clients gauge
cnpg_pgbouncer_lists_login_clients 0
# HELP cnpg_pgbouncer_lists_pools Count of pools.
# TYPE cnpg_pgbouncer_lists_pools gauge
cnpg_pgbouncer_lists_pools 1
# HELP cnpg_pgbouncer_lists_used_clients Count of used clients.
# TYPE cnpg_pgbouncer_lists_used_clients gauge
cnpg_pgbouncer_lists_used_clients 1
# HELP cnpg_pgbouncer_lists_used_servers Count of used servers.
# TYPE cnpg_pgbouncer_lists_used_servers gauge
cnpg_pgbouncer_lists_used_servers 0
# HELP cnpg_pgbouncer_lists_users Count of users.
# TYPE cnpg_pgbouncer_lists_users gauge
cnpg_pgbouncer_lists_users 2
# HELP cnpg_pgbouncer_pools_cl_active Client connections that are linked to server connection and can process queries.
# TYPE cnpg_pgbouncer_pools_cl_active gauge
cnpg_pgbouncer_pools_cl_active{database="pgbouncer",user="pgbouncer"} 1
# HELP cnpg_pgbouncer_pools_cl_active_cancel_req Client connections that have forwarded query cancellations to the server and are waiting for the server response.
# TYPE cnpg_pgbouncer_pools_cl_active_cancel_req gauge
cnpg_pgbouncer_pools_cl_active_cancel_req{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_cl_cancel_req Client connections that have not forwarded query cancellations to the server yet.
# TYPE cnpg_pgbouncer_pools_cl_cancel_req gauge
cnpg_pgbouncer_pools_cl_cancel_req{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_cl_waiting Client connections that have sent queries but have not yet got a server connection.
# TYPE cnpg_pgbouncer_pools_cl_waiting gauge
cnpg_pgbouncer_pools_cl_waiting{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_cl_waiting_cancel_req Client connections that have not forwarded query cancellations to the server yet.
# TYPE cnpg_pgbouncer_pools_cl_waiting_cancel_req gauge
cnpg_pgbouncer_pools_cl_waiting_cancel_req{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_load_balance_hosts The host load balancing mode in use. 1 for disable, 2 for round-robin, 0 when the pool has a single host, -1 if unknown
# TYPE cnpg_pgbouncer_pools_load_balance_hosts gauge
cnpg_pgbouncer_pools_load_balance_hosts{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_maxwait How long the first (oldest) client in the queue has waited, in seconds. If this starts increasing, then the current pool of servers does not handle requests quickly enough. The reason may be either an overloaded server or just too small of a pool_size setting.
# TYPE cnpg_pgbouncer_pools_maxwait gauge
cnpg_pgbouncer_pools_maxwait{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_maxwait_us Microsecond part of the maximum waiting time.
# TYPE cnpg_pgbouncer_pools_maxwait_us gauge
cnpg_pgbouncer_pools_maxwait_us{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_pool_mode The pooling mode in use. 1 for session, 2 for transaction, 3 for statement, -1 if unknown
# TYPE cnpg_pgbouncer_pools_pool_mode gauge
cnpg_pgbouncer_pools_pool_mode{database="pgbouncer",user="pgbouncer"} 3
# HELP cnpg_pgbouncer_pools_sv_active Server connections that are linked to a client.
# TYPE cnpg_pgbouncer_pools_sv_active gauge
cnpg_pgbouncer_pools_sv_active{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_sv_active_cancel Server connections that are currently forwarding a cancel request
# TYPE cnpg_pgbouncer_pools_sv_active_cancel gauge
cnpg_pgbouncer_pools_sv_active_cancel{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_sv_idle Server connections that are unused and immediately usable for client queries.
# TYPE cnpg_pgbouncer_pools_sv_idle gauge
cnpg_pgbouncer_pools_sv_idle{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_sv_login Server connections currently in the process of logging in.
# TYPE cnpg_pgbouncer_pools_sv_login gauge
cnpg_pgbouncer_pools_sv_login{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_sv_tested Server connections that are currently running either server_reset_query or server_check_query.
# TYPE cnpg_pgbouncer_pools_sv_tested gauge
cnpg_pgbouncer_pools_sv_tested{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_sv_used Server connections that have been idle for more than server_check_delay, so they need server_check_query to run on them before they can be used again.
# TYPE cnpg_pgbouncer_pools_sv_used gauge
cnpg_pgbouncer_pools_sv_used{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_pools_sv_wait_cancels Servers that normally could become idle, but are waiting to do so until all in-flight cancel requests have completed that were sent to cancel a query on this server.
# TYPE cnpg_pgbouncer_pools_sv_wait_cancels gauge
cnpg_pgbouncer_pools_sv_wait_cancels{database="pgbouncer",user="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_bind_count Average number of prepared statements readied for execution by clients and forwarded to PostgreSQL by pgbouncer.
# TYPE cnpg_pgbouncer_stats_avg_bind_count gauge
cnpg_pgbouncer_stats_avg_bind_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_client_parse_count Average number of prepared statements created by clients.
# TYPE cnpg_pgbouncer_stats_avg_client_parse_count gauge
cnpg_pgbouncer_stats_avg_client_parse_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_query_count Average queries per second in last stat period.
# TYPE cnpg_pgbouncer_stats_avg_query_count gauge
cnpg_pgbouncer_stats_avg_query_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_query_time Average query duration, in microseconds.
# TYPE cnpg_pgbouncer_stats_avg_query_time gauge
cnpg_pgbouncer_stats_avg_query_time{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_recv Average received (from clients) bytes per second.
# TYPE cnpg_pgbouncer_stats_avg_recv gauge
cnpg_pgbouncer_stats_avg_recv{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_sent Average sent (to clients) bytes per second.
# TYPE cnpg_pgbouncer_stats_avg_sent gauge
cnpg_pgbouncer_stats_avg_sent{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_server_assignment_count Average number of times a server was assigned to a client per second in the last stat period.
# TYPE cnpg_pgbouncer_stats_avg_server_assignment_count gauge
cnpg_pgbouncer_stats_avg_server_assignment_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_server_parse_count Average number of prepared statements created by pgbouncer on a server.
# TYPE cnpg_pgbouncer_stats_avg_server_parse_count gauge
cnpg_pgbouncer_stats_avg_server_parse_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_wait_time Time spent by clients waiting for a server, in microseconds (average per second).
# TYPE cnpg_pgbouncer_stats_avg_wait_time gauge
cnpg_pgbouncer_stats_avg_wait_time{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_xact_count Average transactions per second in last stat period.
# TYPE cnpg_pgbouncer_stats_avg_xact_count gauge
cnpg_pgbouncer_stats_avg_xact_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_avg_xact_time Average transaction duration, in microseconds.
# TYPE cnpg_pgbouncer_stats_avg_xact_time gauge
cnpg_pgbouncer_stats_avg_xact_time{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_bind_count Total number of prepared statements readied for execution by clients and forwarded to PostgreSQL by pgbouncer
# TYPE cnpg_pgbouncer_stats_total_bind_count gauge
cnpg_pgbouncer_stats_total_bind_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_client_parse_count Total number of prepared statements created by clients.
# TYPE cnpg_pgbouncer_stats_total_client_parse_count gauge
cnpg_pgbouncer_stats_total_client_parse_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_query_count Total number of SQL queries pooled by pgbouncer.
# TYPE cnpg_pgbouncer_stats_total_query_count gauge
cnpg_pgbouncer_stats_total_query_count{database="pgbouncer"} 15
# HELP cnpg_pgbouncer_stats_total_query_time Total number of microseconds spent by pgbouncer when actively connected to PostgreSQL, executing queries.
# TYPE cnpg_pgbouncer_stats_total_query_time gauge
cnpg_pgbouncer_stats_total_query_time{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_received Total volume in bytes of network traffic received by pgbouncer.
# TYPE cnpg_pgbouncer_stats_total_received gauge
cnpg_pgbouncer_stats_total_received{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_sent Total volume in bytes of network traffic sent by pgbouncer.
# TYPE cnpg_pgbouncer_stats_total_sent gauge
cnpg_pgbouncer_stats_total_sent{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_server_assignment_count Total number of times a server was assigned to a client.
# TYPE cnpg_pgbouncer_stats_total_server_assignment_count gauge
cnpg_pgbouncer_stats_total_server_assignment_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_server_parse_count Total number of prepared statements created by pgbouncer on a server.
# TYPE cnpg_pgbouncer_stats_total_server_parse_count gauge
cnpg_pgbouncer_stats_total_server_parse_count{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_wait_time Time spent by clients waiting for a server, in microseconds.
# TYPE cnpg_pgbouncer_stats_total_wait_time gauge
cnpg_pgbouncer_stats_total_wait_time{database="pgbouncer"} 0
# HELP cnpg_pgbouncer_stats_total_xact_count Total number of SQL transactions pooled by pgbouncer.
# TYPE cnpg_pgbouncer_stats_total_xact_count gauge
cnpg_pgbouncer_stats_total_xact_count{database="pgbouncer"} 15
# HELP cnpg_pgbouncer_stats_total_xact_time Total number of microseconds spent by pgbouncer when connected to PostgreSQL in a transaction, either idle in transaction or executing queries.
# TYPE cnpg_pgbouncer_stats_total_xact_time gauge
cnpg_pgbouncer_stats_total_xact_time{database="pgbouncer"} 0
:::info 메트릭에 대한 더 나은 이해를 위해 PgBouncer 문서를 참고하세요. :::
클러스터와 마찬가지로 특정 pooler는 Prometheus operator의 PodMonitor 리소스를 사용해 모니터링할 수 있어요.
다음 기본 예시를 사용해 특정 pooler용 PodMonitor를 배포하고 필요에 따라 변경할 수 있어요:
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: <POOLER_NAME>
spec:
selector:
matchLabels:
cnpg.io/poolerName: <POOLER_NAME>
podMetricsEndpoints:
- port: metrics
메트릭 엔드포인트용 TLS
.spec.monitoring.tls.enabled: true로 설정해 메트릭 엔드포인트를 HTTPS로 서빙해요. 기본적으로 클러스터의 서버 인증서가 사용돼요. 인증서는 매 TLS 핸드셰이크마다 리로드되므로, 파드를 재시작하지 않고도 회전이 반영돼요.
spec:
monitoring:
tls:
enabled: true
.spec.pgbouncer.clientTLSSecret가 설정되면 메트릭 서버는 대신 해당 인증서를 제시해요.
spec:
pgbouncer:
clientTLSSecret:
name: <CLIENT_TLS_SECRET>
monitoring:
tls:
enabled: true
생성된 PodMonitor는 insecureSkipVerify=true로 스크래핑해요. Prometheus가 파드를 IP로 스크래핑하고 인증서의 SAN이 일반적으로 파드 IP를 포함하지 않기 때문이에요.
엄격한 검증이 필요하면 .spec.monitoring.enablePodMonitor: false로 설정하고 PodMonitor를 직접 관리하세요. 오퍼레이터가 생성한 것은 insecureSkipVerify=true로 하드코딩되고 매 정합성 조정마다 spec을 덮어쓰므로, 생성된 PodMonitor에 수동 패치를 해도 유지되지 않아요.
자동 PodMonitor 생성의 폐기(deprecation)
:::warning[Feature Deprecation Notice]
Pooler 리소스의 .spec.monitoring.enablePodMonitor 필드는 이제 deprecated이며 향후 오퍼레이터 버전에서 제거될 거예요.
:::
현재 이 기능을 사용 중이라면 .spec.monitoring.enablePodMonitor를 제거하거나 false로 설정하고 위에서 설명한 대로 pooler용 PodMonitor 리소스를 수동으로 만들 것을 강력히 권장해요. 이 변경은 모니터링 구성을 완전히 소유하도록 보장하며, 오퍼레이터가 이를 관리하거나 덮어쓰는 것을 방지해요.
로깅
로그는 다음과 같은 예시처럼 JSON 형식으로 표준 출력에 직접 전송돼요:
{
"level": "info",
"ts": SECONDS.MICROSECONDS,
"msg": "record",
"pipe": "stderr",
"record": {
"timestamp": "YYYY-MM-DD HH:MM:SS.MS UTC",
"pid": "<PID>",
"level": "LOG",
"msg": "kernel file descriptor limit: 1048576 (hard: 1048576); max_client_conn: 100, max expected fd use: 112"
}
}
연결 일시 중지
Pooler 사양을 사용하면 선언적 구성만으로 PgBouncer의 PAUSE와 RESUME 명령을 활용할 수 있어요. 기본값이 false인 paused 옵션으로 이를 할 수 있어요. true로 설정하면 오퍼레이터가 내부적으로 PgBouncer에 PAUSE 명령을 호출하며, 이는:
- 쿼리 완료를 기다린 후 PostgreSQL 서버로의 모든 활성 연결을 닫음
- 클라이언트에서 오는 새 연결을 일시 중지함
paused 옵션을 false로 재설정하면 오퍼레이터가 PgBouncer에 RESUME 명령을 호출해 Pooler 리소스에 정의된 PostgreSQL 서비스에 대한 연결을 다시 열어요.
:::note[PAUSE]
자세한 내용은 PgBouncer 문서의 PAUSE를 참고하세요.
:::
:::info[Important]
향후 버전에서는 스위치오버 작업이 PgBouncer pooler와 완전히 통합되고 PAUSE/RESUME 기능을 활용해 클라이언트 애플리케이션이 체감하는 다운타임을 줄일 거예요. 현재는 paused 속성을 true로 설정하고, cnpg 플러그인으로 스위치오버 명령을 발행한 다음 paused 속성을 false로 복원하면 같은 결과를 얻을 수 있어요.
:::
제한 사항
단일 PostgreSQL 클러스터
pooler의 현재 구현은 특정 CloudNativePG 클러스터(서비스)의 일부로 동작하도록 설계됐어요. 현재로선 여러 클러스터에 걸친 pooler를 만드는 것은 불가능해요.
제어된 구성 가능성
CloudNativePG는 PgBouncer 계층이 PostgreSQL과 통신하는 데 사용되는 여러 구성 옵션을 투명하게 관리해요. 이러한 옵션은 외부에서 구성할 수 없으며 TLS 인증서, 인증 설정, databases 섹션, users 섹션을 포함해요. 또한 단일 PostgreSQL 클러스터라는 특정 사용 사례를 고려해, 채택한 기준은 사용자가 구성할 수 있는 옵션을 명시적으로 나열하는 것이에요.
:::note 채택된 해결책은 아마도 대부분의 사용 사례를 해결할 거예요. 더 고급이고 커스터마이즈된 시나리오로 범위를 완성하기 위해 별도의 PgBouncer용 오퍼레이터의 미래 구현을 위한 여지를 남겨 둬요. :::