Alcion 使用亚马逊 OpenSearch 无服务器支持其多租户平台

这是一篇与来自Alcion的扎克·罗斯曼共同撰写的客座博客文章。

Alcion 是一个安全第一、人工智能驱动的备份即服务 (BaaS) 平台,可帮助 Microsoft 365 管理员快速直观地保护数据免受网络威胁和意外数据丢失。如果数据丢失,Alcion客户需要搜索备份项目(文件、电子邮件、联系人、事件等)的元数据,以选择要恢复的特定项目版本。Alcion 使用 Amazon OpenSearch 服务 为其客户提供跨该备份目录的准确、高效和可靠的搜索功能。该平台是多租户的,这意味着Alcion需要数据隔离和强大的安全性,以确保租户只能搜索自己的数据。

OpenSearch 服务是一项完全托管的服务,可以轻松地在 亚马逊云科技 云中部署、扩展和操作 OpenSearch。OpenSearch 是 Apache-2.0 许可的开源搜索和分析套件,包括 OpenSearch(搜索、分析引擎和矢量数据库)、OpenSearch 仪表板(可视化和实用程序用户界面)以及提供企业级安全、异常检测、可观察性、警报等高级功能的插件。 Amazon OpenSearch 无服务器 是一种无服务器部署选项,无需配置、管理和扩展 OpenSearch 服务域即可轻松使用 OpenSearch。

在这篇文章中,我们将分享采用 OpenSearch Serverless 如何使 Alcion 能够满足其规模需求,减少运营开销,并通过在多租户环境 中强制执行 租户隔离来保护租户 的数据。

开放搜索服务管理的域名

在搜索架构的首次迭代中,Alcion选择了OpenSearch Service中的托管域部署选项,并得以在不到一个月的时间内在生产环境中启动其搜索功能。为了满足其安全、规模和租赁要求,他们将每个租户的数据存储在专用索引中,并在 OpenSearch Service 中使用 精细的访问控制 来防止跨租户数据泄露。随着工作量的变化,Alcion工程师通过提供的 Amazon Cloud Watch 指标来跟踪OpenSearch域名利用率,并做出更改以增加存储空间和优化计算资源。

Alcion的团队使用OpenSearch Service管理域的多项功能来改善其运营态势。他们引入了索引别名,该别名为访问(读取和写入)多个基础索引提供了一个别名。他们还配置了 索引状态管理 (ISM) 策略,通过根据索引大小滚动索引来帮助他们控制数据生命周期。ISM 策略和索引别名共同构成了为大型租户扩展索引的必要条件。Alcion 还使用 索引模板 来定义其数据的每个索引(分区)的分片,以实现数据生命周期自动化,提高其域的性能和稳定性。

以下架构图显示了 Alcion 如何配置其 OpenSearch 管理域。

下图显示了如何将 Microsoft 365 数据编入租户特定索引并从中查询。Alcion 通过为每个 API 请求提供 OpenSearch 主用户凭证来实现请求身份验证。

OpenSearch 无服务器概述和租户模型选项

OpenSearch Service 管理的域名为 Alcion 的搜索功能提供了稳定的基础,但该团队需要手动为域名配置资源以应对高峰工作负载。这为成本优化留出了空间,因为Alcion的工作负载非常激增——无论是单个客户还是整体客户,每秒的搜索和索引交易数量都有很大的差异。为了减少成本和运营负担,该团队转向了提供自动扩展功能的OpenSearch Serverless。

要使用 OpenSearch 无服务器,第一步是创建集合。 集合 是一组 OpenSearch 索引,它们协同工作以支持特定的工作负载或用例。名为 OpenSearch 计算单位 (OCU) 的集合的计算资源由共享加密密钥的账户中的所有集合共享。OCU 池会自动向上和向下扩展,以满足索引和搜索流量的需求。

由于 OpenSearch 无服务器集合支持与 OpenSearch Service 托管域相同的 OpenSearch API 和库,从 OpenSearch 托管域迁移到 OpenSearch 无服务器所需的工作量是可以控制的。这使Alcion能够专注于优化新搜索架构的租赁模型。具体而言,该团队需要决定如何在集合和索引中对租户数据进行分区,同时平衡安全性和总拥有成本。Alcion 工程师与 OpenSearch 无服务器团队合作, 考虑了三种租赁模式

  • 筒仓模型:为每个租户创建一个集合
  • 池模型:创建单个集合并为多个租户使用单个索引
  • 桥接模型:创建单个集合并为每个租户使用单个索引

所有三种设计选择都有优点和缺点,在设计最终解决方案时必须考虑这些优点和缺点。

筒仓模型:为每个租户创建一个集合

在这种模型中,每当有新客户登录他们的平台时,Alcion就会创建一个新的系列。尽管租户数据将在各馆藏之间完全分开,但该选项被取消了资格,因为集合创建时间意味着客户无法在注册后立即备份和搜索数据。

池模型:创建单个集合并为多个租户使用单个索引

在此模型中,Alcion将为每个 亚马逊云科技 账户创建一个集合,并在属于该集合的众多共享索引之一中索引租户特定的数据。最初,从规模的角度来看,将租户数据汇集到共享索引中很有吸引力,因为这样可以最有效地使用索引资源。但是经过进一步分析,Alcion发现,即使他们为每个租户分配了一个索引,他们也将完全处于每个馆藏指数的配额之内。解决了可扩展性问题后,Alcion选择了第三种选择,因为将租户数据隔离到专用索引中会比共享索引模型更强的租户隔离。

桥接模型:创建单个集合并为每个租户使用单个索引

在此模型中,Alcion将为每个亚马逊云科技账户创建一个集合,并为该账户管理的数百个租户创建一个索引。通过将每个租户分配到一个专用索引并将这些索引汇集到一个集合中,Alcion缩短了新租户的入职时间,并将租户数据孤立到完全分开的存储桶中。

实现基于角色的访问控制以支持多租户

OpenSearch Serverless 提供了一组可继承的多点安全控制,涵盖数据访问、网络访问和加密。Alcion 充分利用 OpenSearch 无服务器 数据访问策略 , 为每个租户特定索引实施基于角色的访问控制 (RBAC),其中包含以下详细信息:

  • 分配带有通用前缀和租户 ID 的索引(例如, index-v 1-)
  • 创建专用 亚马逊云科技 身份和访问管理 (IAM) 角色,用于签署向 OpenSearch 无服务器集合发出的请求
  • 创建 OpenSearch 无服务器数据访问策略,向该租户的 IAM 角色授予专用租户索引中的文档读/写权限
  • OpenSearch API 对租户索引的请求使用属于租户特定 IAM 角色的临时证书进行签名

以下是 ID 为 t-eca0acc1-12345678910 的模拟租户的 OpenSearch 无服务器数据访问策略示例。 此策略授予 IAM 角色文档对专用租户的读/写访问权限。

[
    {
        "Rules": [
            {
                "Resource": [
                    "index/collection-searchable-entities/index-v1-t-eca0acc1-12345678910"
                ],
                "Permission": [
                    "aoss:ReadDocument",
                    "aoss:WriteDocument",
                ],
                "ResourceType": "index"
            }
        ],
        "Principal": [
            "arn:aws:iam::12345678910:role/OpenSearchAccess-t-eca0acc1-1b9f-4b3f-95d6-12345678910"
        ],
        "Description": "Allow document read/write access to OpenSearch index belonging to tenant t-eca0acc1-1b9f-4b3f-95d6-12345678910"
    }
] 

以下架构图描述了 Alcion 如何使用 OpenSearch 无服务器共享集合方法实现对微软 365 资源的索引和搜索。

以下是向 OpenSearch 无服务器集合发送 API 请求的示例代码片段。请注意,API 客户端是如何使用签名者对象初始化的,该对象使用与前面代码片段中的 OpenSearch 无服务器数据访问策略相关的 IAM 委托人签署请求。

package alcion

import (
	"context"
	"encoding/json"
	"strings"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	"github.com/opensearch-project/opensearch-go/v2"
	"github.com/opensearch-project/opensearch-go/v2/opensearchapi"
	"github.com/opensearch-project/opensearch-go/v2/signer"
	awssignerv2 "github.com/opensearch-project/opensearch-go/v2/signer/awsv2"
	"github.com/pkg/errors"
)

const (
	// Scope the API request to the AWS OpenSearch Serverless service
	aossService = "aoss"

	// Mock values
	indexPrefix        = "index-v1-"
	collectionEndpoint = "<https://kfbr3928z4y6vot2mbpb.us-east-1.aoss.amazonaws.com>"
	tenantID           = "t-eca0acc1-1b9f-4b3f-95d6-b0b96b8c03d0"
	roleARN            = "arn:aws:iam::1234567890:role/OpenSearchAccess-t-eca0acc1-1b9f-4b3f-95d6-b0b96b8c03d0"
)

func CreateIndex(ctx context.Context, tenantID string) (*opensearchapi.Response, error) {

	sig, err := createRequestSigner(ctx)
	if err != nil {
		return nil, errors.Wrapf(err, "error creating new signer for AWS OSS")
	}

	cfg := opensearch.Config{
		Addresses: []string{collectionEndpoint},
		Signer:    sig,
	}

	aossClient, err := opensearch.NewClient(cfg)
	if err != nil {
		return nil, errors.Wrapf(err, "error creating new OpenSearch API client")
	}

  body, err := getSearchBody()
  if err != nil {
    return nil, errors.Wrapf(err, "error getting search body")
  }

	req := opensearchapi.SearchRequest{
		Index: []string{indexPrefix + tenantID},
		Body:  body,
	}

	return req.Do(ctx, aossClient)
}

func createRequestSigner(ctx context.Context) (signer.Signer, error) {

	awsCfg, err := config.LoadDefaultConfig(ctx)
	if err != nil {
		return nil, errors.Wrapf(err, "error loading default config")
	}

	stsClient := sts.NewFromConfig(awsCfg)
	provider := stscreds.NewAssumeRoleProvider(stsClient, roleARN)

	awsCfg.Credentials = aws.NewCredentialsCache(provider)
	return awssignerv2.NewSignerWithService(awsCfg, aossService)
}

func getSearchBody() (*strings.Reader, error) {
	// Match all documents, page size = 10
	query := map[string]interface{}{
		"size": 10,
	}

	queryJson, err := json.Marshal(query)
  if err != nil {
    return nil, err
  }

	return strings.NewReader(string(queryJson)), nil
} 

结论

2023 年 5 月,Alcion 在所有生产和预生产环境中推出了基于共享馆藏和专用的每租户索引模型的搜索架构。该团队得以拆除专门用于扩展 OpenSearch Service 托管域名的复杂代码和操作流程。此外,得益于OpenSearch Serverless的自动扩展功能,Alcion已将其OpenSearch成本降低了30%,并预计成本状况将有利地扩展。

在从托管开放搜索服务到无服务器 OpenSearch 服务的旅程中,Alcion 最初选择了 OpenSearch Service 托管域名,因此受益匪浅。在向前迁移的过程中,他们能够在 OpenSearch 无服务器集合中重复使用与用于 OpenSearch 服务托管域相同的 OpenSearch API 和库。此外,他们更新了租赁模型,以利用 OpenSearch 无服务器数据访问策略。借助 OpenSearch Serverless,他们能够毫不费力地适应客户的规模需求,同时确保租户隔离。

有关Alcion的更多信息,请访问他们的 网站


作者简介

扎克·罗斯曼是 Alcion 的技术人员。 他是搜索和人工智能平台的技术负责人。在加入Alcion之前,Zack是Okta的高级软件工程师,负责为目录团队开发核心员工身份和访问管理产品。

Niraj Jetly 是亚马逊 OpenSearch Serverless 的软件开发经理。 Niraj 领导着多个数据飞机团队,负责推出亚马逊 OpenSearch 无服务器。在加入 亚马逊云科技 之前,Niraj 曾领导多个产品和工程团队,担任首席技术官、工程副总裁和产品管理主管超过 15 年。Niraj 获得了超过 15 个创新奖项,包括在 2014 年被评为年度首席信息官以及在 2013 年和 2016 年被评为前 100 位首席信息官。他经常在多个会议上发表演讲,NPR、《华尔街日报》和《波士顿环球报》都引述了他的话。

乔恩·汉德勒 是总部位于加利福尼亚州帕洛阿尔托的亚马逊网络服务的高级首席解决方案架构师。Jon 与 OpenSearch 和亚马逊 OpenSearch Service 紧密合作,为想要迁移到 亚马逊云科技 云的搜索和日志分析工作负载的众多客户提供帮助和指导。在加入 亚马逊云科技 之前,Jon 的软件开发生涯包括 4 年编写大型电子商务搜索引擎。Jon 拥有宾夕法尼亚大学文学学士学位和西北大学计算机科学与人工智能理学硕士和博士学位。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。