RPM オーケストレーターを使用して AlloyDB Omni をバックアップし、S3 互換ストレージに復元する

1. はじめに

この Codelab では、RPM オーケストレーターを使用して、AlloyDB Omni の高可用性(HA)クラスタを S3 互換のオブジェクト ストレージにバックアップします。

この Codelab では、Codelab RPM オーケストレーターを使用して GCE VM に AlloyDB Omni 高可用性クラスタをデプロイするのクラスタを使用して、Compute Engine 仮想マシン(VM)に HA クラスタを作成します。この Codelab では、VM の 1 つに S3 互換のオブジェクト ストレージ サーバーを追加します。オブジェクト ストレージ サーバーを HA クラスタに使用して、データをバックアップし、サーバーからクラスタを復元します。

Cloud Shell が制御 VM に接続します。制御 VM は Ansible と MinIO を実行します。psql は、HAProxy VM の仮想 IP を介してクラスタに接続します。データベース VM は、ポート 9000 で MinIO にバックアップを送信します。

前提条件

  • 課金を有効にした Google Cloud プロジェクト
  • HA の Codelab に記載されている IAM ロール。
  • Linux コマンドと SQL の基本的な知識があること。

学習内容

  • バックアップ用の S3 互換オブジェクト ストレージ サーバーをインストールして実行する方法。
  • バックアップを S3 互換ストレージに書き込むバックアップ プランを作成する方法。
  • オンデマンド バックアップを作成する方法。
  • バックアップから特定の時点にクラスタを復元する方法。

必要なもの

2. 設定と要件

  1. このステップでは、Codelab の RPM オーケストレーターを使用して GCE VM に AlloyDB Omni 高可用性クラスタをデプロイするを使用して HA クラスタを作成します。次の手順を行います。Codelab のクラスタがすでにある場合は、次のステップに進みます。
  2. 注: この段階では、「リソースをクリーンアップする」の手順は実行しないでください。この手順は、この Codelab の最後で行います。
  3. この Codelab に戻る

3. クラスタに接続してサンプルデータを追加する

この手順では、クラスタ設定を制御 VM に読み込みます。次に、クラスタに接続して、サンプルデータを含むテーブルを追加します。

  1. 制御 VM に接続していない場合は、Cloud Shell から接続します。
    gcloud compute ssh "$CLUSTER-control" --zone="$ZONE" --project="$PROJECT"
    
    CLUSTER、ZONE、PROJECT の各変数は、RPM オーケストレーターを使用して GCE VM に AlloyDB Omni 高可用性クラスタをデプロイする Codelab のデプロイ用に VM を準備するの手順で取得します。Cloud Shell にこれらの変数がない場合は、再度設定します。
  2. 制御 VM で、ホーム ディレクトリに移動して環境変数を読み込みます。次に、クラスタ名と 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 が表示されていることを確認します。注: 後で制御 VM に再度接続する場合は、続行する前に cd ~ と source ~/.codelab.env を実行します。
  3. 環境ファイルに接続設定を追加します。read コマンドでパスワードを求められたら、postgres ユーザーのパスワードを入力します。このパスワードは、RPM オーケストレーターを使用して GCE VM に 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 互換のオブジェクト ストレージをインストールして実行する

この手順では、制御 VM に MinIO サーバーをインストールする Ansible Playbook を作成します。このプレイブックでは、バックアップ用のバケットとユーザーも作成し、データベース VM が MinIO に接続できることを確認します。この手順のコマンドはすべて、コントロール VM で実行します。

  1. 制御 VM で、ストレージ設定を環境ファイルに追加します。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"
    
    出力に、コントロール VM の名前と内部 IP アドレス(例: my-cluster-control 10.1.0.2)が表示されていることを確認します。データベース VM は、このホスト名と IP アドレスを使用して MinIO に接続します。VM のメタデータ サーバーが IP アドレスを付与します。詳細については、VM メタデータのドキュメントをご覧ください。MINIO_URL と MC_URL の値は、MinIO サーバーと MinIO クライアント(mc)のダウンロード URL です。
  2. 必要な community.crypto Ansible コレクションを制御 VM にインストールします。
    ansible-galaxy collection install community.crypto
    
  3. s3_server.yaml ハンドブックを作成します。
    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. ハンドブックを実行します。
    ansible-playbook s3_server.yaml -i deployment_spec.yaml
    
    PLAY RECAP 出力に、localhost と各データベース VM の failed=0 が表示されていることを確認します。
  5. バケットが存在することを確認します。
    mc --insecure ls s3repo
    
    出力に alloydb-backups/ バケットが表示されていることを確認します。

5. バックアップ プランを作成する

バックアップ プランには、クラスタのバックアップの保存場所、スケジュール、保持期間が含まれます。この手順では、MinIO バケットのバックアップ プランを作成します。

  1. 制御 VM で、バックアップ ハンドブックとステータス ハンドブックを作成します。Secret、BackupPlan、Backup リソースにはバックアップ ハンドブックを使用します。ステータス ハンドブックを使用して、リソースのステータスを表示します。
    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 にする必要があります。BackupPlan リソースを適用する前に、Secret リソースを適用する必要があります。
  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 でない場合は、1 分待ってからコマンドを再度実行します。
  5. バックアップ プランが作成されると、RPM オーケストレーターが最初のフル バックアップを自動的に開始します。オンデマンド バックアップを作成する前に、この初期バックアップが完了していることを確認します。
    ansible-playbook status.yaml -i deployment_spec.yaml \
        -e resource_type=Backup
    
    初期バックアップ フェーズが成功したことを確認します。

6. オンデマンド バックアップを作成する

このステップでは、クラスタのオンデマンド バックアップを作成します。

  1. コントロール VM で、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 でない場合は、1 分待ってからコマンドを再度実行します。
  3. バックアップ ファイルがバケットにあることを確認します。
    mc --insecure tree "s3repo/$S3_BUCKET"
    
    出力に db フォルダを含む backup フォルダが表示されていることを確認します。

7. バックアップからクラスタを復元する

RPM オーケストレーターは、クラスタをインプレースで復元します。このステップでは、テーブルを削除してバックアップから復元します。必要に応じて、クラスタを特定の時点に復元できます。

注意: 復元オペレーションは、データベース クラスタ内のすべてのデータを上書きします。

バックアップから復元する

  1. 制御 VM で、orders テーブルを削除します。次に、行数をカウントします。
    psql -c "DROP TABLE public.orders;"
    psql -c "SELECT pg_catalog.count(*) FROM public.orders;"
    
    2 番目のコマンドがエラーで失敗することを確認します。エラー メッセージは、リレーションが存在しないことを示しています。例: 「ERROR: table "orders" does not exist」
  2. 復元ハンドブックを作成します。
    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 でない場合は、1 分待ってからコマンドを再度実行します。
  5. 復元されたテーブルの行数をカウントします。
    psql -c "SELECT pg_catalog.count(*) FROM public.orders;"
    
    出力に 1000 が表示されていることを確認します。

(省略可)特定の時点に復元する

ポイントインタイム復元では、バックアップとアーカイブされた write-ahead log(WAL)を使用します。クラスタは、バックアップ プランの復元ウィンドウ内の任意の時点に復元できます。

目標時間のルール

目標時間の値と形式を選択するには、次のすべてのルールに従う必要があります。

  • UTC で RFC 3339 形式を使用します(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 でない場合は、1 分待ってからコマンドを再度実行します。
  7. 行数をカウントします。
    psql -c "SELECT pg_catalog.count(*) FROM public.orders;"
    
    出力に 1500 が表示されていることを確認します。

8. クリーンアップ

Google Cloud アカウントに課金されないようにするには、この Codelab のリソースと RPM Orchestrator を使用して GCE VM に AlloyDB Omni 高可用性クラスタをデプロイする Codelab のリソースを削除します。まずバックアップ リソースを削除してから、クラスタとコンピューティング環境を削除します。

  1. 制御 VM で、削除プレイブックを作成します。
    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 VM に AlloyDB Omni 高可用性クラスタをデプロイする Codelab のリソースをクリーンアップするの手順に沿って、クラスタとコンピューティング環境を削除します。MinIO サーバーとそのバックアップは、制御 VM にあります。コンピューティング環境を削除すると、サーバーとバックアップの両方が削除されます。

9. 完了

これで操作は完了です。RPM オーケストレーターを使用して、AlloyDB Omni の高可用性(HA)クラスタを S3 互換のオブジェクト ストレージにバックアップしました。その後、バックアップからクラスタを復元しました。

学習した内容

  • MinIO を S3 互換のオブジェクト ストレージとしてインストールして実行する方法。
  • バックアップ プランとオンデマンド バックアップを作成する方法。
  • バックアップから特定の時点にクラスタを復元する方法。

詳しくは次のページをご覧ください。

10. アンケート

このチュートリアルの利用方法をお選びください。

全体を通して読むだけ 内容を読んで演習をやり遂げる