

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 为 OpenSearch UI 应用程序设置友好 URL（自助服务）
<a name="application-custom-domain"></a>

OpenSearch UI 应用程序具有自动生成的 URL，例如`https://application-{{name}}-{{id}}.{{Region}}.opensearch.amazonaws.com`。这些 URL 很长，很难记住。您可以设置友好 URL（例如`https://prod.example.com`）。此 URL 重定向到您的应用程序，因此您无需通过 AWS 管理控制台导航即可直接访问仪表板。

## 工作原理
<a name="application-custom-domain-overview"></a>

此解决方案用于将请求从您的友好 URL 重定 CloudFront 向到您的 OpenSearch UI 应用程序终端节点。该架构包含以下组件：
+ **ACM 证书 ** — 为您的友好 URL 提供 HTTPS 加密。
+ **CloudFront KeyValueStore**— 存储子域名和应用程序 URL 之间的映射。
+ **CloudFront 函数 **-读取 KeyValueStore 并返回指向正确应用程序 URL 的 302 重定向。
+ **CloudFront 分发 ** — 使用 TLS 为您的友好网址提供服务，并通过该函数路由请求。
+ **Route 53 DNS 记录 ** — 将你的友好 URL 指向 CloudFront 发行版。

当您访问友好 URL（例如`https://prod.example.com`）时，该 CloudFront 函数会查找相应的应用程序 URL 并将您的浏览器重定向到该网址。应用程序直接处理身份验证。

## 先决条件
<a name="application-custom-domain-prerequisites"></a>
+ 一个或多个 OpenSearch UI 应用程序及其终端节点 URL。
+ 您拥有的域名（例如，`example.com`）。
+ 您的域名在 Route 53 中的公共托管区域。如果您没有，有关更多信息，请参阅[在没有 Route 53 的情况下进行设置（手动配置）](#application-custom-domain-without-route53)。
+ 一个 Amazon S3 存储桶，用于存储您的应用程序 URL 映射文件。

## 使用进行部署 AWS CloudFormation
<a name="application-custom-domain-deploy-cfn"></a>

使用以下过程使用 CloudFormation 模板部署友好的 URL 重定向基础架构。

**为您的 OpenSearch UI 应用程序设置友好 URL**

1. 创建一个 JSON 文件，将您的友好名称映射到应用程序 URL。每个密钥都成为一个子域（例如，密钥`prod`变成`https://prod.example.com`）。

   ```
   {
     "data": [
       {"key": "prod", "value": "https://application-prod-abc123.us-west-2.opensearch.amazonaws.com"},
       {"key": "staging", "value": "https://application-staging-def456.us-east-1.opensearch.amazonaws.com"},
       {"key": "analytics", "value": "https://application-analytics-ghi789.eu-west-1.opensearch.amazonaws.com"}
     ]
   }
   ```

   将此文件另存为 `opensearch-ui-friendly-url-app-mappings-v1.json`。后`v1`缀将其标识为您的映射的第一个版本。更新映射时，您可以递增此版本。

1. 将映射文件上传到您账户中的 Amazon S3 存储桶：

   ```
   aws s3 cp opensearch-ui-friendly-url-app-mappings-v1.json \
     s3://{{your-bucket-name}}/opensearch-ui-friendly-url-app-mappings-v1.json
   ```

1. 通过添加存储桶策略来 CloudFront 授予读取映射文件的权限。将{{your-bucket-name}}和{{your-account-id}}替换为您的值：

   ```
   aws s3api put-bucket-policy \
     --bucket {{your-bucket-name}} \
     --policy '{
       "Version": "2012-10-17",
       "Statement": [{
         "Sid": "AllowCloudFrontKVSImport",
         "Effect": "Allow",
         "Principal": {"Service": "cloudfront.amazonaws.com"},
         "Action": "s3:GetObject",
         "Resource": "arn:aws:s3:::{{your-bucket-name}}/*",
         "Condition": {"StringEquals": {"aws:SourceAccount": "{{your-account-id}}"}}
       }]
     }'
   ```
**注意**  
此存储桶策略 CloudFront 允许在 KeyValueStore 创建过程中读取映射文件。没有它，堆栈部署将失败，出现 “SourceARN无法访问” 错误。

1. 使用以下选项之一部署 CloudFormation 堆栈：

   **选项 1：启动堆栈（推荐）**

   复制以下 URL 并将其粘贴到浏览器中，打开预先 AWS CloudFormation 加载了模板的快速创建页面。填写参数并选择**创建堆栈**。

   ```
   https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https://opensearch-ui-quickstart-sample-cfn-templates.s3.us-west-2.amazonaws.com/opensearch-ui-friendly-url/main.yaml&stackName=opensearch-ui-friendly-url&param_MappingsVersion=v1
   ```

   **选项 2： AWS CLI **

   将占位符值替换为你自己的值：

   ```
   aws cloudformation create-stack \
     --stack-name opensearch-ui-friendly-url \
     --template-url https://opensearch-ui-quickstart-sample-cfn-templates.s3.us-west-2.amazonaws.com/opensearch-ui-friendly-url/main.yaml \
     --parameters \
       ParameterKey=DomainName,ParameterValue={{your-domain.com}} \
       ParameterKey=HostedZoneId,ParameterValue={{Z0123456789ABCDEFG}} \
       ParameterKey=MappingsBucket,ParameterValue={{your-bucket-name}} \
       ParameterKey=MappingsKey,ParameterValue=opensearch-ui-friendly-url-app-mappings-v1.json \
       ParameterKey=MappingsVersion,ParameterValue=v1 \
     --region us-east-1
   ```

1. 如果您使用了 AWS CLI（选项 2），请在`us-east-1`区域中部署堆栈。 CloudFront 需要 ACM 证书才能进入。`us-east-1`您的 OpenSearch UI 应用程序可以位于任何[支持区域和 OpenSearch UI 配额中](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/opensearch-ui-endpoints-quotas.html)。

1. 等待堆栈完成（大约 15 分钟）。您可以监控进度：

   ```
   aws cloudformation wait stack-create-complete \
     --stack-name opensearch-ui-friendly-url \
     --region us-east-1
   ```

1. 测试重定向。打开浏览器并导航至`https://{{prod}}.{{your-domain.com}}`。您应该被重定向到您的 OpenSearch UI 应用程序。

   您也可以通过以下方式进行验证`curl`：

   ```
   curl -I https://{{prod}}.{{your-domain.com}}
   # Expected: HTTP/2 302
   # location: https://application-prod-abc123.us-west-2.opensearch.amazonaws.com
   ```

### CloudFormation 模板来源
<a name="application-custom-domain-template-source"></a>

以下是上述 “启动堆栈” 链接和 CLI 命令使用的完整 CloudFormation 模板。您也可以直接从中下载`https://opensearch-ui-quickstart-sample-cfn-templates.s3.us-west-2.amazonaws.com/opensearch-ui-friendly-url/main.yaml`。

```
AWSTemplateFormatVersion: '2010-09-09'
Metadata:
  TemplateVersion: '1.0.0'
  LastUpdated: '2026-08-05'
  Author: 'OpenSearch UI Team'
Description: >
  OpenSearch UI Friendly URL - Creates a CloudFront distribution with a CloudFront Function
  that redirects friendly subdomain URLs to OpenSearch Application endpoints.
  Deploy this template in us-east-1 (required for ACM certificates used with CloudFront).

Parameters:
  DomainName:
    Type: String
    Description: "Your domain name (e.g., example.com or subdomain.example.com)"
    AllowedPattern: "^[a-zA-Z0-9][a-zA-Z0-9-]*(\\.[a-zA-Z0-9][a-zA-Z0-9-]*)+$"
  HostedZoneId:
    Type: AWS::Route53::HostedZone::Id
    Description: "Route 53 Hosted Zone ID for your domain"
  MappingsBucket:
    Type: String
    Description: "S3 bucket name containing your app mappings JSON file"
  MappingsKey:
    Type: String
    Description: "S3 key (path) to your app mappings JSON file"
    Default: "opensearch-ui-friendly-url-app-mappings.json"
  MappingsVersion:
    Type: String
    Description: "Version identifier for your mappings (change this when updating mappings, e.g., v1, v2, v3)"
    Default: "v1"
    AllowedPattern: "^[a-zA-Z0-9-]+$"

Resources:
  # 1. Wildcard TLS Certificate (DNS validated via Route 53)
  Certificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Sub "*.${DomainName}"
      ValidationMethod: DNS
      DomainValidationOptions:
        - DomainName: !Sub "*.${DomainName}"
          HostedZoneId: !Ref HostedZoneId

  # 2. CloudFront KeyValueStore with S3 import for app mappings
  AppMappingStore:
    Type: AWS::CloudFront::KeyValueStore
    Properties:
      Name: !Sub "${AWS::StackName}-kvs-${MappingsVersion}"
      Comment: !Sub "OpenSearch UI app mappings ${MappingsVersion} for ${DomainName}"
      ImportSource:
        SourceType: S3
        SourceArn: !Sub "arn:aws:s3:::${MappingsBucket}/${MappingsKey}"

  # 3. CloudFront Function (reads KVS, returns 302 redirect)
  RedirectFunction:
    Type: AWS::CloudFront::Function
    Properties:
      Name: !Sub "${AWS::StackName}-fn"
      AutoPublish: true
      FunctionConfig:
        Comment: !Sub "Redirects friendly URLs to OpenSearch Application endpoints (${MappingsVersion})"
        Runtime: cloudfront-js-2.0
        KeyValueStoreAssociations:
          - KeyValueStoreARN: !GetAtt AppMappingStore.Arn
      FunctionCode: !Sub |
        import cf from 'cloudfront';

        const kvsHandle = cf.kvs("${AppMappingStore.Id}");

        async function handler(event) {
          var request = event.request;
          var host = request.headers.host.value;
          
          // Extract subdomain prefix (e.g., "app1" from "app1.example.com")
          var subdomain = host.split('.')[0];
          
          try {
            var targetUrl = await kvsHandle.get(subdomain);
            return {
              statusCode: 302,
              statusDescription: 'Found',
              headers: {
                'location': { value: targetUrl },
                'cache-control': { value: 'no-cache, no-store, must-revalidate' }
              }
            };
          } catch (e) {
            // Key not found - return 404
            return {
              statusCode: 404,
              statusDescription: 'Not Found',
              headers: {
                'content-type': { value: 'text/html' }
              },
              body: {
                encoding: 'text',
                data: '<html><body><h1>Application Not Found</h1><p>No OpenSearch application is mapped to the requested subdomain.</p></body></html>'
              }
            };
          }
        }

  # 4. CloudFront Distribution
  Distribution:
    Type: AWS::CloudFront::Distribution
    DependsOn: Certificate
    Properties:
      DistributionConfig:
        Enabled: true
        Comment: !Sub "OpenSearch UI Friendly URL for ${DomainName}"
        Aliases:
          - !Sub "*.${DomainName}"
        ViewerCertificate:
          AcmCertificateArn: !Ref Certificate
          SslSupportMethod: sni-only
          MinimumProtocolVersion: TLSv1.2_2021
        DefaultCacheBehavior:
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad"  # AWS managed CachingDisabled policy
          TargetOriginId: dummy-origin
          FunctionAssociations:
            - EventType: viewer-request
              FunctionARN: !GetAtt RedirectFunction.FunctionMetadata.FunctionARN
        Origins:
          - Id: dummy-origin
            DomainName: "example.com"
            CustomOriginConfig:
              OriginProtocolPolicy: https-only
        HttpVersion: http2and3
        IPV6Enabled: true
        PriceClass: PriceClass_All

  # 5. Wildcard DNS record pointing to CloudFront
  WildcardDnsRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref HostedZoneId
      Name: !Sub "*.${DomainName}"
      Type: A
      AliasTarget:
        HostedZoneId: Z2FDTNDATAQYW2  # CloudFront's fixed hosted zone ID (constant for all distributions)
        DNSName: !GetAtt Distribution.DomainName
        EvaluateTargetHealth: false

Outputs:
  CloudFrontDomain:
    Description: "CloudFront distribution domain name"
    Value: !GetAtt Distribution.DomainName
  CertificateArn:
    Description: "ACM Certificate ARN"
    Value: !Ref Certificate
  KeyValueStoreArn:
    Description: "CloudFront KeyValueStore ARN"
    Value: !GetAtt AppMappingStore.Arn
  ExampleUrl:
    Description: "Example friendly URL"
    Value: !Sub "https://prod.${DomainName}"
  MappingsFileLocation:
    Description: "S3 location of your app mappings file"
    Value: !Sub "s3://${MappingsBucket}/${MappingsKey}"
  CurrentMappingsVersion:
    Description: "Current mappings version deployed"
    Value: !Ref MappingsVersion
  TemplateVersion:
    Description: "Template version and last updated date"
    Value: "1.0.0 (2026-08-05)"
```

### 添加或删除应用程序
<a name="application-custom-domain-add-apps"></a>

要添加、更新或删除应用程序映射，请创建 S3 映射文件的新版本并使用新版本更新堆栈。版本控制方法使您的 S3 文件成为真实来源，并允许您随时向前滚动到新配置或回滚到以前的版本。

1. 编辑您的映射文件以添加或删除条目。使用增量版本后缀（例如，`opensearch-ui-friendly-url-app-mappings-v2.json`）进行保存。

1. 将新版本上传到 S3：

   ```
   aws s3 cp opensearch-ui-friendly-url-app-mappings-v2.json \
     s3://{{your-bucket-name}}/opensearch-ui-friendly-url-app-mappings-v2.json
   ```

1. 使用新的`MappingsKey`和`MappingsVersion`以下内容更新堆栈：

   **使用控制台：**打开[CloudFormation 控制台](https://console.aws.amazon.com/cloudformation/)，选择您的堆栈，选择**更新**，选择**使用当前模板**，然后使用新值更新`MappingsKey`和`MappingsVersion`参数。

   **使用 C AWS LI：**

   ```
   aws cloudformation update-stack \
     --stack-name opensearch-ui-friendly-url \
     --use-previous-template \
     --parameters \
       ParameterKey=DomainName,UsePreviousValue=true \
       ParameterKey=HostedZoneId,UsePreviousValue=true \
       ParameterKey=MappingsBucket,UsePreviousValue=true \
       ParameterKey=MappingsKey,ParameterValue=opensearch-ui-friendly-url-app-mappings-v2.json \
       ParameterKey=MappingsVersion,ParameterValue=v2 \
     --region us-east-1
   ```

**重要**  
每次更新映射时都必须更改`MappingsVersion`参数。版本驱动 KeyValueStore 名称，它会触发 CloudFormation 将替换为 KeyValueStore 更新的数据。为清楚起见，请保持`MappingsKey`文件名并`MappingsVersion`保持同步（例如，`...-v2.json`与`MappingsVersion=v2`）。

要回滚到先前版本，请更新指向早期文件和版本的堆栈。

**使用控制台：**打开[CloudFormation 控制台](https://console.aws.amazon.com/cloudformation/)，选择您的堆栈，选择**更新**，选择**使用当前模板**，然后设置`MappingsKey`并`MappingsVersion`返回到以前的值（例如，`v1`）。

**使用 C AWS LI：**

```
aws cloudformation update-stack \
  --stack-name opensearch-ui-friendly-url \
  --use-previous-template \
  --parameters \
    ParameterKey=DomainName,UsePreviousValue=true \
    ParameterKey=HostedZoneId,UsePreviousValue=true \
    ParameterKey=MappingsBucket,UsePreviousValue=true \
    ParameterKey=MappingsKey,ParameterValue=opensearch-ui-friendly-url-app-mappings-v1.json \
    ParameterKey=MappingsVersion,ParameterValue=v1 \
  --region us-east-1
```

这种版本控制方法使您可以完全控制根据需要向前滚动或回滚应用程序映射，同时将您的 S3 文件作为每个配置版本的最终记录。

## 在没有 Route 53 的情况下进行设置（手动配置）
<a name="application-custom-domain-without-route53"></a>

如果您的域名由 Route 53 以外的 DNS 提供商管理，则可以在 AWS 管理控制台中手动设置友好的 URL 重定向。这种方法不使用 Amazon S3 进行映射，您可以直接在控制台中管理键值对。 CloudFront 

**在没有 Route 53 的情况下设置友好 URL 重定向**

1. 

**在 ACM 中申请证书**

   1. 在`us-east-1`区域[https://console.aws.amazon.com/acm/](https://console.aws.amazon.com/acm/)中打开 ACM 控制台。

   1. 选择 “**申请证书” **。

   1. 对于**域名**，输入`*.{{your-domain.com}}`（通配符证书）。

   1. 对于**验证方法**，选择 ** DNS 验证**。

   1. 选择**请求**。

   1. 在证书详细信息页面上，记下 “**域验证**” 下的 CNAME 记录名称和值。在您的 DNS 提供商处创建此 CNAME 记录以验证域所有权。

   1. 等待证书状态更改为**已颁发**（通常在创建 DNS 记录后 5 到 30 分钟）。

1. 

**创建一个 CloudFront KeyValueStore**

   1. 在处打开 CloudFront 控制台[https://console.aws.amazon.com/cloudfront/](https://console.aws.amazon.com/cloudfront/)。

   1. 在导航窗格中，选择**函数**。然后选择**KeyValueStores**选项卡。

   1. 选择**创建 KeyValueStore**。

   1. 输入名称（例如，`opensearch-ui-friendly-url-app-mappings-store`）。

   1. 选择**创建**。

   1. 创建后，选择 “**编辑” ** 以添加键值对。为每个应用程序添加一个密钥（子域名，例如`prod`）和一个值（完整的 OpenSearch UI 应用程序 URL）。

1. 

**创建 CloudFront 函数**

   1. 在处打开 CloudFront 控制台[https://console.aws.amazon.com/cloudfront/](https://console.aws.amazon.com/cloudfront/)。

   1. 在导航窗格中，选择**函数**。

   1. 在**函数**选项卡上，选择**创建函数**。

   1. 输入函数的名称（例如，`opensearch-ui-friendly-url-redirect-function`）。

   1. 对于**运行时**，选择 c ** loudfront-js-2.0 **。

   1. 将函数代码替换为以下内容：

      ```
      import cf from 'cloudfront';
      
      const kvsHandle = cf.kvs("{{YOUR_KVS_ID}}");
      
      async function handler(event) {
        var request = event.request;
        var host = request.headers.host.value;
        var subdomain = host.split('.')[0];
      
        try {
          var targetUrl = await kvsHandle.get(subdomain);
          return {
            statusCode: 302,
            statusDescription: 'Found',
            headers: {
              'location': { value: targetUrl },
              'cache-control': { value: 'no-cache, no-store, must-revalidate' }
            }
          };
        } catch (e) {
          return {
            statusCode: 404,
            statusDescription: 'Not Found',
            headers: { 'content-type': { value: 'text/html' } },
            body: {
              encoding: 'text',
              data: '<html><body><h1>Application Not Found</h1><p>No OpenSearch application is mapped to the requested subdomain.</p></body></html>'
            }
          };
        }
      }
      ```

      {{YOUR\_KVS\_ID}}替换 KeyValueStore 为您创建的 ID（在 KeyValueStore 详细信息页面上可见）。

   1. 在**KeyValueStore 关联中**，关联 KeyValueStore 您创建的。

   1. 选择 “**保存更改”**，然后选择 “**发布函数” **。

1. 

**创建 CloudFront 发行版**

   1. 在 CloudFront 控制台中，选择 “**创建分发” **。

   1. 对于 ** Origin 域**，输入任何有效的域（例如，`example.com`）。由于该函数在到达源之前会返回响应，因此永远不会联系到源。

   1. 在 “**默认缓存行为” 下**，对于**查看器协议策略**，选择 “**将 HTTP 重定向到 HTTPS” **。

   1. 对于**缓存策略**，选择**CachingDisabled**。

   1. 在 “**函数关联” 下**，对于**查看者请求**，选择您的 CloudFront函数。

   1. 在 ** “设置” 下**，在 “**备用域名 (CNAME)**” 中输入`*.{{your-domain.com}}`。

   1. 对于**自定义 SSL 证书**，选择您在 ACM 中创建的证书。

   1. 选择**创建分配**。

   1. 记下分发域名（例如`d1234abcdef8.cloudfront.net`）。

1. 

**在您的提供商处配置 DNS**

   在您的 DNS 提供商处，创建指`*.{{your-domain.com}}`向您的 CloudFront分发域名的 CNAME 记录（例如，`d1234abcdef8.cloudfront.net`）。
**注意**  
某些 DNS 提供商不支持通配符 CNAME 记录。在这种情况下，为要使用的每个子域名创建单独的 CNAME 记录（例如`prod.your-domain.com`，`staging.your-domain.com`）。

### 手动添加应用程序映射
<a name="application-custom-domain-manual-add-apps"></a>

要在初始手动设置之后添加新的应用程序映射，请执行以下操作：

1. 在处打开 CloudFront 控制台[https://console.aws.amazon.com/cloudfront/](https://console.aws.amazon.com/cloudfront/)。

1. 在导航窗格中，选择**函数**。然后选择**KeyValueStores**选项卡。

1. 选择你的 KeyValueStore 名字（例如，`opensearch-ui-friendly-url-app-mappings-store`）。

1. 选择**编辑**。

1. 选择 “**添加配对” **。输入子域名作为密钥，输入完整的 OpenSearch UI 应用程序 URL 作为值。

1. 选择**保存更改**。

新的友好 URL 在保存后立即可用。

## 清理
<a name="application-custom-domain-cleanup"></a>

要删除友好的 URL 重定向基础架构，请运行以下命令：

```
aws cloudformation delete-stack \
  --stack-name opensearch-ui-friendly-url \
  --region us-east-1
```

此命令删除 CloudFront 分发、功能 KeyValueStore、ACM 证书和 DNS 记录。它不会影响您的 OpenSearch UI 应用程序。

## 问题排查
<a name="application-custom-domain-troubleshooting"></a>

ACM 证书仍然有效 `Pending validation`  
确保您的域名的 DNS 已正确委托给 Route 53（或者您在 DNS 提供商处创建了验证 CNAME）。`dig {{your-domain}} NS`用于验证域名服务器。

浏览器显示证书错误  
验证 ACM 证书是否以通配符覆盖您的域名 () `*.your-domain.com`。证书必须位于该`us-east-1`地区。

404 未找到应用程序  
您尝试访问的子域中没有映射。 KeyValueStore验证您的映射文件中是否存在密钥，并确认您在上次更改后更新了堆栈（或手动添加了该对）。

CloudFront 分发显示`Deploying`状态的时间超过 15 分钟  
CloudFront 在全球部署分发最多可能需要 15 分钟。如果部署需要更长的时间，请查看 CloudFront 控制台以了解错误的详细信息。

创建堆栈时出现 SourceARN 无法访问错误  
确保 S3 存储桶策略授予读取映射文件的`cloudfront.amazonaws.com`权限。还要验证部署堆栈的 IAM 角色在存储桶上拥有`s3:GetObject``s3:HeadObject`、和`s3:GetBucketLocation`权限。