RHEL 실전 #2 DB 운영: PostgreSQL on RHEL

9 분 소요

RHEL 실전 #1에서 nginx로 웹 계층을 올렸다면, 이번에는 그 뒤를 받치는 데이터 계층으로 갑니다. RHEL에서 데이터베이스를 올리는 일도 웹 서버와 마찬가지로 패키지 하나로 끝나지 않습니다. 다만 PostgreSQL에는 RHEL 고유의 한 단계가 더 붙습니다. 바로 AppStream module로 버전을 고르는 과정입니다.

RHEL은 동일 패키지의 여러 버전을 module이라는 단위로 묶어 제공합니다. PostgreSQL처럼 메이저 버전이 빠르게 올라가는 소프트웨어는 이 module 구조를 거쳐야 원하는 버전을 깔끔하게 설치할 수 있습니다. 이번 글은 그 설치부터 시작해, 초기화,원격 접속,SELinux,백업까지 운영 한 사이클을 따라가겠습니다.

AppStream module로 버전 고르기 #

먼저 어떤 PostgreSQL 버전들이 module로 제공되는지 확인합니다. 아래 명령이 사용 가능한 stream(버전 흐름)과 기본값을 한눈에 보여 줍니다.

# 제공되는 postgresql module과 stream 목록
dnf module list postgresql

출력에서 [d]가 붙은 stream이 기본값이고, [e]가 붙은 stream은 이미 활성화된 것입니다. 원하는 버전이 기본값과 다르다면 명시적으로 활성화한 뒤 설치하겠습니다. 여기서는 16버전을 예로 들겠습니다.

# 16 stream 활성화
sudo dnf module enable -y postgresql:16

# 서버 패키지 설치
sudo dnf install -y postgresql-server postgresql-contrib

postgresql-server가 데이터베이스 본체이고, postgresql만 설치하면 클라이언트(psql 등)만 들어옵니다. 서버를 올리려면 반드시 postgresql-server를 설치해야 합니다. postgresql-contribpg_stat_statements같은 실무에서 자주 쓰는 확장 모듈을 담고 있어 함께 설치하는 편이 좋습니다.

한번 stream을 활성화하면 이후 업데이트도 그 버전 흐름 안에서만 일어납니다. 메이저 버전을 올릴 때는 module을 reset하거나 다른 stream으로 enable해야 하므로, 운영 중인 데이터베이스에서는 신중하게 다뤄야 합니다.

초기화와 서비스 등록 #

설치 직후에는 데이터 디렉터리가 비어 있어 서비스가 바로 뜨지 않으므로, RHEL 패키지가 제공하는 초기화 전용 명령을 먼저 실행해야 합니다.

# 데이터 디렉터리 초기화
sudo postgresql-setup --initdb

이 명령은 기본 데이터 디렉터리인 /var/lib/pgsql/data에 설정 파일과 시스템 카탈로그를 생성합니다. 초기화가 끝나면 systemd로 등록하고 즉시 시작합니다.

# 부팅 시 자동 시작 + 즉시 시작
sudo systemctl enable --now postgresql

# 상태 확인
systemctl status postgresql

enable --now로 한 번에 처리하는 흐름은 nginx 때와 동일합니다. 정상적으로 떴다면 로컬에서 먼저 접속을 확인하겠습니다. 설치 시 postgres라는 OS 사용자가 만들어지며, 이 사용자로 전환해 접속하는 것이 가장 단순합니다.

# postgres 사용자로 전환 후 psql 접속
sudo -u postgres psql

psql 프롬프트가 뜨면 데이터베이스 본체는 정상입니다. \l로 데이터베이스 목록을, \q로 종료합니다.

데이터 디렉터리 #

기본 데이터 디렉터리는 /var/lib/pgsql/data입니다. 주요 파일과 위치를 정리하면 다음과 같습니다.

  • postgresql.conf: 서버 전역 설정(접속 주소, 메모리, 로그 등)
  • pg_hba.conf: 클라이언트 인증 규칙(누가 어디서 어떤 방식으로 접속하는가)
  • base/, global/: 실제 데이터 파일
  • pg_wal/: 트랜잭션 로그(WAL)

운영 환경에서 데이터 디렉터리를 별도 디스크로 옮기는 경우가 많은데, 이때 SELinux 컨텍스트를 함께 챙기지 않으면 서비스가 뜨지 않습니다. 이 부분은 아래 SELinux 절에서 다루겠습니다.

원격 접속 설정 #

기본 설치 상태의 PostgreSQL은 로컬에서만 접속을 받습니다. 외부에서 접속하려면 두 파일을 고쳐야 합니다. postgresql.conf에서 수신 주소를 열고, pg_hba.conf에서 인증 규칙을 추가하는 두 단계입니다.

먼저 postgresql.conf에서 listen_addresses를 수정합니다. 기본값은 localhost라 외부 접속을 막습니다.

# /var/lib/pgsql/data/postgresql.conf
listen_addresses = '*'        # 모든 인터페이스에서 수신
port = 5432

'*'는 모든 네트워크 인터페이스에서 받겠다는 뜻입니다. 특정 IP만 받고 싶다면 그 주소를 직접 적습니다. 다음으로 pg_hba.conf에 인증 규칙을 추가합니다. 이 파일은 위에서 아래로 읽으며 먼저 일치하는 규칙이 적용됩니다.

# /var/lib/pgsql/data/pg_hba.conf
# TYPE  DATABASE  USER  ADDRESS          METHOD
host    all       all   192.168.1.0/24   scram-sha-256

host는 TCP/IP 접속을, scram-sha-256은 RHEL 기본 인증 방식인 암호 기반 인증을 뜻합니다. 과거의 md5보다 안전하므로 새로 구성하는 환경에서는 scram-sha-256을 쓰는 것이 옳습니다. ADDRESS에는 접속을 허용할 네트워크 대역을 적습니다. 두 파일을 고친 뒤에는 서비스를 다시 읽어야 반영됩니다.

# 설정 다시 읽기 (대부분의 설정은 reload로 충분)
sudo systemctl reload postgresql

listen_addresses처럼 일부 항목은 reload로는 반영되지 않고 restart가 필요합니다. 변경한 항목이 reload로 들어가는지 확실치 않다면 sudo systemctl restart postgresql로 재시작하겠습니다.

firewalld로 5432 열기 #

PostgreSQL의 기본 포트는 5432입니다. RHEL은 firewalld가 활성화되어 있어 이 포트가 막혀 있으므로 영구 개방합니다.

# postgresql 서비스 영구 허용 (5432/tcp)
sudo firewall-cmd --add-service=postgresql --permanent

# 반영
sudo firewall-cmd --reload

# 확인
sudo firewall-cmd --list-all

firewalld에는 postgresql이라는 사전 정의 서비스가 있어 포트 번호를 직접 적지 않아도 됩니다. 비표준 포트를 쓴다면 --add-port=5433/tcp처럼 포트로 직접 열겠습니다. --permanent로 영구 규칙을 만든 뒤 --reload로 반영하는 두 단계 흐름은 RHEL 방화벽 작업의 기본입니다.

SELinux가 막는 지점 #

표준 위치(/var/lib/pgsql/data)에서 표준 포트(5432)로 동작하는 한 SELinux는 조용합니다. 그러나 웹 서버 때와 똑같이 비표준 디렉터리비표준 포트로 벗어나면 즉시 막힙니다.

비표준 데이터 디렉터리 #

데이터 디렉터리를 /data/pgsql같은 경로로 옮기면, 그 디렉터리의 SELinux 컨텍스트가 PostgreSQL 데이터 타입이 아니라 서비스가 뜨지 않습니다. 이 경우 postgresql_db_t 타입을 영구 부여한 뒤 실제 파일에 적용합니다.

# /data/pgsql 이하를 PostgreSQL 데이터 타입으로 영구 등록
sudo semanage fcontext -a -t postgresql_db_t "/data/pgsql(/.*)?"

# 디스크의 실제 레이블에 적용
sudo restorecon -Rv /data/pgsql

semanage fcontext로 정책에 규칙을 추가하고 restorecon으로 실제 파일에 적용하는 두 단계가 핵심입니다. 이와 별개로 systemd 유닛이 기본 디렉터리를 가리키므로, 데이터 디렉터리 자체를 옮길 때는 유닛의 Environment=PGDATA= 값도 함께 맞춰야 합니다.

비표준 포트 #

PostgreSQL을 5433처럼 다른 포트로 돌리면, 설정이 맞아도 SELinux가 그 포트를 데이터베이스 포트로 인정하지 않아 막힙니다.

# 현재 postgresql_port_t에 등록된 포트 확인
sudo semanage port -l | grep postgresql

# 5433을 PostgreSQL 포트로 영구 등록
sudo semanage port -a -t postgresql_port_t -p tcp 5433

semanage가 없다면 policycoreutils-python-utils 패키지를 설치합니다. 포트를 등록한 뒤 서비스를 재시작하면 정상적으로 뜹니다.

사용자와 DB 생성 #

데이터베이스를 실제로 쓰려면 애플리케이션용 사용자(role)와 데이터베이스를 만들어야 합니다. postgres 슈퍼유저로 작업합니다. 방법은 두 가지인데, 셸 명령으로 만드는 방식과 psql 안에서 SQL로 만드는 방식입니다.

먼저 셸에서 바로 만드는 방식입니다.

# 대화형으로 role 생성 (암호 입력 프롬프트 포함)
sudo -u postgres createuser --pwprompt appuser

# appuser를 소유자로 하는 데이터베이스 생성
sudo -u postgres createdb --owner=appuser appdb

같은 작업을 psql 안에서 SQL로 처리할 수도 있습니다. 권한을 세밀하게 주고 싶을 때 이쪽이 편합니다.

-- sudo -u postgres psql로 접속한 뒤
CREATE ROLE appuser WITH LOGIN PASSWORD '바꿀_암호';
CREATE DATABASE appdb OWNER appuser;
GRANT ALL PRIVILEGES ON DATABASE appdb TO appuser;

LOGIN 속성이 있어야 접속 가능한 사용자가 됩니다. 이렇게 만든 appuser로 원격에서 접속할 때, 앞서 pg_hba.conf에 추가한 규칙과 scram-sha-256 인증이 함께 맞물려야 접속이 성립합니다.

백업과 복구 #

운영 데이터베이스에서 백업은 선택이 아니라 기본입니다. PostgreSQL은 논리 백업 도구로 pg_dumppg_dumpall을 제공합니다. pg_dump는 단일 데이터베이스를, pg_dumpall은 role을 포함한 클러스터 전체를 받습니다.

# 단일 데이터베이스 백업 (커스텀 포맷, 압축,병렬 복구에 유리)
sudo -u postgres pg_dump -Fc appdb -f /var/backups/appdb.dump

# 클러스터 전체 백업 (role,권한 포함, 평문 SQL)
sudo -u postgres pg_dumpall -f /var/backups/all.sql

-Fc는 커스텀 포맷으로, 복구 시 테이블 단위 선택이나 병렬 처리가 가능해 단일 데이터베이스 백업에 권장됩니다. 복구는 포맷에 따라 도구가 다릅니다. 커스텀 포맷은 pg_restore로, 평문 SQL은 psql로 되돌립니다.

# 커스텀 포맷 복구 (대상 DB는 미리 생성)
sudo -u postgres createdb appdb_restore
sudo -u postgres pg_restore -d appdb_restore /var/backups/appdb.dump

# 평문 SQL 복구
sudo -u postgres psql -f /var/backups/all.sql

이 백업들은 특정 시점의 스냅샷이므로, 장애 시 마지막 백업 이후의 변경은 사라집니다. 손실을 최소화하려면 WAL 아카이빙 기반의 시점 복구(PITR)를 별도로 구성해야 하지만, 그 주제는 별도의 글로 다룰 만큼 깊으므로 여기서는 논리 백업까지만 잡겠습니다. 백업 파일은 cron이나 systemd timer로 주기 실행하고, 같은 호스트가 아닌 별도 저장소로 옮겨 두어야 의미가 있습니다.

막혔을 때 진단 #

데이터베이스에 접속이 안 될 때는 다음 순서로 좁혀 가면 대부분 원인이 드러납니다.

  1. 서비스가 떠 있는가. systemctl status postgresql, 실패 시 journalctl -u postgresql로 기동 로그 확인
  2. 로컬에서 열리는가. sudo -u postgres psql로 접속해 서버 자체 문제와 원격 접근 문제를 분리
  3. 방화벽이 열렸는가. firewall-cmd --list-all에 postgresql 또는 5432가 있는가
  4. 인증이 막는가. 원격에서 psql -h 호스트 -U appuser -d appdb로 접속해 보고, 거부되면 pg_hba.conf의 규칙과 순서를 확인
  5. SELinux가 막는가. sudo ausearch -m AVC -ts recent에 postgresql 관련 denied가 있는가

특히 pg_hba.conf는 위에서 아래로 읽으며 먼저 일치하는 규칙이 적용되므로, 더 넓은 규칙이 위에 있으면 아래의 의도한 규칙이 가려질 수 있습니다. 인증 오류 메시지는 PostgreSQL 로그(/var/lib/pgsql/data/log/)에 어떤 규칙에서 막혔는지 함께 남으므로, 접속 거부 시 이 로그를 먼저 보는 것이 빠릅니다.

운영 포인트 #

이번 글에서 잡은 것:

  • 버전은 module로. dnf module list postgresql로 확인하고 dnf module enable postgresql:16postgresql-server 설치
  • 초기화가 별도 단계. postgresql-setup --initdbsystemctl enable --now postgresql, 데이터는 /var/lib/pgsql/data
  • 원격 접속은 두 파일. postgresql.conflisten_addresses, pg_hba.confscram-sha-256 인증 규칙, 그리고 firewalld 5432 개방
  • SELinux가 막는 두 지점. 비표준 데이터 디렉터리(semanage fcontext -t postgresql_db_t), 비표준 포트(semanage port -t postgresql_port_t)
  • 백업. 단일 DB는 pg_dump -Fc + pg_restore, 클러스터 전체는 pg_dumpall + psql, 별도 저장소로 보관
  • 진단 순서. 서비스 → 로컬 psql → 방화벽 → 인증(pg_hba) → SELinux

다음: 컨테이너 워크로드 #

웹 계층과 데이터 계층을 RHEL 위에 직접 올렸으니, 이번에는 같은 워크로드를 컨테이너로 묶는 방식으로 갑니다.

#3 컨테이너 워크로드: Podman, systemd (quadlet)에서는 RHEL의 기본 컨테이너 엔진인 Podman으로 컨테이너를 실행하고, 이를 systemd 서비스로 등록하는 quadlet 방식까지, 컨테이너를 RHEL 운영 체계 안으로 끌어들이는 한 사이클을 정리하겠습니다.

X