인증서 관리

문서 버전을 선택합니다.

커스텀 인증서 발급기관을 사용하면 Kubernetes 기반 AlloyDB Omni 데이터베이스 클러스터에서 전송 계층 보안 (TLS) 인증서를 관리할 수 있습니다.

기본적으로 AlloyDB Omni Kubernetes 연산자는 cert-manager를 사용하여 각 데이터베이스 클러스터에 TLS 인증서 집합을 프로비저닝합니다. 데이터베이스 서버 인증서 외에도 연산자는 컨트롤 플레인 구성요소의 인증서를 만들어 내부 연결도 안전하게 보호합니다. 기본적으로 각 인증서는 운영자가 관리하는 발급기관에 의해 서명됩니다.

컨트롤 플레인 구성요소의 인증서를 비롯한 모든 인증서가 신뢰할 수 있는 자체 루트 인증 기관 (CA)으로 다시 연결되도록 하려면 원하는 발급기관을 사용하도록 운영자를 구성하면 됩니다. 이렇게 하면 각 내부 엔티티의 구현 세부정보를 노출하지 않고 컨트롤 플레인 기능에 사용되는 인증서를 포함한 모든 인증서를 자체 공개 키 인프라 (PKI)에 연결할 수 있습니다.

기본 인증서 수명

기본적으로 AlloyDB Omni 연산자는 cert-manager를 사용하여 연산자, 컨트롤 플레인, 데이터베이스 인스턴스의 TLS 인증서를 발급하고 자동으로 갱신합니다.

다음 표에는 AlloyDB Omni 연산자가 관리하는 인증서의 기본 유효 기간이 나와 있습니다.

인증서 유형 인증서 설명 보안 비밀 이름 네임스페이스 기본 기간
운영자 mTLS 클라이언트 인증서 고가용성 (HA) 상태 점검 및 연산자와 컨트롤 플레인 에이전트 간의 연결을 위해 local-controller-manager 배포에서 사용됩니다. dbs-al-cert-l2-operator 연산자 네임스페이스 1440h0m0s (60일)
연산자 웹훅 서버 인증서 커스텀 리소스를 검증하고 변경하기 위해 연산자 허용 웹훅 서버에서 사용됩니다. fleet-webhook-server-cert

local-webhook-server-cert
연산자 네임스페이스 1440h0m0s (60일)
인스턴스별 컨트롤 플레인 에이전트 인증서 컨트롤 플레인 연결을 위해 인스턴스별 데이터베이스 에이전트, 노드 관리자, 백업 데몬에서 사용됩니다. dbs-al-cert-dbd-INSTANCE_ID-DB_CLUSTER_NAME

dbs-al-cert-al-INSTANCE_ID-DB_CLUSTER_NAME-nm

dbs-al-cert-backup-daemon-BACKUP_REPO_NAME
데이터베이스 클러스터 네임스페이스 1440h0m0s (60일)
백업 인증서 백업 서비스에서 백업 및 복원 연결에 사용됩니다. dbs-al-cert-backup-client

dbs-al-cert-backup-client-DATA_PLANE_ISSUER_NAME

dbs-al-cert-backup-server-INSTANCE_ID-DB_CLUSTER_NAME

dbs-al-cert-standby-backup-server-INSTANCE_ID-DB_CLUSTER_NAME
데이터베이스 클러스터 네임스페이스 1440h0m0s (60일)
데이터베이스 서버 인증서 데이터베이스 인스턴스에 대한 데이터 영역 클라이언트 연결을 보호합니다. dbs-al-cert-DB_CLUSTER_NAME 데이터베이스 클러스터 네임스페이스 87600h0m0s (10년)
PgBouncer 인증 쿼리 인증서 PgBouncer 배포에서 데이터베이스에 연결하고 연결 풀링을 수행하는 데 사용하는 데이터베이스 클라이언트 인증서입니다. PgBouncer 리소스를 만들 때만 생성됩니다. dbs-al-pgb-cert-PGBOUNCER_NAME 데이터베이스 클러스터 네임스페이스 87600h0m0s (10년)
운영자 관리형 자체 서명 CA 인증서 운영자, 컨트롤 플레인 에이전트, 백업, 데이터베이스 서버 인증서에 서명하기 위해 운영자의 기본 자체 서명 발급기관에서 사용하는 루트 CA 인증서입니다. dbs-al-ca-l2-operator

dbs-al-ca-DB_CLUSTER_NAME

dbs-al-ca-dbd-INSTANCE_ID-DB_CLUSTER_NAME

dbs-al-ca-backup-server

dbs-al-ca-backup-daemon-BACKUP_REPO_NAME
연산자 네임스페이스 및 데이터베이스 클러스터 네임스페이스 87600h0m0s (10년)

이 표에서 보안 비밀 이름은 다음 자리표시자를 사용합니다.

  • INSTANCE_ID: 데이터베이스 인스턴스에 생성된 4자(영문 기준) 식별자입니다.
  • DB_CLUSTER_NAME: DBCluster 리소스 이름입니다.
  • DATA_PLANE_ISSUER_NAME: spec.primarySpec.tls.dataPlaneCertIssuer.name에 구성된 커스텀 cert-manager 발급기관의 이름입니다(지정된 경우).
  • BACKUP_REPO_NAME: 구성된 경우 BackupRepository 리소스의 이름입니다.
  • PGBOUNCER_NAME: PgBouncer 리소스 이름입니다.

커스텀 발급기관 구성

데이터베이스 클러스터, AlloyDB Omni 연산자, PgBouncer의 커스텀 인증서 발급기관을 구성할 수 있습니다.

인증서 발급기관을 구성하려면 먼저 인증서에 서명할 CA와 연결된 cert-manager 발급기관을 만들어야 합니다. cert-manager 발급기관 유형 중 하나 또는 호환되는 외부 발급기관을 사용할 수 있습니다. AlloyDB Omni 연산자는 인증서를 만들 때 이 발급기관을 참조합니다.

커스텀 cert-manager 인증서 발급기관은 생성된 인증서 보안 비밀의 ca.crt 필드를 루트 CA의 인증서로 채워야 합니다.

데이터베이스 클러스터 인증서

인증서 발급기관을 구성하는 데이터베이스 클러스터 사양 필드는 두 가지가 있습니다.

  • spec.primarySpec.tls.dataPlaneCertIssuer: 데이터 플레인 인증서를 프로비저닝하는 cert-manager 발급기관에 대한 참조입니다. 발급기관 CA는 TLS 연결 중에 데이터베이스를 확인하기 위해 데이터베이스 클라이언트가 신뢰할 수 있는 CA입니다.

  • spec.primarySpec.tls.controlPlaneAgentsCertIssuer: 컨트롤 플레인 인증서를 프로비저닝하는 cert-manager 발급기관에 대한 참조입니다. 이러한 인증서는 내부 연결에 사용됩니다.

spec:
  primarySpec:
    tls:
      dataPlaneCertIssuer:
        name: DATA_PLANE_ISSUER_NAME
        kind: DATA_PLANE_ISSUER_KIND
        group: DATA_PLANE_ISSUER_GROUP
      controlPlaneAgentsCertIssuer:
        name: CONTROL_PLANE_ISSUER_NAME
        kind: CONTROL_PLANE_ISSUER_KIND
        group: CONTROL_PLANE_ISSUER_GROUP

다음을 바꿉니다.

  • DATA_PLANE_ISSUER_NAME: 데이터 영역 인증서(예: 데이터베이스 서버용)를 프로비저닝하는 cert-manager 발급기관의 이름입니다.

  • DATA_PLANE_ISSUER_KIND: cert-manager 발급기관의 API 종류입니다. 기본값은 Issuer입니다.

  • DATA_PLANE_ISSUER_GROUP: 발급기관의 API 그룹입니다. 기본값은 cert-manager.io입니다.

  • CONTROL_PLANE_ISSUER_NAME: 내부 구성요소의 컨트롤 플레인 인증서를 프로비저닝하는 cert-manager 발급기관의 이름입니다.

  • CONTROL_PLANE_ISSUER_KIND: cert-manager 발급기관의 API 종류입니다. 기본값은 Issuer입니다.

  • CONTROL_PLANE_ISSUER_GROUP: 발급기관의 API 그룹입니다. 기본값은 cert-manager.io입니다.

운영자 인증서

오퍼레이터 인증서용 cert-manager 발급기관을 구성하는 단계는 AlloyDB Omni 오퍼레이터를 설치하는 데 사용된 방법에 따라 다릅니다.

Helm을 사용하여 구성

AlloyDB Omni 오퍼레이터를 처음 설치하는 경우 helm install를 사용하고 적절한 값을 설정합니다. 여기서는 Helm을 사용하여 연산자를 배포하는 안내를 따른다고 가정합니다.

helm install alloydbomni-operator alloydbomni-operator-OPERATOR_VERSION.tgz \
--create-namespace \
--namespace alloydb-omni-system \
--set operatorCertIssuer.certManagerIssuerName="OPERATOR_CERT_ISSUER_NAME" \
--set operatorCertIssuer.certManagerIssuerKind="OPERATOR_CERT_ISSUER_KIND" \
--set operatorCertIssuer.certManagerIssuerGroup="OPERATOR_CERT_ISSUER_GROUP" \
--atomic \
--timeout 5m

이미 운영자가 설치되어 있고 인증서 발급기관을 설정해야 하는 경우 helm upgrade를 사용하여 적절한 값을 설정합니다.

helm upgrade alloydbomni-operator alloydbomni-operator-OPERATOR_VERSION.tgz \
--create-namespace \
--namespace alloydb-omni-system \
--set operatorCertIssuer.certManagerIssuerName="OPERATOR_CERT_ISSUER_NAME" \
--set operatorCertIssuer.certManagerIssuerKind="OPERATOR_CERT_ISSUER_KIND" \
--set operatorCertIssuer.certManagerIssuerGroup="OPERATOR_CERT_ISSUER_GROUP" \
--atomic \
--timeout 5m

다음을 바꿉니다.

  • OPERATOR_VERSION: 설치할 AlloyDB Omni 연산자 버전입니다.

  • OPERATOR_CERT_ISSUER_NAME: 내부 연결에 사용되는 운영자 인증서를 프로비저닝하는 cert-manager 발급기관의 이름입니다.

  • OPERATOR_CERT_ISSUER_KIND: cert-manager 발급기관의 API 종류입니다. 기본값은 Issuer입니다.

  • OPERATOR_CERT_ISSUER_GROUP: 발급기관의 API 그룹입니다. 기본값은 cert-manager.io입니다.

Operator Lifecycle Manager를 사용하여 구성

Operator Lifecycle Manager (OLM)를 사용하여 AlloyDB Omni 오퍼레이터를 설치한 경우 Subscription 리소스 spec.config.env 필드를 수정하여 오퍼레이터 Deployment의 CERT_MANAGER_ISSUER_NAME, CERT_MANAGER_ISSUER_KIND, CERT_MANAGER_ISSUER_GROUP 환경 변수를 설정할 수 있습니다.

spec:
  config:
    env:
    - name: CERT_MANAGER_ISSUER_NAME
      value: OPERATOR_CERT_ISSUER_NAME
    - name: CERT_MANAGER_ISSUER_KIND
      value: OPERATOR_CERT_ISSUER_KIND
    - name: CERT_MANAGER_ISSUER_GROUP
      value: OPERATOR_CERT_ISSUER_GROUP

다음을 바꿉니다.

  • OPERATOR_CERT_ISSUER_NAME: 내부 연결에 사용되는 운영자 인증서를 프로비저닝하는 cert-manager 발급기관의 이름입니다.

  • OPERATOR_CERT_ISSUER_KIND: cert-manager 발급기관의 API 종류입니다. 기본값은 Issuer입니다.

  • OPERATOR_CERT_ISSUER_GROUP: 발급기관의 API 그룹입니다. 기본값은 cert-manager.io입니다.

PgBouncer 인증서

커스텀 발급기관을 지원하기 위해 PgBouncer 컨트롤러는 DBCluster spec.primarySpec.tls.dataPlaneCertIssuer 필드를 확인하고 이를 사용하여 PgBouncer 인증서를 프로비저닝합니다. PgBouncer 컨트롤러는 spec.primarySpec.tls.dataPlaneCertRequest에 구성된 속성을 사용하여 PgBouncer 인증서를 프로비저닝합니다. 이렇게 하면 PgBouncer가 데이터베이스와 동일한 속성을 가진 동일한 CA의 인증서를 사용합니다.

커스텀 인증서 속성 구성

인증서 기간, 갱신 기간, 주체 대체 이름 (SAN), 일반 이름, 비공개 키 구성과 같이 연산자가 생성한 TLS 인증서의 속성을 맞춤설정할 수 있습니다.

DBCluster 매니페스트에서 커스텀 인증서 속성을 구성합니다.

  • spec.primarySpec.tls.dataPlaneCertRequest: pgBackRest 서버, PgBouncer 인증 쿼리, 데이터베이스 서버 인증서를 포함한 데이터 영역 인증서의 속성을 지정합니다.
  • spec.primarySpec.tls.controlPlaneAgentsCertRequest: 컨트롤 플레인 인증서 프로비저닝을 위한 속성을 지정합니다. 이러한 인증서는 내부 연결에 사용됩니다.

인증서 요청 구성

다음 형식으로 요청 속성을 지정합니다.

spec:
  primarySpec:
    tls:
      dataPlaneCertRequest:
        commonName: DATABASE_COMMON_NAME
        dnsNames:
        - DATABASE_DNS_NAME_1
        - DATABASE_DNS_NAME_2
        duration: DATA_PLANE_DURATION
        renewBefore: DATA_PLANE_RENEW_BEFORE
        privateKey:
          algorithm: DATA_PLANE_ALGORITHM
          size: DATA_PLANE_KEY_SIZE
          rotationPolicy: DATA_PLANE_ROTATION_POLICY
      controlPlaneAgentsCertRequest:
        duration: CONTROL_PLANE_DURATION
        renewBefore: CONTROL_PLANE_RENEW_BEFORE
        privateKey:
          algorithm: CONTROL_PLANE_ALGORITHM
          size: CONTROL_PLANE_KEY_SIZE
          rotationPolicy: CONTROL_PLANE_ROTATION_POLICY

다음을 바꿉니다.

  • DATABASE_COMMON_NAME: (선택사항, 데이터 영역만 해당) 데이터베이스 인증서의 일반 이름입니다.
  • DATABASE_DNS_NAME_1, DATABASE_DNS_NAME_2: (선택사항, 데이터 영역만 해당) 데이터베이스 인증서의 소유자 대체 이름 (SAN) 목록입니다.
  • DATA_PLANE_DURATION 및 CONTROL_PLANE_DURATION: 요청된 인증서 유효 기간(문자열 기간)입니다(예: 커스텀 30일 기간의 경우 720h0m0s). 각 인증서 요청 블록에서 duration 및 renewBefore(renewBefore가 duration보다 작음)을 모두 지정하거나 두 필드를 모두 생략해야 합니다. 두 필드가 모두 생략된 경우 기본 기간은 컨트롤 플레인 및 백업 인증서의 경우 1440h0m0s 또는 60일이고 데이터베이스 서버 및 PgBouncer 인증 쿼리 인증서의 경우 87600h0m0s 또는 10년입니다.
  • DATA_PLANE_RENEW_BEFORE 및 CONTROL_PLANE_RENEW_BEFORE: cert-manager가 갱신 시도를 시작해야 하는 만료 전 기간(예: 만료 5일 전의 경우 120h0m0s)입니다. 각 인증서 요청 블록에서 duration 및 renewBefore를 모두 지정하거나(renewBefore가 duration보다 작음) 두 필드를 모두 생략해야 합니다. 두 필드가 모두 생략된 경우 기본 갱신 기간은 컨트롤 플레인 및 백업 인증서의 경우 120h0m0s(5일), 데이터베이스 서버 및 PgBouncer 인증 쿼리 인증서의 경우 1440h0m0s(60일)입니다.
  • DATA_PLANE_ALGORITHM 및 CONTROL_PLANE_ALGORITHM: 비공개 키 알고리즘입니다. 유효한 값은 RSA, ECDSA, Ed25519입니다.
  • DATA_PLANE_KEY_SIZE 및 CONTROL_PLANE_KEY_SIZE: 비공개 키 크기(비트)입니다. 알고리즘이 RSA인 경우 유효한 값은 4096 또는 8192입니다(기본값은 4096). 알고리즘이 ECDSA인 경우 유효한 값은 384 또는 521입니다(기본값은 384). 알고리즘이 Ed25519인 경우 크기는 무시됩니다.
  • DATA_PLANE_ROTATION_POLICY 및 CONTROL_PLANE_ROTATION_POLICY: 비공개 키 재생성을 제어하는 정책입니다. 유효한 값은 Always 또는 Never입니다.

운영자 및 백업 클라이언트 인증서

네임스페이스의 모든 인스턴스에서 공유되고 데이터베이스 클러스터 사양에서 관리되지 않는 연산자 및 백업 클라이언트의 인증서 속성을 맞춤설정하려면 Certificate 커스텀 리소스를 수정합니다. 연산자는 이러한 수동 수정사항을 유지합니다.

인증서 보안 비밀 삭제

DBCluster 리소스를 삭제하면 AlloyDB Omni 오퍼레이터가 연결된 Certificate 리소스를 삭제하지만 cert-manager는 기본적으로 인증서 Secret 리소스를 정리하지 않습니다. 인증서가 삭제될 때 보안 비밀을 삭제하도록 cert-manager 컨트롤러를 구성하려면 cert-manager 문서의 인증서가 삭제될 때 보안 비밀 삭제를 참조하세요.

자동 삭제를 구성하지 않고 DBCluster 리소스를 이미 삭제한 경우 분리된 인증서 보안 비밀을 수동으로 삭제합니다.

kubectl delete secret CERTIFICATE_SECRET_NAME -n NAMESPACE

다음을 바꿉니다.

  • CERTIFICATE_SECRET_NAME: 삭제할 인증서 보안 비밀의 이름입니다.
  • NAMESPACE: 데이터베이스 클러스터의 네임스페이스입니다.

확인

Deployment 연산자가 정상이고 환경 변수 CERT_MANAGER_ISSUER_NAME, CERT_MANAGER_ISSUER_KIND, CERT_MANAGER_ISSUER_GROUP가 설정되었는지 확인하려면 다음 명령어를 실행합니다.

kubectl get deployments local-controller-manager -n alloydb-omni-system -o yaml

올바른 발급기관이 사용되었는지 확인하려면 다음과 같이 Certificate 객체를 검사하세요.

kubectl get certificate -n NAMESPACE

NAMESPACE을 데이터베이스 클러스터의 네임스페이스로 바꾸거나 alloydb-omni-system을 연산자 인증서로 바꿉니다.

각 인증서의 출력에서 issuerRef 필드를 확인하여 커스텀 발급기관과 일치하는지 확인합니다.

1.8.2 이전 버전의 연산자에서 연산자 클라이언트 인증서 만료 완화

1.8.2 이전의 AlloyDB Omni 연산자 버전에서 local-controller-manager 배포는 컨트롤 플레인 에이전트에 대한 HA 상태 점검 연결을 위해 60일 dbs-al-cert-l2-operator mTLS 클라이언트 인증서를 메모리에 캐시합니다. cert-manager는 만료 5일 전에 dbs-al-cert-l2-operator 보안 비밀을 자동으로 갱신하지만 local-controller-manager 배포는 운영자 포드가 다시 시작될 때까지 갱신된 인증서를 다시 로드하지 않습니다. 60일째에 캐시된 인증서가 만료되면 후속 상태 점검 재연결이 tls: expired certificate 오류로 실패하고 계획되지 않은 HA 장애 조치가 트리거될 수 있습니다.

이 문제를 영구적으로 해결하려면 AlloyDB Omni 연산자를 버전 1.8.2 이상으로 업그레이드하세요.

운영자 버전 1.8.2 이상으로 즉시 업그레이드할 수 없는 경우 dbs-al-cert-l2-operator 보안 비밀을 순환하고 인증서가 만료되기 최소 10일 전에 local-controller-manager 배포를 다시 시작합니다. 이 절차는 데이터베이스 포드를 다시 시작하거나 데이터베이스 트래픽을 중단하지 않습니다.

시작하기 전에

rotate_cert.sh 스크립트를 실행하기 전에 환경이 다음 요구사항을 충족하는지 확인합니다.

  • PATH에 bash, kubectl, openssl, base64를 설치합니다.
  • Kubernetes 클러스터에 대한 액세스를 구성하고 활성 kubectl 컨텍스트를 대상 클러스터로 설정합니다. 활성 컨텍스트를 확인하려면 kubectl config current-context를 실행합니다. 1.8.2 이전의 연산자 버전을 실행하는 각 클러스터에 대해 이 절차를 반복합니다.
  • 계정에 기본값이 alloydb-omni-system인 연산자 네임스페이스의 Kubernetes 역할 기반 액세스 제어 (RBAC) 권한이 있는지 확인합니다.
    • Certificate 리소스 및 Pod 로그에 대한 get
    • Pod 리소스에 대한 get 및 list 권한
    • dbs-al-cert-l2-operator 보안 비밀에 대한 get 및 delete(백업된 보안 비밀을 복원하기 위한 create 및 patch)
    • local-controller-manager 배포에 대한 get 및 patch 권한
    • CertificateRequest 리소스에 대한 delete(백업된 보안 비밀 복원)

운영자 클라이언트 인증서 순환

rotate_cert.sh 스크립트는 두 가지 모드와 선택적 네임스페이스 인수를 지원하며, 기본값은 alloydb-omni-system입니다.

  • ./rotate_cert.sh check: 읽기 전용 검사를 수행하고 다음 정보를 출력합니다.
    • 현재 인증서 일련번호 및 유효 날짜
    • 권장 순환 기한, 만료 10일 전으로 설정
    • local-controller-manager 컨테이너 시작 시간
    • [OK] 또는 [WARNING]의 인메모리 인증서 상태
    • kubectl 백업 명령어
  • ./rotate_cert.sh rotate: 다음 작업을 수행합니다.
    • dbs-al-cert-l2-operator 보안 비밀을 삭제합니다.
    • cert-manager가 새 인증서 일련번호를 발급하고 dbs-al-cert-request-l2-operator Certificate 리소스를 업데이트할 때까지 기다립니다.
    • local-controller-manager 배포를 다시 시작합니다.
    • 새 포드의 로그를 확인하고 새 만료일을 출력합니다.

운영자 클라이언트 인증서를 확인하고 순환하려면 다음 단계를 따르세요.

  1. 다음 스크립트를 rotate_cert.sh 파일에 저장한 후 파일을 실행 가능하게 만듭니다.

    cat << 'EOF' > rotate_cert.sh
    #!/usr/bin/env bash
    set -euo pipefail
    MODE="${1:-}"
    NS="${2:-alloydb-omni-system}"
    SECRET="dbs-al-cert-l2-operator"
    CERT="dbs-al-cert-request-l2-operator"
    DEPLOY="local-controller-manager"
    get_cert_info() {
      kubectl get secret "$SECRET" -n "$NS" -o jsonpath='{.data.tls\.crt}' 2>/dev/null \
        | base64 -d 2>/dev/null | openssl x509 -noout -serial -enddate 2>/dev/null || true
    }
    calc_rotate_by() {
      date -u -d "$1 - 10 days" +"%Y-%m-%d" 2>/dev/null \
        || date -u -j -v-10d -f "%Y-%m-%dT%H:%M:%SZ" "$1" +"%Y-%m-%d" 2>/dev/null || echo "unknown"
    }
    get_container_name() {
      local names
      names="$(kubectl get pods -n "$NS" -l local-control-plane=controller-manager \
        --field-selector=status.phase=Running --sort-by=.metadata.creationTimestamp \
        -o jsonpath='{.items[-1].spec.containers[*].name}' 2>/dev/null || true)"
      if [[ " $names " == *" local-manager "* ]]; then
        echo "local-manager"
      else
        echo "manager"
      fi
    }
    case "$MODE" in
      check)
        INFO="$(get_cert_info)"
        SERIAL="$(echo "$INFO" | sed -n 's/^serial=//p')"
        ENDDATE="$(echo "$INFO" | sed -n 's/^notAfter=//p')"
        NOT_BEFORE="$(kubectl get certificate "$CERT" -n "$NS" -o jsonpath='{.status.notBefore}')"
        NOT_AFTER="$(kubectl get certificate "$CERT" -n "$NS" -o jsonpath='{.status.notAfter}')"
        CONTAINER="$(get_container_name)"
        STARTED_AT="$(kubectl get pods -n "$NS" -l local-control-plane=controller-manager \
          --field-selector=status.phase=Running --sort-by=.metadata.creationTimestamp \
          -o jsonpath="{.items[-1].status.containerStatuses[?(@.name==\"$CONTAINER\")].state.running.startedAt}")"
        echo "Certificate Serial      : $SERIAL"
        echo "Certificate Start Date  : $NOT_BEFORE"
        echo "Certificate End Date    : $ENDDATE ($NOT_AFTER)"
        echo "Recommended Rotate By   : $(calc_rotate_by "$NOT_AFTER") (10 days before expiry)"
        echo "Operator Started At     : $STARTED_AT"
        if [[ "$STARTED_AT" < "$NOT_BEFORE" ]]; then
          echo "[WARNING] Operator started ($STARTED_AT) BEFORE current cert was issued ($NOT_BEFORE). It is still caching the old cert in memory! Run './rotate_cert.sh rotate $NS' now."
        else
          echo "[OK] Operator is running with the current certificate in memory."
        fi
        echo "Optional Backup Command : kubectl get secret $SECRET -n $NS -o yaml > $SECRET-backup.yaml"
        ;;
      rotate)
        OLD_SERIAL="$(get_cert_info | sed -n 's/^serial=//p')"
        OLD_REV="$(kubectl get certificate "$CERT" -n "$NS" -o jsonpath='{.status.revision}' 2>/dev/null || true)"
        echo "Current Certificate Serial: ${OLD_SERIAL:-none}"
        kubectl delete secret "$SECRET" -n "$NS" --ignore-not-found
        NEW_SERIAL=""
        NOT_AFTER=""
        for _ in {1..30}; do
          sleep 2
          NEW_SERIAL="$(get_cert_info | sed -n 's/^serial=//p')"
          NEW_REV="$(kubectl get certificate "$CERT" -n "$NS" -o jsonpath='{.status.revision}' 2>/dev/null || true)"
          READY_COND="$(kubectl get certificate "$CERT" -n "$NS" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true)"
          NOT_AFTER="$(kubectl get certificate "$CERT" -n "$NS" -o jsonpath='{.status.notAfter}' 2>/dev/null || true)"
          [[ -n "$NEW_SERIAL" && "$NEW_SERIAL" != "$OLD_SERIAL" && "$NEW_REV" != "$OLD_REV" && "$READY_COND" == "True" && -n "$NOT_AFTER" ]] && break
        done
        [[ -n "$NEW_SERIAL" && "$NEW_SERIAL" != "$OLD_SERIAL" && "$READY_COND" == "True" ]] || { echo "[ERROR] Timed out waiting for new cert."; exit 1; }
        kubectl rollout restart "deployment/$DEPLOY" -n "$NS"
        kubectl rollout status "deployment/$DEPLOY" -n "$NS" --timeout=180s
        NEW_POD="$(kubectl get pods -n "$NS" -l local-control-plane=controller-manager \
          --field-selector=status.phase=Running --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')"
        CONTAINER="$(get_container_name)"
        READY=false
        for _ in {1..40}; do
          LOGS="$(kubectl logs "pod/$NEW_POD" -n "$NS" -c "$CONTAINER" 2>/dev/null || true)"
          grep -q "tls: expired certificate" <<<"$LOGS" && { echo "[ERROR] Found 'tls: expired certificate' in operator logs!"; exit 1; }
          grep -q "L2 controller cert generation completed successfully" <<<"$LOGS" && { READY=true; break; }
          sleep 3
        done
        [[ "$READY" == "true" ]] || { echo "[ERROR] Timed out waiting for L2 controller cert generation log."; exit 1; }
        echo "[SUCCESS] New Serial: $NEW_SERIAL | End Date: $NOT_AFTER | Next Rotate By: $(calc_rotate_by "$NOT_AFTER") (10 days before expiry)"
        ;;
      *)
        echo "Usage: $0 <check|rotate> [namespace]"
        echo "  check       : Validate current certificate status and operator start time."
        echo "  rotate      : Trigger immediate certificate rotation and restart the operator."
        echo "  [namespace] : Namespace of the operator (defaults to 'alloydb-omni-system')."
        exit 1 ;;
    esac
    EOF
    chmod +x rotate_cert.sh
    
  2. check 모드에서 rotate_cert.sh 스크립트를 실행하여 현재 인증서 만료일, 권장 순환 날짜, 연산자 인메모리 상태를 검사합니다.

    ./rotate_cert.sh check
    
  3. 선택사항: 인증서를 순환하기 전에 dbs-al-cert-l2-operator 보안 비밀을 백업합니다.

    kubectl get secret dbs-al-cert-l2-operator -n alloydb-omni-system \
      -o yaml > dbs-al-cert-l2-operator-backup.yaml
    
  4. check 출력에 [WARNING]가 표시되거나 현재 날짜가 Recommended Rotate By 날짜 이전인 경우 rotate 모드에서 rotate_cert.sh 스크립트를 실행합니다.

    ./rotate_cert.sh rotate
    

    출력이 [SUCCESS]로 끝나고 새 인증서 일련번호, 만료일, 다음 권장 순환 날짜가 표시되는지 확인합니다. 출력에 [ERROR]가 표시되거나 스크립트가 0이 아닌 상태로 종료되면 ./rotate_cert.sh check를 다시 실행하고 kubectl logs deployment/local-controller-manager -n alloydb-omni-system -c local-manager(또는 1.6.0 이전 연산자 버전의 경우 -c manager)를 실행하여 연산자 로그를 검사합니다.

  5. 선택사항: 3단계에서 백업 파일을 만든 경우 파일에 이전 비공개 키가 포함되어 있으므로 순환이 성공했는지 확인한 후 로컬 백업 파일을 삭제합니다.

    rm -f dbs-al-cert-l2-operator-backup.yaml
    

백업된 인증서 복원

순환 중에 cert-manager가 새 인증서를 발급하지 못하고 만료되지 않은 dbs-al-cert-l2-operator-backup.yaml 파일로 롤백해야 하는 경우 다음 단계를 따르세요.

  1. cert-manager가 SecretMismatch 이유로 복원된 비공개 키를 거부하고 복원된 보안 비밀을 덮어쓰지 않도록 dbs-al-cert-request-l2-operator 인증서의 대기 중이거나 실패한 CertificateRequest 리소스를 삭제합니다.

    REV="$(kubectl get certificate dbs-al-cert-request-l2-operator -n alloydb-omni-system -o jsonpath='{.status.revision}')"
    kubectl delete certificaterequest \
      "dbs-al-cert-request-l2-operator-$((REV + 1))" \
      -n alloydb-omni-system --ignore-not-found
    
  2. dbs-al-cert-l2-operator-backup.yaml 파일에서 최상위 metadata.resourceVersion 및 metadata.uid 필드를 삭제하고 서버 측 적용을 사용하여 보안 비밀을 적용합니다.

    grep -vE '^  (resourceVersion|uid):' dbs-al-cert-l2-operator-backup.yaml \
      | kubectl apply --server-side --force-conflicts -f -
    
  3. local-controller-manager 배포를 다시 시작하여 복원된 인증서를 메모리에 로드합니다.

    kubectl rollout restart deployment/local-controller-manager -n alloydb-omni-system
    kubectl rollout status deployment/local-controller-manager -n alloydb-omni-system --timeout=180s
    
  4. check 모드에서 rotate_cert.sh 스크립트를 실행하여 Certificate Serial 값이 백업된 인증서 일련번호와 일치하고 스크립트가 [OK]를 보고하는지 확인합니다.

    ./rotate_cert.sh check
    

    복원된 인증서는 원래 만료일을 유지하므로 Recommended Rotate By 날짜 또는 그 이전에 인증서를 순환해야 합니다.

  5. 복원을 확인한 후 로컬 백업 파일을 삭제합니다.

    rm -f dbs-al-cert-l2-operator-backup.yaml