본문으로 건너뛰기

9.2 Role 관리(선언적)

PostgreSQL의 role은 접속과 권한의 주체입니다. 전통적으로는 CREATE ROLE·ALTER ROLE·DROP ROLE을 손으로 실행해 관리하지만, CloudNativePG에서는 원하는 role의 상태를 매니페스트에 적어 두면 Operator가 그 상태로 맞춥니다. role의 생성·수정·삭제가 코드로 남으므로, 누가 어떤 권한을 가졌는지가 Git 이력에 그대로 드러납니다.

선언하는 자리는 둘입니다. Cluster CRD 안의 .spec.managed.roles에 inline으로 적는 방법과, 1.30에서 추가된 별도 DatabaseRole 리소스로 적는 방법입니다. 앞부분은 inline 방식을, 뒷부분은 DatabaseRole을 다룹니다.

managed.roles 구조​

role은 .spec.managed.roles 아래에 리스트로 선언합니다. 각 항목은 PostgreSQL의 role 속성을 그대로 필드로 옮긴 형태입니다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg
spec:
managed:
roles:
- name: dante
ensure: present
comment: application role
login: true
superuser: false
createdb: false
createrole: false
inherit: true
connectionLimit: 10
inRoles:
- pg_monitor
- pg_signal_backend
passwordSecret:
name: pg-dante

주요 필드는 다음과 같습니다.

필드의미기본값
namerole 이름 (필수)—
ensurepresent(생성·유지) 또는 absent(삭제)present
login로그인 가능 여부false
superusersuperuser 권한false
createdbdatabase 생성 권한false
createrolerole 생성 권한false
inherit소속 role의 권한 상속true
connectionLimit최대 동시 접속 수 (-1은 무제한)-1
validUntil비밀번호 만료 시각없음(무기한)
inRoles소속시킬 role 그룹 목록빈 목록
passwordSecret비밀번호를 담은 Secret 참조선택
disablePassword비밀번호를 NULL로 설정false

조정 흐름​

DBA가 managed.roles를 수정하면 Operator가 이를 PostgreSQL catalog와 비교해 부족한 것은 만들고, 다른 것은 고칩니다.

노트

선언적 role 관리는 매니페스트에 적히지 않은 role은 건드리지 않습니다. 그래서 기존 클러스터에 이 기능을 도입할 때 한꺼번에 모든 role을 옮길 필요 없이, 관리 대상을 하나씩 늘려 가며 점진적으로 적용할 수 있습니다.

비밀번호 관리​

role의 비밀번호는 매니페스트에 평문으로 적지 않고, kubernetes.io/basic-auth 타입의 Secret을 참조합니다.

apiVersion: v1
kind: Secret
type: kubernetes.io/basic-auth
metadata:
name: pg-dante
labels:
cnpg.io/reload: "true"
data:
username: ZGFudGU=
password: <base64-encoded-password>

Operator는 기본적으로 비밀번호를 클라이언트 측에서 SCRAM-SHA-256으로 해시한 뒤 PostgreSQL에 보냅니다. 그래서 평문이 서버 로그에 남지 않습니다. cnpg.io/reload: "true" label이 붙은 Secret은 비밀번호 변경이 즉시 반영되고, 없으면 다음 조정 주기에 반영됩니다.

  • validUntil: 비밀번호 만료 시각을 지정합니다. 생략하면 Operator가 만료되지 않도록(VALID UNTIL 'infinity') 유지합니다.
  • disablePassword: true: 비밀번호를 NULL로 만듭니다. passwordSecret과 함께 쓸 수 없습니다.

제약과 주의점​

선언적 관리라 해도 PostgreSQL 자체의 규칙은 그대로 적용됩니다.

  • 삭제 제약: role이 객체를 소유하고 있으면 DROP ROLE이 실패합니다. Operator는 소유 객체를 자동으로 지우지 않으므로, DBA가 먼저 소유권을 옮기거나 객체를 정리해야 합니다.
  • 실현 불가능한 설정: 존재하지 않는 그룹에 소속시키는 등 PostgreSQL이 거부하는 요청은 조정에 실패합니다. 이런 role은 아래 status의 cannotReconcile에 나타나며 사람의 개입을 요구합니다.
  • 예약 role: postgres, streaming_replica, cnpg_pooler_pgbouncer는 Operator가 관리하는 예약 role이라 선언적 관리로 삭제할 수 없습니다.

status로 상태 확인​

Cluster의 .status.managedRolesStatus에서 각 role의 조정 결과를 확인합니다.

status:
managedRolesStatus:
byStatus:
reconciled:
- dante
reserved:
- postgres
- streaming_replica
cannotReconcile:
petrarca:
- 'could not perform UPDATE_MEMBERSHIPS: role "poets" does not exist'
  • reconciled: 매니페스트대로 반영된 role
  • reserved: Operator가 관리하는 시스템 role
  • cannotReconcile: 해결되지 않은 오류로 개입이 필요한 role
경고

cannotReconcile에 role이 남아 있다면 그 role의 상태는 매니페스트와 다르다는 뜻입니다. 메시지에 적힌 원인(위 예시에서는 존재하지 않는 poets 그룹)을 먼저 해소해야 조정이 완료됩니다.

DatabaseRole 리소스​

1.30은 role을 Cluster 밖의 독립 리소스로 선언하는 DatabaseRole CRD를 추가했습니다. 공식 문서는 GitOps 환경에서 이쪽을 권장합니다. role의 수명주기가 Cluster 매니페스트에서 분리되어, 애플리케이션 팀이 Cluster 전체를 편집하지 않고 자기 role만 관리할 수 있고 RBAC도 리소스 단위로 나눌 수 있습니다.

apiVersion: postgresql.cnpg.io/v1
kind: DatabaseRole
metadata:
name: role-dante
spec:
cluster:
name: cluster-example
name: dante
comment: "Dante Alighieri"
login: true
superuser: false
createdb: true
inRoles:
- pg_monitor
passwordSecret:
name: cluster-example-dante
databaseRoleReclaimPolicy: delete

필드는 managed.roles의 항목과 거의 같습니다. login, superuser, createdb, createrole, replication, bypassrls, inherit(기본 true), connectionLimit(기본 -1), inRoles, validUntil, comment, passwordSecret을 그대로 씁니다. DatabaseRole은 namespace 범위이며, 참조하는 Cluster와 passwordSecret이 모두 같은 namespace에 있어야 합니다.

inline과 다른 점​

같은 role을 두 방식으로 적을 수 있으니 차이를 알아야 합니다.

구분managed.roles (inline)DatabaseRole
반영 시점주기적으로 catalog와 비교해 되돌림spec이나 password Secret이 바뀔 때 적용
손으로 바꾼 ALTER ROLE다음 주기에 spec대로 되돌림다음 적용 시점까지 감지하지 않음
삭제 표현ensure: absentdatabaseRoleReclaimPolicy
충돌 시우선적용되지 않고 status에 충돌 보고

같은 이름이 양쪽에 있으면 Cluster spec이 항상 이깁니다. 이때 DatabaseRole의 status.applied는 false가 되고 "database role is already managed by the CNPG cluster" 메시지가 남습니다.

reclaim policy​

DatabaseRole 리소스를 지웠을 때 PostgreSQL 안의 role을 어떻게 할지는 databaseRoleReclaimPolicy가 정합니다. PersistentVolume의 reclaim policy와 같은 발상입니다.

  • retain (기본): role을 database에 그대로 둡니다. 매니페스트를 실수로 지워도 사용자와 그 소유 객체가 남으므로 운영 환경의 기본값으로 적당합니다.
  • delete: 리소스가 finalize되기 전에 DROP ROLE을 실행합니다. 임시 환경이나 자동화된 환경에 맞습니다.
경고

delete인데 그 role이 테이블이나 schema를 소유하고 있으면 DROP ROLE이 실패하고 DatabaseRole은 Terminating에 머물며 주기적으로 재시도합니다. Operator가 소유 객체를 대신 지우거나 재할당하는 일은 없습니다. PostgreSQL에서 객체를 옮기거나 지우거나, 정책을 retain으로 바꿔야 삭제가 끝납니다.

기존 role을 채택할 때​

이미 있는 role 이름으로 DatabaseRole을 만들면 Operator가 그 role을 채택(adopt)하고, 매니페스트에 적힌 대로 모든 속성을 맞춥니다. 적지 않은 속성은 기본값으로 되돌아갑니다. inRoles에 없는 membership은 회수되고, 생략한 connectionLimit은 -1로, 만료일이 있던 role의 validUntil은 infinity로 바뀝니다. 채택 전에 현재 속성과 membership을 확인해 매니페스트에 전부 옮겨 적어야 합니다. 반대로 지우기만 하고 싶은 role에는 DatabaseRole을 쓰지 않습니다. 지워지기 전에 먼저 변경되기 때문입니다. 그런 경우는 inline의 ensure: absent나 직접 DROP ROLE이 맞습니다.

client 인증서 발급​

clientCertificate.enabled: true를 주면 Operator가 Cluster의 client CA로 서명한 TLS client 인증서를 만들어 Secret에 저장합니다. 비밀번호 대신 PostgreSQL cert 인증(8.3)을 쓸 수 있고, 갱신도 Operator가 맡습니다. 단 CA를 직접 가져온(user-provided) Cluster에서는 자동 갱신이 되지 않으므로 인증서를 직접 발급해야 합니다.

spec:
cluster:
name: cluster-example
name: dante
login: true
clientCertificate:
enabled: true
databaseRoleReclaimPolicy: retain

status​

DatabaseRole은 자기 status를 갖습니다. applied가 true면 마지막 spec이 반영된 상태고, observedGeneration으로 어느 세대까지 처리했는지 봅니다. PasswordSecretChange condition은 Operator가 마지막으로 본 password Secret의 resourceVersion을 담는 내부 신호로, 이 값이 바뀌면 인스턴스 매니저가 비밀번호를 다시 적용합니다. replica cluster(6.4)에서는 role을 primary 쪽이 소유하므로 applied가 비어 있고 설명 메시지만 남습니다. 승격되면 정상 반영됩니다.

정리​

  • role은 .spec.managed.roles에 리스트로 선언하고, Operator가 catalog와 주기적으로 비교해 맞춥니다. ensure가 present면 생성·유지, absent면 삭제입니다.
  • 비밀번호는 basic-auth Secret으로 참조하며, 기본적으로 SCRAM-SHA-256으로 해시해 전달합니다.
  • 1.30의 DatabaseRole은 Cluster 밖에서 role을 선언하고, 같은 이름이면 Cluster spec이 이기며, 삭제 동작은 databaseRoleReclaimPolicy(기본 retain)가 정합니다.

매니페스트에 없는 role은 건드리지 않으므로 기존 클러스터에도 점진적으로 도입하기 쉽습니다. 객체를 소유한 role은 삭제에 실패하고, 예약 role은 관리 대상에서 빠지며, 조정 결과는 .status.managedRolesStatus의 reconciled·cannotReconcile로 확인합니다.