亚马逊AWS官方博客

基于 Amazon OpenSearch 的 Apache Solr 的迁移方案设计及实现

1. 背景

Apache Solr是一个高度可靠、可扩展和容错的开源搜索引擎,它提供分布式索引、复制和负载平衡查询等功能。 很多客户在为他们的业务网站或应用程序构建搜索时,都是基于Apache Solr来实现的。但随着时间的推移,客户在针对Solr服务器进行弹性扩展和管理、高可用以及运维成本高等方面遇到了挑战。

在我们交付项目中,也碰到了类似的情况。某知名国际金融客户在全面进行数字化转型的过程中,需要完成几个大型数据中心的迁移上云,其中就包含了部署在数据中心的Solr搜索引擎。由于客户所在行业对于安全、高可用以及运维等方面都有很高的要求,我们需要为客户提供一个高可用、可扩展、易维护以及安全的Solr搜索引擎的替代解决方案。

2. 线下架构

客户线下数据中心中,将Solr搜索引擎,CronJob服务,Indexer服务均部署在同一台虚拟机上。其数据中心的部署架构如图2-1所示:

图2-1

2.1  业务流程

客户应用程序使用Solr的基本业务流程如下:

  1. CronJob定期的触发Indexer服务
  2. Indexer将业务数据从数据库、FTP服务器中取出,然后进行数据的对比,并将增量和更新数据写入Apache Solr
  3. 客户通过web的方式搜索查询
  4. web application通过http请求到Solr引擎搜索数据

2.2 面临的问题

客户在使用Solr的过程中,遇到了各种问题,如何解决这些问题是新的解决方案需要考虑的。这些问题包括:

  1. 运维升级难: Solr的版本低(10年前的版本),没有相关技能的运维人员进行维护
  2. 应用变更难:相关技术人员流失,导致对应用关于Solr的变更或新需求无法满足
  3. 不支持高可用:线下Solr只部署在一台虚拟机上,一旦虚拟机出现异常,将导致查询服务不可用
  4. 缺少安全控制:Solr密钥以明文的方式保存在Indexer和web application里,且没有对密码进行轮换
  5. 无审计机制:没有保存Solr服务器安全日志,未来在发生安全事件时无法进行审计

3. 迁移方案选择

基于客户数据化转型的整体规划以及本地Solr服务器所面临的问题,将Solr服务器的迁移也势在必行。选择什么方式进行Solr的迁移是客户非常关心的问题。针对Solr线下环境中所面临的问题,我们提供给客户两种可选迁移方案:

  • 第一种:重新托管(Re-Host),将Solr重新部署在亚马逊云科技的服务上(如Amazon EC2,EKS等)
  • 第二种:更换平台(Re-Platform),选择托管的Amazon OpenSearch服务替换Apache Solr

针对两种方案,我们从部署、可扩展性、高可用、运维以及成本的角度进行了对比,如下表所示:

更换平台(Amazon OpenSearch) 重新托管(Apache Solr)
部署和更新 亚马逊云科技托管 客户手动部署和更新
扩展性 高。可根据需求添加新的节点,或者修改数据节点 需要客户手动执行
高可用 支持多可用区部署方式,多数据节点保证高可用 需要客户进行设计
运维 亚马逊云科技托管 由客户进行运维,需要专业的solr技能
成本 可通过ISM管理,将数据存储在UltraWarm和Code Stroage中,以节省成本 跟部署的机型和数量有关

经过对比,最终客户选择更换平台的方式进行Solr的迁移,即采用亚马逊云科技托管的Amazon OpenSearch服务替代Solr。

4. 迁移架构设计

OpenSearch 是一个分布式、社区驱动、Apache 2.0 许可、100% 开源搜索和分析套件。广泛应用于应用程序实时监控、日志分析和网站搜索等领域。Amazon OpenSearch Service 通过将OpenSearch与亚马逊云科技其他服务如Amazon CloudWatch,Amazon KMS,IAM等集成,给客户提供高可用,高扩展,高灵活性,安全和易操作的搜索服务。

在基于Amazon OpenSearch的搜索服务架构中,通过以下亚马逊云科技服务来完成相关线下Solr功能的实现:

  • 通过Event Bridge Scheduler实现原有架构的CronJob功能
  • 通过Amazon Lambda实现原有架构的Indexer功能
  • 通过Amazon OpenSearch实现Solr的功能
  • 通过在VPC中部署Amazon OpenSearch,以实现业务的安全隔离
  • 通过开启Amazon OpenSearch的审计日志、查询日志、索引日志以及应用日志并存储到Amazon CloudWatch的日志组中,以实现安全审计、错误调试、性能优化等方面的功能
  • 通过Amazon Secrets Manager以及KMS服务实现OpenSearch认证密码的安全存储和轮换功能

以下是基于Amazon OpenSearch服务的迁移架构图:

通过该架构,解决线下架构面临的问题:

  1. Amazon OpenSearch Service 由亚马逊云科技管理,会定期的发布新的版本和补丁
  2. Amazon OpenSearch Service提供OpenSearch Dashboard UI,客户不需要专业的OpenSearch技能即可完成OpenSearch的配置和运维
  3. Indexer和Query使用不同的的密钥,根据最小权限原则分配权限。将密钥存储在Amazon Secrets Manager中
  4. 在Amazon OpenSearch上开启审计日志,并进行配置,满足客户的审计需求
  5. 在Amazon OpenSearch上开启Index Slow Log和Search Slow Log,方便开发人员进行性能问题排查
  6. 只允许通过VPC的方式连接到Amazon OpenSearch,并开启精细化访问权限控制(Fine-Grain Access Control)

5. 云上搜索服务的自动化部署

对于云上搜索服务器的部署,我们采用当下流行的Infra as Code的方式进行自动部署,由Terraform来部署相关资源。通过IaC代理创建的亚马逊云科技云上资源包括:

  • Amazon OpenSearch in VPC
  • Amazon Lambda in VPC
  • Amazon Secrets Manager
  • Amazon KMS
  • CloudWatch Log
  • Amazon Event Bridge

以下是相关IaC代码的样例:

  • main.tf
  resource "random_password" "admin" {
  length = 16
}

resource "aws_security_group" "security_group" {
  name        = "opensearch-mydomain-sg"
  description = "Managed by Terraform"
  vpc_id      = data.aws_vpc.opensearch_vpc.id

  ingress {
    from_port = 443
    to_port   = 443
    protocol  = "tcp"

    cidr_blocks = [
      data.aws_vpc.opensearch_vpc.cidr_block,
    ]
  }
}
resource "aws_iam_service_linked_role" "opensearch_service_link_role" {
  aws_service_name = "opensearchservice.amazon.com"
}

resource "aws_cloudwatch_log_group" "log_groups" {
  for_each = local.log_groups
  name     = format("/aws/OpenSearch/mydomain/%s", each.key)
}


resource "aws_cloudwatch_log_resource_policy" "opensearch_log_policy" {
  policy_name = "opensearch_log_policy"

  policy_document = <<CONFIG
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "es.amazonaws.com"
      },
      "Action": [
        "logs:PutLogEvents",
        "logs:PutLogEventsBatch",
        "logs:CreateLogStream"
      ],
      "Resource": "arn:aws:logs:*"
    }
  ]
}
CONFIG
}

resource "aws_opensearch_domain" "opensearch" {
  domain_name    = "mydomain"
  engine_version = "OpenSearch_1.3"
  cluster_config {
    dedicated_master_enabled = true
    dedicated_master_count   = 3
    dedicated_master_type    = "m5.large.search"

    instance_count = 3
    instance_type  = "m5.large.search"
  }

  vpc_options {
    subnet_ids         = [data.aws_subnets.subnets.ids[0], data.aws_subnets.subnets.ids[1]]
    security_group_ids = [aws_security_group.security_group.id]
  }

  access_policies = <<CONFIG
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "es:*",
            "Principal": "*",
            "Effect": "Allow",
            "Resource": "arn:aws:es:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:domain/mydomain/*"
        }
    ]
}
CONFIG

  advanced_security_options {
    enabled                        = true
    internal_user_database_enabled = true
    master_user_options {
      master_user_name     = "admin"
      master_user_password = random_password.admin.result
    }
  }

  encrypt_at_rest {
    enabled = true
  }

  node_to_node_encryption {
    enabled = true
  }

  domain_endpoint_options {
    enforce_https = true
  }

  log_publishing_options {
    cloudwatch_log_group_arn = aws_cloudwatch_log_group.log_groups["index_slow_logs"].arn
    log_type                 = "INDEX_SLOW_LOGS"
  }

  log_publishing_options {
    cloudwatch_log_group_arn = aws_cloudwatch_log_group.log_groups["search_slow_logs"].arn
    log_type                 = "SEARCH_SLOW_LOGS"
  }

  log_publishing_options {
    cloudwatch_log_group_arn = aws_cloudwatch_log_group.log_groups["application_logs"].arn
    log_type                 = "ES_APPLICATION_LOGS"
  }

  log_publishing_options {
    cloudwatch_log_group_arn = aws_cloudwatch_log_group.log_groups["audit_logs"].arn
    log_type                 = "AUDIT_LOGS"
  }

  snapshot_options {
    automated_snapshot_start_hour = 24
  }

  ebs_options {
    ebs_enabled = true
    volume_size = 100
    volume_type = "gp3"
  }

  depends_on = [
    aws_iam_service_linked_role.opensearch_service_link_role
  ]
}

resource "aws_secretsmanager_secret" "opensearch_admin" {
  name = "opensearch_admin"
}

resource "aws_secretsmanager_secret_version" "secret_version" {
  secret_id = aws_secretsmanager_secret.opensearch_admin.id
  secret_string = jsonencode({
    "domain" : aws_opensearch_domain.opensearch.domain_name,
    "user" : "admin",
    "password" : random_password.admin.result,
    "endpoint" : aws_opensearch_domain.opensearch.endpoint
  })

  lifecycle {
    ignore_changes = [secret_string]
  }
}
  • terraform.tf
 terraform {
  backend "s3" {
    bucket = "terraform-opensearch"
    key    = "opensearch/terraform.tfstate"
    region = "ap-southeast-1"
  }
}
  • data.tf
locals {
  log_groups = {
    "index_slow_logs"  = "index_slow_logs"
    "search_slow_logs" = "search_slow_logs"
    "application_logs" = "application_logs"
    "audit_logs"       = "audit_logs"
  }
}

data "aws_region" "current" {}

data "aws_caller_identity" "current" {}

data "aws_vpc" "opensearch_vpc" {
  filter {
    name   = "tag:Name"
    values = ["opensearch"]
  }
}

data "aws_subnets" "subnets" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.opensearch_vpc.id]
  }
}
  • output.tf
output "openseach_endpoint" {
  description = "opensearch endpoint"
  value       = aws_opensearch_domain.opensearch.endpoint
}

部署执行步骤:

  • terraform init
  • terraform plan
  • terraform apply

6. Amazon OpenSearch配置

在自动化部署完Amazon OpenSearch后,我们需要进行相关的配置工作,如开启审计日志、开启慢查询等满足应用程序的调试和安全审计要求。

6.1 开启审计日志

  1. 登录到OpenSearch Dashboard,选择Security,并选择“Audit logs”,点击“Enabled”。

  1. 查看审计日志:CloudWatch→ Log Groups → Audit-logs

6.2 开启慢日志

Amazon OpenSearch提供了2种类型的慢日志。可以通过开启慢日志,提供的有用信息来分析和排查应用的性能问题。

图6-1

上图6-1为在OpenSearch控制台开启慢index日志样例, 阀值为1s。

图6-2

图6-2为开启慢index日志,CloudWatch 中的日志样例。

图6-3

图6-3为开启慢查询日志,CloudWatch中的日志样例。

7. 数据迁移

Solr中的数据不进行迁移。采用Indexer重新index的方式将迁移上云的DB数据、PDF文件数据写入到OpenSearch。客户包含两种类型的数据:

  • DB数据
  • PDF文件数据

其中,针对DB数据迁移,采用Amazon DMS(DataBase Migration Service)一次性将线下数据库中数据迁移到云上RDS Oracle数据库;

针对PDF文件数据迁移,采用Amazon DataSync服务一次性将线下PDF文件迁移到云上S3存储桶中。

当以上两种数据完成迁移后,由Indexer Lambda将RDS数据库中的数据和S3中 pdf数据写入到OpenSearch。其数据迁移流程如图7-1所示:

图7-1

8. 架构演进

本方案中使用Amazon EventBridge作为触发器以固定的频率调用index lambda进行数据的增量更新。当没有增量数据时,调用index lambda是没有必要的。在后续的架构演进中,将Oracle database 演进到Amazon DynamoDB以节约费用。 使用DynamoDB Stream替代Event Bridge来触发index lambda。只有当有数据更新或者新增时,index lambda才会被调用。提高数据的实时性和减少lambda的调用次数。同时,对于存储在S3的pdf,使用S3的Event Notification功能,在有pdf更新或者新建的时候调用index lambda,提高数据的实时性和减少lambda的调用次数。

9. 参考文档

https://docs.aws.amazon.com/opensearch-service/latest/developerguide/gsg.html

https://docs.aws.amazon.com/datasync/latest/userguide/what-is-datasync.html

https://docs.aws.amazon.com/dms/latest/userguide/Welcome.html

https://canvatechblog.com/migrating-from-solr-to-elasticsearch-and-their-differences-c08863268c68

本篇作者

周理华

亚马逊云科技云应用架构师,负责基于亚马逊云科技云平台上应用架构设计。专注于微服务,无服务和 devops。

叶文军

亚马逊云科技专业服务团队云架构咨询顾问。负责企业客户的云架构设计、迁移、安全和优化,云上自动化运维,容器等相关咨询服务。在云原生技术、DevOps 及私有云部署运维等方面有着丰富的经验。