Zalando를 사용하여 GKE에 PostgreSQL 배포

이 가이드에서는 Zalando Postgres 연산자를 사용하여 Postgres 클러스터를 Google Kubernetes Engine(GKE)에 배포하는 방법을 설명합니다.

PostgreSQL은 수십 년간의 활발한 개발이 이루어진 강력한 오픈소스 객체 관계형 데이터베이스 시스템으로, 안정성, 기능 견고성, 성능으로 탄탄한 평판을 쌓았습니다.

이 가이드는 PostgreSQL용 Cloud SQL을 사용하는 대신 PostgreSQL을 GKE에서 데이터베이스 애플리케이션으로 실행하는 데 관심이 있는 플랫폼 관리자, 클라우드 설계자, 운영 전문가를 대상으로 합니다.

환경 설정

환경을 설정하려면 다음 단계를 수행합니다.

  1. 환경 변수를 설정합니다.

    export PROJECT_ID=PROJECT_ID
    export KUBERNETES_CLUSTER_PREFIX=postgres
    export REGION=us-central1
    

    PROJECT_ID를 Google Cloud프로젝트 ID로 바꿉니다.

  2. GitHub 저장소를 클론합니다.

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  3. 작업 디렉터리로 변경합니다.

    cd kubernetes-engine-samples/databases/postgres-zalando
    

클러스터 인프라 만들기

이 섹션에서는 Terraform 스크립트를 실행하여 가용성이 높은 비공개 리전 GKE 클러스터를 만듭니다.

Standard 또는 Autopilot 클러스터를 사용하여 연산자를 설치할 수 있습니다.

표준

다음 다이어그램에서는 서로 다른 영역 3개에 배포된 비공개 리전 Standard GKE 클러스터를 보여줍니다.

다음 인프라를 배포합니다.

export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
terraform -chdir=terraform/gke-standard init
terraform -chdir=terraform/gke-standard apply \
  -var project_id=${PROJECT_ID} \
  -var region=${REGION} \
  -var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}

메시지가 표시되면 yes를 입력합니다. 이 명령어가 완료되고 클러스터에 준비 상태가 표시되는 데 몇 분 정도 걸릴 수 있습니다.

Terraform에서 다음 리소스를 만듭니다.

  • Kubernetes 노드의 VPC 네트워크 및 비공개 서브넷
  • NAT를 통해 인터넷에 액세스할 수 있는 라우터
  • us-central1 리전의 비공개 GKE 클러스터
  • 자동 확장이 사용 설정된 노드 풀(영역당 노드 1~2개, 최소 영역당 노드 1개)
  • 로깅 및 모니터링 권한이 있는 ServiceAccount
  • 재해 복구를 위한 Backup for GKE
  • 클러스터 모니터링을 위한 Google Cloud Managed Service for Prometheus

출력은 다음과 비슷합니다.

...
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.
...

Autopilot

다음 다이어그램에서는 비공개 리전 Autopilot GKE 클러스터를 보여줍니다.

인프라를 배포합니다.

export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
terraform -chdir=terraform/gke-autopilot init
terraform -chdir=terraform/gke-autopilot apply \
  -var project_id=${PROJECT_ID} \
  -var region=${REGION} \
  -var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}

메시지가 표시되면 yes를 입력합니다. 이 명령어가 완료되고 클러스터에 준비 상태가 표시되는 데 몇 분 정도 걸릴 수 있습니다.

Terraform에서 다음 리소스를 만듭니다.

  • Kubernetes 노드의 VPC 네트워크 및 비공개 서브넷
  • NAT를 통해 인터넷에 액세스할 수 있는 라우터
  • us-central1 리전의 비공개 GKE 클러스터
  • 로깅 및 모니터링 권한이 있는 ServiceAccount
  • 클러스터 모니터링을 위한 Google Cloud Managed Service for Prometheus

출력은 다음과 비슷합니다.

...
Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
...

클러스터에 연결

클러스터와 통신하도록 kubectl을 구성합니다.

gcloud container clusters get-credentials ${KUBERNETES_CLUSTER_PREFIX}-cluster --location ${REGION}

클러스터에 Zalando 연산자 배포

Helm 차트를 사용하여 Kubernetes 클러스터에 Zalando 연산자를 배포합니다.

  1. Zalando 연산자 Helm 차트 저장소를 추가합니다.

    helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator
    
  2. Zalando 연산자 및 Postgres 클러스터의 네임스페이스를 만듭니다.

    kubectl create ns postgres
    kubectl create ns zalando
    
  3. Helm 명령줄 도구를 사용하여 Zalando 연산자를 배포합니다.

    helm install postgres-operator postgres-operator-charts/postgres-operator -n zalando \
        --set configKubernetes.enable_pod_antiaffinity=true \
        --set configKubernetes.pod_antiaffinity_preferred_during_scheduling=true \
        --set configKubernetes.pod_antiaffinity_topology_key="topology.kubernetes.io/zone" \
        --set configKubernetes.spilo_fsgroup="103"
    

    Postgres 클러스터를 나타내는 커스텀 리소스에서 직접 podAntiAffinity 설정을 구성할 수 없습니다. 대신 연산자 설정에서 모든 Postgres 클러스터에 대해 podAntiAffinity 설정을 전역적으로 설정합니다.

  4. Helm을 사용하여 Zalando 연산자의 배포 상태를 확인합니다.

    helm ls -n zalando
    

    출력은 다음과 비슷합니다.

    NAME                 NAMESPACE    REVISION    UPDATED                                STATUS      CHART                       APP VERSION
    postgres-operator    zalando     1           2023-10-13 16:04:13.945614 +0200 CEST    deployed    postgres-operator-1.10.1    1.10.1
    

Postgres 배포

Postgres 클러스터 인스턴스의 기본 구성에는 다음 구성요소가 포함됩니다.

  • Postgres 복제본 3개: 리더 1개와 대기 복제본 2개
  • CPU 요청 1개 및 CPU 한도 2개의 CPU 리소스 할당(4GB 메모리 요청 및 한도)
  • Kubernetes 노드 간 적절한 배포를 보장하고 해당 노드 풀 및 서로 다른 가용성 영역을 활용하는 각 워크로드에 구성된 톨러레이션(toleration), nodeAffinities, topologySpreadConstraints

이 구성은 프로덕션에 사용 가능한 Postgres 클러스터를 만드는 데 필요한 최소 설정을 나타냅니다.

다음 매니페스트에서는 Postgres 클러스터를 설명합니다.

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: my-cluster
spec:
  dockerImage: ghcr.io/zalando/spilo-15:3.0-p1
  teamId: "my-team"
  numberOfInstances: 3
  users:
    mydatabaseowner:
    - superuser
    - createdb
    myuser: []
  databases:
    mydatabase: mydatabaseowner
  postgresql:
    version: "15"
    parameters:
      shared_buffers: "32MB"
      max_connections: "10"
      log_statement: "all"
      password_encryption: scram-sha-256
  volume:
    size: 5Gi
    storageClass: premium-rwo
  enableShmVolume: true
  podAnnotations:
    cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
  tolerations:
  - key: "app.stateful/component"
    operator: "Equal"
    value: "postgres-operator"
    effect: NoSchedule
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 1
      preference:
        matchExpressions:
        - key: "app.stateful/component"
          operator: In
          values:
          - "postgres-operator"
  resources:
    requests:
      cpu: "1"
      memory: 4Gi
    limits:
      cpu: "2"
      memory: 4Gi
  sidecars:
    - name: exporter
      image: quay.io/prometheuscommunity/postgres-exporter:v0.14.0
      args:
      - --collector.stat_statements
      ports:
      - name: exporter
        containerPort: 9187
        protocol: TCP
      resources:
        limits:
          cpu: 500m
          memory: 256M
        requests:
          cpu: 100m
          memory: 256M
      env:
      - name: "DATA_SOURCE_URI"
        value: "localhost/postgres?sslmode=require"
      - name: "DATA_SOURCE_USER"
        value: "$(POSTGRES_USER)"
      - name: "DATA_SOURCE_PASS"
        value: "$(POSTGRES_PASSWORD)"

이 매니페스트에는 다음과 같은 필드가 있습니다.

  • spec.teamId: 선택한 클러스터 객체의 프리픽스
  • spec.numberOfInstances: 클러스터의 총 인스턴스 수
  • spec.users: 권한이 있는 사용자 목록
  • spec.databases: dbname: ownername 형식의 데이터베이스 목록
  • spec.postgresql: postgres 매개변수
  • spec.volume: 영구 디스크 매개변수
  • spec.tolerations: 클러스터 포드를 pool-postgres 노드에 예약할 수 있는 톨러레이션(toleration) 포드 템플릿
  • spec.nodeAffinity: 클러스터 포드가 pool-postgres 노드에 예약되는 것을 GKE에 알려주는 nodeAffinity 포드 템플릿
  • spec.resources: 클러스터 포드의 요청 및 한도
  • spec.sidecars: postgres-exporter가 포함된 사이드카 컨테이너 목록

자세한 내용은 Postgres 문서의 클러스터 매니페스트 참조를 확인하세요.

기본 Postgres 클러스터 만들기

  1. 기본 구성을 사용하여 새 Postgres 클러스터를 만듭니다.

    kubectl apply -n postgres -f manifests/01-basic-cluster/my-cluster.yaml
    

    이 명령어는 다음을 사용하여 Zalando 연산자의 PostgreSQL 커스텀 리소스를 만듭니다.

    • CPU 및 메모리 요청과 한도
    • 프로비저닝된 포드 복제본을 GKE 노드 간에 배포하기 위한 taint 및 어피니티
    • 데이터베이스
    • 데이터베이스 소유자 권한이 있는 사용자 두 명
    • 권한이 없는 사용자
  2. GKE가 필요한 워크로드를 시작할 때까지 기다립니다.

    kubectl wait pods -l cluster-name=my-cluster  --for condition=Ready --timeout=300s -n postgres
    

    이 명령어를 완료하는 데 몇 분이 걸릴 수 있습니다.

  3. GKE에서 Postgres 워크로드를 생성했는지 확인합니다.

    kubectl get pod,svc,statefulset,deploy,pdb,secret -n postgres
    

    출력은 다음과 비슷합니다.

    NAME                                    READY   STATUS  RESTARTS   AGE
    pod/my-cluster-0                        1/1     Running   0         6m41s
    pod/my-cluster-1                        1/1     Running   0         5m56s
    pod/my-cluster-2                        1/1     Running   0         5m16s
    pod/postgres-operator-db9667d4d-rgcs8   1/1     Running   0         12m
    
    NAME                        TYPE        CLUSTER-IP  EXTERNAL-IP   PORT(S)   AGE
    service/my-cluster          ClusterIP   10.52.12.109   <none>       5432/TCP   6m43s
    service/my-cluster-config   ClusterIP   None        <none>      <none>  5m55s
    service/my-cluster-repl     ClusterIP   10.52.6.152 <none>      5432/TCP   6m43s
    service/postgres-operator   ClusterIP   10.52.8.176 <none>      8080/TCP   12m
    
    NAME                        READY   AGE
    statefulset.apps/my-cluster   3/3   6m43s
    
    NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/postgres-operator   1/1     1           1           12m
    
    NAME                                                MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
    poddisruptionbudget.policy/postgres-my-cluster-pdb   1              N/A             0                   6m44s
    
    NAME                                                            TYPE                DATA   AGE
    secret/my-user.my-cluster.credentials.postgresql.acid.zalan.do  Opaque              2   6m45s
    secret/postgres.my-cluster.credentials.postgresql.acid.zalan.do   Opaque            2   6m44s
    secret/sh.helm.release.v1.postgres-operator.v1                  helm.sh/release.v1   1      12m
    secret/standby.my-cluster.credentials.postgresql.acid.zalan.do  Opaque              2   6m44s
    secret/zalando.my-cluster.credentials.postgresql.acid.zalan.do  Opaque              2   6m44s
    

연산자는 다음 리소스를 만듭니다.

  • Postgres용 포드 복제본 세 개를 제어하는 Postgres StatefulSet
  • 사용 가능한 복제본 최소 1개를 보장하는 PodDisruptionBudgets
  • 리더 복제본만 대상으로 하는 my-cluster 서비스
  • my-cluster-repl 서비스, 수신되는 연결과 Postgres 복제본 간의 복제를 위한 Postgres 포트 노출
  • 실행 중인 Postgres 포드 복제본의 목록을 가져오기 위한 my-cluster-config 헤드리스 서비스
  • Postgres 노드 간 복제와 데이터베이스에 액세스하기 위한 사용자 인증 정보가 포함된 보안 비밀

Postgres 인증

Postgres 사용자를 만들고 데이터베이스 권한을 할당할 수 있습니다. 예를 들어 다음 매니페스트는 사용자와 역할을 할당하는 커스텀 리소스를 설명합니다.

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: my-cluster
spec:
  ...
  users:
    mydatabaseowner:
    - superuser
    - createdb
    myuser: []
  databases:
    mydatabase: mydatabaseowner

이 매니페스트에서 각 항목은 다음을 수행합니다.

  • mydatabaseowner 사용자에게는 전체 관리자 권한(예: Postgres 구성 관리, 새 데이터베이스, 테이블, 사용자 만들기)을 허용하는 SUPERUSERCREATEDB 역할이 있습니다. 이 사용자를 클라이언트와 공유해서는 안 됩니다. 예를 들어 Cloud SQL에서는 고객이 SUPERUSER 역할이 있는 사용자에게 액세스하는 것을 허용하지 않습니다.
  • myuser 사용자에게 할당된 역할이 없습니다. 따라서 SUPERUSER를 사용하여 최소 권한으로 사용자를 만드는 권장사항을 따릅니다. mydatabaseowner에서 myuser에게 세분화된 권한을 부여합니다. 보안을 유지하려면 myuser 사용자 인증 정보를 클라이언트 애플리케이션에만 공유해야 합니다.

비밀번호 저장

scram-sha-256 비밀번호 저장에 권장되는 방법을 사용해야 합니다. 예를 들어 다음 매니페스트는 postgresql.parameters.password_encryption 필드를 사용하여 scram-sha-256 암호화를 지정하는 커스텀 리소스를 설명합니다.

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: my-cluster
spec:
  ...
  postgresql:
    parameters:
      password_encryption: scram-sha-256

사용자 인증 정보 순환

Zalando를 사용하여 Kubernetes 보안 비밀에 저장된 사용자 인증 정보를 순환할 수 있습니다. 예를 들어 다음 매니페스트는 usersWithSecretRotation 필드를 사용하여 사용자 인증 정보 순환을 정의하는 커스텀 리소스를 설명합니다.

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: my-cluster
spec:
  ...
  usersWithSecretRotation:
  - myuser
  - myanotheruser
  - ...

인증 예시: Postgres에 연결

이 섹션에서는 예시 Postgres 클라이언트를 배포하고 Kubernetes 보안 비밀의 비밀번호를 사용하여 데이터베이스에 연결하는 방법을 보여줍니다.

  1. 클라이언트 포드를 실행하여 Postgres 클러스터와 상호 작용합니다.

    kubectl apply -n postgres -f manifests/02-auth/client-pod.yaml
    

    myusermydatabaseowner 사용자의 사용자 인증 정보는 관련 보안 비밀에서 가져와 환경 변수로 포드에 마운트합니다.

  2. 준비되면 포드에 연결합니다.

    kubectl wait pod postgres-client --for=condition=Ready --timeout=300s -n postgres
    kubectl exec -it postgres-client -n postgres -- /bin/bash
    
  3. Postgres에 연결하고 myuser 사용자 인증 정보를 사용하여 새 테이블 만들기를 시도합니다.

    PGPASSWORD=$CLIENTPASSWORD psql \
      -h my-cluster \
      -U $CLIENTUSERNAME \
      -d mydatabase \
      -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);"
    

    다음과 유사한 오류가 표시되면서 명령어가 실패합니다.

    ERROR:  permission denied for schema public
    LINE 1: CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR...
    

    기본적으로 권한이 할당되지 않은 사용자는 Postgres에 로그인하고 데이터베이스를 나열할 수만 있기 때문에 명령어가 실패합니다.

  4. mydatabaseowner 사용자 인증 정보로 테이블을 만들고 테이블에 대한 모든 권한을 myuser에 부여합니다.

    PGPASSWORD=$OWNERPASSWORD psql \
      -h my-cluster \
      -U $OWNERUSERNAME \
      -d mydatabase \
      -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);GRANT ALL ON test TO myuser;GRANT ALL ON SEQUENCE test_id_seq TO myuser;"
    

    출력은 다음과 비슷합니다.

    CREATE TABLE
    GRANT
    GRANT
    
  5. myuser 사용자 인증 정보를 사용하여 테이블에 임의의 데이터를 삽입합니다.

    for i in {1..10}; do
      DATA=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13)
      PGPASSWORD=$CLIENTPASSWORD psql \
      -h my-cluster \
      -U $CLIENTUSERNAME \
      -d mydatabase \
      -c "INSERT INTO test(randomdata) VALUES ('$DATA');"
    done
    

    출력은 다음과 비슷합니다.

    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    INSERT 0 1
    
  6. 삽입한 값을 가져옵니다.

    PGPASSWORD=$CLIENTPASSWORD psql \
      -h my-cluster \
      -U $CLIENTUSERNAME \
      -d mydatabase \
      -c "SELECT * FROM test;"
    

    출력은 다음과 비슷합니다.

    id |  randomdata
    ----+---------------
      1 | jup9HYsAjwtW4
      2 | 9rLAyBlcpLgNT
      3 | wcXSqxb5Yz75g
      4 | KoDRSrx3muD6T
      5 | b9atC7RPai7En
      6 | 20d7kC8E6Vt1V
      7 | GmgNxaWbkevGq
      8 | BkTwFWH6hWC7r
      9 | nkLXHclkaqkqy
     10 | HEebZ9Lp71Nm3
    (10 rows)
    
  7. 포드 셸을 종료합니다.

    exit
    

Prometheus가 Postgres 클러스터에 대해 측정항목을 수집하는 방법 이해

다음 다이어그램은 Prometheus 측정항목 수집의 작동 방식을 보여줍니다.

다이어그램에서 GKE 비공개 클러스터에는 다음이 포함됩니다.

  • / 경로 및 9187 포트로 측정항목을 수집하는 Postgres 포드
  • Postgres 포드의 측정항목을 처리하는 Prometheus 기반 수집기
  • Cloud Monitoring으로 측정항목을 전송하는 PodMonitoring 리소스

Google Cloud Managed Service for Prometheus는 Prometheus 형식의 측정항목 수집을 지원합니다. Cloud Monitoring은 Postgres 측정항목에 대해 통합 대시보드를 사용합니다.

Zalando는 postgres_exporter 구성요소사이드카 컨테이너로 사용하여 Prometheus 형식으로 클러스터 측정항목을 노출합니다.

  1. labelSelector로 측정항목을 스크래핑하도록 PodMonitoring 리소스를 만듭니다.

    kubectl apply -n postgres -f manifests/03-prometheus-metrics/pod-monitoring.yaml
    
  2. Google Cloud 콘솔에서 GKE 클러스터 대시보드 페이지로 이동합니다.

    GKE 클러스터 대시보드로 이동

    대시보드에 0이 아닌 측정항목 수집 비율이 표시됩니다.

  3. Google Cloud 콘솔에서 대시보드 페이지로 이동합니다.

    대시보드로 이동

  4. PostgreSQL Prometheus 개요 대시보드를 엽니다. 대시보드에 가져온 행의 수가 표시됩니다. 대시보드가 자동 프로비저닝되는 데 몇 분 정도 걸릴 수 있습니다.

  5. 클라이언트 포드에 연결합니다.

    kubectl exec -it postgres-client -n postgres -- /bin/bash
    
  6. 임의 데이터를 삽입합니다.

    for i in {1..100}; do
      DATA=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13)
      PGPASSWORD=$CLIENTPASSWORD psql \
      -h my-cluster \
      -U $CLIENTUSERNAME \
      -d mydatabase \
      -c "INSERT INTO test(randomdata) VALUES ('$DATA');"
    done
    
  7. 페이지를 새로고침합니다. 블록 그래프가 실제 데이터베이스 상태를 표시하도록 업데이트됩니다.

  8. 포드 셸을 종료합니다.

    exit