亚马逊AWS官方博客

在 EKS 中使用 qtree 扩展 ONTAP 存储卷数量上限,实现千万 PVC 数量级持久化存储

背景

目前,在已经上线 FSx for NetApp ONTAP 文件存储服务的 AWS 各区域中,每个文件系统的卷上限为 500 个。在多租户场景需求中,客户通常需要创建数千或数万个小型的存储空间供给前端的容器使用,且要求存储空间必须互相隔离且具备配额功能。在此类场景中,对于读写性能的需求通常不会很高,但要求存储卷间数据隔离且支持配额。FSx for NetApp ONTAP 可以很好的满足功能上的需求,但卷数量上限使得客户不得不寻求将业务数据分散在多套文件系统中或者考虑其他自建存储解决方案,推高运维成本。为了应对此类型的需求,可以使用 qtree 的方式扩展单个文件系统可支持的 PVC 数量上限,其优势有:

  • 将每个文件系统支持的 PVC 数量从 500 提升到 10 万
  • 将每个 Amazon 区域文件系统支持的 PVC 数量从 5 万提升到 1000 万
  • 通过存储超分配有效降低初期配置成本

相关概念

Amazon EKS
Amazon Elastic Kubernetes Service(Amazon EKS)是一项托管服务,可让您在 AWS 上轻松运行 Kubernetes,而无需安装、操作或维护您自己的 Kubernetes 控制面板或节点。Kubernetes 是一个用于实现容器化应用程序的部署、扩展和管理的自动化的开源系统。
 
Amazon FSx for NetApp ONTAP
Amazon FSx for NetApp ONTAP 是一项完全托管的服务,它提供了基于 NetApp 流行的 ONTAP 文件系统构建的高度可靠、可扩展、高性能和全功能的文件存储。FSx for ONTAP 结合了熟悉的功能、性能、功能和 API 操作 NetApp 具有完全托管的灵活性、可扩展性和便捷性的文件系统 AWS 服务。
 
Kubernetes CSI
CSI 是容器编排引擎和存储系统间的一套标准的存储调用接口。Kubernetes API Server 可以在不修改核心代码的情况下,通过套接字与第三方存储进行交互,以利用第三方存储。
 
Trident
Trident 是一个由 NetApp 维护的开源项目。它的设计初衷是使用行业标准接口(如容器存储接口(CSI))帮助客户满足容器化应用程序的持久性存储需求。
Trident 作为 pod 部署在 Kubernetes 集群中,并为您的 Kubernetes 工作负载提供动态存储编排服务。它使容器化的应用程序能够快速、轻松地使用 NetApp 广泛产品组合中的持久存储,包括 ONTAP 线下和云端托管产品(如 FSx for NetApp ONTAP)。
 
NetApp qtree
Qtree 是一种以 volume 为根的逻辑文件系统,它可以将一个 volume 抽象成数百或数千个逻辑存储空间,提供给客户端使用。对于需求大量独立、隔离存储空间的场景中会发挥较大的作用。在 ONTAP 存储系统中,这些逻辑存储空间将被聚合为一个独立的 volume 进行展示和管理。

架构介绍

在多租户场景中,每个 Kubernetes Pod 挂载一个独立的 PVC。配合 Trident CSI,FSx for NetApp ONTAP 可以提供多种类型的存储资源,包括基于 NFS 的文件存储或 ISCSI 协议的块存储,均支持使用 qtree 来扩展 PV 的数量,默认每个 NFS 卷支持 200 个 qtree。由于使用了存储超配,需要使用 CloudWatch 对文件系统的整体用量进行监控,及时扩容以避免出现数据读写问题。

使用 StorageClass,开发团队或管理员可以实现 PVC 的动态部署,其过程可参考下图:

为什么选择 FSx for NetApp ONTAP

在基于容器的海量小存储卷,需求隔离,配额和超分配的共享存储场景中,使用 FSx for NetApp ONTAP 能提供最好的兼容性并支持最多的卷数量上限,其中:

  • EFS 是基于 NFS 的共享存储服务,每个 PV 对应一个文件系统,支持 CSI 但无法设置配额。
  • FSx for Lustre 是并行高性能存储服务,适用于高吞吐需求和高并发场景,如机器学习、大数据分析等,并不适用于此场景。
  • FSx for OpenZFS 并未提供 CSI,批量部署 PV 时会增加复杂度。

存储服务

PV 隔离

PV 配额

支持 CSI

存储超分配

单区域数量上限

EFS

1000 个文件系统

FSx for Lustre

100 个文件系统

FSx for ONTAP

100 个文件系统

500 个卷/文件系统

FSx for OpenZFS

100 个文件系统

100 个卷/文件系统

准备工作

创建 FSx for NetApp ONTAP 文件存储服务

在管理门户中选择 FSx 产品,点击 Create File System 以创建新的 FSx 文件存储服务(注:下面的截图来自于美国东部 AWS 区域)

部署类型我们本次选择快速创建,此类型只需要指定容量、可用区、网络等少量关键信息即可完成创建工作。

注意点:

  1. FSx for NetApp ONTAP 最小配置大小为 1024GB
  2. 确保 VPC 要与 EKS 集群相同
  3. 在生产环境中建议使用多可用区 Multi-AZ 类型并启用 Storage Efficiency 以实现成本优化

等待文件系统创建完成,Lifecycle State 的状态为:Available

确认 Amazon EKS 集群的可用状态

  1. 再次确认 EKS 集群和 FSx 在同一个 VPC
  2. 确认当前 EKS 账号有权限创建 k8s 资源
  3. 确认集群所有节点处于 Ready 的状态
[root@ip-10-100-100-43 ~]# kubectl get node
NAME                                        STATUS    ROLES    AGE        VERSION
ip-10-100-1-106.us-east-2.compute.internal   Ready    <none>   41h   v1.22.12-eks-ba74326
ip-10-100-2-13.us-east-2.compute.internal    Ready    <none>   41h   v1.22.12-eks-ba74326
ip-10-100-3-246.us-east-2.compute.internal   Ready    <none>   41h   v1.22.12-eks-ba74326

配置步骤

Amazon EKS 集群中安装 Trident

Trident 是 ONTAP CSI 的管理工具,需要安装在可管理 EKS 的节点上。在 VPC 网络中要求既可以访问到 EKS API,也可以访问到存储服务。Trident 对于 Kubernetes 的版本有一定要求。如在 v21.07 版本中,需要对应 Kubernetes 1.17 – 1.22 版本,在过低或过高的 Kubernetes 集群中将无法正常完成安装操作。目前最新版本 22.10 已经可以支持 1.25 版本。本示例将采用 Kubernetes1.22 + Trident 21.07 版本进行演示。

有关最新的 Trident 和 Kubernetes 的兼容性信息,可以参考:https://github.com/NetApp/trident/releases

部署 Trident 有两种方式,Helm 或命令行,本次我们采用命令行的方式进行手动部署。

第一步:下载 Trident Installer

$ wget https://github.com/NetApp/trident/releases/download/v21.07.2/trident-installer-21.07.2.tar.gz
$ tar -xf trident-installer-21.07.2.tar.gz

第二步:在 EKS 中创建 Trident 所需的 CRD

$ cd trident-installer
$ kubectl create -f 
deploy/crds/trident.NetApp.io_tridentorchestrators_crd_post1.16.yaml

第三步:部署 Trident Operator

$ kubectl apply -f deploy/namespace.yaml
$ kubectl create -f deploy/bundle.yaml

$ kubectl get pod -n trident
NAME                                READY   STATUS    RESTARTS   AGE
trident-operator-846d8f5598-vfb2v   1/1     Running   0          40h

$ kubectl create -f deploy/crds/tridentorchestrator_cr.yaml
tridentorchestrator.trident.NetApp.io/trident created

确保以下输出的 Status 值为:Installed

$ kubectl describe torc trident
Name:         tridentNamespace:
Labels:       Annotations:  
API Version:  trident.NetApp.io/v1
Kind:         TridentOrchestrator
Metadata:
  Creation Timestamp:  2022-10-18T12:11:21Z
  Generation:          1
  Resource Version:  20754
  UID:               c2e8cfd7-f5d5-4355-ab90-a7a17c6a719f
Spec:
  Debug:      true
  Namespace:  trident
Status:
  Current Installation Params:
    IPv6:                       false
    Autosupport Hostname:
    Autosupport Image:          NetApp/trident-autosupport:21.01
    Autosupport Proxy:
    Autosupport Serial Number:
    Debug:                      true
    Enable Node Prep:           false
    Image Pull Secrets:
    Image Registry:
    k8sTimeout:           30
    Kubelet Dir:          /var/lib/kubelet
    Log Format:           text
    Probe Port:           17546
    Silence Autosupport:  false
    Trident Image:        NetApp/trident:21.07.2
  Message:                Trident installed
  Namespace:              trident
  Status:                 Installed
  Version:                v21.07.2
Events:                   

再次查看 namespace 下的 pod 信息,确保所有 pod 在 running 的状态:

$ kubectl get pod -n trident
NAME                                READY   STATUS    RESTARTS   AGE
trident-csi-55987877-5xjq8          6/6     Running   0          40h
trident-csi-9bglv                   2/2     Running   0          40h
trident-csi-dtmhk                   2/2     Running   0          40h
trident-csi-tpfgn                   2/2     Running   0          40h
trident-operator-846d8f5598-vfb2v   1/1     Running   0          40h

通过下面的命令确认 Trident 服务器端和客户端的状态和版本:

$ ./tridentctl -n trident version
+----------------+----------------+
| SERVER VERSION | CLIENT VERSION |
+----------------+----------------+
| 21.07.2        | 21.07.2        |
+----------------+----------------+

至此 Trident CSI 的安装操作完成,接下来就可以开始为 EKS 连接 FSx for NetApp ONTAP 存储了。如果相关 CSI 容器状态异常,可按照此文档进行问题排查:https://NetApp-trident.readthedocs.io/en/stable-v21.07/kubernetes/deploying/operator-deploy.html#observing-the-status-of-the-operator

Trident 后端存储服务选择和配置

在为 EKS 连接到存储服务前,我们需要了解 ONTAP 都提供了哪些存储驱动选项:

Driver Protocol VolumeMode Access Modes Supported File Systems Supported
ontap-nas NFS Filesystem RWO,RWX,ROX nfs
ontap-nas-economy NFS Filesystem RWO,RWX,ROX nfs
ontap-san iSCSI Block RWO,ROX,RWX Raw block device
ontap-san-economy iSCSI Block RWO,ROX,RWX Raw block device
ontap-san iSCSI Filesystem RWO,ROX xfs, ext3, ext4
ontap-san-economy iSCSI Filesystem RWO,ROX xfs, ext3, ext4

其中:

  • ontap-nas 和 ontap-nas-economy 选项使用标准 NFS 协议
  • ontap-san 和 ontap-economy 选项使用 ISCSI 协议,支持提供裸卷或文件系统
  • 带有“xxx-economy”的选项将启用 ONTAP qtree 功能

本次示例中将展示 ontap-nas-economy 这种文件系统选项,使用 NFS 协议挂载给容器。该选项将启用 qtree,实现默认 200 个 qtree 映射到一个 ONTAP 卷。这意味着这一个 ONTAP 卷就可以创建 200 个独立且相互隔离的的 PV 存储资源,而一个 FSx for NetApp ONTAP 将最多可提供 10 万个隔离的 PVC 给到容器平台使用。

每个连接到 Trident 的 ONTAP 被称为 backend,使用 json 格式的配置文件,其中包含几个关键配置信息:


{
  "version": 1,
  "backendName": "ExampleBackend",
  "storageDriverName": "ontap-nas-economy",
  "managementLIF": "<SVM 管理地址>",
  "dataLIF": "<SVM 数据地址>",
  "svm": "<SVM 的名称>",
  "username": "vsadmin",
  "password": "xxxxxxxxxxxxxxx",
}

这些信息需要从 FSx for NetApp ONTAP 的 SVM 配置中获取,分别对应:

  • ManagementLIF – Management DNS Name 或 Manage IP address
  • DataLIF – NFS DNS Name 或 NFS IP address

下面的 username 和 password 在 FSx for NetApp ONTAP 创建好后需要配置和更新。在 SVM 页面的右上角点击“Update”按钮,输入新的管理员账户和密码来完成密码的更新操作。

Trident 连接到 Amazon FSx for NetApp ONTAP

编辑好 backend.json 配置文件后,使用如下命令创建新的 Backup


$ tridentctl create backend -f cert-backend.json -n trident
$ tridentctl get backend -n trident

创建 StorageClass 和 PVC

StorageClass 的配置文件:


apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: basic
provisioner: csi.trident.NetApp.io
parameters:
  backendType: "ontap-nas-economy"
allowVolumeExpansion: true

为了测试该模式下是否支持超过 500 个 PV,使用以下的 shell 脚本批量创建 600 个 PVC


for i in {1..600}; do
cat <<EOF | kubectl create -f -
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: trident-pvc-${i}
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi
  storageClassName: basic
EOF
done

确认 PVC 的数量和状态:


$ kubectl get pvc | grep trident |wc -l
600

$ kubectl get pvc | grep trident
…
trident-pvc-590   Bound    pvc-1f7779c5-beb6-43c6-b50e-fdeda585edf3   500Mi      RWO            basic          2d12h
trident-pvc-591   Bound    pvc-97650edd-6b67-4f96-880d-cbd28a6893d2   500Mi      RWO            basic          2d12h
trident-pvc-592   Bound    pvc-7a92d53c-4155-4644-97c6-df25ae5bd6c8   500Mi      RWO            basic          2d12h
trident-pvc-593   Bound    pvc-1448cb28-09a9-4cb0-bf66-6b3702c0ac2a   500Mi      RWO            basic          2d12h
trident-pvc-594   Bound    pvc-98917202-5788-4959-b7c9-ef86f091d25d   500Mi      RWO            basic          2d12h
trident-pvc-595   Bound    pvc-8e173cfb-e217-47f3-98ff-1fdbd8c4a949   500Mi      RWO            basic          2d12h
trident-pvc-596   Bound    pvc-17b349bb-103a-4de2-96bb-7dc1d44d8764   500Mi      RWO            basic          2d12h
trident-pvc-597   Bound    pvc-39653587-3328-4652-afad-672a2e441025   500Mi      RWO            basic          2d12h
trident-pvc-598   Bound    pvc-53103e41-f601-40bd-b70a-0dff479ada59   500Mi      RWO            basic          2d12h
trident-pvc-599   Bound    pvc-97edcd6b-25ef-4825-9960-ff39a10fb8bf   500Mi      RWO            basic          2d12h
trident-pvc-6     Bound    pvc-11b53d5f-7b57-44a5-94f0-40c8f5955a51   500Mi      RWO            basic          2d12h
trident-pvc-60    Bound    pvc-107b1ca2-c067-44d5-a0c0-d5df413aab10   500Mi      RWO            basic          2d12h
…

从 FSx for NetApp ONTAP 看,共创建了 3 个卷,每个映射 200 个 qtree,总大小约 100GB(500MB*200 个),与预期的数量一致:

至此,我们已经实现通过 qtree 创建超过 500 个 PV。这使得单个 FSx for NetApp ONTAP 支持的最大 PV 数量获得了极大的扩展。

如何获得 PVC 的使用状况:

在日常运维过程中,我们经常需要监控存储的使用状况。在 qtree 场景中,每个 qtree 的详细用量和大小信息只能在 ONTAP 管理虚拟机中通过命令行或者 Rest API 获取,无法通过 CloudWatch 或 FSx 集成的界面查询,因此我们需要一种方式可以列出和查询每个 qtree PV 的用量信息,方法如下:

方法一: 使用 df-pv 获取 pv 的用量信息

使用 kubectl 插件管理工具 krew 安装 df-pv


$ kubectl krew install df-pv

使用如下命令查看每个 PVC 的绑定以及用量情况。如输出中第一个 PVC:nas-trident-11 的用量为 640KB,总量为 1024MB,绑定到的 Pod 是 trident-app-11。


$ kubectl df-pv -n default

 PV NAME                                   PVC NAME        NAMESPACE  NODE NAME                                  POD NAME        VOLUME MOUNT NAME   SIZE    USED   AVAILABLE  %USED  IUSED  IFREE  %IUSED
 pvc-b1d6a85f-be92-48d1-987d-70e6cf27821b  nas-trident-11  default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-11  persistent-storage  1024Mi  640Ki  1023Mi     0.06   97     31025  0.31
 pvc-2af53cd5-dedf-43a4-8ee1-c2a8c8d60bb6  nas-trident-12  default    ip-10-100-2-13.us-east-2.compute.internal  trident-app-12  persistent-storage  1024Mi  576Ki  1023Mi     0.05   97     31025  0.31
 pvc-19a87a37-f268-4093-8c33-71413ccec84a  nas-trident-4   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-4   persistent-storage  1024Mi  576Ki  1023Mi     0.05   97     31025  0.31
 pvc-bc69d594-a18c-4136-a9f1-7463ea188ae1  nas-trident-10  default    ip-10-100-2-13.us-east-2.compute.internal  trident-app-10  persistent-storage  1024Mi  576Ki  1023Mi     0.05   97     31025  0.31
 pvc-af3134a7-cf43-465f-b59f-b348e020bb58  nas-trident-7   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-7   persistent-storage  1024Mi  768Ki  1023Mi     0.07   97     31025  0.31
 pvc-730160bc-b319-4500-b829-71ab39becefe  nas-trident-6   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-6   persistent-storage  1024Mi  640Ki  1023Mi     0.06   97     31025  0.31
 pvc-3f1f4065-74bb-49ad-b64c-7982f77807c2  nas-trident-2   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-2   persistent-storage  1024Mi  640Ki  1023Mi     0.06   97     31025  0.31
 pvc-a6da688d-e665-4d8d-ac4b-ee863573521d  nas-trident-3   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-3   persistent-storage  1024Mi  640Ki  1023Mi     0.06   97     31025  0.31
 pvc-1924b485-20e8-4140-befa-2f1ded345e7e  nas-trident-9   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-9   persistent-storage  1024Mi  576Ki  1023Mi     0.05   97     31025  0.31
 pvc-80e44481-232a-4186-ad7c-d7d11e25f673  nas-trident-8   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-8   persistent-storage  1024Mi  640Ki  1023Mi     0.06   97     31025  0.31
 pvc-2bde216d-28ff-46bb-9818-4c3343246bfa  nas-trident-5   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-5   persistent-storage  1024Mi  640Ki  1023Mi     0.06   97     31025  0.31
 pvc-7b31616f-eb22-4419-8c1c-a0dadb9a6d1b  nas-trident-1   default    ip-10-100-3-79.us-east-2.compute.internal  trident-app-1   persistent-storage  1024Mi  576Ki  1023Mi     0.05   97     31025  0.31

方法二:通过部署 Prometheus 到 EKS 集群,查寻下列指标获取:

  • 使用量: kubelet_volume_stats_used_bytes
  • 大小: kubelet_volume_stats_capacity_bytes
  • 剩余容量:  kubelet_volume_stats_available_bytes

方法三:使用 ONTAP volume quota

在方法一和方法二中可以列出的是正在使用的 PVC 用量信息,如果需要在 PVC 被容器释放后仍然可以查询到其信息,可以在 ONTAP 的管理虚拟机 SVM 中为 qtree 启用 quota,实现在存储侧监控使用状况。操作步骤如下:

1.SSH 登录到 ONTAP SVM:

$ ssh vsadmin@

2.为 volume 配置quota:


$ quota policy rule create \
-vserver fsx -policy-name qtree_policy_0 \
-volume trident_qtree_pool_trident_HHMNSUFVCR \
-type tree \
-target "" \
-soft-disk-limit –

3.获取 quota 的 report:

qtree/volume 动态扩容

独立的存储空间都有固定的容量上限,在容量不够的情况下需要进行扩容。

ONTAP Trident CSI 支持存储的在线扩容功能,启用此功能需要在 StorageClass 里添加一行:

allowVolumeExpansion: true

启用此功能后,直接通过

$kubectl edit pvc

或者

$oc patch pvc

即可直接改变 PVC 的大小。以下测试案例创建了一个 pod 实现每 5 秒写一个时间戳信息到日志文件:


apiVersion: v1
kind: Pod
metadata:
  name: ex-test-app
spec:
  containers:
  - name: ontap-app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/ex-ontap.txt; sleep 5; done"]
    volumeMounts:
    - name: ex-trident
      mountPath: /data
  volumes:
  - name: ex-trident
    persistentVolumeClaim:
      claimName: ex-trident-1

确认 PV 的存储空间已经扩容:


$ kubectl exec ex-test-app -- df -Th | grep nfssvm-040955f6f528a79ee.fs-00faa123b0c42cc68.fsx.us-east-2.amazonaws.com:/trident_qtree_pool_trident_VJXEUTKFRF/trident_pvc_e0f23ad1_16cb_49d2_8a93_a3dbc7a92567 nfs4   500M     0  500M   0% /data

$ kubectl exec ex-test-app -- df -Th | grep nfs
svm-040955f6f528a79ee.fs-00faa123b0c42cc68.fsx.us-east-2.amazonaws.com:/trident_qtree_pool_trident_VJXEUTKFRF/trident_pvc_e0f23ad1_16cb_49d2_8a93_a3dbc7a92567 nfs4   4.9G     0  4.9G   0% /data

通过查看容器的状态和日志,可以看到容器并没有发生重启,日志文件也没有被清空:


$ kubectl get pod
NAME          READY   STATUS    RESTARTS      AGE
ex-test-app   1/1     Running   0             40m
$ kubectl exec ex-test-app -- cat /data/ex-ontap.txt
Mon Oct 24 01:57:03 UTC 2022
Mon Oct 24 01:57:08 UTC 2022
Mon Oct 24 01:57:13 UTC 2022
Mon Oct 24 01:57:18 UTC 2022
Mon Oct 24 01:57:23 UTC 2022
Mon Oct 24 01:57:28 UTC 2022
Mon Oct 24 01:57:33 UTC 2022
Mon Oct 24 01:57:38 UTC 2022
Mon Oct 24 01:57:43 UTC 2022
Mon Oct 24 01:57:48 UTC 2022
Mon Oct 24 01:57:53 UTC 2022
Mon Oct 24 01:57:58 UTC 2022
Mon Oct 24 01:58:03 UTC 2022
Mon Oct 24 01:58:08 UTC 2022
Mon Oct 24 01:58:13 UTC 2022
Mon Oct 24 01:58:18 UTC 2022
Mon Oct 24 01:58:23 UTC 2022
Mon Oct 24 01:58:28 UTC 2022
Mon Oct 24 01:58:33 UTC 2022
Mon Oct 24 01:58:38 UTC 2022
Mon Oct 24 01:58:43 UTC 2022
Mon Oct 24 01:58:48 UTC 2022

以上就是有关在 EKS 中使用 qtree 扩展 ONTAP PV 数量的有关内容。通过该解决方案,客户可获得如下的收益:

  • 高可用的托管文件系统提供稳定的存储资源,降低运维成本
  • 将每个文件系统支持的 PVC 数量从 500 提升到 10 万
  • 将每个区域的文件系统支持的 PVC 数量从 5 万提升到 1000 万
  • 每个 PVC 支持独立配额并可以动态扩容以适应多租户多级场景需求
  • 通过存储超分配有效降低初期配置成本

参考资料

https://NetApp-trident.readthedocs.io/en/latest/support/requirements.html

https://NetApp-trident.readthedocs.io/en/latest/kubernetes/fsx.html

https://library.NetApp.com/ecmdocs/ECMP1154894/html/GUID-05E060D3-AF1B-48D5-87E7-437D87716478.html

https://docs.thinkwithwp.com/fsx/latest/ONTAPGuide/what-is-fsx-ontap.html

https://docs.thinkwithwp.com/eks/latest/userguide/fsx-ontap.html

本篇作者

王志达

AWS 解决方案架构师,主要负责基础架构如计算、存储的云端设计、改造和优化方案。有多年存储、容器平台和 Devops 运维经验。