Oracle Fleet Provisioning and Patchingによるパッチ適用作業の改善


# はじめに
この記事では大規模環境でのパッチ適用の課題と、その課題への対応案としてOracle Fleet Provisioning and Patching（Oracle FPP）の紹介を行います。

Oracle Databaseでは既知の不具合の回避のために定期的なパッチ適用が推奨されており、私の環境でも定期的なプロアクティブパッチの適用を行っております。パッチ適用はかなりの工数がかかる作業のため、工数削減の手段としてOracle FPPの導入の検討を行いました。

# Oracle DB運用とパッチ適用
## パッチの種類
Oracle Databaseでは製品のパッチとして大別して以下のようなものが存在します（2024/01時点）

これらのうち、RUが特に積極的な適用を推奨されているパッチとなり、私の環境でも定期的なRUの適用を行っております。
| パッチ種別 | 概要 | Version例 |
| --- | --- | --- |
| Release Updates (RU) | 四半期ごとの集積パッチ | 19.**20**.0.0.0 |
| Monthly Recommended Patch (MRP) | 月ごとにリリースされる集積パッチ | 19.20.0.0.**240116** |
| Interim Patches | 個々の不具合修正を行うパッチ | 変更されない |

※それぞれについての詳細はOracle社のマニュアル
[Oracle Databaseパッチ・メンテナンス（19c）](https://docs.oracle.com/cd/E82638_01/dbptc/index.html)を参照ください

# 定期的なパッチ適用の課題
定期的にパッチ（RU）適用にあたり、以下のような課題に直面しています。
## パッチ適用作業の工数の肥大化
パッチ適用はなかなか大掛かりな作業です。

私の環境ではRACのローリング再起動でパッチをIn-place patchingしていく方式を採用しており、具体的には以下のようなフローでパッチの適用を行っています。
1. 適用対象のDBインスタンスを停止
2. ORACLE_HOMEにパッチを適用
3. 停止していたDBインスタンスを起動

1度のパッチ適用だけで3時間ぐらいの作業となり、環境数が多いと一度のパッチ適用だけで以下のような大きな工数がかかってしまうことが課題となっています。
| 項目 | 見積もり |
| --- | --- |
| 1回のパッチ適用作業 | 3時間 * 2名（複数名での作業を実施） |
| パッチ適用対象 | 約230環境（ORACLE_HOMEの数を計上）|
| 見込み工数 | 約1500人時（3時間 * 2名 * 230回） |

## 環境間の差異・作業の安定性
また、RUを適用する際にInterim Patchが適用されている場合、Interim Patch毎にrollbackが要否を確認し必要ならrollbackする作業が発生します。

適切な構成管理ができておらず環境間でのパッチ適用状況の差異が大きい場合、パッチ適用の例外作業が増え作業が長時間化するうえに作業の安定性も損なってしまいます。

# Oracle Fleet Provisioning and Patching (FPP)による課題解消
そこで、上述の課題の解消を試みるためにOracle DBの機能であるFleet Provisioning and Patching（以下FPP）の利用を考えています。

FPPは旧来の高速ホームプロビジョニング（Rapid Home Provisioning, RHP）と呼ばれている機能が名称が変更されたものです。利用するにはEnterprise Editionである必要があるため、ライセンスにはご注意ください（詳細はOracle社が提供している情報を参照ください）

## FPPの機能
FPPは大別して以下のような機能を備えています。パッチ適用やアップグレード等の操作を集中管理するインターフェースを提供し、大規模な環境でのOracle DBの管理をサポートしてくれます。
- パッチ適用とアップグレード、プロビジョニング
    - imageの配布によるOut of Placeパッチ適用
    - アップグレードおよびマイグレーション
    - プロビジョニング（gridおよびDBの新規作成）
- 構成管理
    - ORACLE_HOME、GRID_HOMEのイメージ管理
    - 各FPP clientのパッチ適用状況の管理
    - 複数のFPP Server間でのイメージ共有
- 管理機能
    - ロールベースの権限管理機能
    - 監査機能
    - ジョブスケジューラ機能

FPPに関するOracle社のマニュアルは以下のような箇所を参照ください
- [Oracle Fleet Patching and Provisioning管理者ガイド](https://docs.oracle.com/cd/F19136_01/fppad/about-oracle-fpp.html)

## FPPのメリット
FPPの利用により以下のようなメリットが見込めます。私の環境では主にパッチ適用の用途で使用するため以下の2点が主となりますが、FPPの持つその他の機能（ジョブ管理や監査、プロビジョニング）も活用できるかもしれません。

### パッチ適用作業時間の削減
FPPは予めパッチ適用済みのimageを配布しておき、稼働する環境を配布済みimage側に切り替えることでパッチが適用された環境に切り替えます。

in-placeのパッチ適用ではgridとdatabaseで合わせて1時間ぐらいを要していますが、FPPによる切替は20分程度で完了します。私の環境では前述のようにパッチ適用対象の環境が多い（約230環境）ため、この時間短縮により大きな工数削減が見込めます。

| 作業項目 | 手動でのパッチ適用 | FPPを利用したパッチ適用 |
| --- | --- | --- |
| DB停止 | 1時間（手動で実施） | 1時間（手動で実施） |
| パッチ適用 | 1時間（opatchautoからin-placeで適用） | 20分（FPPから実施） |
| DB起動 | 1時間（手動で実施） | 1時間（手動で実施） |

### ORACLE_HOMEの構成管理
FPP Serverで一元的に管理されたimageを各環境に適用する形式のため、各環境のパッチ適用状況をFPP Serverから管理することが可能になります。一元的な管理により環境間でのパッチレベルの差を小さくし、パッチ適用時に環境固有の作業を抑止し余計な作業の発生を抑止します。

# FPPのサンプル手順
FPP向けの構築スクリプトとしてOracle社から以下のリポジトリが提供されています。リポジトリ内の`OracleFPP`のディレクトリを参照ください。

以下の手順ではこちらで提供されているスクリプトを使用します。なお、2024/01時点の情報を元にして書いておりますので、リポジトリに更新があった場合は読み替えていただけるようお願いいたします。
- [oracle/vagrant-projects](https://github.com/oracle/vagrant-projects)

## 環境情報
この検証は以下の環境で実施したものとなります。

FPPはOracle Grid Infrastructure（以下GI）の機能となるため、前提としてGIのインストールが必要となるため、以下のようにGI 19cを利用します。
| 項目 | 詳細 |
| --- | --- |
| ホストOS | Rocky Linux 8.8 |
| 仮想化機構 | Vagrant + VirtualBox |
| ゲストOS | Oracle Linux 7 |
| Grid Infrastructure | 19c |

なお、FPPではGIの以下のコンポーネントの準備が必要となります。スクリプト内では`12_Setup_FPP.sh`でセットアップされてますが、手動で構築する場合はこれらの構築手順も準備ください。
- ACFS
- gns
- havip
- mgmtdb

また、FPPではGIとDBの両方の管理・切替等が可能ですが、今回は簡略化のためGIのみのケースとします。DBでもオプションの値が変わる程度でフローは大きく変わりません。

## セットアップ
### Vagrant, VirtualBox
仮想化機構としてVagrantおよびVirtualBoxを使用するため、rpmからインストールを行います。
```bash
# ダウンロードしたrpmファイルからインストールを実施
$ sudo dnf install VirtualBox-7.0-7.0.8_156879_el7-1.x86_64.rpm
$ sudo dnf install vagrant-2.3.6-1.x86_64.rpm
```

Vagrantのpluginが必要となるため、gemからインストールを行います。
```bash
# ダウンロードしたgemファイルからインストールの実施
$ sudo vagrant plugin install --plugin-clean-sources ./vagrant-reload-0.0.1.gem
$ sudo vagrant plugin install --plugin-clean-sources ./vagrant-proxyconf-2.0.10.gem

# インストール結果の確認
$ sudo vagrant plugin list
vagrant-proxyconf (2.0.10, global)
  - Version Constraint: 2.0.10
vagrant-reload (0.0.1, global)
  - Version Constraint: 0.0.1
```

上述のOracleFPPのリポジトリをcloneする等してスクリプトを配置します。ここではOS上のホームディレクトリで実行すると仮定していますが、環境に応じて読み替えてください。

GIのインストール用zipファイルは同梱されていないため個別にダウンとーどして所定のディレクトリ（~/vagrant-projects/OracleFPP/ORCL_software/）に配置します。
```bash
# リポジトリのclone
$ git clone https://github.com/oracle/vagrant-projects.git
$ cd ~/vagrant-projects/OracleFPP

# GIのインストール用zipファイルの配置
$ cp LINUX.X64_193000_grid_home.zip ./ORCL_software/
```

Vagrantの動作確認を行います。
```bash
$ cd ~/vagrant-projects/OracleFPP
$ sudo vagrant status

# インストール用が問題無ければ以下のようにconf/vagrant.yamlに定義されたVMが2つ認識されているはずです
--------------------
Detected virtualbox
--------------------
getting Proxy Configuration from Host...
Current machine states:
 
host1                     not created (virtualbox)
host2                     not created (virtualbox)
 
This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.
```

### Vagrant VMの起動とFPPのセットアップ
OracleFPPのVagrantfileを使用することでVagrant VMの起動とともにFPP Serverのセットアップまで実施してくれます。なお、VMの設定（割り当てリソース、IPアドレス等）はconf/vagrant.yamlに定義されているため事前に必要に応じて修正ください。

準備ができたらvagrant upからVMを起動します。
``` bash
$ cd ~/vagrant-projects/OracleFPP
# 環境の都合でhttp_proxy等の設定を入れていますが不要なら省略してください
$ sudo http_proxy=http://xx.xx.xx.xx/ \
    https_proxy=http://xx.xx.xx.xx/ \
    HTTP_PROXY=http://xx.xx.xx.xx/ \
    HTTPS_PROXY=http://xx.xx.xx.xx/ \
    vagrant up --provision | tee -a vagrant_up_$(date +%Y%m%d-%H%M%S).log &

# スクリプトが最後まで流れたらVMのstatusを確認します。以下のようにrunningになっている想定です
$ sudo vagrant status

host1                     running (virtualbox)
host2                     running (virtualbox)
```

作成したVMへのログインは以下のように行います。（host1はvagrant.yamlで指定したVMのホスト名です）
```bash
sudo vagrant ssh host1
```

## FPPによるORACLE_HOMEの切替え
FPPでは以下のような流れでORACLE_HOMEの切替えを行います。
- 切替後ORACLE_HOME用のgold imageの作成
- ORACLE_HOMEを切り替えたいクラスタをFPP clientとして登録する
- 作成したgold imageを使用してworkingcopyを作成
- ORACLE_HOMEをworkingcopyに対して切り替える

なお、今回は以下のようなケースを想定した手順となります。
- ORACLE_HOMEを切り替えるクラスタはFPPが未導入な環境（unmanaged）とします

### gold imageの作成
FPPでは以下の2つの手段でgold imageを作成できます。今回は簡略化のためにインストール用zipファイルを使用します。
- インストール用zipファイルから作成
- 既存のORACLE_HOMEから作成
- workingcopyから作成

```bash
# VMにログインしてgridユーザにスイッチ
$ sudo vagrant ssh host1
$ sudo su - grid

# イメージをzipファイルからimport
# Vagrant VM上では~/vagrant-projects/OracleFPPが/vagrantにマウントされているため、配置していたGI 19.3のzipファイルを流用します
$ rhpctl import image \
    -image GI1903 \
    -imagetype ORACLEGISOFTWARE \
    -zip /vagrant/ORCL_software/LINUX.X64_193000_grid_home.zip

fpps01.localdomain: Audit ID: 3
fpps01.localdomain: Creating a new ACFS file system for image "GI1903" ...
fpps01.localdomain: Copying files...
fpps01.localdomain: Copying home contents...
fpps01.localdomain: Changing the home ownership to user grid...
fpps01.localdomain: Changing the home ownership to user grid and group oinstall...

# 作成したimageの状態を確認します。
$ rhpctl query image -image GI1903
 
Image name: GI1903
Owner: grid@vgt-ol7-fpp-c
Site: vgt-ol7-fpp-c
Access control: USER:grid@vgt-ol7-fpp-c
Access control: ROLE:OTHER
Access control: ROLE:GH_IMG_PUBLISH
Access control: ROLE:GH_IMG_ADMIN
Access control: ROLE:GH_IMG_VISIBILITY
Parent Image:
Software home path: /rhp_storage/images/iGI1903938233/.ACFS/snaps/iGI1903/swhome
Image state: PUBLISHED
Image size: 6032 Megabytes
Image Type: ORACLEGISOFTWARE
Image Version: 19.0.0.0.0:19.3.0.0.0
Groups configured in the image: OSDBA=dba,OSASM=dba,OSBACKUP=dba,OSDG=dba,OSKM=dba,OSRAC=dba
Image platform: Linux_AMD64
Interim patches installed: 29585399,29517247,29517242,29401763
Contains a non-rolling patch: FALSE
Complete: TRUE

# dfで確認すると自動でimage用のACFSのパスが切られていることがわかる
Filesystem                         Size  Used Avail Use% Mounted on
/dev/asm/ghvol518383-388            14G  7.6G  6.5G  54% /rhp_storage/images/iGI1903451415
/dev/asm/ghchkpt-388               5.2G  780M  4.5G  15% /rhp_storage/chkbase
```

### FPP Clientの登録
FPP Server用VM(host1)上でclient追加のコマンドを実行し、追加用のxmlファイルを生成します
```bash
# host1(fpps01)上で実行します
$ sudo su - grid
$ rhpctl add client -client vgt-ol7-fppc-c -toclientdata ./

fpps01.localdomain: Audit ID: 4
fpps01.localdomain: Creating client data ...
fpps01.localdomain: Client data created for client "vgt-ol7-fppc-c".

# 生成されたファイルを確認する
$ ls -l vgt-ol7-fppc-c.xml
-rw-r--r--. 1 grid oinstall 4838 Feb  6 11:25 vgt-ol7-fppc-c.xml
```

FPP client用VM(host2)の準備を行います。今回はGIの切替えを行いたいため、host2上に事前にGIをインストールしてください。（手順は割愛しますがOracleFPPのスクリプト内のコマンドを流用すると簡単です）

GIをインストールしたら先ほど生成したファイルからclientを追加します。
```bash
# host2(fppc01)上で実行します
# 先ほど生成したファイルをfpps01からコピーします
$ scp fpps01:/home/grid/vgt-ol7-fppc-c.xml /home/grid/

# FPP Clientの追加の前にGIのrhpserverを削除します
# GIのインストール時に有効になってしまいますが、clientを追加できないため事前に削除します
# ORACLE_HOMEは環境に応じて読み替えてください
$ su - 
$ ORACLE_HOME=/u01/app/19.3.0.0/grid; $ORACLE_HOME/bin/srvctl remove rhpserver

# FPP Clientを追加します
$ ORACLE_HOME=/u01/app/19.3.0.0/grid; $ORACLE_HOME/bin/srvctl add rhpclient -clientdata /home/grid/vgt-ol7-fppc-c.xml

# 成功したらclientを起動します
$ su - grid
$ srvctl start rhpclient
```

### gold imageを使用してworkingcopyを作成
登録したgold imageを用いてFPP Clientに対してworkingcopyを作成します。この操作によりFPP Clientのサーバ上でGIがsoftwareonlyでインストールされます。

ここでは`-storagetype`でLOCALを指定しているため、OSのファイルシステム上に展開されます。これ以外にACFS上に展開することも可能になっており、ACFS上に展開した方が使用できる機能は多くなります。
```bash
# host1(fpps01)で実行
$ su - grid
$ rhpctl add workingcopy \
    -workingcopy WC_GI1903_FPPC \
    -image GI1903 \
    -client vgt-ol7-fppc-c \
    -softwareonly \
    -storagetype LOCAL \
    -oraclebase /u01/app/grid \
    -path /u01/grid/19.3.0.0

fpps01.localdomain: Audit ID: 5
fpps01.localdomain: Storing metadata in repository for working copy "WC_GI1903_FPPC" ...
fpps01.localdomain: Connecting to RHPC...
fpps01.localdomain: Starting client-side action for 'add workingcopy'...
fppc01.localdomain: Making a local copy ...
fppc01.localdomain: Provisioning Oracle home...
fppc01.localdomain: Changing the home ownership to user grid and group oinstall...
fppc01.localdomain: Starting clone operation...
========================================
fppc01.localdomain:
[INFO] [INS-32183] Use of clone.pl is deprecated in this release. Clone operation is equivalent to performing a Software Only installation from the image.
You must use /u01/grid/19.3.0.0/gridSetup.sh script available to perform the Software Only install. For more details on image based installation, refer to help documentation.

Starting Oracle Universal Installer...
[WARNING] [INS-32029] The Installer has detected that the Oracle Base location is not empty.
   ACTION: Oracle recommends that the Oracle Base location is empty.
You can find the log of this install session at:
 /u01/app/oraInventory/logs/cloneActions2024-02-06_11-39-23AM.log
..................................................   5% Done.
..................................................   10% Done.
..................................................   15% Done.
..................................................   20% Done.
..................................................   25% Done.
..................................................   30% Done.
..................................................   35% Done.
..................................................   40% Done.
..................................................   45% Done.
..................................................   50% Done.
..................................................   55% Done.
..................................................   60% Done.
..................................................   65% Done.
..................................................   70% Done.
..................................................   75% Done.
..................................................   80% Done.
..................................................   85% Done.
..........
Copy files in progress.
Copy files successful.

Link binaries in progress.
..........
Link binaries successful.

Setup files in progress.
..........
Setup files successful.
Setup Inventory in progress.
Setup Inventory successful.
..........
Finish Setup successful.
The cloning of WC_GI1903_FPPC was successful.
Please check '/u01/app/oraInventory/logs/cloneActions2024-02-06_11-39-23AM.log' for more details.

Setup Oracle Base in progress.

Setup Oracle Base successful.
..................................................   95% Done.

As a root user, execute the following script(s):
        1. /u01/grid/19.3.0.0/root.sh

Execute /u01/grid/19.3.0.0/root.sh on the following nodes:
[fppc01]


..................................................   100% Done.
fppc01.localdomain: Successfully executed clone operation.
fppc01.localdomain: Executing root script on nodes [fppc01].
fppc01.localdomain: Successfully executed root script on nodes [fppc01].
fppc01.localdomain: Oracle home provisioned.
fpps01.localdomain: Client-side action completed.
fpps01.localdomain: Oracle home provisioned.
fpps01.localdomain: Working copy creation completed.
```

### ORACLE_HOMEの切替
FPP Client上のORACLE_HOMEを先ほど作成したworkingcopyに切り替えます。コマンドとしてはFPP Serverからmove gihomeを実行するだけです。
```bash
# host1(fpps01)で実行
$ su - grid
$ rhpctl move gihome \
    -sourcehome /u01/app/19.3.0.0/grid \
    -destwc WC_GI1903_FPPC

fpps01.localdomain: Audit ID: 6
fpps01.localdomain: verifying versions of Oracle homes ...
fpps01.localdomain: verifying owners of Oracle homes ...
fpps01.localdomain: verifying groups of Oracle homes ...
fpps01.localdomain: Connecting to RHPC...
fpps01.localdomain: initiating sharedness check for the 'move gihome' operation on client cluster
fpps01.localdomain: completed the sharedness check for the source and destination Oracle home on client cluster
fpps01.localdomain: Connecting to RHPC...
fppc01.localdomain: retrieving status of databases ...
fppc01.localdomain: retrieving status of services of databases ...
fppc01.localdomain: relocating services of databases ...
fppc01.localdomain: stopping services of databases ...
fppc01.localdomain: stopping instances of databases ...
fppc01.localdomain: Executing prepatch and postpatch on nodes: "fppc01".
fppc01.localdomain: Executing root script on nodes [fppc01].
fppc01.localdomain: Successfully executed root script on nodes [fppc01].
fppc01.localdomain: Executing root script on nodes [fppc01].
Using configuration parameter file: /u01/grid/19.3.0.0/crs/install/crsconfig_params
The log of current session can be found at:
  /u01/app/grid/crsdata/fppc01/crsconfig/crsmove_fppc01_2024-02-06_12-02-47AM.log
2024/02/06 12:04:32 CLSRSC-329: Replacing Clusterware entries in file 'oracle-ohasd.service'
Oracle Clusterware active version on the cluster is [19.0.0.0.0]. The cluster upgrade state is [NORMAL]. The cluster active patch level is [724960844].
2024/02/06 12:13:53 CLSRSC-4015: Performing install or upgrade action for Oracle Trace File Analyzer (TFA) Collector.
2024/02/06 12:16:10 CLSRSC-4003: Successfully patched Oracle Trace File Analyzer (TFA) Collector.
fppc01.localdomain: Updating inventory on nodes: fppc01.
========================================
fppc01.localdomain:
Starting Oracle Universal Installer...

Checking swap space: must be greater than 500 MB.   Actual 4080 MB    Passed
The inventory pointer is located at /etc/oraInst.loc
You can find the log of this install session at:
 /u01/app/oraInventory/logs/UpdateNodeList2024-02-06_12-20-33PM.log
'UpdateNodeList' was successful.
fppc01.localdomain: Updated inventory on nodes: fppc01.
fppc01.localdomain: Updating inventory on nodes: fppc01.
========================================
fppc01.localdomain:
Starting Oracle Universal Installer...

Checking swap space: must be greater than 500 MB.   Actual 4080 MB    Passed
The inventory pointer is located at /etc/oraInst.loc
You can find the log of this install session at:
 /u01/app/oraInventory/logs/UpdateNodeList2024-02-06_12-20-39PM.log
'UpdateNodeList' was successful.
fppc01.localdomain: Updated inventory on nodes: fppc01.
fppc01.localdomain: completed the move of Oracle Grid Infrastructure home on client cluster "vgt-ol7-fppc-c"
fppc01.localdomain: Completed the 'move gihome' operation on cluster vgt-ol7-fppc-c.
```

FPP Clientで起動しているGIが切り替わっていることを確認します。CRS="true"となっているORACLE_HOMEが先ほど作成したworkingcopy（WC_GI1903_FPPC）になっていることが分かります。

確認出来たらgridユーザのORACLE_HOMEの環境変数を新しいORACLE_HOME側を指すように修正しておきます。
```bash
# host2(fppc01)で実行
$ cat /u01/app/oraInventory/ContentsXML/inventory.xml
<?xml version="1.0" standalone="yes" ?>
<!-- Copyright (c) 1999, 2024, Oracle and/or its affiliates.
All rights reserved. -->
<!-- Do not modify the contents of this file by hand. -->
<INVENTORY>
<VERSION_INFO>
   <SAVED_WITH>12.2.0.7.0</SAVED_WITH>
   <MINIMUM_VER>2.1.0.6.0</MINIMUM_VER>
</VERSION_INFO>
<HOME_LIST>
<HOME NAME="OraGI19Home1" LOC="/u01/app/19.3.0.0/grid" TYPE="O" IDX="1"/>
<HOME NAME="WC_GI1903_FPPC" LOC="/u01/grid/19.3.0.0" TYPE="O" IDX="2" CRS="true"/>
</HOME_LIST>
<COMPOSITEHOME_LIST>
</COMPOSITEHOME_LIST>
</INVENTORY>
```

上記は既存のORACLE_HOME（FPP管理外、unmanaged）からFPP管理下のworkingcopyに切替る事例ですが、以下のようにworkingcopy間の切替も可能です。

workingcopy間の切替の場合はmove gihomeの切り戻し（`-revert`オプション）が可能など機能面では優れています。
```bash
# host1(fpps01)で実行、workingcopy間を切り替える事例
$ su - grid
$ rhpctl move gihome \
    -sourcewc SOURCE_WORKINGCOPY \
    -destwc DEST_WORKINGCOPY
```

## FPPのtips
検証でつまずきやすい点としては以下のような箇所が挙げられます
- FPPのmove gihome等のコマンドが失敗した場合、再実行すると処理の最初からでは無く前回失敗した所から進むように見受けられます。失敗時は原因を取り除いて再実行することで成功できるケースがあります
    - 例として、move gihomeがclientでのroot.shの実行に失敗してNGになった場合、client側でroot.shを手動実行してmove gihomeが成功するケースがありました
- move gihome等のコマンドの出力からも分かるように、内部的にはouiやgridSetup.sh等が実行されていると思われます。コマンドに失敗した場合の原因調査はアップグレードガイド等のマニュアルが参考になります

# おわりに
この記事ではOracle Databaseへのパッチ適用の作業工数削減の手段として、Oracle FPPの事例を紹介しました。

FPPは統一的な管理が可能で、Inplaceのパッチ適用よりは高速に切替られるためダウンタイムも小さくできます。管理するORACLE_HOMEが多い環境では有用なため、Enterprise Editionを導入している環境であれば利用を検討してみてはいかがでしょうか。