使用 RPM 编排器将 AlloyDB Omni 备份和恢复到与 S3 兼容的存储空间

1. 简介

在此 Codelab 中,您将使用 RPM 编排器将高可用性 (HA) 集群备份到与 S3 兼容的 AlloyDB Omni 对象存储区。

此 Codelab 使用了 Codelab 使用 RPM 编排器在 GCE 虚拟机上部署 AlloyDB Omni 高可用性集群中的集群,以在 Compute Engine 虚拟机 (VM) 上创建 HA 集群。在此 Codelab 中,您将在其中一个虚拟机上添加与 S3 兼容的对象存储区服务器。您可以使用 HA 集群的对象存储服务器将数据备份到该服务器,并从该服务器恢复集群。

Cloud Shell 连接到控制虚拟机。控制虚拟机运行 Ansible 和 MinIO。psql 通过 HAProxy 虚拟机的虚拟 IP 连接到集群。数据库虚拟机会将备份发送到端口 9000 上的 MinIO。

前提条件

  • 启用了结算功能的 Google Cloud 项目。
  • HA Codelab 列出的 IAM 角色。
  • 具备 Linux 命令和 SQL 方面的基础知识。

学习内容

  • 如何安装和运行与 S3 兼容的对象存储区服务器以进行备份。
  • 如何创建将备份写入与 S3 兼容的存储空间的备份方案。
  • 如何创建按需备份。
  • 如何使用备份将集群恢复到某个时间点。

所需条件

2. 设置和要求

  1. 在此步骤中,您将使用 Codelab 使用 RPM 编排器在 GCE 虚拟机上部署 AlloyDB Omni 高可用性集群创建高可用性集群。完成以下步骤。如果您已拥有 Codelab 中的集群,请继续执行下一步。
  2. 注意:在此阶段,请勿执行清理资源步骤。您将在本 Codelab 结束时执行该步骤。
  3. 返回到此 Codelab

3. 连接到集群并添加示例数据

在此步骤中,您将在控制虚拟机上加载集群设置。然后,您连接到集群并添加包含示例数据的表。

  1. 如果您未连接到控制虚拟机,请从 Cloud Shell 连接到该虚拟机:
    gcloud compute ssh "$CLUSTER-control" --zone="$ZONE" --project="$PROJECT"
    
    CLUSTER、ZONE 和 PROJECT 变量来自 使用 RPM 编排器在 GCE 虚拟机上部署 AlloyDB Omni 高可用性集群 Codelab 中的为部署准备虚拟机这一步。如果 Cloud Shell 没有这些变量,请重新设置。
  2. 在控制虚拟机上,切换到您的主目录并加载环境变量。然后,将集群名称和 SSH 用户添加到环境文件中:
    cd ~
    source /tmp/cluster.conf
    cat >> ~/.codelab.env <<EOF
    export CLUSTER="$cluster"
    export SSH_USER="$service_account"
    EOF
    source ~/.codelab.env
    env | grep -E '^(CLUSTER|SSH_USER|VIRTUAL_IP)='
    
    验证输出是否显示 CLUSTER、SSH_USER 和 VIRTUAL_IP。注意:如果您稍后再次连接到控制虚拟机,请先运行 cd ~ 和 source ~/.codelab.env,然后再继续。
  3. 将连接设置添加到环境文件中。当 read 命令要求输入密码时,输入 postgres 用户的密码。您可以在使用 RPM 编排器在 GCE 虚拟机上部署 AlloyDB Omni 高可用性集群 Codelab 的引导 AlloyDB Omni 集群步骤中设置此密码
    cat >> ~/.codelab.env <<'EOF'
    export PATH="/usr/pgsql-18/bin:$PATH"
    export PGHOST="$VIRTUAL_IP"
    export PGUSER="postgres"
    EOF
    read -rsp "Password of the postgres user: " PGPASSWORD; echo
    printf 'export PGPASSWORD=%q\n' "$PGPASSWORD" >> ~/.codelab.env
    source ~/.codelab.env
    env | grep -E '^(PGHOST|PGUSER|PGPASSWORD)='
    
  4. 创建表并添加 1,000 行:
    psql <<'SQL'
    CREATE TABLE public.orders (
      id integer PRIMARY KEY,
      item text NOT NULL,
      created_at timestamptz NOT NULL DEFAULT pg_catalog.now()
    );
    INSERT INTO public.orders (id, item)
    SELECT g, 'item-' || g FROM pg_catalog.generate_series(1, 1000) AS g;
    SQL
    
  5. 计算行数:
    psql -c "SELECT pg_catalog.count(*) FROM public.orders;"
    
    验证输出是否返回 1000。

4. 安装并运行与 S3 兼容的对象存储区

在此步骤中,您将编写一个 Ansible playbook,用于在控制虚拟机上安装 MinIO 服务器。该 playbook 还会创建用于备份的存储桶和用户,并确保数据库虚拟机可以连接到 MinIO。在此步骤中,请在控制虚拟机上运行所有命令。

  1. 在控制虚拟机上,将存储设置添加到环境文件中。openssl 命令可生成随机密码。
    MD=http://metadata.google.internal/computeMetadata/v1/instance
    S3_IP=$(curl -s -H "Metadata-Flavor: Google" "$MD/network-interfaces/0/ip")
    cat >> ~/.codelab.env <<EOF
    export S3_IP="$S3_IP"
    export S3_HOST="$CLUSTER-control"
    export S3_BUCKET="alloydb-backups"
    export S3_REGION="us-east-1"
    export MINIO_ROOT_USER="s3admin"
    export MINIO_ROOT_PASSWORD="$(openssl rand -hex 16)"
    export S3_ACCESS_KEY_ID="alloydb-backup"
    export S3_SECRET_ACCESS_KEY="$(openssl rand -hex 16)"
    export MINIO_URL="https://github.com/minio/minio/releases/download/RELEASE.2024-05-10T01-41-38Z/minio.linux-amd64.RELEASE.2024-05-10T01-41-38Z"
    export MC_URL="https://github.com/minio/mc/releases/download/RELEASE.2024-05-09T17-04-24Z/mc.linux-amd64.RELEASE.2024-05-09T17-04-24Z"
    EOF
    source ~/.codelab.env
    echo "$S3_HOST $S3_IP"
    
    请确保输出显示控制虚拟机的名称和内部 IP 地址,例如 my-cluster-control 10.1.0.2。数据库虚拟机使用此主机名和 IP 地址连接到 MinIO。虚拟机的元数据服务器会提供 IP 地址。如需了解详情,请参阅虚拟机元数据文档。MINIO_URL 和 MC_URL 值分别是 MinIO 服务器和 MinIO 客户端 (mc) 的下载网址。
  2. 在控制虚拟机上安装所需的 community.crypto Ansible 集合:
    ansible-galaxy collection install community.crypto
    
  3. 创建 s3_server.yaml playbook:
    cat > s3_server.yaml <<EOF
    - name: Install and run MinIO on the control VM
      hosts: localhost
      connection: local
      become: true
      gather_facts: false
      tasks:
        - name: Create MinIO directories
          ansible.builtin.file:
            path: "{{ item }}"
            state: directory
            mode: "0755"
          loop:
            - /var/lib/minio/data
            - /var/lib/minio/certs
    
        - name: Download MinIO server and client
          ansible.builtin.get_url:
            url: "{{ item.url }}"
            dest: "{{ item.dest }}"
            mode: "0755"
          loop:
            - url: "$MINIO_URL"
              dest: /usr/local/bin/minio
            - url: "$MC_URL"
              dest: /usr/local/bin/mc
    
        - name: Generate MinIO TLS private key
          community.crypto.openssl_privatekey:
            path: /var/lib/minio/certs/private.key
            size: 2048
            mode: "0600"
    
        - name: Generate MinIO certificate signing request
          community.crypto.openssl_csr_pipe:
            privatekey_path: /var/lib/minio/certs/private.key
            common_name: "$S3_HOST"
            subject_alt_name:
              - "DNS:$S3_HOST"
              - "DNS:$S3_BUCKET.$S3_HOST"
              - "DNS:localhost"
              - "IP:127.0.0.1"
              - "IP:$S3_IP"
          register: minio_csr
    
        - name: Generate self-signed MinIO TLS certificate
          community.crypto.x509_certificate:
            path: /var/lib/minio/certs/public.crt
            csr_content: "{{ minio_csr.csr }}"
            privatekey_path: /var/lib/minio/certs/private.key
            provider: selfsigned
            selfsigned_not_after: "+365d"
            mode: "0644"
    
        - name: Create MinIO systemd service unit
          ansible.builtin.copy:
            dest: /etc/systemd/system/minio.service
            mode: "0600"
            content: |
              [Unit]
              Description=MinIO Object Storage
              After=network.target
    
              [Service]
              Environment="MINIO_ROOT_USER=$MINIO_ROOT_USER"
              Environment="MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD"
              Environment="MINIO_DOMAIN=$S3_HOST"
              ExecStart=/usr/local/bin/minio server /var/lib/minio/data --certs-dir /var/lib/minio/certs
              Restart=on-failure
    
              [Install]
              WantedBy=multi-user.target
    
        - name: Start and enable MinIO service
          ansible.builtin.systemd_service:
            name: minio
            daemon_reload: true
            state: started
            enabled: true
    
        - name: Add storage host names to /etc/hosts on the control VM
          ansible.builtin.lineinfile:
            path: /etc/hosts
            line: "$S3_IP $S3_HOST $S3_BUCKET.$S3_HOST"
            state: present
    
        - name: Wait for MinIO health endpoint
          ansible.builtin.uri:
            url: "https://$S3_HOST:9000/minio/health/live"
            ca_path: /var/lib/minio/certs/public.crt
            status_code: 200
          register: minio_health
          until: minio_health.status == 200
          retries: 6
          delay: 5
    
        - name: Create backup bucket and user in MinIO
          ansible.builtin.command:
            argv: "{{ item }}"
          loop:
            - - /usr/local/bin/mc
              - --insecure
              - alias
              - set
              - s3repo
              - "https://$S3_HOST:9000"
              - "$MINIO_ROOT_USER"
              - "$MINIO_ROOT_PASSWORD"
            - - /usr/local/bin/mc
              - --insecure
              - mb
              - --ignore-existing
              - "s3repo/$S3_BUCKET"
            - - /usr/local/bin/mc
              - --insecure
              - admin
              - user
              - add
              - s3repo
              - "$S3_ACCESS_KEY_ID"
              - "$S3_SECRET_ACCESS_KEY"
            - - /usr/local/bin/mc
              - --insecure
              - admin
              - policy
              - attach
              - s3repo
              - readwrite
              - --user
              - "$S3_ACCESS_KEY_ID"
          become: false
          no_log: true
    
    - name: Configure database VMs for MinIO
      hosts: primary_instance_nodes
      become: true
      gather_facts: false
      vars:
        ansible_user: $SSH_USER
        ansible_ssh_private_key_file: $HOME/ssh-key-cluster-sa
      tasks:
        - name: Set SELinux to permissive mode
          ansible.builtin.command:
            cmd: setenforce 0
    
        - name: Add storage host names to /etc/hosts
          ansible.builtin.lineinfile:
            path: /etc/hosts
            line: "$S3_IP $S3_HOST $S3_BUCKET.$S3_HOST"
            state: present
    
        - name: Verify storage host resolution
          ansible.builtin.getent:
            database: hosts
            key: "$S3_BUCKET.$S3_HOST"
    
        - name: Verify connection to MinIO
          ansible.builtin.uri:
            url: "https://$S3_HOST:9000/minio/health/live"
            validate_certs: false
            status_code: 200
    EOF
    
    借助 MINIO_DOMAIN 变量,MinIO 可以接受虚拟主机样式的请求。RPM 编排器使用 alloydb-backup 用户写入备份。readwrite 政策是 MinIO 内置政策。
  4. 运行 playbook:
    ansible-playbook s3_server.yaml -i deployment_spec.yaml
    
    验证 PLAY RECAP 输出是否显示 localhost 和每个数据库虚拟机的 failed=0。
  5. 验证存储桶是否存在:
    mc --insecure ls s3repo
    
    验证输出是否显示 alloydb-backups/ 存储桶。

5. 创建备份方案

备份方案包含集群备份的存储位置、时间表和保留期限。在此步骤中,您将为 MinIO 存储桶创建备份方案。

  1. 在控制虚拟机上,创建备份 playbook 和状态 playbook。您使用 Secret、BackupPlan 和 Backup 资源的备份剧本。您可以使用状态 playbook 来显示资源的状态。
    cat > backup.yaml <<EOF
    - name: Manage AlloyDB Omni backups
      hosts: localhost
      roles:
        - role: google.alloydbomni_orchestrator.backup
    EOF
    cat > status.yaml <<EOF
    - name: Get resource status
      hosts: localhost
      roles:
        - role: google.alloydbomni_orchestrator.status
    EOF
    
  2. 为 S3 凭据创建 Secret 资源。然后应用资源:
    S3_KEY_ID_B64=$(printf '%s' "$S3_ACCESS_KEY_ID" | base64 -w 0)
    S3_KEY_B64=$(printf '%s' "$S3_SECRET_ACCESS_KEY" | base64 -w 0)
    cat > s3_secret.yaml <<EOF
    Secret:
      metadata:
        name: s3-backup-secret
      spec:
        type: Opaque
        data:
          access-key-id: "$S3_KEY_ID_B64"
          access-key: "$S3_KEY_B64"
    EOF
    ansible-playbook backup.yaml -i deployment_spec.yaml \
        -e resource_spec=s3_secret.yaml
    
    data 部分中的键名称必须是 access-key-id 和 access-key。您必须先应用 Secret 资源,然后才能应用 BackupPlan 资源。
  3. 创建 BackupPlan 资源,然后应用该资源:
    cat > backup_plan.yaml <<EOF
    BackupPlan:
      metadata:
        name: s3-backup-plan
      spec:
        dbclusterRef: $CLUSTER
        backupLocation:
          type: S3
          s3Options:
            bucket: $S3_BUCKET
            key: /backups
            endpoint: https://$S3_HOST:9000
            region: $S3_REGION
            secretRef:
              name: s3-backup-secret
            caBundle:
              - |
    $(sed 's/^/            /' /var/lib/minio/certs/public.crt)
    EOF
    ansible-playbook backup.yaml -i deployment_spec.yaml \
        -e resource_spec=backup_plan.yaml
    
    key 值是相应存储桶中备份的路径。此路径必须以斜杠 (/) 开头。caBundle 字段包含用于验证 S3 存储服务器证书的 PEM 编码的 CA 证书。
  4. 验证备份方案是否已准备就绪:
    ansible-playbook status.yaml -i deployment_spec.yaml \
        -e resource_type=BackupPlan -e resource_name=s3-backup-plan
    
    验证 phase 字段是否显示 Ready。如果阶段不是 Ready,请等待一分钟,然后再次运行该命令。
  5. 创建备份方案后,RPM Orchestrator 会自动启动初始完整备份。在创建按需备份之前,请验证此初始备份是否已完成:
    ansible-playbook status.yaml -i deployment_spec.yaml \
        -e resource_type=Backup
    
    验证初始备份阶段是否成功。

6. 创建按需备份

在此步骤中,您将创建集群的按需备份。

  1. 在控制虚拟机上,创建 Backup 资源。然后应用资源:
    cat > create_backup.yaml <<EOF
    Backup:
      metadata:
        name: s3-backup-1
      spec:
        backupPlanRef: s3-backup-plan
        dbclusterRef: $CLUSTER
    EOF
    ansible-playbook backup.yaml -i deployment_spec.yaml \
        -e resource_spec=create_backup.yaml
    
    backupPlanRef 字段是备份方案的名称。dbclusterRef 字段是集群的名称。
  2. 验证备份是否已完成:
    ansible-playbook status.yaml -i deployment_spec.yaml \
        -e resource_type=Backup -e resource_name=s3-backup-1
    
    验证 phase 字段是否显示 Succeeded。如果阶段不是 Succeeded,请等待一分钟,然后再次运行该命令。
  3. 验证备份文件是否位于相应存储桶中:
    mc --insecure tree "s3repo/$S3_BUCKET"
    
    验证输出是否显示包含 db 文件夹的 backup 文件夹。

7. 从备份恢复集群

RPM 编排程序会就地恢复集群。在此步骤中,您将删除一个表,然后从备份中恢复该表。然后,您可以选择将集群恢复到某个时间点。

注意:恢复操作会覆盖数据库集群中的所有数据。

使用备份进行恢复

  1. 在控制虚拟机上,删除 orders 表。然后计算行数:
    psql -c "DROP TABLE public.orders;"
    psql -c "SELECT pg_catalog.count(*) FROM public.orders;"
    
    验证第二个命令是否因错误而失败。出错提示显示关系不存在。例如,“ERROR: table "orders" does not exist”(错误:表“orders”不存在)
  2. 创建恢复 playbook:
    cat > restore.yaml <<EOF
    - name: Restore AlloyDB Omni cluster
      hosts: localhost
      roles:
        - role: google.alloydbomni_orchestrator.restore
    EOF
    
  3. 为 s3-backup-1 备份创建 Restore 资源。然后应用资源:
    cat > restore_backup.yaml <<EOF
    Restore:
      metadata:
        name: s3-restore-1
      spec:
        sourceDBCluster: $CLUSTER
        backup: s3-backup-1
    EOF
    ansible-playbook restore.yaml -i deployment_spec.yaml \
        -e resource_spec=restore_backup.yaml
    
  4. 验证恢复操作是否已完成:
    ansible-playbook status.yaml -i deployment_spec.yaml \
        -e resource_type=Restore -e resource_name=s3-restore-1
    
    验证 phase 字段是否显示 ProvisionSucceeded。如果阶段不是 ProvisionSucceeded,请等待一分钟,然后再次运行该命令。
  5. 统计已恢复表中的行数:
    psql -c "SELECT pg_catalog.count(*) FROM public.orders;"
    
    验证输出是否显示 1000。

(可选)恢复到某个时间点

时间点恢复使用备份和归档的预写式日志 (WAL)。您可以将集群恢复到备份方案的恢复时段内的任何时间点。

目标时间规则

您必须遵循以下所有规则来选择目标时间的值和格式:

  • 使用 RFC 3339 格式(采用世界协调时间 [UTC]):YYYY-MM-DDThh:mm:ssZ。例如,2026-06-19T14:30:00Z 是 2026 年 6 月 19 日 14:30:00 UTC。字母 T 用于分隔日期和时间。字母 Z 表示世界协调时间 (UTC)。
  • 使用过去的时间。
  • 使用 BackupPlan CRD 的 recoveryWindow 字段的 begin 值之后的时间。

如需以 RFC 3339 格式显示当前时间,请运行以下命令:

date -u +%Y-%m-%dT%H:%M:%SZ

如需将本地时间更改为这种格式,请将本地时间的 UTC 偏移量添加到 date 命令中。例如,以下命令会更改 UTC+02:00 处 19 June 2026 上的 16:30:00:

date -u -d "2026-06-19 16:30:00 +0200" +%Y-%m-%dT%H:%M:%SZ

该命令返回 2026-06-19T14:30:00Z。

恢复到录制时间

在此过程中,您将向表格添加行并记录时间。您记录时间时,会为数据库完全完好无损(正好有 1,500 行)的确切时刻添加书签。然后,您删除行并将集群恢复到记录的时间。

  1. 向表中添加 500 行:
    psql <<'SQL'
    INSERT INTO public.orders (id, item)
    SELECT g, 'item-' || g FROM pg_catalog.generate_series(1001, 1500) AS g;
    SQL
    
  2. 记录时间。此时,表格有 1,500 行。
    sleep 5
    PITR_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    echo "export PITR_TIME=$PITR_TIME" >> ~/.codelab.env
    echo "$PITR_TIME"
    sleep 5
    
    验证输出是否显示 YYYY-MM-DDThh:mm:ssZ 格式的时间,例如 2026-10-05T11:16:02Z。
  3. 从表中删除行,以模拟意外删除数据的情况。然后启动新的 WAL 文件:
    psql -c "DELETE FROM public.orders WHERE id > 1000;"
    psql -c "SELECT pg_catalog.count(*) FROM public.orders;"
    psql -c "SELECT pg_catalog.pg_switch_wal();"
    
    验证 count 查询是否显示 1000。pg_switch_wal 函数可使服务器开始使用新的 WAL 文件。然后,服务器可以归档包含您所做更改的 WAL 文件。
  4. 验证备份方案的恢复时段是否包含记录的时间:
    test -n "$PITR_TIME" || { echo "PITR_TIME is not set. Run step 2 again."; exit 1; }
    echo "$PITR_TIME"
    ansible-playbook status.yaml -i deployment_spec.yaml \
        -e resource_type=BackupPlan -e resource_name=s3-backup-plan
    
    验证 PITR_TIME 值是否晚于 recoveryWindow 字段的 begin 值。
  5. 为记录的时间创建 Restore 资源。然后应用资源:
    cat > restore_pitr.yaml <<EOF
    Restore:
      metadata:
        name: s3-restore-pitr
      spec:
        sourceDBCluster: $CLUSTER
        pointInTime: "$PITR_TIME"
    EOF
    ansible-playbook restore.yaml -i deployment_spec.yaml \
        -e resource_spec=restore_pitr.yaml
    
  6. 验证恢复操作是否已完成:
    ansible-playbook status.yaml -i deployment_spec.yaml \
        -e resource_type=Restore -e resource_name=s3-restore-pitr
    
    验证 phase 字段是否显示 ProvisionSucceeded。如果阶段不是 ProvisionSucceeded,请等待一分钟,然后再次运行该命令。
  7. 计算行数:
    psql -c "SELECT pg_catalog.count(*) FROM public.orders;"
    
    验证输出是否显示 1500。

8. 清理

为避免您的 Google Cloud 账号产生费用,请删除本 Codelab 和 使用 RPM Orchestrator 在 GCE 虚拟机上部署 AlloyDB Omni 高可用性集群 Codelab 的资源。首先,删除备份资源,然后删除集群和计算环境。

  1. 在控制虚拟机上,创建删除 playbook:
    cat > delete.yaml <<EOF
    - name: Delete AlloyDB Omni resources
      hosts: localhost
      roles:
        - role: google.alloydbomni_orchestrator.delete
    EOF
    
  2. 列出备份:
    ansible-playbook status.yaml -i deployment_spec.yaml -e resource_type=Backup
    
    验证输出是否显示 s3-backup-1 备份。
  3. 删除 s3-backup-1 备份:
    ansible-playbook delete.yaml -i deployment_spec.yaml \
        -e resource_type=Backup -e resource_name=s3-backup-1
    
  4. 删除备份方案:
    ansible-playbook delete.yaml -i deployment_spec.yaml \
        -e resource_type=BackupPlan -e resource_name=s3-backup-plan
    
  5. 按照 使用 RPM 编排器在 GCE 虚拟机上部署 AlloyDB Omni 高可用性集群 Codelab 中的清理资源步骤,删除集群和计算环境。MinIO 服务器及其中的备份位于控制虚拟机上。删除计算环境时,服务器和备份都会被删除。

9. 恭喜

恭喜!您使用 RPM 编排器将高可用性 (HA) 集群备份到与 AlloyDB Omni 兼容的 S3 对象存储区。然后,您从备份中恢复了集群。

所学内容

  • 如何安装和运行 MinIO 作为与 S3 兼容的对象存储区。
  • 如何创建备份方案和按需备份。
  • 如何使用备份将集群恢复到某个时间点。

如需了解详情,请参阅以下页面:

10. 调查问卷

您打算如何使用本教程?

仅通读 阅读并完成练习