0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloud Protectでオンプレミス(想定の)DBをOCI BaseDBへ復旧する

0
Posted at

はじめに

オンプレミスのOracle Databaseで障害が発生したとき、Cloud Protectのバックアップから、元ホストとは異なるOCI Base Database Service(BaseDB)へ復旧できるかを検証しました。
本記事では、ソースとターゲットの前提条件、バックアップ取得、障害前のファイル保全、RMANによる復旧、データ照合までを順に説明します。

ソースにはオンプレミスDBの代用としてBaseDBを使用し、Cloud Protect登録以降の処理がBaseDB管理機能に依存しない条件で動くことも確認しました。
復旧の結果とともに、BaseDBの管理情報が自動同期されない制約や、実際のオンプレミス環境へ適用するときの確認事項も整理します。

検証結果

Cloud Protectで取得したバックアップから、ソース削除後に作成した別ホストのBaseDBへ復旧できました。
ソース側のCloud Protect登録以降は、BaseDB管理API・DCS・インスタンスメタデータを利用できない条件で実行し、保護DBのdatabase_idも空であることを確認しました。

指定SCN以前の2行が戻り、その後に追加した3行目が含まれないこと、DB再起動後にも同じ結果となることを確認しています。

ただし、確認したのはBaseDBホスト上への手動RMAN復旧です。
OCI API・dbcli・OS起動エントリには初期DBの管理情報が残りました。
BaseDBの管理機能へ自動統合されたという結果ではありません。
この違いも説明します。

1. 今回の問いと構成

きっかけはOracle MAAの異ホスト復旧記事です。
オンプレミスDBをCloud Protectで保護し、障害後に新しいOCI Base Database ServiceへRMANで復旧する流れが紹介されています。

今回はオンプレミス環境を用意できないため、ソースにも最小構成のBaseDBを使いました。
役割はオンプレミスDBの代用です。
BaseDBの通常バックアップを別のBaseDBへクローンする検証ではありません。

範囲 今回の扱い
ソースのOS・Oracle Home・初期DBの用意 BaseDBを使用
ソースのCloud Protect登録以降 BaseDB管理機能にアクセスできない条件で検証
ターゲットの準備・復旧 OCI/BaseDBを利用してよい
実際のオンプレミス回線・DNS 今回は検証しない

ソース側Cloud Protectを隔離し、別ホストのBaseDBへ復旧した検証構成

ソースとターゲットは順に作成しており、同時稼働ではありません。
Recovery Service本体と、VCN内のサービス接続用IP群も区別しています。
図はOracle公式OCIツールキットの素材を使って作成しました。

バックアップから復旧までの流れ

上の構成図が「どこに何があるか」を示すのに対し、次のフロー図は5~9章で何をどの順番に行うかを示しています。
まずソースでバックアップを取得し、復旧に必要な鍵や設定をソース外へ保全します。
その後にソースを削除し、別ホストでバックアップを読み出して復旧します。

① ソースで登録・バックアップする(5~6章)

登録はSYSBACKUP接続から行います。
「復旧確認用データを2件登録」は、検証用テーブルCP_LAB.RECOVERY_PROBEを作り、IDが1と2のレコードをINSERTしてCOMMITする作業です。
ここでいう「行」は、記事やスクリプトの行番号ではなく、DBのテーブルに保存するデータ1件を指します。
この2件を復旧後にも存在するはずのデータとして用意します。

L0の取得後に、戻したい時点を表すSCNを記録します。
今回の値は4133351です。
その後でIDが3のレコードを追加・COMMITし、L1を取得します。
ID=3は戻したい時点より後に追加したデータなので、そのSCNへ正しく復旧できれば存在しないはずです。
復旧後に「ID=1・2はある/ID=3はない」を照合することで、単にDBが起動しただけでなく、指定した時点のデータに戻ったことを確かめます。

作るデータ 作成するタイミング 指定SCNへの復旧後に期待する状態
ID=1・2の2件 L0取得・SCN記録より前 2件とも存在する
ID=3の1件 SCN記録より後 存在しない

L0と必要なアーカイブログを読み出せることまで確認して、②へ進みます。

② 復旧資材をソース外へ保全する(7章)

保全するのはTDEウォレット、カタログwallet、DBパスワードファイル、PFILE、DBID・SCNなどです。
ソースを削除するのは、保全と照合が済んでからです。
ソースの終了を確認したら、③へ進みます。

③ 別ホストで復旧・結果確認する(8~9章)

初期DBの削除は、今回作成した検証専用ターゲットと識別情報が一致することを確認してから行います。
L0から11データファイルを復元し、必要なログで指定SCNまで回復します。
復旧直後とDB再起動後の両方で、1・2行目が存在し、3行目が存在しないことを確認します。

流れを読むときのポイントは、次の3つです。

  • データ本体と復旧資材は別の経路です。
    データファイルやアーカイブログはRecovery ServiceからSBT経由で読み出します。

    ソース外へ保全してターゲットへ転送するのは、鍵・接続設定・PFILE・DB識別情報などです。
  • 戻す地点を決めてから、その後のデータも作っています。
    SCNを記録した後に3行目を追加することで、復旧後に「必要な1・2行目が戻り、戻してはいけない3行目はない」と判定できます。
  • 今回の復旧はL0+アーカイブログです。
    L1の取得にも成功していますが、データファイルの復元には指定L0を使い、指定SCNまでの回復にはアーカイブログを使いました。

8章のDB状態は、**NOMOUNT(制御ファイルをまだ読み込まない起動状態)→MOUNT(復元した制御ファイルを読み込んだ状態)→OPEN(DBを利用できる状態)**の順に進みます。
RESTOREはファイルを戻す操作、RECOVERはログを適用して目的の地点まで進める操作です。
指定SCNまでの回復が正常終了してから、OPEN RESETLOGSでDBを開きます。

章 その段階の目的 次へ進む前に確認すること
5章 Cloud Protectで保護できる状態にする 登録9工程が完了し、保護DBがACTIVEであること
6章 バックアップを取得し、戻せる地点を確定する 目標SCNの地点が復旧可能範囲内にあり、指定L0と必要ログを読み出せること
7章 ソースが失われても使える復旧資材を残す 鍵・設定・DB識別情報がソース外にあり、ハッシュと内容の照合が済んでいること
8章 別ホストでファイルを復元し、目的の地点でDBを開く RESTORE/RECOVERが正常終了し、CDB/PDBを開けること
9章 復旧した内容と再起動後の状態を確かめる 1・2行目が存在し、3行目が存在せず、DB再起動後も同じ結果であること

このフローの完了は、復旧したDBをSQLで利用・再起動できることを指します。
BaseDB管理情報との統合やOS再起動後の自動起動まで確認したものではありません。
その制約は10章で説明します。

項目 実測・設定
リージョン Sydney(ap-sydney-1)
BaseDB VM.BaseDB.x86、4 ECPU/8GB(今回利用可能な最小構成)
DB 26ai RU 23.26.4.0.0、Standard Edition 2、COMPATIBLE 23.6.0
ライセンス ライセンス込み
ストレージ LVM、データ領域の作成要求256GB、Oracle Home・FRA・REDO等は別領域
検証DB 11データファイル、3,952,082,944 bytes(約3.68GiB)
VCN/サブネット 10.88.0.0/16、DB用10.88.1.0/24、Recovery用10.88.2.0/24
保護ポリシー 既存のBronze(14日保持)、共有ポリシーは変更しない
通常の自動バックアップ BaseDB作成要求で無効化
リアルタイムREDO保護 無効

ECPUとOCPUは異なる単位です。
構成はBaseDBのシェイプ仕様と対象環境の割当で確認します。
今回はADあたり4 ECPUの割当だったため、ソース削除後にターゲットを作成しました。

環境準備は、専用VCNと2つのサブネットを作る→Internet GatewayとDB用サブネットのデフォルトルートを設定する→専用Security Listを割り当てる→最小BaseDBを作る→Recovery Serviceへサブネットを登録する、の順です。
外部SSHは確認済みの作業元IP /32 のTCP 22だけ、VCN内は22/2484/8005を許可しました。
Recovery用サブネットには公開IPを割り当てません。

段階 ホスト DB名 DBID
ソース cpnsource CPNSRC 1690709810
ターゲット作成直後 cpntarget CPNTGT 3999134644
復旧後 cpntarget CPNSRC 1690709810

2. 用語と鍵の役割

用語 意味
Cloud Protect Fleet Agent SQLclのrcvコマンドからDB登録・バックアップ等を実行する仕組み
Recovery Service バックアップとリカバリカタログを管理するOCIサービス
RMAN Oracle Databaseのバックアップ・復旧ツール
RESTORE/RECOVER バックアップからのファイル復元/REDO適用による回復
DBID/SCN バックアップ元DBの識別番号/DB内の変更順序を表す番号
RESETLOGS 不完全リカバリ後、新しいリカバリ履歴でDBを開く操作
DCS 今回のBaseDBホストの管理用ソフトウェア・関連ファイル群
L0/L1 増分バックアップの基点となるレベル0/基点以降の変更を取得するレベル1

COMPATIBLEとは

COMPATIBLEは、データベースがディスクに保存するデータ形式の互換性レベルを指定する初期化パラメータです。
利用できる一部の機能や、古いリリースへ戻せる範囲にも関わります。
OracleソフトウェアのバージョンやRUそのものを表す値ではありません。
今回のソフトウェアは26ai RU 23.26.4.0.0ですが、COMPATIBLEは23.6.0でした。

復旧では、バックアップ元のデータ形式をターゲットのOracle Homeが扱える必要があります。
本記事ではバージョン変更を伴う復旧を扱わず、ソースとターゲットのOracle Homeを同じRUにし、復旧用PFILEもソースと同じCOMPATIBLE=23.6.0を使用しました。
Cloud Protectのソース要件である19.0.0以上は登録対象の条件であり、任意のリリース間で復旧できることを意味しません。

SQL*PlusまたはSQLclで、次のコマンドから設定値を確認できます。

SHOW PARAMETER compatible;

COMPATIBLEは引き上げると元の低い値へ戻せないため、復旧準備のためだけに安易に変更しません。
根拠:Oracle Database Reference:COMPATIBLE。

認証・暗号資材も分けて扱います。

資材 目的 今回の取扱い
OCI API署名鍵・config OCI API認証 ソースに専用一時鍵を配置、ターゲットには転送しない
Recoveryカタログwallet RMANのカタログ接続 生成されたOracle Net設定とともに保全・配置
TDEウォレット 暗号化DB/バックアップの鍵 ソース外に保全し、ターゲットへ配置
DBパスワードファイル DB管理用の認証 ソースのファイルを保全・配置

手元経由で転送したのは鍵・設定・メタデータです。
データファイル本体をSCPしたのではなく、Recovery Serviceから復元しました。

3. ソースとターゲットの前提条件

Cloud Protectの保護対象を本記事では「ソース」、復旧先ホストを「ターゲット」と呼びます。
公式資料の「target database」が保護対象DBを指す場合があるため、読み替えに注意します。

ソースの条件

項目 公式条件・必要な準備 今回
OS/CPU Linux x86-64 Oracle Linux 8.10/x86_64
リリース 19c RU 19.18以降、または26ai RU 23.4以降 26ai RU 23.26.4
COMPATIBLE 19.0.0以上 23.6.0
鍵 DBのTDE利用有無にかかわらずTDEウォレットを準備して開く。
PDBローカルウォレット利用時も開く
CDB/PDBでOPEN
SBT libra.soが必要、19.27以降と26ai RU 23.8以降はOracle Homeに同梱、19.26以前は資料指定のMOS Patch 37855779を確認 同梱ライブラリを使用
SQLcl Cloud Protect対応SQLclとschedulerを構成 SQLcl 26.3

根拠:Cloud Protectの前提条件、SQLclの導入。
最低対応RUと、ライブラリ同梱開始RUを混同しないようにします。

接続・運用 必要な条件 今回の範囲
OCI/DB認証 Recovery Serviceを操作できるOCI認証・権限と、公式のSYSBACKUP接続 API署名鍵と生成された名前付き接続
プライベート経路 ソースからRecovery Serviceへ到達可能 同一VCNで代用
DNS オンプレミスのDNS要求を受けるDNS Listener、Recovery ServiceサブネットへのFQDN登録 OCI内DNS。
オンプレミス側構築は未検証
通信 カタログTCP 2484、バックアップTCP 8005、必要なAPI HTTPS 必要先に限定して許可
アーカイブログ 今回のオンラインバックアップ・PITRではARCHIVELOGと目的地点までのログ 必要ログのSBT読出しを確認
バックアップ運用 既存運用との競合を確認 通常自動バックアップ無効

根拠:必須チェックリスト、API認証設定、オンプレミスDBの登録。
実オンプレミスではVPN/FastConnect等の経路とDNSを別途準備します。
同一VCNでの成功は、その構築まで検証済みという意味ではありません。

ターゲットの条件

以下は、本記事の手順を同じ構成で再現するための条件です。
バージョン・エディション・構成の固定値は今回の検証条件であり、Cloud Protectの全復旧方式に共通する最低要件ではありません。

項目 復旧開始前に満たす条件 今回の値・成立を確認した内容
OS/CPU ソースと同じOracle Linux 8.10/x86_64であること Oracle Linux 8.10/x86_64
DBソフトウェア ソースと同じ26ai RU 23.26.4.0.0のOracle Homeがインストール済みであること /u01/app/oracle/product/23.0.0/dbhome_1
エディション ソースと同じStandard Edition 2であること Standard Edition 2、ライセンス込み
COMPATIBLE 復旧用PFILEの値がソースと同じ23.6.0であり、ターゲットのOracle Homeがその形式を扱えること 初期DBの値とソースの値がともに23.6.0。
復旧用PFILEも同値
DB構成 単一インスタンスのCDBを復旧する構成であること RACではないCDBとPDB LABPDB
SBTライブラリ $ORACLE_HOME/lib/libra.soが存在し、RMANのSBTチャネルからロードできること Oracle Home同梱のlibra.soを使用
Recovery Serviceへの接続 カタログ宛TCP 2484とバックアップ宛TCP 8005に到達でき、接続先名をDNSで解決できること 同じVCNから接続し、実RESTORE/RECOVERで読出し成功
RMANの接続情報 ソース用のカタログwallet・Oracle Net設定・rman_env.shが配置済みで、対象カタログへ認証できること ソース外に保全したファイルを転送し、ハッシュを照合
暗号鍵・DB認証 ソースのTDE鍵を含むウォレットとDBパスワードファイルが配置済みで、Oracle所有者が読み取れること。
暗号化データを扱う時点で必要なTDEウォレットを開けること
秘密ファイルはoracle所有0600、鍵ディレクトリは0700。
復旧後CDB/PDBでウォレットOPEN
保存先・権限・容量 PFILEと制御ファイルが参照するデータ・制御・REDO・TEMP・FRA・鍵の保存先が存在し、oracleが書き込めること。
復元する全ファイルと回復処理に必要な空き容量があること
LVM上にソースと同じ保存先パスを作成。
データファイルは計約3.68GiBで、その他のファイルも含めて配置可能なことを確認
復旧対象の情報 ソースのDBID・DB_UNIQUE_NAME・version/incarnation情報・PFILEと、使用する制御ファイルのバックアップピース名がソース外に保全されていること DBID=1690709810、DB_UNIQUE_NAME=CPNSRC_zf4_syd。
制御ファイルピースはc-1690709810-20261028-05
バックアップ・回復地点 指定L0バックアップ・制御ファイル・目標SCNまでの必要アーカイブログが読出し可能であり、目標がRecovery Serviceの復旧可能範囲内にあること 目標SCN=4133351。
L0タグ指定の11データファイルと必要ログをSBTで読出し検証済み
初期DB・ソースとの分離 初期DBの削除対象が今回作成したターゲットと一致し、復旧用インスタンス起動前にその初期DBが停止・削除済みであること。
本記事では同じDBIDのソースが終了済みであること
ホスト=cpntarget、初期DB=CPNTGT、DBID=3999134644、DB_UNIQUE_NAME=CPNTGT_4wc_sydを照合して削除。
ソースの終了も確認済み

根拠:MAAブログ、RMAN環境準備、RMANの別ホスト復旧、COMPATIBLEの定義。

復旧後の確認項目は、開始前の条件とは分けます。
今回はDB/PDBのOPEN、ウォレットOPEN、データ照合、SQLによるDB再起動後の再照合に成功しました。
BaseDBの管理情報との統合、OS再起動後の自動起動、アプリ接続切替、新しいバックアップ運用は確認していません。

同一RU・エディションに固定したのは今回の検証条件です。
異なる組合せを一律に可・不可とする条件ではありません。

クロスバージョン、クロスプラットフォーム、RAC、non-CDBからPDB、Data Guard等は構成ごとに別のリハーサルが必要です。

RESTOREしたDBは元のDBIDを引き継ぎます。
稼働中の本番と同じDBIDで同じカタログを使う試験はカタログ情報への影響を考慮します。
今回はソースの実終了を確認してから復旧しました。

4. ソースをBaseDB管理機能から独立させる

鍵を汎用パスへ置く

初期DBのTDEウォレットを標準OS操作で次のパスへコピーし、整合性・所有者・権限を確認しました。
その後、標準SQLでWALLET_ROOTを変更して再起動しています。

/u01/app/oracle/admin/CPNSRC/keystore/tde/ewallet.p12
/u01/app/oracle/admin/CPNSRC/keystore/tde/cwallet.sso
alter system set wallet_root='/u01/app/oracle/admin/CPNSRC/keystore' scope=spfile;
shutdown immediate;
startup;
alter pluggable database all open;
alter pluggable database all save state;
select con_id,wallet_type,status,wrl_parameter from v$encryption_wallet;

これは既存の鍵を持つ今回の初期DBの移設手順です。
オンプレミスの未構成DBでは先に公式のキーストア設定が必要です。
以降の登録・鍵利用・保全ではDCSから鍵を取得せず、汎用パスを使います。
pwfileもOracle Homeの通常ファイルとして確認しました。

CLI・scheduler・実RMANを同じ制約で確認する

今回の隔離は非依存性を確かめる検証用の追加条件で、一般のオンプレミス利用で必須の設定ではありません。
Cloud Protectのコードは改造していません。

制約 実装と観測
ユーザー CLIとsql-schedulerをoracle UID 101で実行
DCSファイル systemdのInaccessiblePaths=/opt/oracle/dcs
権限 NoNewPrivileges=yes、空のCapabilityBoundingSet
送信 oracle UIDにIPv4/IPv6の専用チェーンを適用。
既存全体はflushしない
許可 DNS、ローカルDB、Recovery API HTTPS、Recovery subnetの2484/8005
拒否 metadata HTTP、DCS REST、BaseDB/Compute API等
証跡 接続先/ファイル参照のsyscall、実RMANのUID・cgroup・mount namespace等。
通信本文や秘密を含む引数は採取しない

OCIではDNSとmetadataが同じ169.254.169.254を使う場合があるため、DNSの53番とHTTPを区別しました。
Recovery APIの許可IPも実環境の名前解決で確認した値を使っています。

root用ラッパーcpn-runは、必要なOracle環境変数を設定し、一時systemd unitで制約付きのoracleプロセスを起動します。
以降のソース側SQLcl/SQL*Plus/RMANはこのラッパー経由です。
schedulerも同等の制約を設定しました。

実ユーザーから禁止先・DCSファイルを利用できず、DB・DNS・Recoveryには到達できることを確認。
登録・L0・L1のタスクログを開いたRMAN PIDをsyscallと照合し、実RMANもUID 101、NoNewPrivileges 1、Capability 0、DCS mode 000、隔離cgroupだったことを確認しました。

対象処理 ログと対応付けた実RMAN PID 結果
登録時の制御ファイル処理 201193 上記制約を継承
L0タスク41 209548 上記制約を継承
L1タスク44 226417 上記制約を継承

同じVMには別の隔離外RMANも観測されました。
所属を推測せず別記録とし、対象タスクを正確なログとPIDで識別しています。**
確認したのは指定したソース手順の独立性です。
OS・Oracle Homeの準備やVM全プロセスまで独立しているという主張ではありません。**

5. Cloud Protectへ手動登録する

ソースへ公式SQLclを導入し、専用一時API鍵とconfigをoracle所有・0600、認証ディレクトリ0700で配置しました。
実測SQLclは26.3です。

今回のOracle Linux用RPMの導入はrootで行い、oracleとしてschedulerを使うための設定を指定しました。

export RPM_SQLCL_INSTALL_AS_ORACLE=true
rpm -Uvh <公式サイトから取得した対応SQLclのRPM>
systemctl enable --now sql-scheduler
systemctl is-active sql-scheduler

その後に前節の検証用隔離設定を適用しました。
rootで行うOS設定、隔離下のSYSDBAによるSQL操作、SYSBACKUP接続によるrcv操作を区別します。

まず認証とSYSBACKUP接続を構成します。

sudo /usr/local/sbin/cpn-run /opt/oracle/sqlcl/bin/sql /nolog
rcv configure authentication -method api_key -oci_config /home/oracle/cpnlab/auth/config
rcv add sysbackup_user -db_unique_name CPNSRC_zf4_syd -oracle_sid CPNSRC -oracle_home /u01/app/oracle/product/23.0.0/dbhome_1
exit

生成された名前付き接続から登録します。

sudo /usr/local/sbin/cpn-run /opt/oracle/sqlcl/bin/sql -name cpnsrc_zf4_syd_rcv_conn
rcv add database -compartment_id <コンパートメントOCID> -recovery_service_subnets <登録済みRecovery ServiceサブネットOCID> -protection_policy_name Bronze -sbt_library /u01/app/oracle/product/23.0.0/dbhome_1/lib/libra.so -region ap-sydney-1 -name cp-independent-CPNSRC-20261028

公式のSYSBACKUP手動登録を使用しました。
-auto_discoverと-database_idは使いません。
今回成功した-recovery_service_subnetsには、VCNの生のsubnetではなく登録済みocid1.recoveryservicesubnet...を渡しました。

9工程の完了とACTIVEを確認し、API応答から次の値を保存しました。
作成後に値を消したのではありません。

{
  "lifecycle_state": "ACTIVE",
  "database_id": null,
  "is_redo_logs_shipped": false
}

6. データを作り、L0/L1を取得する

データ操作は隔離下のSQL*Plus(SYSDBA)、バックアップは隔離下のSQLcl(SYSBACKUP接続)を使用しました。

sudo /usr/local/sbin/cpn-run /u01/app/oracle/product/23.0.0/dbhome_1/bin/sqlplus / as sysdba
alter session set container=LABPDB;
create user cp_lab no authentication default tablespace users quota 10m on users;
create table cp_lab.recovery_probe(id number primary key,marker varchar2(100) not null);
insert into cp_lab.recovery_probe values(1,'CP_INDEPENDENT_SOURCE_20261028');
insert into cp_lab.recovery_probe values(2,'MUST_EXIST_AFTER_RECOVERY');
commit;

SQLclからL0を取得します。

rcv backup database -level 0

完了ログを確認後、CDBのselect current_scn from v$database;で復旧地点を保存しました。
今回の値は4133351、2026-10-28 06:38:10 UTCです。
その後にLABPDBで3行目を追加します。

alter session set container=LABPDB;
insert into cp_lab.recovery_probe values(3,'MUST_NOT_EXIST_AFTER_PITR');
commit;

続けてSQLclからL1を取得します。

rcv backup database -level 1
項目 実測(UTC)
L0タスク41完了 2026-10-28 06:30:44
L1タスク44完了 2026-10-28 06:39:21
L0データタグ CE_L0_DB_BP_20261028062946
制御ファイルピース c-1690709810-20261028-05

コマンド応答だけでなく、タスク内RMANログでデータ・アーカイブログ・制御ファイル/SPFILEのバックアップ完了を確認しました。

消す前に復旧可能範囲とSBT読出しを確認

rcv show restore_range
rcv configure rman_env

公式手順でRMAN環境を生成し、カタログとSBTに接続しました。
範囲表示は06:27:53~06:39:15 UTCで、目標時刻を含みます。
L1完了直後は表示が古く、反映を待って確認しました。

指定L0タグから11ファイル全てのSBT読出し、必要archive seq6/7/8を含む3バックアップセットのSBT読出しもVALIDATEで成功しています。
FRAのローカルログで代用していません。

RESTORE DATABASE FROM TAG ... PREVIEWでは、別タグのarchiveも同じタグで絞られてRMAN-05119となりました。
この失敗は保存し、データファイルのタグ指定VALIDATEと必要ログのBACKUPSET指定VALIDATEを分けて検証しました。
RESTOREのタグ・検証仕様。

7. 障害前に復旧資材をソース外へ保全する

資材 今回の内容・場所
DBメタデータ DBID/DB_UNIQUE_NAME/SID/Oracle Home/ファイル一覧/制御ファイルtrace
初期化設定・地点 source_init.ora、target_scn.txt、target_time_utc.txt
RMAN環境 /u01/app/oracle/rcv/dbs/cpnsrc_zf4_syd/rman_env/
接続情報 同じdbs配下のnetwork/とwallet/
TDE /u01/app/oracle/admin/CPNSRC/keystore/tde/
pwfile $ORACLE_HOME/dbs/orapwCPNSRC

PFILEやDBIDはCDB$ROOTへ接続し直し、標準SQLから取得します。

create pfile='/home/oracle/cpnlab/recovery/source_init.ora' from spfile;
select dbid,name,db_unique_name from v$database;
select name from v$datafile;
select name from v$tempfile;
select name from v$controlfile;
select member from v$logfile;

OCI API署名鍵・configは復旧パッケージから除外しました。
ソース外への保全時にハッシュ、DBID・SCN、アーカイブの安全なパスを確認しています。
鍵・認証資材の具体的な転送先と内容を承認したうえでSSH転送し、展開後の全ファイルのハッシュと機微ファイルoracle所有0600/鍵ディレクトリ0700も照合しました。

復旧用資材をソース外に保全した後、ソースDBシステムを終了しました。
TERMINATEDと、その後の削除要求後404を確認しています。

8. 別ホストへ復旧する

ターゲットの識別と準備

新しい最小BaseDBを作り、ホスト名・バージョン・DB名・DBID・DB_UNIQUE_NAME・空き容量・libra.soを確認しました。
初期DBはCPNTGT/3999134644/CPNTGT_4wc_sydです。

この識別情報をスクリプト内で照合し、不一致なら停止するガードを入れました。
今回新規作成した検証専用ターゲットの一致確認後にだけ、次を実行しました。

shutdown immediate;
startup mount restrict;
drop database;

保全した19ファイルの保存先をもとに親ディレクトリを作成し、データ・制御・REDO・TEMP・FRAと鍵をソースと同じパスで使えるようにしました。
ASM移行やSET NEWNAMEによる保存先変更は未検証です。

NOMOUNTと制御ファイル復元

ターゲットのoracleユーザーで実行します。
ターゲットにOCI API署名鍵は転送せず、保全したRMAN環境・カタログwallet・Oracle Net設定を使います。

export ORACLE_SID=CPNSRC
export ORACLE_UNQNAME=CPNSRC_zf4_syd
export ORACLE_HOME=/u01/app/oracle/product/23.0.0/dbhome_1
export PATH="$ORACLE_HOME/bin:$PATH"
sqlplus / as sysdba
startup nomount pfile='/home/oracle/cpnlab/recovery/source_init.ora';
exit
source /u01/app/oracle/rcv/dbs/cpnsrc_zf4_syd/rman_env/rman_env.sh
rman target / catalog /@cpnsrc_zf4_syd_DBRS
SET DBID 1690709810;
RUN {
  ALLOCATE CHANNEL recovery_sbt TYPE SBT_TAPE PARMS "SBT_LIBRARY=/u01/app/oracle/product/23.0.0/dbhome_1/lib/libra.so, ENV=(RA_WALLET='location=file:/u01/app/oracle/rcv/dbs/cpnsrc_zf4_syd/wallet credential_alias=cpnsrc_zf4_syd_DBRS', RA_FORMAT=TRUE)";
  RESTORE CONTROLFILE FROM 'c-1690709810-20261028-05';
  ALTER DATABASE MOUNT;
}

障害前に確認した制御ファイルピースを明示しました。
自動バックアップから検索する方法を採る場合も、DBID・履歴・利用可能ピースを確認して選びます。

L0から復元し、指定SCNへ回復

RUN {
  ALLOCATE CHANNEL recovery_sbt TYPE SBT_TAPE PARMS "SBT_LIBRARY=/u01/app/oracle/product/23.0.0/dbhome_1/lib/libra.so, ENV=(RA_WALLET='location=file:/u01/app/oracle/rcv/dbs/cpnsrc_zf4_syd/wallet credential_alias=cpnsrc_zf4_syd_DBRS', RA_FORMAT=TRUE)";
  SET UNTIL SCN 4133351;
  RESTORE DATABASE FROM TAG 'CE_L0_DB_BP_20261028062946';
  RECOVER DATABASE;
}
EXIT;

隔離下で取得したL0を確実に選ぶためタグを固定しました。
実ログでも11ファイル全てがこのタグから復元され、RECOVERではタスク41のarchive seq7、タスク44のarchive seq8を読み出しています。

L1取得成功も確認しましたが、今回のデータ復元はL0、回復はアーカイブログです。
L1データバックアップの適用や速度改善を実証したわけではありません。

RMANの正常完了を確認してからSQL*Plusで開きます。

alter database open resetlogs;
alter pluggable database all open;

9. データ・鍵・再起動を照合する

select instance_name,host_name,status from v$instance;
select name,dbid,db_unique_name,open_mode from v$database;
select con_id,name,open_mode from v$pdbs;
select con_id,wallet_type,status from v$encryption_wallet;
select file#,status,error,recover,fuzzy from v$datafile_header order by file#;
select * from v$recover_file;
alter session set container=LABPDB;
select id,marker from cp_lab.recovery_probe order by id;
HOST_NAME  DB_NAME  DBID        OPEN_MODE
cpntarget  CPNSRC   1690709810  READ WRITE

ID  MARKER
1   CP_INDEPENDENT_SOURCE_20261028
2   MUST_EXIST_AFTER_RECOVERY

PDB$SEEDはREAD ONLY、LABPDBはREAD WRITE、CDB/PDBのwalletはOPEN。
11データファイルのERRORはNULL、RECOVERはNO、V$RECOVER_FILEは0件でした。
今回はOPEN中にFUZZY=YESの行もあったため、その値だけで判定せず、回復要求・エラー・実データ・再起動結果を合わせて記録しました。
V$DATAFILE_HEADERの各列の定義。

続いてCDBでSPFILEを作り、PDB状態を保存してDB再起動を行いました。

create spfile from pfile='/home/oracle/cpnlab/recovery/source_init.ora';
alter pluggable database all save state;
shutdown immediate;
startup;

再起動後も同じ2行だけであることをSQL条件で照合し、不一致ならエラーにしました。
これはSQLによるDB再起動で、OS再起動の検証とは区別します。

確認 結果
制御ファイル・指定L0からの11ファイル復元 成功
指定SCNまでの回復・RESETLOGS 成功
CDB/PDB・wallet・回復要求の点検 成功
2行存在・3行目不在、DB再起動後も再照合 成功
RESTORE開始~RECOVER・OPEN 2026-10-28 22:15:52~22:17:53 UTC、121秒

121秒は約3.68GiBの小さな検証DB・単一SBTチャネルでの一例です。
BaseDB作成・回線準備・鍵保全・接続待ち等を含む全体のRTOではありません。

10. BaseDBの管理情報は別の確認事項

確認先 復旧後
SQLのV$DATABASE CPNSRC/CPNSRC_zf4_syd/DBID 1690709810
OCI API 初期CPNTGT/CPNTGT_4wc_syd/AVAILABLE
dbcliのDB/ストレージ 初期CPNTGT/CPNTGT_4wc_syd
/etc/oratab 初期CPNTGTのエントリ

標準RMAN復旧だけではこれらが自動同期されませんでした。**
「復旧DBをSQLで利用・再起動できる」ことと、「BaseDB管理機能でそのDBを運用できる」ことを分けて判断します。**

マネージドバックアップ・パッチ・OS起動時の自動起動・アプリ接続切替は成功範囲に含めません。
実運用先とする場合は、対象サービスでサポートされる管理統合と運用を別途確認する必要があります。
未確認の登録コマンドで管理情報を書き換えることはしていません。

付録:今回のソース隔離スクリプト

通常のオンプレミス登録で必須の設定ではなく、今回の非依存性試験の実装です。
前提はroot、systemd、iptables/ip6tables、strace、導入済みSQLclとschedulerです。
ホスト名・UID/GID・SID・Oracle Home・ローカルDBのIP・Recovery subnetを今回の値として固定しています。
別環境で再現する場合は実環境の値と必要通信を先に確認します。
CLIだけでなくschedulerにも制約をかけ、適用前のunitとルールを保存しています。

隔離の実装とcpn-runの作成
set -eu
test "$(hostname -s)" = cpnsource
test "$(id -u oracle)" = 101
test ! -e /etc/systemd/system/sql-scheduler.service.d/cpn-isolation.conf
install -d -m 700 -o oracle -g oinstall /home/oracle/cpnlab/evidence /home/oracle/cpnlab/evidence/traces
umask 077
systemctl cat sql-scheduler > /home/oracle/cpnlab/evidence/original-scheduler-unit.txt
iptables-save > /home/oracle/cpnlab/evidence/original-iptables.txt
ip6tables-save > /home/oracle/cpnlab/evidence/original-ip6tables.txt
systemctl stop sql-scheduler
test "$(systemctl is-active sql-scheduler || true)" = inactive
api_ips=$(getent ahostsv4 recovery.ap-sydney-1.oci.oraclecloud.com | awk '{print $1}' | sort -u)
test -n "$api_ips"
printf '%s\n' "$api_ips" > /home/oracle/cpnlab/evidence/recovery-api-ipv4.txt

# Build dedicated rules before attaching them. Never flush the host's rules.
iptables -N CPN_ORACLE_OUT
iptables -A CPN_ORACLE_OUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A CPN_ORACLE_OUT -d 169.254.169.254/32 -p udp --dport 53 -j ACCEPT
iptables -A CPN_ORACLE_OUT -d 169.254.169.254/32 -p tcp --dport 53 -j ACCEPT
for address in 127.0.0.1/32 10.88.1.241/32; do
  iptables -A CPN_ORACLE_OUT -d "$address" -p tcp -m multiport --dports 1521,2484 -j ACCEPT
done
iptables -A CPN_ORACLE_OUT -d 10.88.2.0/24 -p tcp -m multiport --dports 2484,8005 -j ACCEPT
for address in $api_ips; do
  iptables -A CPN_ORACLE_OUT -d "$address/32" -p tcp --dport 443 -j ACCEPT
done
iptables -A CPN_ORACLE_OUT -j REJECT --reject-with icmp-admin-prohibited
iptables -I OUTPUT 1 -m owner --uid-owner 101 -j CPN_ORACLE_OUT
ip6tables -N CPN_ORACLE_OUT6
ip6tables -A CPN_ORACLE_OUT6 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ip6tables -A CPN_ORACLE_OUT6 -d ::1/128 -p tcp -m multiport --dports 1521,2484 -j ACCEPT
ip6tables -A CPN_ORACLE_OUT6 -j REJECT --reject-with icmp6-adm-prohibited
ip6tables -I OUTPUT 1 -m owner --uid-owner 101 -j CPN_ORACLE_OUT6

install -d -m 755 /etc/systemd/system/sql-scheduler.service.d
cat > /etc/systemd/system/sql-scheduler.service.d/cpn-isolation.conf <<'UNIT'
[Service]
User=oracle
Group=oinstall
Type=forking
GuessMainPID=yes
PIDFile=
ExecStart=
ExecStart=/opt/oracle/sqlcl/bin/sql -daemon start
ExecStop=
ExecStop=/opt/oracle/sqlcl/bin/sql -daemon stop
ExecReload=
Environment=HOME=/home/oracle
Environment=ORACLE_HOME=/u01/app/oracle/product/23.0.0/dbhome_1
Environment=ORACLE_SID=CPNSRC
Environment=ORACLE_UNQNAME=CPNSRC_zf4_syd
Environment=PATH=/opt/oracle/sqlcl/bin:/u01/app/oracle/product/23.0.0/dbhome_1/bin:/usr/local/bin:/usr/bin:/bin
InaccessiblePaths=/opt/oracle/dcs
NoNewPrivileges=yes
CapabilityBoundingSet=
TimeoutStartSec=90
UNIT
cat > /usr/local/sbin/cpn-run <<'WRAPPER'
#!/usr/bin/bash
set -eu
test "$(hostname -s)" = cpnsource
test "$(id -u)" = 0
unit="cpn-cli-$(date +%s%N)"
exec systemd-run --unit="$unit" --quiet --wait --pipe --collect \
  --property=User=oracle --property=Group=oinstall \
  --property=InaccessiblePaths=/opt/oracle/dcs \
  --property=NoNewPrivileges=yes --property=CapabilityBoundingSet= \
  --setenv=HOME=/home/oracle \
  --setenv=ORACLE_HOME=/u01/app/oracle/product/23.0.0/dbhome_1 \
  --setenv=ORACLE_SID=CPNSRC --setenv=ORACLE_UNQNAME=CPNSRC_zf4_syd \
  --setenv=PATH=/opt/oracle/sqlcl/bin:/u01/app/oracle/product/23.0.0/dbhome_1/bin:/usr/local/bin:/usr/bin:/bin \
  /usr/bin/strace -f -e trace=connect,openat,readlink \
  -o "/home/oracle/cpnlab/evidence/traces/$unit.trace" "$@"
WRAPPER
chmod 755 /usr/local/sbin/cpn-run
systemctl daemon-reload
systemctl start sql-scheduler
systemctl is-active sql-scheduler
pid=$(systemctl show sql-scheduler -p MainPID --value)
test "$pid" -gt 1
test "$(stat -c %u /proc/$pid)" = 101
printf 'Scheduler PID=%s UID=101\n' "$pid"
systemctl show sql-scheduler -p User -p Group -p InaccessiblePaths -p NoNewPrivileges -p CapabilityBoundingSet
iptables -nvL CPN_ORACLE_OUT
ip6tables -nvL CPN_ORACLE_OUT6

# The observer records paths and connection attempts, never payloads or argv.
systemd-run --unit=cpn-scheduler-trace --quiet --property=UMask=0077 \
  /usr/bin/strace -ff -e trace=connect,openat,readlink \
  -o /home/oracle/cpnlab/evidence/traces/scheduler -p "$pid"
printf 'SOURCE_ISOLATION_ENABLED\n'
CLIとschedulerの到達性・ファイル参照の検証
set -eu
test "$(hostname -s)" = cpnsource
printf 'Root observer verifies the restricted services exist\n'
python3 - <<'PY'
import socket
from pathlib import Path
assert Path('/opt/oracle/dcs').is_dir()
for host,port in [('127.0.0.1',7070),('169.254.169.254',80)]:
    with socket.create_connection((host,port),timeout=4):
        print('Observer baseline reachable:',host,port)
PY
/usr/local/sbin/cpn-run /usr/bin/python3 - <<'PY'
import os,socket,json
from pathlib import Path
assert os.getuid()==101
report={'uid':os.getuid(),'dcs_directory_inaccessible':False,'blocked':{},'allowed':{}}
try:
    os.listdir('/opt/oracle/dcs')
except PermissionError:
    report['dcs_directory_inaccessible']=True
assert report['dcs_directory_inaccessible']
recovery_ips={x[4][0] for x in socket.getaddrinfo('recovery.ap-sydney-1.oci.oraclecloud.com',443,socket.AF_INET,socket.SOCK_STREAM)}
for host,port in [('169.254.169.254',80),('127.0.0.1',7070),('127.0.0.1',7060),('database.ap-sydney-1.oraclecloud.com',443),('iaas.ap-sydney-1.oraclecloud.com',443)]:
    addresses={x[4][0] for x in socket.getaddrinfo(host,port,socket.AF_INET,socket.SOCK_STREAM)}
    assert not (addresses & recovery_ips),'Control and Recovery API share an allowed address; reassess isolation'
    failures=[]
    for address in addresses:
        try:
            with socket.create_connection((address,port),timeout=3):
                raise AssertionError('Restricted endpoint was reachable')
        except OSError as exc:
            failures.append({'errno':exc.errno,'error':type(exc).__name__})
    report['blocked'][host+':'+str(port)]=failures
for host,port in [('127.0.0.1',1521),('recovery.ap-sydney-1.oci.oraclecloud.com',443)]:
    with socket.create_connection((host,port),timeout=4):report['allowed'][host+':'+str(port)]=True
status=Path('/proc/self/status').read_text()
assert 'NoNewPrivs:\t1' in status
report['no_new_privileges']=True
Path('/home/oracle/cpnlab/evidence/cli-isolation-proof.json').write_text(json.dumps(report,indent=2))
print('CLI_ISOLATION_PROVED: DCS path, metadata HTTP, local DCS REST, BaseDB API, Compute API blocked; DB and Recovery API reachable')
PY
pid=$(systemctl show sql-scheduler -p MainPID --value)
test "$pid" -gt 1
grep -E '^(Uid|Gid|CapEff|NoNewPrivs):' "/proc/$pid/status"
nsenter --target "$pid" --mount --setgid 1001 --setuid 101 /usr/bin/python3 - <<'PY'
import os,json
from pathlib import Path
assert os.getuid()==101
try:
    os.listdir('/opt/oracle/dcs')
except PermissionError:
    Path('/home/oracle/cpnlab/evidence/scheduler-mount-proof.json').write_text(json.dumps({'uid':101,'dcs_directory_inaccessible':True}))
    print('SCHEDULER_DCS_PATH_INACCESSIBLE')
else:
    raise AssertionError('Scheduler mount namespace can access DCS')
PY
systemctl is-active sql-scheduler
systemctl is-active cpn-scheduler-trace
iptables -nvL CPN_ORACLE_OUT
printf 'SOURCE_ISOLATION_PROVED\n'

検証から分かったこと

今回指定した範囲では、ソース側のCloud Protect手順はBaseDB管理機能を利用できない条件で成立し、そのバックアップからソース不在の別ホストへ復旧できました。

復旧を成立させたのは、バックアップに加えて、障害前にソース外へ保全した鍵・DBID・PFILE・Oracle Net/カタログ接続情報と、復旧可能地点・必要ログの確認です。
実際のオンプレミスでは回線・DNS・対象DBの構成と、復旧後のOCIでの運用まで含めたリハーサルが必要です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?