pg_ctl start 와 systemctl start postgresql 은 데몬을 배경으로 띄우고 곧바로 끝난다. 컨테이너의 PID 1 은 그 즉시 종료되므로 컨테이너도 함께 죽는다. 컨테이너 안에서는 postgres 실행 파일을 전면에서 직접 돌린다.
exec postgres -D "$PGDATA"
exec 를 붙이는 이유는 셸을 PostgreSQL 프로세스로 치환하기 위해서다. 그래야 postgres 가 PID 1 이 되고, 쿠버네티스나 도커가 보내는 SIGTERM 을 직접 받아 정상 종료 절차(체크포인트 후 종료)를 밟는다. exec 없이 실행하면 셸이 PID 1 로 남아 신호를 전달하지 않고, 종료 시간이 되면 SIGKILL 로 강제 종료돼 복구가 필요한 상태로 끝난다.
데이터 디렉터리가 비어 있을 때만 초기화하고, 그다음엔 그냥 띄운다.
#!/bin/sh
set -eu
PGDATA="${PGDATA:-/var/lib/postgresql/data}"
if [ ! -s "$PGDATA/PG_VERSION" ]; then
initdb -D "$PGDATA" --encoding=UTF8 --locale=C
cat >> "$PGDATA/postgresql.conf" <<'CONF'
listen_addresses = '*'
CONF
cat >> "$PGDATA/pg_hba.conf" <<'CONF'
host all all 0.0.0.0/0 scram-sha-256
host all all ::/0 scram-sha-256
CONF
fi
exec postgres -D "$PGDATA"
존재 여부를 -d "$PGDATA" 가 아니라 PG_VERSION 파일로 판단한다. 볼륨을 마운트하면 디렉터리는 항상 존재하고, 마운트 지점에 lost+found 가 있으면 "비어 있음" 판정도 빗나간다. PG_VERSION 이 있으면 이미 초기화된 클러스터다.
entrypoint 예제에서 자주 보이는 형태다.
chmod 0700 "$PGDATA" || true
|| 는 앞 명령이 실패했을 때만 뒤를 실행한다. true 는 아무것도 하지 않고 성공으로 끝나므로, 이 줄 전체는 실패해도 넘어간다는 뜻이 된다. set -e 가 걸린 스크립트에서 실패해도 무방한 명령에 붙인다.
NFS 볼륨처럼 chmod 가 허용되지 않는 스토리지에서 이것이 필요하다. 다만 남발하면 진짜 실패를 가린다. mkdir -p 같은 멱등 명령에는 애초에 필요 없다.
listen_addresses = '*' 는 IPv4 와 IPv6 를 모두 듣는다. IPv4 만 원하면 '0.0.0.0', IPv6 만 원하면 '::' 이다.
접속 허용은 pg_hba.conf 가 따로 통제한다. listen_addresses 를 열어도 pg_hba.conf 에 항목이 없으면 no pg_hba.conf entry for host ... 로 거부된다. 둘 다 맞춰야 한다.
0.0.0.0/0 를 여는 것은 컨테이너 네트워크 안에서만 접근 가능한 구성일 때의 이야기다. 그렇지 않다면 대역을 좁힌다. 인증 방식은 md5 대신 scram-sha-256 을 쓴다.
이미지를 만들 때 데이터 디렉터리의 소유자를 실행할 UID 로 맞춰 둔다.
RUN mkdir -p /var/lib/postgresql/data \
&& chown -R 1000:1000 /var/lib/postgresql
USER 1000:1000
쿠버네티스에서는 securityContext 로 지정한다.
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: postgres
# ...
fsGroup 은 볼륨의 그룹 소유권을 그 GID 로 바꿔 준다. 이것이 없으면 PVC 를 붙여도 initdb 가 권한 오류로 실패한다. 다만 NFS 볼륨에는 fsGroup 이 적용되지 않는 경우가 있어, 그때는 서버 쪽에서 미리 소유권을 맞춰야 한다.
PostgreSQL 은 데이터 디렉터리 권한이 0700 또는 0750 이 아니면 기동을 거부한다. 그룹 읽기가 필요하면 0750 까지만 허용된다.
두 프로브의 목적이 다르므로 같은 것을 쓰지 않는다.
readinessProbe:
exec:
command: ["pg_isready", "-U", "postgres", "-h", "127.0.0.1"]
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
exec:
command: ["pg_isready", "-U", "postgres", "-h", "127.0.0.1"]
initialDelaySeconds: 30
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 6
-h 127.0.0.1 을 준다. 생략하면 유닉스 소켓으로 붙는데, 소켓 디렉터리가 다르면 실제 서비스 포트가 죽어도 프로브가 통과해 버린다.
liveness 는 readiness 보다 훨씬 느슨하게 잡는다. 무거운 질의나 체크포인트로 잠시 응답이 늦어질 때 컨테이너가 재시작되면 상황이 더 나빠진다. 위 설정에서 liveness 는 20초 × 6회 = 2분 동안 계속 실패해야 재시작한다.
복구가 오래 걸리는 클러스터라면 startupProbe 를 따로 두고 liveness 의 initialDelaySeconds 를 줄인다.
startupProbe:
exec:
command: ["pg_isready", "-U", "postgres", "-h", "127.0.0.1"]
periodSeconds: 10
failureThreshold: 60 # 최대 10분까지 기동을 기다린다
매니페스트를 적용할 때 나는 오류다.
StatefulSet in version "v1" cannot be handled as a StatefulSet:
strict decoding error: unknown field "spec.template.spec.initcontainers"
필드 이름은 camelCase 인 initContainers 다. 쿠버네티스 API 는 알 수 없는 필드를 거부한다. 같은 이유로 securitycontext, imagepullpolicy, volumemounts 도 모두 틀린 철자다.
적용 전에 미리 잡으려면 서버 측 검증만 돌려 본다.
kubectl apply --dry-run=server -f statefulset.yaml
listen_addresses 와 pg_hba.conf 가 얽힌 문제.