

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

# 容器产品计费、计量和许可集成
<a name="container-products-billing-integration"></a>

AWS Marketplace 与其他产品集成 AWS 服务 ，为您的集装箱产品提供计量和基于合同的定价。对于按用量定价的基于容器的产品，您可以使用 [AWS Marketplace Metering Service](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/Welcome.html) 来检查使用产品的权利以及对用量进行计量以进行计费。对于采用合同定价的基于容器的产品，您可以使用将许可证与您的产品相关联。 AWS License Manager 以下部分提供有关 AWS Marketplace Metering Service 的按小时计量和自定义计量以及 AWS License Manager的合同定价的更多信息。

**Topics**
+ [按小时计量和自定义计量 AWS Marketplace Metering Service](#entitlement-and-metering-for-paid-products)
+ [与的合同定价 AWS License Manager](#container-products-contracts-license-manager)
+ [使用 AWS Marketplace Metering Service 配置按小时计量](container-metering-registerusage.md)
+ [使用 AWS Marketplace Metering Service 对容器产品配置自定义计量](container-metering-meterusage.md)
+ [集装箱产品的合同定价 AWS License Manager](container-license-manager-integration.md)

## 按小时计量和自定义计量 AWS Marketplace Metering Service
<a name="entitlement-and-metering-for-paid-products"></a>

要检查使用产品的权利以及对用量进行计量以进行计费，您可以使用 [AWS Marketplace Metering Service](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/Welcome.html)。如果您想定义自己的定价单位并将使用量计给我们进行计费，请使用 [MeterUsage](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_MeterUsage.html)API 操作进行集成。如果您想根据使用的任务或容器数量对产品进行定价，并自动 AWS 计量该使用情况，请使用 [RegisterUsage](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_RegisterUsage.html)API 操作进行集成。对于这两种类型的定价，您可以添加长期合同价格，而无需更改与 AWS Marketplace Metering Service集成的方式。

当您在中创建新的容器产品时 AWS Marketplace 管理门户，我们会提供一组产品标识符（产品代码和公钥），用于将您的产品与集成 AWS Marketplace Metering Service。

### 权利
<a name="seller-container-entitlement"></a>

通过与集成， AWS Marketplace Metering Service 您可以验证运行付费软件的客户是否订阅了您的产品 AWS Marketplace，从而保护您在容器启动时免受未经授权的使用。要验证授权，请使用[MeterUsage](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_MeterUsage.html)或 [RegisterUsage](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_RegisterUsage.html)API 操作，具体取决于您的定价模式。对于每小时和固定每月定价模式，请使用 `RegisterUsage` API 操作。对于自定义计量定价模式，请使用 `MeterUsage` API 操作。

如果买家无权使用您的产品，则这些 API 操作会返回 `CustomerNotEntitledException` 异常。

**注意**  
如果买家在运行您的产品时取消订阅了产品，他们有权继续运行产品。但是，他们无法为您的产品启动其他容器。

### 集成指南
<a name="integration-guidelines"></a>

在创建和发布容器产品以及使用 `MeterUsage` 或 `RegisterUsage` API 操作验证权利和计量时，请记住以下准则：
+ 请勿在软件或 Docker 容器镜像中配置 AWS 凭据。 AWS 当您的容器映像在 Amazon ECS 任务或 Amazon EKS 容器中运行时，系统会在运行时自动获取买家证书。
+  要从 Amazon EKS 调用`MeterUsage`或 `RegisterUsage` API 操作，您必须[使用支持的 AWS 软件开发工具包](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html)。要测试 Amazon EKS 的 `MeterUsage` 或 `RegisterUsage` 集成，您运行的 Amazon EKS 集群必须运行 Kubernetes 1.13.x 或更高版本。 AWS Identity and Access Management (IAM) 角色需要 Kubernetes 1.13 才能支持容器。正在运行的 pod 需要 IAM 角色才能获得在 Amazon EKS 上调用这些操作所需的 AWS 证书。
+ 您可以进行本地开发，但您将获得 `PlatformNotSupportedException` 异常。当您在容器服务（亚马逊 ECS、Amazon EKS 和 Fargate）上启动 AWS 容器时，不会发生此异常。

### 支持的 AWS 区域
<a name="supported-regions-metering"></a>

有关所有 AWS Marketplace 支持的列表 AWS 区域，请参阅全球基础设施网站上的[区域表](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/)。

#### 获取 AWS 区域 用于计量的
<a name="metering-aws-region-configuration"></a>

将用于计量的容器与`MeterUsage`或 `RegisterUsage` API 操作集成时，请不要将 AWS SDK 配置为使用特定的 AWS 区域。必须在运行时动态获取区域。

**Example**  
例如，客户启动 Amazon ECS 任务或 Amazon EKS 容器组 (pod)。`RegisterUsage` API 操作在与启动 Amazon ECS 任务或 Amazon EKS 容器组 (pod) 的区域不同的区域中调用。因此，`RegisterUsage` API 操作会抛出 `InvalidRegionException` 错误。



AWS SDK 语言无法以一致`AWS_REGION`的方式确定。如果您的 SDK 没有自动获取 `AWS_REGION`，则需要手动编写软件来确定 `AWS_Region`。例如，在环境变量或其他配置不存在时， 适用于 Java 的 AWS SDK 会自动使用 [Amazon EC2 实例元数据](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html)（具体为 `ec2InstanceMetadata`）获取区域。在这种情况下，只有在 `AWS_REGION` 环境变量不存在时才调用 `ec2InstanceMetadata`。

有关如何在运行 AWS 区域 时动态获取的信息，请参阅适用于您的编程语言的 [AWS SDK 开发人员指南](https://aws.amazon.com/tools)。

### 防止计量修改
<a name="prevent-metering-modification"></a>

为买家引入修改或覆盖对 `RegisterUsage` 或 `MeterUsage` 的调用的方法可能导致不受欢迎的账单和付款问题。我们强烈建议您集成计量和授权逻辑。

在设计您的产品以防止计量修改时，请注意以下事项：
+ 如果买家可以插入包含 `CMD` 或 `ENTRYPOINT` 指令的新映像层，请直接将 `RegisterUsage` 或 `MeterUsage` 集成到买家正在通过容器映像运行的软件中。否则，从基本映像中通过 `CMD` 或 `ENTRYPOINT` 执行的对 `RegisterUsage` 或 `MeterUsage` 的调用可能会被买家覆盖。
+ 我们建议您管理您的软件用作输入 AWS Marketplace 的产品代码，`RegisterUsage`或者以买家无法修改`MeterUsage`的方式进行管理。但是，如果您的产品以客户可以覆盖的方式管理产品代码 AWS CloudFormation，例如 Helm chart 或 Kubernetes 清单，则必须维护*可信* AWS Marketplace 产品代码列表。这样可确保您的软件作为 `RegisterUsage` 或 `MeterUsage` 的输入传递的产品代码有效。
+  如果任何受信任的产品代码适用于免费产品，请确保它们无法用于替代付费产品代码。

## 与的合同定价 AWS License Manager
<a name="container-products-contracts-license-manager"></a>

对于采用合同定价的基于容器的产品，您可以使用 AWS License Manager 将许可证与您的产品关联起来。

AWS License Manager 是一种许可证管理工具，可让您的应用程序跟踪和更新客户购买的许可证（也称为授权）。本部分提供有关如何将您的产品与 AWS License Manager集成的信息。集成完成后，您可以在 AWS Marketplace上发布您的产品清单。

有关的更多信息 AWS License Manager，请参阅《[AWS License Manager 用户指南》和《AWS CLI](https://docs.aws.amazon.com/license-manager/latest/userguide/license-manager.html)*命令参考*》一[AWS License Manager](https://docs.aws.amazon.com/cli/latest/reference/license-manager/index.html)节。

**注意**  
合同到期后，客户无法启动新的容器实例。但是，在合同有效期内，他们可以启动任意数量的实例。这些许可证不绑定到特定的节点或实例。在任何节点上的任何容器上运行的任何软件都可以签出许可证，只要它具有分配的 AWS 凭证。
**专属优惠创建** – 卖家可以使用 AWS Marketplace 管理门户中的专属优惠创建工具为产品生成专属优惠。
**报告** – 您可以通过在 AWS Marketplace 管理门户中的**报告**部分设置 Amazon S3 存储桶来设置数据源。有关更多信息，请参阅 [中的卖家报告、数据源和控制面板 AWS Marketplace](reports-and-data-feed.md)。

### 集成工作流
<a name="container-LM-LM-workflow"></a>

以下步骤显示了将容器产品与 AWS License Manager集成的工作流：

1. 卖家创建具有 AWS License Manager 集成功能的产品。

1. 卖家在上架商品 AWS Marketplace。

1. 买家在上面找到产品 AWS Marketplace 并购买。

1. 许可证通过买家的 AWS 账户发送给他们。

1. 买家通过启动 Amazon EC2 实例、 Amazon EC2 任务或 Amazon EKS 容器组 (pod) 软件来使用该软件。客户使用 IAM 角色进行部署。

1. 软件读取买方 AWS License Manager 账户中的许可证，发现购买的权利并相应地配置功能。
**注意**  
License Manager 不进行任何跟踪或更新；这些是通过卖家的应用程序完成的。

# 使用 AWS Marketplace Metering Service 配置按小时计量
<a name="container-metering-registerusage"></a>

**注意**  
 对于 Amazon EKS 部署，您的软件必须使用[服务账户的 IAM 角色（IRSA）](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)来签署对 [https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_RegisterUsage.html](https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_RegisterUsage.html) API 操作的 API 调用。不支持使用 [EKS 容器组身份](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html)、节点角色或长期访问密钥。  
对于 Amazon ECS 部署，您的软件必须使用 [Amazon ECS 任务 IAM](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html) 角色来签署对 [https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_RegisterUsage.html](https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_RegisterUsage.html) API 操作的 API 调用。不支持使用节点角色或长期访问密钥。

如果您的容器产品使用每小时每任务或每容器组 (pod) 定价，而不是自定义计量定价维度，则无需定义自定义计量维度。您可以在 AWS Marketplace中使用 AWS Marketplace Metering Service 对容器产品进行按小时计量。以下部分将介绍如何使用 AWS Marketplace Metering Service 配置按小时计量。

`RegisterUsage`API 操作每 Amazon Elastic Container Service (Amazon ECS) 任务或 Amazon Elastic Kubernetes Service (Amazon EKS) 容器组 (pod) 每小时计量软件使用情况，用量按比例计算到秒。最低 1 分钟用量适用于有效期较短的任务或 pod。软件使用的连续计量由自动处理 AWS Marketplace Metering Control Plane。除了调用 `RegisterUsage` 一次以启动软件使用的计量外，您的软件无需执行任何计量特定操作。

`RegisterUsage` 必须在启动容器时立即调用。如果您在容器启动后的前 6 个小时内没有注册容器，AWS Marketplace Metering Service 将不为前几个月提供任何计量保证。但是，计量将在当月继续进行，直到容器结束。

无论客户的订阅状态如何，都会 AWS Marketplace Metering Control Plane继续向客户收取运行 Amazon ECS 任务和 Amazon EKS Pod 的费用。这样，您的软件就无需在任务或容器组 (pod) 首次成功启动后执行授权检查。

有关将 AWS Marketplace Metering Service API 与按小时定价的容器产品集成的更多信息，请参阅*AWS Marketplace 卖家研讨会*的 “[与小时计量集成](https://catalog.workshops.aws/mpseller/en-US/container/integrate-hourly)” 实验室。

**Topics**
+ [每小时计量先决条件](#hourly-metering-prereqs)
+ [测试 `RegisterUsage` 集成](#testing-integration-for-registerusage)
+ [`RegisterUsage` 中的错误处理](#hourly-metering-entitlement-error-handling)
+ [使用以下方法将您的容器产品与 AWS Marketplace 计量服务集成 适用于 Java 的 AWS SDK](java-integration-example-registerusage.md)

## 每小时计量先决条件
<a name="hourly-metering-prereqs"></a>

发布产品之前，您必须首先完成以下操作：

1. 在中创建新的容器产品 AWS Marketplace 管理门户，并记下其产品代码。

   有关更多信息，请参阅 [概述：创建容器产品](container-product-getting-started.md#create-container-product)。

1. 为运行应用程序的任务或容器使用 AWS Identity and Access Management (IAM) 角色，并获得调用所需的 IAM 权限`RegisterUsage`。IAM 托管策略 `AWSMarketplaceMeteringRegisterUsage` 具有这些权限。有关该策略的更多信息，请参阅[ AWSMarketplaceMeteringFullAccess](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSMarketplaceMeteringFullAccess.html)《*AWS 托管策略参考*》。

1. （可选）如果您想查看日志记录，我们建议您在任务或 pod 定义中启用 AWS CloudTrail 日志记录。

1. 使用您定义的所有定价维度的记录对 `RegisterUsage` API 操作进行测试调用。

## 测试 `RegisterUsage` 集成
<a name="testing-integration-for-registerusage"></a>

在将图片提交给发布之前，请使用 `RegisterUsage` API 操作测试您的集成。 AWS Marketplace 

通过在 Amazon ECS 或 Amazon EKS 上运行您的产品，从容器映像中调用 `RegisterUsage`。使用您用于发布商品的 AWS 账户 AWS Marketplace。您的计量集成必须动态设置 AWS 区域，而不是对其进行硬编码。但是，在测试时，请至少在美国东部（弗吉尼亚州北部）区域启动一个包含您的付费容器的 Amazon ECS 任务或 Amazon EKS 容器组 (pod)。这样， AWS Marketplace 运营团队就可以使用该区域的日志来验证您的工作。

**注意**  
如果您的产品同时支持 Amazon ECS 和 Amazon EKS，则您只需在 Amazon EKS 中启动，我们便能验证您的集成。

在使用所有必需的元数据和定价信息发布您的产品之前，您无法完全测试集成。如果需要， AWS Marketplace 目录运营团队可以验证您的计量记录是否收到。

## `RegisterUsage` 中的错误处理
<a name="hourly-metering-entitlement-error-handling"></a>

如果您的容器镜像与集成， AWS Marketplace Metering Service 并且`ThrottlingException`在容器启动之外收到异常，则应终止容器以防止未经授权的使用。

仅在初始调用 `RegisterUsage` API 操作时引发 `ThrottlingException` 以外的异常。从同一 Amazon ECS 任务或 Amazon EKS 容器组 (pod) 进行的后续调用不会引发 `CustomerNotSubscribedException`，即使客户在任务或 容器组 (pod) 仍在运行时取消订阅也是如此。这些客户在取消订阅并跟踪其使用情况后仍需支付运行容器的费用。

下表描述了 `RegisterUsage` API 操作可能会引发的错误。每种 AWS SDK 编程语言都有一套错误处理指南，您可以参考这些指南以获取更多信息。


|  **错误**  |  **描述**  | 
| --- | --- | 
|  InternalServiceErrorException  |  RegisterUsage 不可用。 | 
|  CustomerNotEntitledException  |  客户没有产品的有效订阅。 | 
|  InvalidProductCodeException  |  作为请求的一部分传入的 ProductCode 值不存在。 | 
|  InvalidPublicKeyException  |  作为请求的一部分传入的 PublicKeyVersion 值不存在。 | 
|  PlatformNotSupportedException  |  AWS Marketplace 不支持从底层平台计量使用情况。仅支持 Amazon ECS、Amazon EKS 和 AWS Fargate 。 | 
|  ThrottlingException  |  对 RegisterUsage 的调用受限。 | 
|  InvalidRegionException  |  RegisterUsage必须使用与启动 Amazon ECS 任务或 Amazon EKS 容器相同的 AWS 区域 方法进行调用。这可防止容器在调用 RegisterUsage 时选择区域（例如，withRegion(“us-east-1”)）。 | 

# 使用以下方法将您的容器产品与 AWS Marketplace 计量服务集成 适用于 Java 的 AWS SDK
<a name="java-integration-example-registerusage"></a>

您可以使用与 AWS Marketplace 计量服务集成。 适用于 Java 的 AWS SDK 软件使用的连续计量由自动处理 AWS Marketplace Metering Control Plane。除了调用 `RegisterUsage` 一次以启动软件使用的计量外，您的软件无需执行任何计量特定操作。本主题提供了一个使用与[AWS Marketplace 计量服务](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/Welcome.html)`RegisterUsage`操作集成的实现示例。 适用于 Java 的 AWS SDK 

`RegisterUsage` 必须在启动容器时立即调用。如果您在容器启动后的前 6 个小时内没有注册容器，AWS Marketplace Metering Service 将不为前几个月提供任何计量保证。但是，计量将在当月继续进行，直到容器结束。

有关完整源代码，请参阅[RegisterUsage Java 示例](#registerusage-java-example)。无论使用哪种 AWS SDK 语言，其中的许多步骤都适用。



**AWS Marketplace Metering Service 集成的示例步骤**

1. 登录到 [AWS Marketplace 管理门户](https://aws.amazon.com/marketplace/management/tour)。

1. 从**资产**中，选择**容器**以开始创建新容器产品。创建产品会生成产品的产品代码以与您的容器映像集成。有关设置 IAM 权限的信息，请参阅[AWS Marketplace 计量和授权 API 权限](iam-user-policy-for-aws-marketplace-actions.md)。

1.  下载公开的 [AWS Java SDK](https://aws.amazon.com/sdk-for-java/)。
**重要**  
 要 APIs 从 Amazon EKS 调用计量，您必须[使用支持的 AWS 软件开发工具包并在运行 K](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html) ubernetes 1.13 或更高版本的亚马逊 EKS 集群上运行。

1.  （可选）如果您要与`RegisterUsage`操作集成，并且想要执行数字签名验证，则需要在应用程序类路径中配置[BouncyCastle](https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on)签名验证库。

   如果要使用 JSON Web Token (JWT)，则还必须在应用程序类路径中包括 [JWT Java](https://jwt.io/) 库。使用 JWT 提供了一种更简单的签名验证方法，但这不是必需的，您可以 BouncyCastle 改用独立版。无论您使用 JWT 还是 BouncyCastle，都需要使用诸如 Maven 之类的构建系统在应用程序类路径中包含 BouncyCastle 或 JWT 的传递依赖关系。

   ```
   // Required for signature verification using code sample
   <dependency>
       <groupId>org.bouncycastle</groupId>
       <artifactId>bcpkix-jdk15on</artifactId>
       <version>1.60</version>
   </dependency>
   
   // This one is only required for JWT
   <dependency>
       <groupId>com.nimbusds</groupId>
       <artifactId>nimbus-jose-jwt</artifactId>
       <version>6.0</version>
   </dependency>
   ```

1.  从您的产品中的每个付费容器映像调用 `RegisterUsage`。`ProductCode` 和 `PublicKeyVersion` 是必需参数，所有其他输入都是可选的。以下是 `RegisterUsage` 的示例负载。

   ```
   {
       "ProductCode" : "string", // (required)
       "PublicKeyVersion": 1,    // (required)
       "Nonce": "string",        // (optional) to scope down the registration
                                 //            to a specific running software
                                 //            instance and guard against
                                 //            replay attacks
   }
   ```
**注意**  
可能出现 AWS Marketplace Metering Service 瞬时连接问题。 AWS Marketplace 强烈建议实施最长 30 分钟的重试，并添加指数退避，以避免短期中断或网络问题。

1.  `RegisterUsage` 使用 SHA-256 生成可用于验证请求真实性的 RSA-PSS 数字签名。该签名包括以下字段：`ProductCode`、`PublicKeyVersion` 和 `Nonce`。要验证数字签名，您必须保留请求中的这些字段。以下代码是 `RegisterUsage` 调用的示例响应。

   ```
   {
   "Signature": "<<JWT Token>>"
   }
   
   // Where the JWT Token is composed of 3 dot-separated, 
   // base-64 URL Encoded sections.
   // e.g. eyJhbGcVCJ9.eyJzdWIMzkwMjJ9.rrO9Qw0SXRWTe
   
   // Section 1: Header/Algorithm
   {
   "alg": "PS256",
   "typ": "JWT"
   }
   
   // Section 2: Payload
   {
   "ProductCode" : "string",
   "PublicKeyVersion": 1,
   "Nonce": "string",
   "iat": date // JWT issued at claim 
   }
   
   // Section 3: RSA-PSS SHA256 signature
   "rrO9Q4FEi3gweH3X4lrt2okf5zwIatUUwERlw016wTy_21Nv8S..."
   ```

1. 重建包含 `RegisterUsage` 调用的新版本容器映像，标记容器，然后将其推送至与 Amazon ECS 或 Amazon EKS 兼容的任何容器注册表，如 Amazon ECR 或 Amazon ECR Public。如果您使用的是 Amazon ECR，请确保启动 Amazon ECS 任务或 Amazon EKS 容器组 (pod) 的账户对 Amazon ECR 存储库拥有权限。否则，启动失败。

1.  创建一个 [IAM](https://aws.amazon.com/iam/) 角色来授予容器调用 `RegisterUsage` 的权限，如以下代码所定义。您必须在 Amazon ECS 任务或 Amazon EKS 容器组 (pod) 定义的[任务角色](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#task_role_arn)参数中提供此 IAM 角色。

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Action": [
                   "aws-marketplace:RegisterUsage"
                   ],
                   "Effect": "Allow",
                   "Resource": "*"
           }
       ]
   }
   ```

------

1. 创建 Amazon ECS 任务或 Amazon EKS 容器定义，引用已与您在步骤 7 中创建的 IAM 角色集成的容器 AWS Marketplace 并引用该角色。如果要查看 AWS CloudTrail 日志记录，则应在任务定义中启用日志记录。

1. 创建 Amazon ECS 或 Amazon EKS 集群来执行您的任务或容器组 (pod)。有关创建 Amazon ECS 集群的更多信息，请参阅《Amazon Elastic Container Service 开发人员指南》**中的[创建集群](https://docs.aws.amazon.com/AmazonECS/latest/userguide/create_cluster.html)。有关（使用 Kubernetes 版本 1.1.3.x 或更高版本）创建 Amazon EKS 集群的更多信息，请参阅[创建 Amazon EKS 集群](https://docs.aws.amazon.com/eks/latest/userguide/create_cluster.html)。

1. 配置 Amazon ECS 或 Amazon EKS 集群，然后在 us-east- AWS 区域 1 中启动你创建的亚马逊 ECS 任务定义或亚马逊 EKS 容器。只有在此测试过程中，在产品上线之前，您才必须使用此区域。

1. 当您从 `RegisterUsage` 获得有效的响应时，您可以开始创建您的容器产品。如有问题，请联系 [AWS Marketplace 卖家运营](https://aws.amazon.com/marketplace/management/contact-us/)团队。

## RegisterUsage Java 示例
<a name="registerusage-java-example"></a>

以下示例使用 适用于 Java 的 AWS SDK 和 AWS Marketplace 计量服务来调用该`RegisterUsage`操作。签名验证是可选的，但如果您要执行签名验证，则必须包含所需的数字签名验证库。此示例仅用于说明。

```
import com.amazonaws.auth.PEM;
import com.amazonaws.services.marketplacemetering.AWSMarketplaceMetering;
import com.amazonaws.services.marketplacemetering.AWSMarketplaceMeteringClientBuilder;
import com.amazonaws.services.marketplacemetering.model.RegisterUsageRequest;
import com.amazonaws.services.marketplacemetering.model.RegisterUsageResult;
import com.amazonaws.util.json.Jackson;
import com.fasterxml.jackson.databind.JsonNode;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
import java.util.Optional;
import java.util.UUID;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 * Class for making calls out to &MKT; Metering Service.
 */
class RegisterUsage {

    private static final String PRODUCT_CODE = ".......";

    private final AWSMarketplaceMetering registerUsageClient;
    private final SignatureVerifier signatureVerifier;
    private final int publicKeyVersion;

    public RegisterUsage(final SignatureVerifier signatureVerifier) {
        this.signatureVerifier = signatureVerifier;
        this.publicKeyVersion = PublicKeyProvider.PUBLIC_KEY_VERSION;
        this.registerUsageClient = AWSMarketplaceMeteringClientBuilder.standard().build();
    }

    /**
     * Shows how to call RegisterUsage client and verify digital signature.
     */
    public void callRegisterUsage() {
        RegisterUsageRequest request = new RegisterUsageRequest()
                .withProductCode(PRODUCT_CODE)
                .withPublicKeyVersion(publicKeyVersion)
                .withNonce(UUID.randomUUID().toString());

        // Execute call to RegisterUsage (only need to call once at container startup)
        RegisterUsageResult result = this.registerUsageClient.registerUsage(request);

        // Verify Digital Signature w/o JWT
        boolean isSignatureValid = this.signatureVerifier.verify(request, result);
        if (!isSignatureValid) {
            throw new RuntimeException("Revoke entitlement, digital signature invalid.");
        }
    }
}

/**
 * Signature verification class with both a JWT-library based verification
 * and a non-library based implementation.
 */
class SignatureVerifier {
    private static BouncyCastleProvider BC = new BouncyCastleProvider();

    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA/PSS";

    private final PublicKey publicKey;

    public SignatureVerifier(PublicKeyProvider publicKeyProvider) {
        this.publicKey = publicKeyProvider.getPublicKey().orElse(null);
        Security.addProvider(BC);
    }

    /**
     * Example signature verification using the NimbusJOSEJWT library to verify the JWT Token.
     *
     * @param request RegisterUsage Request.
     * @param result  RegisterUsage Result.
     * @return true if the token matches.
     */
    public boolean verifyUsingNimbusJOSEJWT(final RegisterUsageRequest request, final RegisterUsageResult result) {
        if (!getPublicKey().isPresent()) {
            return false;
        }

        try {
            JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) getPublicKey().get());
            JWSObject jwsObject = JWSObject.parse(result.getSignature());
            return jwsObject.verify(verifier) && validatePayload(jwsObject.getPayload().toString(), request, result);
        } catch (Exception e) {
            // log error
            return false;
        }
    }

    /**
     * Example signature verification without any JWT library support.
     *
     * @param request RegisterUsage Request.
     * @param result  RegisterUsage Result.
     * @return true if the token matches.
     */
    public boolean verify(final RegisterUsageRequest request, final RegisterUsageResult result) {
        if (!getPublicKey().isPresent()) {
            return false;
        }
        try {
            String[] jwtParts = result.getSignature().split("\\.");
            String header = jwtParts[0];
            String payload = jwtParts[1];
            String payloadSignature = jwtParts[2];

            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, BC);
            signature.initVerify(getPublicKey().get());
            signature.update(String.format("%s.%s", header, payload).getBytes(StandardCharsets.UTF_8));
            boolean verified = signature.verify(Base64.getUrlDecoder()
                    .decode(payloadSignature.getBytes(StandardCharsets.UTF_8)));

            String decodedPayload = new String(Base64.getUrlDecoder().decode(payload));
            return verified && validatePayload(decodedPayload, request, result);
        } catch (Exception e) {
            // log error
            return false;
        }
    }

    /**
     * Validate each value in the returned payload matches values originally
     * supplied in the request to RegisterUsage. TimeToLiveInMillis and
     * PublicKeyExpirationTimestamp will have the values in the payload compared
     * to values in the signature
     */
    private boolean validatePayload(final String payload, final RegisterUsageRequest request,
                                    final RegisterUsageResult result) {
        try {
            JsonNode payloadJson = Jackson.getObjectMapper().readTree(payload);
            boolean matches = payloadJson.get("productCode")
                    .asText()
                    .equals(request.getProductCode());
            matches = matches && payloadJson.get("nonce")
                    .asText()
                    .equals(request.getNonce());
            return matches = matches && payloadJson.get("publicKeyVersion")
                    .asText()
                    .equals(String.valueOf(request.getPublicKeyVersion()));

        } catch (Exception ex) {
            // log error
            return false;
        }
    }

    private Optional<PublicKey> getPublicKey() {
        return Optional.ofNullable(this.publicKey);
    }
}

/**
 * Public key provider taking advantage of the &AWS; PEM Utility.
 */
class PublicKeyProvider {
    // Replace with your public key. Ensure there are new-lines ("\n") in the
    // string after "-----BEGIN PUBLIC KEY-----\n" and before "\n-----END PUBLIC KEY-----".
    private static final String PUBLIC_KEY =
            "-----BEGIN PUBLIC KEY-----\n"
                    + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd\n"
                    + "UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs\n"
                    + "HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D\n"
                    + "o2kQ+X5xK9cipRgEKwIDAQAB\n"
                    + "-----END PUBLIC KEY-----";

    public static final int PUBLIC_KEY_VERSION = 1;

    public Optional<PublicKey> getPublicKey() {
        try {
            return Optional.of(PEM.readPublicKey(new ByteArrayInputStream(
                    PUBLIC_KEY.getBytes(StandardCharsets.UTF_8))));
        } catch (Exception e) {
            // log error
            return Optional.empty();
        }
    }
}
```

# 使用 AWS Marketplace Metering Service 对容器产品配置自定义计量
<a name="container-metering-meterusage"></a>

**注意**  
 对于 Amazon EKS 部署，您的软件必须使用[服务账户的 IAM 角色（IRSA）](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)来签署对 [https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_MeterUsage.html](https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_MeterUsage.html) API 操作的 API 调用。不支持使用 [EKS 容器组身份](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html)、节点角色或长期访问密钥。  
对于 Amazon ECS 部署，您的软件必须使用 [Amazon ECS 任务 IAM](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html) 角色来签署对 [https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_MeterUsage.html](https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_MeterUsage.html) API 操作的 API 调用。不支持使用节点角色或长期访问密钥。  
对于 Amazon Bedrock AgentCore 运行时部署，您的软件必须使用[AgentCore 运行时执行角色](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html#runtime-permissions-execution)来签署 API 操作的 [https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_MeterUsage.html](https://docs.aws.amazon.com/marketplace/latest/APIReference/API_marketplace-metering_MeterUsage.html)API 调用。不支持使用长期访问密钥。

AWS Marketplace 集装箱产品可以对每种产品多达 24 种不同的定价维度进行自定义计量。每个维度都可以具有与之相关的长期合同价格。要启用自定义计量，请将您的容器产品与 AWS Marketplace Metering Service 集成。您可以使用 [https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_MeterUsage.html](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_MeterUsage.html)API 操作为该使用量定义自己的定价单位和自定义计量以 AWS 进行计费。以下部分将介绍如何为容器产品配置自定义计量。

价格维度在两个位置定义，一次是在 AWS Marketplace 管理门户 （卖家门户）创建产品时，另一次是在软件中执行 `MeterUsage` 操作时。此双因素方法可确保后续的产品在向公众提供之前按预期工作。

要设置自定义计量，您需要选择使用类别、单位类型和定价维度：
+ **用户类别** - 用户类别可帮助买家了解您的产品是什么以及如何使用它。
+ **单位类型** - 单位类型定义计费的计量单位。例如，以 GBps 或为单位的带宽 MBps、以主机数量为单位的带宽，或者以 MB、GB 或 TB 为单位的数据。
+ **定价维度**-定价维度表示您为其设置了单位价格的功能或服务（例如，用户、扫描CPUs、v 或已部署的代理）。定价维度是公开的。但是，您仍然可以为公开产品定义专属和自带许可 (BYOL) 优惠。请勿在计量记录中发送定价。您可以计量单位数量，我们将其与您在创建产品时定义的价格一起使用，以计算买家的账单。

  如果您的产品定价不符合任何预定义类别或单位类型，您可以选择通用**单位**类别。然后，使用维度描述来描述单位是什么。

或者，您可以按您跟踪的属性将用量分发到分配中。分配以标签形式呈现给买家。这些标签允许买家按标签值查看按用量划分的费用。例如，如果您按用户收费，并且用户具有“部门”属性，则可以使用键为“部门”的标签创建使用分配，每个值一个分配。这不会更改您报告的价格、维度或总用量，但允许您的客户按与您的产品相应类别查看其成本。

我们建议您每小时发送一次计量记录。但是，您也可以汇总每日或每月的用量。如果您遇到中断，您可以聚合买家的软件用量，并在接下来的小时计量中发送。每小时不能发送多条记录。

有关将集装箱产品 AWS Marketplace Metering Service 的 API 与自定义计量定价集成的更多信息，请参阅*AWS Marketplace 卖家研讨会*的 “[与自定义计量集成](https://catalog.workshops.aws/mpseller/en-US/container/integrate-custom)” 实验室。

**重要**  
免费试用和预付费权利按小时进行跟踪。因此，单独发送这些记录可能会导致买家被多收费用。

**Topics**
+ [自定义计量先决条件](#custom-metering-prereqs)
+ [测试 ECS 和 EKS 的 `MeterUsage` 集成](#testing-meterusage-integration)
+ [测试 MeterUsage 集成 AgentCore](#testing-agentcore-metering)
+ [`MeterUsage` 中的错误处理](#custom-metering-entitlement-error-handling)
+ [（可选）供应商计量标记](#container-vendor-metered-tagging)
+ [代码示例](#container-meter-code-example)
+ [使用自定义计量将您的容器产品与 AWS Marketplace Metering Service 和集成 适用于 Java 的 AWS SDK](java-integration-example-meterusage.md)

## 自定义计量先决条件
<a name="custom-metering-prereqs"></a>

发布产品之前，您必须首先完成以下操作：

1. 在中创建新的容器产品 AWS Marketplace 管理门户，并记下其产品代码。

1. 为运行应用程序的任务、容器或 AgentCore 运行时终端节点使用 AWS Identity and Access Management (IAM) 角色，并获得调用所需的 IAM 权限`MeterUsage`。IAM 托管策略 `AWSMarketplaceMeteringRegisterUsage` 具有这些权限。有关该策略的更多信息，请参阅[ AWSMarketplaceMeteringFullAccess](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSMarketplaceMeteringFullAccess.html)《*AWS 托管策略参考*》。

1. （可选）如果您想查看 AWS CloudTrail 日志记录，我们建议您在任务或 pod 定义中启用日志记录。

1. 使用您定义的所有定价维度的记录对 `MeterUsage` API 操作进行测试调用。

## 测试 ECS 和 EKS 的 `MeterUsage` 集成
<a name="testing-meterusage-integration"></a>

在将图片提交给发布之前，请使用该`MeterUsage`操作来 AWS Marketplace 测试您的集成。

通过在亚马逊弹性容器服务 (Amazon ECS) 或亚马逊 Elastic Kubernetes Service (Amazon EKS) 上运行您的产品，使用您用来发布商品的容器镜像调`MeterUsage`用。 AWS 账户 AWS Marketplace您的计量集成必须动态设置 AWS 区域，而不是对其进行硬编码。但是，在测试时，请至少在美国东部（弗吉尼亚北部）地区启动一个包含您的付费容器的 Amazon ECS 任务或 Amazon EKS Pod，以便 AWS Marketplace 运营团队可以使用该地区的日志验证您的工作。

**注意**  
如果您的产品同时支持 Amazon ECS 和 Amazon EKS，则您只需在 Amazon EKS 中启动，我们便能验证您的集成。
在向公众发布产品之前和添加新维度之后，对每个维度进行测试。如果您没有发送与容器产品关联的每个维度的计量记录，则会导致错误并导致请求失败。

在使用所有必需的元数据和定价信息发布您的产品之前，您无法完全测试集成。如果需要， AWS Marketplace 目录运营团队可以验证您的计量记录是否收到。

## 测试 MeterUsage 集成 AgentCore
<a name="testing-agentcore-metering"></a>

在将图片提交给发布之前，请使用该`MeterUsage`操作来 AWS Marketplace 测试您的集成。

使用您上架商品时使用的 AWS 账户在 Amazon Bedrock AgentCore 上 AWS Marketplace运行您的商品，`MeterUsage`从容器图片中调用。您的计量集成必须动态设置 AWS 区域，而不是对其进行硬编码。但是，在测试时，请至少在美国东部（弗吉尼亚北部）地区启动一个包含您的付费容器的 Amazon Bedrock AgentCore 代理，以便 AWS Marketplace 运营团队可以使用该地区的日志验证您的工作。

 您无需汇总每小时使用记录。每次调用代理时，都应针对该次调用的使用量调用 `MeterUsage` 操作。

您必须使用适用于您的语言的最新发布版本的 AWS SDK。这会自动使用自动生成的值填充 `ClientToken` 参数，以帮助确定幂等性。之前发布的未填充此字段的 SDK 将不适用于来自 Amazon Bedro AgentCore ck 内部的`MeterUsage`呼叫。由于网络问题，重试时必须重复使用完全相同的请求。这样做可以确保以幂等方式处理请求。

由于 Amazon Bedrock AgentCore 与其他容器产品之间的预期计量行为存在差异，因此我们不建议在亚马逊 Bedrock AgentCore 和亚马逊 ECS 或 EKS 上共享相同的容器映像。

## `MeterUsage` 中的错误处理
<a name="custom-metering-entitlement-error-handling"></a>

容器启动时调用 `MeterUsage`，并将 `DryRun` 参数设为 true，以此验证计量集成是否正常工作。如果您的容器映像与 `MeterUsage` 操作集成并在容器启动时收到 `ThrottlingException` 之外的异常，您应终止容器以防止未经授权的使用。

仅在初始调用 `MeterUsage` 时引发 `ThrottlingException` 以外的异常。即使客户在任务或容器仍在 AgentCore 运行时取消订阅，来自同一 Amazon ECS 任务或 Amazon EKS 容器或运行时终端节点的后续调用也不会抛出`CustomerNotSubscribedException`。这些客户在取消订阅并跟踪其使用情况后仍需支付运行容器的费用。

有关常见错误[MeterUsage](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_MeterUsage.html)的详细说明，请参阅 *AWS Marketplace Metering Service API 参考*中的`MeterUsage`。每种 AWS SDK 编程语言都有一套错误处理指南，您可以参考这些指南以获取更多信息。

## （可选）供应商计量标记
<a name="container-vendor-metered-tagging"></a>

供应商计量标签可帮助独立软件供应商 (ISVs) 让购买者更精细地了解其软件使用情况，并可以帮助他们进行成本分配。

**注意**  
Amazon Bedrock 产品的计量请求不支持供应商计量标签。 AgentCore 

您可通过多种方式为买家的软件使用情况添加标签。一种方法是先询问买家他们希望在成本分配中看到什么。然后，您可以在为买家帐户跟踪的属性之间分配使用情况。属性的示例包括 `AccountId`、`Business Unit`、`Cost Centers`，以及您的产品的其他相关元数据。这些属性作为标签展示给买家。使用标签，买家可以在 AWS 账单控制台（[https://console.aws.amazon.com/costmanagement/](https://console.aws.amazon.com/costmanagement/)）中查看按标签值划分为使用量的费用。供应商计量标记不会更改您报告的价格、维度或总用量。它允许您的客户按您产品的相应类别查看成本。

在常见情形中，买家会通过一个 AWS 账户订阅您的产品。买家还有许多与同一产品订阅相关的用户。您可以使用具有 `AccountId` 键的标签创建用量分配，然后将用量分配给每个用户。在这种情况下，买家可以在其账单与成本管理控制台中激活 `AccountId` 标签并分析个人用户的使用情况。

### 卖家体验
<a name="container-vendor-metered-tag-seller"></a>

卖家可以汇总具有相同标签集的资源的计量记录，而不是汇总所有资源的使用情况。例如，卖家可以构造包含不同的 `UsageAllocations` 存储桶的计量记录。每个存储桶代表一组标签的 `UsageQuantity`，例如 `AccountId` 和 `BusinessUnit`。

在下图中，**资源 1** 具有一组唯一的 `AccountId` 和 `BusinessUnit` 标签，并作为单个条目出现在**计量记录**中。

**资源 2** 和**资源 3** 都具有相同的 `AccountId` 标签 `2222` 和相同的 `BusinessUnit` 标签 `Operations`。因此，它们在**计量记录**中合并为一个 `UsageAllocations` 条目。

![\[该图显示了供应商计量标签如何合并用量数据。三个具有不同 AccountIds 和的资源（资源 1、2 和 3）在发送到 M AWS arketplace 计量服务 BusinessUnit 之前，将 UsageAllocations 按分组 AccountId 并整合到单个计量记录中。 BusinessUnits\]](http://docs.aws.amazon.com/zh_cn/marketplace/latest/userguide/images/seller-vendor-meter-tag.png)


卖家还可以将没有标签的资源组合成一个包含分配用量的 `UsageAllocation`，然后将其作为 `UsageAllocations` 中的一个条目发送。

限制包括：
+ 标签数 – 5
+ `UsageAllocations`（基数）的大小 – 2500

验证包括：
+ 标签键和值允许使用的字符 — a-zA-Z 0-9\$1-= 。 \$1:\$1 /@
+ `UsageAllocation` 列表中的最大标签数 – 5
+ 两个 `UsageAllocations` 的标签不能相同（也就是说，标签键和值的组合相同）。如果是这样的话，他们必须使用相同的 `UsageAllocation`。
+ `UsageAllocation` 的 `AllocatedUsageQuantity` 之和必须等于 `UsageQuantity`，即聚合用量。

### 买家体验
<a name="container-vendor-metered-tag-buyer"></a>

下表显示了买家激活 `AccountId` 和 `BusinessUnit` 供应商标签后的买家体验示例。

在此示例中，买家可以在其**成本使用报告**中看到分配的用量。供应商计量标签使用前缀 `“aws:marketplace:isv”`。在账单与成本管理中，买家可以再**成本分配标签**下的 **AWS生成的成本分配标签**中激活它们。

**成本使用报告**的第一行和最后一行与卖家向 Metering Service 发送的内容相关（如 [卖家体验](#container-vendor-metered-tag-seller) 示例所示）。


**成本使用报告（简化）**  

| ProductCode  | 买家 | UsageDimension | UsageQuantity | `aws:marketplace:isv:AccountId ` | `aws:marketplace:isv:BusinessUnit` | 
| --- | --- | --- | --- | --- | --- | 
| xyz | 111122223333 | 网络：每 (GB) 检查一次  | 70 | 2222 | 运营 | 
| xyz | 111122223333 | 网络：每 (GB) 检查一次  | 30 | 3333 | 财务 | 
| xyz | 111122223333 | 网络：每 (GB) 检查一次  | 20 | 4444 | IT | 
| xyz | 111122223333 | 网络：每 (GB) 检查一次  | 20 | 5555 | 市场营销 | 
| xyz | 111122223333 | 网络：每 (GB) 检查一次  | 30 | 1111 | 市场营销 | 

有关代码示例，请参阅 [带有用量分配标签的 `MeterUsage` 代码示例（可选）](#container-meterusage-code-example)。

## 代码示例
<a name="container-meter-code-example"></a>

以下代码示例旨在帮助您将容器产品与发布和维护产品 AWS Marketplace APIs 所需的产品集成。

### 带有用量分配标签的 `MeterUsage` 代码示例（可选）
<a name="container-meterusage-code-example"></a>

以下代码示例与具有消费定价模式的容器产品相关。Python 示例将带有相应使用量分配标签的计量记录发送给您的客户 AWS Marketplace ，以向您的客户收取 pay-as-you-go费用。

```
# NOTE: Your application will need to aggregate usage for the 
#       customer for the hour and set the quantity as seen below. 
# AWS Marketplace can only accept records for up to an hour in the past. 
#
# productCode is supplied after the AWS Marketplace Ops team has 
# published the product to limited

# Import AWS Python SDK
import boto3
import time

usageRecord = [
    { 
        "AllocatedUsageQuantity": 2, 
        "Tags": 
            [ 
                { "Key": "BusinessUnit", "Value": "IT" },
                { "Key": "AccountId", "Value": "123456789" },
            ]

    },
    { 
        "AllocatedUsageQuantity": 1, 
        "Tags": 
            [ 
                { "Key": "BusinessUnit", "Value": "Finance" },
                { "Key": "AccountId", "Value": "987654321" },
            ]

    }
]

marketplaceClient = boto3.client("meteringmarketplace")

response = marketplaceClient.meter_usage(
    ProductCode="testProduct",
    Timestamp=int(time.time()),
    UsageDimension="Dimension1",
    UsageQuantity=3,
    DryRun=False,
    UsageAllocations=usageRecord 
)
```

有关的更多信息`MeterUsage`，请参阅 *AWS Marketplace Metering Service API 参考[MeterUsage](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_MeterUsage.html)*中的。

### 响应示例
<a name="container-meterusage-code-response"></a>

```
{ "MeteringRecordId": "string" }
```

# 使用自定义计量将您的容器产品与 AWS Marketplace Metering Service 和集成 适用于 Java 的 AWS SDK
<a name="java-integration-example-meterusage"></a>

AWS Marketplace 集装箱产品可以对每种产品多达 24 种不同的定价维度进行自定义计量。要启用自定义计量，请将您的容器产品与 AWS Marketplace Metering Service 集成。您可以使用 [https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_MeterUsage.html](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_MeterUsage.html) API 操作为该用量定义自己的定价单位和自定义计量以便 AWS 进行计费。以下示例概述了使用与[AWS Marketplace 计量服务](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/Welcome.html)`MeterUsage`操作集成的实现。 适用于 Java 的 AWS SDK 

有关完整的详细信息，请参阅 [`MeterUsage` Java 示例](#meterusage-java-example)。以下步骤中的许多步骤适用于任何语言。

**示例： AWS Marketplace 计量服务集成**

1. 登录到 [AWS Marketplace 管理门户](https://aws.amazon.com/marketplace/management/tour)。

1. 从**资产**中，选择**容器**以开始创建新容器产品。创建产品会生成产品的产品代码以与您的容器映像集成。有关设置 AWS Identity and Access Management (IAM) 权限的信息，请参阅[AWS Marketplace 计量和授权 API 权限](iam-user-policy-for-aws-marketplace-actions.md)。

1.  下载公开的 [AWS Java SDK](https://aws.amazon.com/sdk-for-java/)。
**重要**  
 要从 Amazon Elastic Kubernetes Service (Amazon EKS) 调用计量 API 操作，您必须[使用支持的](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html) AWS SDK 并在运行 Kubernetes 1.13 或更高版本的 Amazon EKS 集群上运行。

1. 针对每个维度使用情况，每小时从任务或容器组 (pod) 调用一次 `MeterUsage` 操作。此 API 操作对于 `Dimension`、`Resource` 和 `Hour` 的唯一组合接受一个计量记录。资源要么是 Amazon Elastic Container Service (Amazon ECS) 任务，要么是 Amazon EKS 容器组 (pod)。

   ```
   {
       "ProductCode" : "string", // (required)
       "UsageDimension" : "string", // (required)
       "UsageQuantity":  int, // (optional) Default is 0. Acceptable value from [0, 2147483647 (INT_MAX)]
       "Timestamp": Date, // (required) Timestamp in UTC. Value can be one hour in the past.
       "UsageAllocations": List<UsageAllocation> // (optional) UsageAllocations across 1 or more tags.
   }
   ```
**注意**  
在连接时可能会出现暂时性问题 AWS Marketplace Metering Service。 AWS Marketplace 强烈建议实施最长 30 分钟的重试，并以指数级退缩，以避免短期中断或网络问题。

1. 重建包含 `MeterUsage` 调用的新版本容器映像，标记容器，然后将其推送至与 Amazon ECS 或 Amazon EKS 兼容的任何 Docker 注册表，如 Amazon Elastic Container Registry (Amazon ECR)。如果您使用的是 Amazon ECR，请确保启动 Amazon ECS 任务或 Amazon EKS 容器组 (pod) 的账户对 Amazon ECR 存储库拥有权限。否则，该操作将失败。

1. 创建一个 [IAM](https://aws.amazon.com/iam/) 角色来授予容器调用 `MeterUsage` 的权限，如以下代码示例所定义。您必须在 Amazon ECS 任务的[任务角色](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#task_role_arn)参数或 Amazon EKS 容器定义中提供此 AWS Identity and Access Management (IAM) 角色。

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Action": [
                   "aws-marketplace:MeterUsage"
                   ],
                   "Effect": "Allow",
                   "Resource": "*"
           }
       ]
   }
   ```

------

1. 创建 Amazon ECS 任务或 Amazon EKS 容器定义，引用已与您在步骤 6 中创建的 IAM 角色集成的容器 AWS Marketplace 并引用该角色。如果要查看日志记录，请在任务定义中启用 AWS CloudTrail 日志记录。

1. 创建 Amazon ECS 或 Amazon EKS 集群来运行您的任务或容器组 (pod)。有关创建 Amazon ECS 集群的更多信息，请参阅《Amazon Elastic Container Service 开发人员指南》**中的[创建集群](https://docs.aws.amazon.com/AmazonECS/latest/userguide/create_cluster.html)。有关（使用 Kubernetes 版本 1.1.3.x 或更高版本）创建 Amazon EKS 集群的更多信息，请参阅[创建 Amazon EKS 集群](https://docs.aws.amazon.com/eks/latest/userguide/create_cluster.html)。

1. 配置 Amazon ECS 或 Amazon EKS 集群，然后在 us-east-1 AWS 区域启动您在步骤 8 中创建的 Amazon ECS 任务定义或亚马逊 EKS 容器。只有在此测试过程中，在产品上线之前，您才必须使用此区域。

1. 当您从正在为产品发布的每个维度的 `MeterUsage` 获得有效相应时，您可以开始创建容器产品。如有问题，请联系 [AWS Marketplace 卖家运营](https://aws.amazon.com/marketplace/management/contact-us/)团队。

## `MeterUsage` Java 示例
<a name="meterusage-java-example"></a>

以下代码示例使用 适用于 Java 的 AWS SDK 和 AWS Marketplace 计量服务来调用该`MeterUsage`操作。

使用以下示例代码调用 `MeterUsage` 操作，不设置 `UsageAllocations`。

```
import com.amazonaws.services.marketplacemetering.AWSMarketplaceMetering;
import com.amazonaws.services.marketplacemetering.AWSMarketplaceMeteringClientBuilder;
import com.amazonaws.services.marketplacemetering.model.MeterUsageRequest;
import com.amazonaws.services.marketplacemetering.model.MeterUsageResult;

import java.util.Date;

public class MeterUsage {
    private static final String PRODUCT_CODE = ".......";
    private final AWSMarketplaceMetering awsMarketplaceMetering;

    public MeterUsage() {
        awsMarketplaceMetering = AWSMarketplaceMeteringClientBuilder.standard().build();
    }

    /**
     * Submits metering record for a FCP Dimension. The API accepts 1 metering record per dimension
     * for a given buyer's resource for a given timestamp hour. Ex. If a buyer is running 10 tasks,
     * the API will accepts 1 call to MeterUsage in an hour for a given dimension for each running task.
     *
     * @param dimension - FCP dimension name provided during the publishing of the product.
     * @param quantity - FCP dimension consumption value for the hour.
     * @param timestamp - Timestamp, in UTC, for which the usage is being reported.
     *                  Timestamp cant be more than 1 hour in the past.
     *                  Make sure the timestamp value is not before the start of the software usage.
     */
    public void callMeterUsage(String dimension, int quantity, Date timestamp) {
        MeterUsageRequest meterUsageRequest = new MeterUsageRequest()
                .withProductCode(PRODUCT_CODE)
                .withUsageDimension(dimension)
                .withUsageQuantity(quantity)
                .withTimestamp(timestamp);
        MeterUsageResult meterUsageResult = awsMarketplaceMetering.meterUsage(meterUsageRequest);
    }
}
```

使用以下示例代码调用 `MeterUsage` 操作，设置 `UsageAllocations`。

```
private static String callMeterUsageWithAllocationsByTag(AWSMarketplaceMetering marketplaceMetering) {
        // Tag Keys for the product
        String tagKey1 = "Key1";
        String tagKey2 = "Key2";
        String tagKey3 = "Key3";

        // 1st Usage Allocation bucket which has two Tags [{Key1, Key1Value1},{Key2, Key2Value1}]
        List<Tag> tagsForUsageAllocation1 = Arrays.asList(new Tag().withKey(tagKey1).withValue("Key1Value1"),
                new Tag().withKey(tagKey2).withValue("Key2Value1"));
        UsageAllocation usageAllocation1 = new UsageAllocation()
                .withTags(tagsForUsageAllocation1)
                .withAllocatedUsageQuantity(20);

        // 2nd Usage Allocation bucket which has two Tags [{Key1, Key1Value2},{Key2, Key2Value1}]
        List<Tag> tagsForUsageAllocation2 = Arrays.asList(new Tag().withKey(tagKey1).withValue("Key1Value2"),
                new Tag().withKey(tagKey2).withValue("Key2Value1"));
        UsageAllocation usageAllocation2 = new UsageAllocation()
                .withTags(tagsForUsageAllocation2)
                .withAllocatedUsageQuantity(20);

        // 3rd Usage Allocation bucket which has two Tags [{Key1, Key1Value2},{Key2, Key2Value2},{Key3, Key3Value1}]
        List<Tag> tagsForUsageAllocation3 = Arrays.asList(new Tag().withKey(tagKey1).withValue("Key1Value2"),
                new Tag().withKey(tagKey2).withValue("Key2Value2"),
                new Tag().withKey(tagKey3).withValue("Key3Value1"));
        UsageAllocation usageAllocation3 = new UsageAllocation()
                .withTags(tagsForUsageAllocation3)
                .withAllocatedUsageQuantity(15);

        // 4th Usage Allocation bucket with no tags
        UsageAllocation usageAllocation4 = new UsageAllocation()
                .withAllocatedUsageQuantity(15);

        List<UsageAllocation> usageAllocationList = Arrays.asList(usageAllocation1,
                usageAllocation2,
                usageAllocation3,
                usageAllocation4);

        MeterUsageRequest meterUsageRequest = new MeterUsageRequest()
                .withProductCode("TestProductCode")
                .withUsageDimension("Dimension1")
                .withTimestamp(new Date())
                //UsageQuantity value must match with sum of all AllocatedUsageQuantity
                .withUsageQuantity(70)
                .withUsageAllocations(usageAllocationList);

        MeterUsageResult meterUsageResult;
        try {
            meterUsageResult = marketplaceMetering.meterUsage(meterUsageRequest);
        } catch (Exception e) {
            // Log Error
            throw e;
        }

        return meterUsageResult.getMeteringRecordId();
    }
```

# 集装箱产品的合同定价 AWS License Manager
<a name="container-license-manager-integration"></a>

对于采用合同定价的基于容器的产品，您可以使用 AWS License Manager 将许可证与您的产品相关联。 AWS License Manager 是一种许可证管理工具，可让您的应用程序跟踪和更新客户购买的许可证（也称为授权）。本节提供有关如何将您的产品与集成的信息 AWS License Manager。集成完成后，您可以在 AWS Marketplace上发布您的产品清单。

如果你要将 License Manager 与 AWS Marketplace 适用于亚马逊 EKS Anywhere、Amazon ECS Anywhere、Amazon Elastic Compute Cloud (Amazon EC2) 或本地基础设施的 For Containers Anywhere 产品集成，请按照中的说明进行操作。[AWS Marketplace 使用 License Manager 集成 for Container](container-anywhere-license-manager-integration.md)

有关的更多信息 AWS License Manager，请参阅《[AWS License Manager 用户指南》和《AWS CLI](https://docs.aws.amazon.com/license-manager/latest/userguide/license-manager.html)*命令参考*》一[AWS License Manager](https://docs.aws.amazon.com/cli/latest/reference/license-manager/index.html)节。

有关通过合同定价 AWS License Manager 与集装箱产品集成的更多信息，请参阅*AWS Marketplace 卖方研讨*会的 “[与前期付款集成](https://catalog.workshops.aws/mpseller/en-US/container/integrate-contract)” 实验室。

**Topics**
+ [容器产品的合同定价](#container-contracts)
+ [许可模式](#container-LM-license-models)
+ [AWS License Manager 集成先决条件](#container-LM-prereqs)
+ [将容器产品与 License Manager 集成](#container-integrate-with-LM)
+ [License Manager API 操作](#container-LM-API-calls)
+ [许可证续订和升级](#container-LM-lic-renew-upgrade)
+ [AWS Marketplace 使用 License Manager 集成 for Container](container-anywhere-license-manager-integration.md)

## 容器产品的合同定价
<a name="container-contracts"></a>

对于采用合同定价的基于集装箱的产品，根据您与客户之间的合同，提前或按照您定义的付款时间表向客户开具 AWS Marketplace 账单。之后，客户将有权使用那些资源。

要设置您的定价，请选择您向客户提供的一个或多个合同期限。您可以为每个合同期限输入不同的价格。您可以选择 1 个月、12 个月、24 个月和 36 个月的合同期限。对于专属优惠，您可以指定以月为单位的自定义期限（最多 60 个月）。

选择最能描述您的产品定价的类别。定价类别会显示在 AWS Marketplace 网站上。您可以选择**带宽** (GB/s, MB/s)、**数据**（GB、MB、TB）、**主机**、**请求**、**层**或**用户**。如果所有预定义的类别均无法满足您的需求，您可以选择更通用的**单位**类别。

该优惠允许向其中添加多达 24 个维度。


**示例：数据存储应用程序**  

|   | 1 个月价格 | 12 个月价格  | 24 个月价格  | 36 个月价格  | 
| --- | --- | --- | --- | --- | 
|  未加密的数据 (GB)  |  1.50 美元/GB  |  16.00 美元/GB  |  30.00 美元/GB  |  60.00 美元/GB  | 
|  加密的数据 (GB)  |  1.55 美元/GB  |  16.60 美元/GB  |  31.20 美元/GB  |  61.20 美元/GB  | 


**示例：日志监控产品**  

|   | 1 个月价格 | 12 个月价格  | 24 个月价格 | 36 个月价格 | 
| --- | --- | --- | --- | --- | 
|  基本（监控 10 台主机，监控 5 个容器）  |  100 USD  |  1000 美元  | 2000 美元  | 4000 美元 | 
|  标准（监控 20 台主机，监控 10 个容器）  |  \$1200  |  2000 美元  | 4000 美元  | 8000 美元 | 
|  专业（监控 40 台主机，监控 20 个容器）  |  400 美元  |  4000 美元  | 8000 美元  | 16,000 美元 | 
|  额外主机的每小时监控成本  | 10 美元  | 100 USD  |  \$1200 | 400 美元 | 
|  额外容器的每小时监控成本  | 10 美元  | 100 USD  |  \$1200 | 400 美元 | 

**注意**  
价格可以采用以下期限：1 个月、12 个月、24 个月或 36 个月。可以选择为产品提供这些选项中的一个或多个选项。期限对于每个维度都必须相同。  

**Example**  
例如，在您有`ReadOnlyUsers`和`AdminUsers`维度的情况下，如果您为其提供年度价格 ReadOnlyUsers，则还必须为`AdminUsers`其提供年度价格。


### 自动续订
<a name="ami-contracts-automatic-renewals"></a>

 当客户 AWS Marketplace 使用集装箱合同购买您的产品时，他们可以同意自动续订合同条款。客户继续按每月或按 1 年、2 年或 3 年期限支付权利费用。

客户可以随时修改续订设置。有关更多信息，请参阅《AWS Marketplace 买家指南》**中的[修改现有合同](https://docs.aws.amazon.com/marketplace/latest/buyerguide/buyer-container-contracts.html#modify-existing-contract)。

## 许可模式
<a name="container-LM-license-models"></a>

AWS Marketplace 与集成 AWS License Manager 支持两种许可模式：
+ [可配置许可模式](#container-LM-config-lic-model)
+ [分层许可模式](#container-LM-tiered-lic-model)

### 可配置许可模式
<a name="container-LM-config-lic-model"></a>

可配置许可证模型（也称为可量化许可证模型）在买家获得许可证后授权买家获得特定数量的资源。

您可以设置定价维度和每单位价格。然后，买家可以选择他们想要购买的资源数量。

**Example 定价维度和每单位价格**  
您可以设置定价维度（例如数据备份）和每单位价格（例如每单位 30 美元）  
买家可以选择购买 5、10 或 20 个单位。  
您的产品会跟踪和计量使用情况，以衡量消耗的资源数量。

在配置模式下，权利将通过以下两种方式之一进行计数：
+ [消耗许可证](#container-floating-lic)
+ [浮动许可证](#container-floating-lic) 

#### 消耗许可证
<a name="container-drawndown-lic"></a>

 在使用时，许可证是从允许数量的许可证池中消耗。该权利已永久签出，无法返回到许可证池。

**Example 处理有限数量的数据示例**  
用户有权处理 500 GB 的数据。当他们继续处理数据时，系统会从 500 GB 的池中消耗数量，直到所有 500 GB 的许可证都用完为止。

对于扣除许可证，您可以使用 `CheckoutLicense` API 操作来查看已消耗的许可证单位（权利）。

**Example 每年备份到 Amazon S3 的多个单位示例**  
您拥有的存储产品允许将多达 1,024 个单位的数据备份到 Amazon Simple Storage Service 中以存储一年的数据。您的应用程序可以通过使用多个 Amazon EC2 实例启动。您的应用程序具有跟踪和聚合数据的机制。您的软件在每次备份时或以固定的时间间隔使用产品 ID 调用 `CheckoutLicense` API 操作来更新消耗的数量。  
在此示例中，您的软件调用 `CheckoutLicense` API 操作以签出 10 个单位的数据。当总容量达到客户购买的备份限制时，API 调用失败。

**请求**

```
linux-machine ~]$ aws license-manager checkout-license\
--product-sku "2205b290-19e6-4c76-9eea-377d6bf7la47" \
--checkout-type "PERPETUAL" \
--key-fingerprint "aws:294406891311:AWS/Marketplace:issuer-fingerprint" \
--entitlements "Name=DataConsumption, Value=l0, Unit=Count" \
--client-token "AKIAIOSFODNN7EXAMPLE"
```

**响应**

```
{"CheckoutType": "PERPETUAL",
"EntitlementsAllowed": [{
"Name": "IntermediateTier",
"Units": "None"
}],
"Expiration": "2021-04-22Tl9:02:36",
"IssuedAt": "2021-04-22Tl8:02:36",
"LicenseArn": "arn:aws:license-manager::294406891311:license:l-16bf01b...",
"LicenseConsumptionToken": "AKIAIOSFODNN7EXAMPLE"
}
```

#### 浮动许可证
<a name="container-floating-lic"></a>

 使用后，许可证将返回到允许数量的许可证池中。

对于浮动许可证，在使用资源时，应用程序使用 `CheckoutLicense` API 操作从权利池中签出权利。`CheckoutLicense` API 操作的响应包括许可证消耗令牌，该令牌是签出的唯一标识符。许可证消耗令牌可用于对已签出的权利执行其他操作，例如将其签回许可证池或延长签出时间。

在不再使用资源时，要将权利签回池中，请使用 `CheckInLicense` API 操作。

```
aws license-manager check-in-license --license-consumption-token "f1603b3c1f574b7284db84..."
```

如果未能签入权利（比如应用程序崩溃），则权利将在 60 分钟后自动签回池中。如果资源的使用时间超过 60 分钟，则只要资源在使用中，最佳实操是使用 `ExtendLicenseConsumption` API 操作保持权利从池中签出。

```
aws license-manager extend-license-consumption --license-consumption-token "f1603b3c1f574b7284..."
```

**Example 固定上限的用户数示例**  
某用户被授予应用程序 500 个并发用户的权利。当用户登录和注销时，用户会被消耗并返回到 500 个用户的池中。但是，应用程序无法从池中消耗超过 500 个用户，因为 500 个并发用户是固定上限。

对于浮动权利，您可以使用 `CheckInLicense` API 操作将许可证单位返回到权利池。

**Example 一年的并发用户数示例**  
您的产品根据并发用户数量定价。客户为 10 个用户购买了一份为期一年的许可证。客户通过提供 AWS Identity and Access Management (IAM) 权限来启动软件。当用户登录时，您的应用程序会调用 `CheckoutLicense` API 操作将数量减少 1。当用户注销时，应用程序会通过调用 `CheckInLicense` API 操作将该许可证返回到池中。如果您不调用 `CheckInLicense`，则许可证单位将在 1 小时后自动签入。

**注意**  
在以下请求中，`key-fingerprint` 不是占位符值，而是发布所有许可证时使用的指纹的实际值。

**请求**

```
aws license-manager checkout-license\
--product-sku "2205b290-19e6-4c76-9eea-377d6bf7la47" \
--checkout-type "PROVISIONAL" \
--key-fingerprint "aws:294406891311:AWS/Marketplace:issuer-fingerprint" \
--entitlements "Name=ReadOnlyUSers, Value=l0, Unit=Count" \
--client-token "AKIAIOSFODNN7EXAMPLE"
```

**响应**

```
{
  "CheckoutType": "PROVISIONAL",
  "EntitlementsAllowed": [
    {
      "Name": "ReadOnlyUsers", 
      "Count": 10,
      "Units": "Count",
      "Value": "Enabled"
    }
},
  "Expiration": "2021-04-22Tl9:02: 36",
  "IssuedAt": "2021-04-22Tl8:02:36",
  "LicenseArn": "arn:aws:license-manager::294406891311:license:l-16bf01b...",
  "LicenseConsumptionToken": "AKIAIOSFODNN7EXAMPLE"
}
```

### 分层许可模式
<a name="container-LM-tiered-lic-model"></a>

分层许可模式在买家获得许可后，使买家有权使用特定级别或层的应用程序特征。

您可以为产品创建层，例如基本、中级和高级。然后，买家选择一个预定义的层。

应用程序无需跟踪或计量应用程序的使用情况。

在分层许可模式下，权利不计算在内，而是表示客户购买的服务层。

如果您想提供捆绑特征，则最好使用层。

**Example 基本、中级和高级层**  
客户可以签署软件三个可能层之一的合同：基础、中级或高级。每个层都有自己的定价。您的软件可以通过调用 `CheckoutLicense` API 操作并在请求中指定所有可能的层来识别客户已注册的层。  
请求响应包含与客户购买的层相对应的权利。基于这些信息，该软件可以预置适当的客户体验。

#### 请求
<a name="container-LM-tiered-request"></a>

```
linux-machine  ~]$ aws  license-manager   checkout-license\
--product-sku  "2205b290-19e6-4c76-9eea-377d6bf7la47"  \
--checkout-type  "PROVISIONAL"  \
--key-fingerprint  "aws:294406891311:AWS/Marketplace:issuer-fingerprint" \
--entitlements  "Name=BasicTier,  Unit=None"   "Name=IntermediateTier,  Unit=None"	\ "Name=PremiumTier, Unit=None"
```

#### 响应
<a name="container-LM-tiered-response"></a>

```
{
  "CheckoutType": "PROVISIONAL",
  "EntitlementsAllowed": [
    {
      "Name": "IntermediateTier", 
      "Units": "None"
    }
},
  "Expiration": "2021-04-22Tl9:02:36",
  "IssuedAt": "2021-04-22Tl8:02:36",
  "LicenseArn": "arn:aws:license-manager::294406891311:license:l-16bf01b...",
  "LicenseConsumptionToken": "AKIAIOSFODNN7EXAMPLE"
}
```

## AWS License Manager 集成先决条件
<a name="container-LM-prereqs"></a>

发布产品之前，您必须首先完成以下操作：

1. 在中创建新的容器产品 AWS Marketplace 管理门户，并记下其产品代码。

   有关更多信息，请参阅 [概述：创建容器产品](container-product-getting-started.md#create-container-product)。

1. 将用于运行应用程序的任务或容器组 (pod) 的IAM 角色与调用 `CheckoutLicense`、`ExtendLicenseConsumption` 和 `CheckInLicense` API 操作所需的 IAM 权限搭配使用。

   以下 IAM 策略中详细介绍了所需的 IAM 权限。

------
#### [ JSON ]

****  

   ```
   {
      "Version":"2012-10-17",		 	 	 
      "Statement":[
         {
            "Sid":"VisualEditorO",
            "Effect":"Allow",
            "Action":[
               "license-manager:CheckoutLicense",
               "license-manager:GetLicense",
               "license-manager:CheckInLicense",
               "license-manager:ExtendLicenseConsumption",
               "license-manager:ListReceivedLicenses"
            ],
            "Resource":"*"
         }
      ]
   }
   ```

------

1. 使用您定义的所有定价维度的记录对 `RegisterUsage` API 操作进行测试调用。

## 将容器产品与 License Manager 集成
<a name="container-integrate-with-LM"></a>

**要将基于容器的产品与 License Manager 集成，请执行以下操作：**

1. 设置 IAM 权限以调用 License Manager。有关更多信息，请参阅 [AWS License Manager 集成先决条件](#container-LM-prereqs)。

1. 下载 AWS 软件开发工具包。
**注意**  
不要在软件中配置 AWS 凭据。 AWS 当您的容器在 Amazon EC2 实例、亚马逊 ECS 任务或 Amazon EKS 容器中运行时，系统会在运行时自动获取买家证书。

1. 向您的产品添加许可证检查。

   无论在何处执行许可证检查，您的产品都可以调用 `CheckoutLicense` API 操作。要查看许可证，您的产品必须了解：

   1. 许可证的可信颁发者 (AWS Marketplace)

   1. 应用程序的产品 SKU（产品 ID）

   1. 查看此应用程序的权利

   根据您设置的定价许可证类型，API 调用会有所不同。

1. 在上发布您的产品清单 AWS Marketplace。

## License Manager API 操作
<a name="container-LM-API-calls"></a>

要管理存储在客户 License Manager 帐户中的许可证，您的软件可以使用以下 API 操作：
+ `GetLicense` – 软件可以查询的 API。它会检索已购买许可证的状态（即已过期或即将到期），并向客户发送状态通知。
+ `CheckoutLicense` – 发现用户已购买的许可证。当用户使用了一定数量的许可证时，您还可以使用 `CheckoutLicense` API 操作来更新许可证数量。使用 `CheckoutLicense`，您可以继续签出客户使用的许可证数量。当客户耗尽所有许可证时，此调用将返回错误。有关建议运行 `CheckoutLicense` 的节奏的信息，请参阅[许可证续订和升级](#container-LM-lic-renew-upgrade)。
+ `ExtendLicenseConsumption` – 如果是浮动维度，当软件签出许可证时，许可证会在 60 分钟后自动返回到池中。如果您想延长许可证的签出时间，可以使用 `ExtendLicenseConsumption` API 操作将许可证再延长 60 分钟。
+ `CheckInLicense` – 如果是浮动维度，则要将许可证返回到权利池时，请使用 `CheckInLicense` 操作。
+ `ListReceivedLicenses` API – 列出买家购买的许可证。

## 许可证续订和升级
<a name="container-LM-lic-renew-upgrade"></a>

客户可以在 AWS Marketplace 管理门户上续订或升级许可证。在他们进行额外购买后， AWS Marketplace 会生成反映新权利的新版本的许可证。您的软件使用相同的 API 操作读取新的权利。您不必在 License Manager 集成方面执行任何不同的操作即可处理续订和升级。

由于许可证续订、升级、取消等，我们建议在使用产品时该产品按固定节奏调用 `CheckoutLicense` API 操作。通过定期使用 `CheckoutLicense` API 操作，产品可以检测权利的变化，例如升级和到期。

我们建议您每 15 分钟执行一次 `CheckoutLicense` API 调用。

# AWS Marketplace 使用 License Manager 集成 for Container
<a name="container-anywhere-license-manager-integration"></a>

作为 AWS Marketplace 卖家，您可以 AWS License Manager 与 AWS Marketplace 适用于亚马逊 EKS Anywhere、Amazon ECS Anywhere、Amazon EC2 或本地基础设施的 for Containers Anywhere 产品集成。以下部分提供了此集成的说明。

有关 License Manager 与集成的一般信息 AWS Marketplace，包括可用的许可模式，请参阅[集装箱产品的合同定价 AWS License Manager](container-license-manager-integration.md)。有关 AWS License Manager的更多信息，请参阅《[AWS License Manager 用户指南](https://docs.aws.amazon.com/license-manager/latest/userguide/license-manager.html)》和《AWS CLI 命令参考》**中的 [AWS License Manager](https://docs.aws.amazon.com/cli/latest/reference/license-manager/index.html) 部分。

**Topics**
+ [将 “容器随处 AWS Marketplace 可用” 产品与 License Manager 集成](#containers-anywhere-integrate-with-LM)
+ [在本地测试 License Manager 集成](#container-testing-LM-integration-locally)
+ [在 Amazon EKS 上测试 License Manager 集成](#container-testing-LM-integration-EKS)
+ [使用 License Manager 管理浮动许可证特权](#container-LM-floating-license)
+ [针对本地部署集成 License Manager 的最佳实操](#container-LM-best-practices-on-prem)
+ [`LicenseManagerCredentialsProvider` - Java 实现。](#container-license-manager-cred-provider-java)
+ [`LicenseManagerCredentialsProvider` - `Golang` 实现。](#container-license-manager-cred-provider-golang)

## 将 “容器随处 AWS Marketplace 可用” 产品与 License Manager 集成
<a name="containers-anywhere-integrate-with-LM"></a>

按照以下说明将你的 for Container AWS Marketplace s Anywhere 产品与集成 AWS License Manager。

**将 for Container AWS Marketplace s Anywhere 产品与 License M**

1. 打开 Web 浏览器并登录 [AWS Marketplace 管理门户](https://aws.amazon.com/marketplace/management/)。

1. 执行以下步骤，为您的容器产品创建产品 ID。您将在容器映像中使用此 ID，以便在后续步骤中进行许可证检查。

   1. 从菜单栏中，展开**资产**，然后选择**容器**。

   1. 为您的产品输入面向客户的名称，然后选择**创建**。您以后可以更改此名称。

   1. 记下**产品 ID**。之后在创建或更新产品定价详情时会使用它。
**提示**  
如果您丢失了产品 ID，则可以从 “**资产**” 菜单中选择 “容器”，在 “**容器**” 中找到它。 AWS Marketplace 管理门户 **容**器页面显示您的产品及其关联产品的列表 IDs。

1. 下载最新的公共 AWS SDK，然后将其安装到您的容器应用程序中。您可以在 [AWS 上构建的 AWS 工具中找到首选软件开发工具包的](https://aws.amazon.com/tools/)安装说明。
**注意**  
要从 Amazon EKS Anywhere 或未提供的 Kubernetes 集群调用 License Manager API 操作 AWS，您必须使用支持的软件开发工具包。 AWS 要查看支持的列表 AWS SDKs，请参阅[使用支持的 AWS SDK](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html)。

1. 使用自定义凭证提供程序创建 AWS License Manager 客户端，以便它可以为部署在 AWS 本地和本地的容器应用程序提供凭证。有关自定义凭证提供程序的完整源代码 `LicenseCredentialProvider`，请参阅以下各部分：
   + [`LicenseManagerCredentialsProvider` - Java 实现。](#container-license-manager-cred-provider-java)
   + [`LicenseManagerCredentialsProvider` - `Golang` 实现。](#container-license-manager-cred-provider-golang)

    `LicenseCredentialsProvider`通过添加`LicenseManagerTokenCredentialsProvider`扩展 AWS SDK 的默认凭证提供程序链，以供本地使用。这通过在本地环境中使用 License Manager OIDC 颁发的身份令牌来提供凭证。必须在应用程序类路径中包含 `LicenseCredentialsProvider` 的源代码。
**注意**  
扩展`DefaultCredentialsProvider`允许同一个容器应用程序在本地环境中运行 AWS 和在本地环境中运行时获得证书。如果容器应用程序已经使用自定义凭证提供程序链而不是默认凭证提供程序链，则也可以通过将 `LicenseManagerTokenCredentialsProvider` 添加到自定义链中来进行扩展。

   以下代码片段是使用 Java 创建 AWS License Manager 客户端的示例。

   ```
   LicenseManagerClientBuilder clientBuilder = LicenseManagerClient.builder().credentialsProvider(LicenseCredentialsProvider.create());
   ```

1. 使用产品中每个付费容器映像中的 `aws license-manager checkout-license` 命令调用 `CheckoutLicense` API 操作。这将检查买家是否有权使用您的应用程序许可证。如果买家有权使用该应用程序，则 `CheckoutLicense` 成功并返回所请求的权利及其值。如果买家无权使用该应用程序，则 `CheckoutLicense` 会抛出异常。

   调用 `CheckoutLicense` API 操作时需要以下参数：
   + `CheckoutType` – 有效值为 `PROVISIONAL` 或 `PERPETUAL`。
     + `PERPETUAL` – 当已签出的权利池中的权利数量将用尽时使用。

       示例：买家有权处理 500 GB 的数据。当他们继续处理数据时，会消耗 500 GB 池的数量并将其耗尽。
     + `PROVISIONAL` 用于浮动许可证权利，其中权利从池中签出，并在使用后返回。

       示例：某用户被授予应用程序 500 个并发用户的权利。当用户登录或注销时，用户会被消耗或返回到 500 个用户的池中。要了解有关浮动许可证权利的更多信息，请参阅[使用 License Manager 管理浮动许可证特权](#container-LM-floating-license)。
   + `ClientToken` – 区分大小写的唯一标识符。我们建议为每个唯一请求使用随机的 UUID。
   + `Entitlements` – 待签出的权利列表。
     + 对于特征权利，请按以下方式提供 `Name` 和 `Unit` 属性。

       ```
       {
         "Name": "<Entitlement_Name>",
         "Unit": "None"
       }
       ```
     + 对于技术特权，请按以下方式提供 `Name`、`Unit` 和 `Count` 属性。

       ```
       {
         "Name": "<Entitlement_Name>",
         "Unit": "<Entitlement_Unit>",
         "Value": <Desired_Count>
       }
       ```
   + `KeyFingerprint` – 由 AWS Marketplace 颁发的许可证的密钥指纹是 `aws:294406891311:AWS/Marketplace:issuer-fingerprint`。使用此密钥指纹可确保许可证由不可靠的实体颁发 AWS Marketplace ，而不是由不可靠的实体颁发。
   + `ProductSKU`— 在之前的步骤 AWS Marketplace 管理门户 中生成的产品 ID。

   以下代码段是通过 AWS CLI使用 `CheckoutLicense` API 操作进行调用的示例。

   ```
   aws license-manager checkout-license \
   --product-sku "2205b290-19e6-4c76-9eea-377d6bf71a47" \
   --checkout-type "PROVISIONAL" \
   --client-token "79464194dca9429698cc774587a603a1" \
   --entitlements "Name=AWS::Marketplace::Usage/Drawdown/DataConsumption, Value=10, Unit=Gigabytes" \
   --key-fingerprint "aws:294406891311:AWS/Marketplace:issuer-fingerprint"
   ```
**注意**  
要查看许可证，容器应用程序需要出站网络访问权限才能使用 License Manager。部署在本地的应用程序可能会遇到出站网络访问不可靠或缓慢的情况。这些应用程序在调用 License Manager 时应包括够的重试次数。有关更多信息，请参阅 [针对本地部署集成 License Manager 的最佳实操](#container-LM-best-practices-on-prem)。

1. 定期调用 `CheckoutLicense` API 操作，以确定由于在 AWS Marketplace上进行续订、升级或取消而对客户的许可证进行的任何更改。调用频率取决于应用程序。我们建议每天检查一次许可证，以便在没有任何买家干预的情况下自动获取更改。

   部署在本地的应用程序可能具有不可靠的出站网络访问权限，无法定期检查许可证。在这种情况下，应用程序应使用缓存许可证以获得足够的弹性。有关更多信息，请参阅 [针对本地部署集成 License Manager 的最佳实操](#container-LM-best-practices-on-prem)。

1. 将 `CheckoutLicense` 调用与容器应用程序集成后，基于更改生成 Docker 容器映像的新版本。

1. 更新应用程序的 Helm 图表以接受 Kubernetes 密钥作为可选输入，其中包含使用许可证管理器访问许可证的配置。 APIs配置密钥将包含由 License Manager 颁发的身份令牌和一个 AWS Identity and Access Management 角色，前面描述的自定义凭据提供者将使用该角色来获取在本地部署容器应用程序 APIs 时调用 License Manager 的 AWS 凭证。另外，将 AWS 区域 作为输入添加，默认值为 `us-east-1`。

   在本地部署容器应用程序的买家可以通过容器产品的购买者体验创建 Kubernetes 秘密。 AWS Marketplace 提供 Kubernetes 密钥名称作为 `helm install` 命令的输入。配置密钥采用以下格式配置。

   ```
   apiVersion: v1
   kind: Secret
   metadata:
     name: aws-marketplace-license-config
   type: Opaque
   stringData:
     license_token: <token_value> // License Manager issued JWT token
     iam_role: <role_arn> // AWS Identity and Access Management role to assume with license token
   ```

1. 更新集成的容器镜像的 Helm 图表中的应用程序部署模板， AWS License Manager 使其包含以下内容：
   + 容器组 (pod) 的服务账户 – 在 Amazon EKS 上部署 Helm 需要服务账户。它用于通过在容器映像上为服务账号设置 IAM 角色来获得调用 License Manager API 操作的权限。有关服务账户的 IAM 角色的更多信息，请参阅[服务账户的 IAM 角色](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)。
   + 本地部署的许可证访问权限 – 需要许可证配置密钥才能提供凭证和相应权限，以便调用 License Manager API 操作，在本地环境中进行 Helm 部署。买家将根据 AWS Marketplace 买家体验生成许可证秘密并将其提供给 Helm。

   以下代码片段是示例部署规范，其中包含服务帐号、许可证配置和映像拉取密钥。

   ```
   apiVersion: apps/v1
   kind: Deployment
   metadata:
     name: example-app
   spec:
     replicas: 1
     selector:
       matchLabels:
         app: example-app
     template:
       metadata:
         labels:
           app: example-app
   spec:
         // Service account for pod
         serviceAccountName: {{ .Values.serviceAccountName }}
         containers:
           - name: example-app
             image: example-app
             ports:
               - containerPort: 8001
   // Add the following conditional attributes
   {{ - if .Values.awsmp.licenseConfigSecretName }}
             //Mount the license volume to the container image
             volumeMounts:
               - name: awsmp-product-license
                 mountPath: "/var/run/secrets/product-license"
             //Add following environment variable to container for credential
   provider
             env:
               - name: AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE
                 value: "/var/run/secrets/product-license/license_token"
               - name: AWS_ROLE_ARN
                   valueFrom:
                       secretKeyRef:
                       name: {{ .Values.aws.licenseConfigSecretName }}
                       key: iam_role
         //Mount the license secret as a volume to the pod
         volumes:
           - name: awsmp-product-license
             secret:
               secretName: {{ .Values.aws.licenseConfigSecretName }}
               optional: true
   {{ - end }}
   ```
**注意**  
许可证配置密钥是可选的。买家仅在本地部署时使用该值。对于 AWS 部署，部署规范必须包含 License Manager 集成映像的服务帐户。

1. 通过执行以下各部分中的步骤，在本地和 Amazon EKS 上测试 License Manager 集成：

   1. [在本地测试 License Manager 集成](#container-testing-LM-integration-locally)

   1. [在 Amazon EKS 上测试 License Manager 集成](#container-testing-LM-integration-EKS)

1. 成功验证本地 AWS 和本地的 License Manager 集成后，您可以按照中的步骤创建容器产品清单[概述：创建容器产品](container-product-getting-started.md#create-container-product)。

## 在本地测试 License Manager 集成
<a name="container-testing-LM-integration-locally"></a>

你可以使用 minikube 或任何其他设置来在本地测试任何 Kubernetes 集群上的 License Manager 集成。确保 Kubernetes 集群具有出站互联网访问权限以调用 License Manager API 操作。

**要在本地测试 License Manager 集成，请执行以下操作：**

1. 使用所需权利在测试卖家账户中创建测试许可证。要设置测试许可证，请参阅 *AWS License Manager API 参考[CreateLicense](https://docs.aws.amazon.com/license-manager/latest/APIReference/API_CreateLicense.html)*中的。或者，使用以下脚本创建测试许可证，然后为测试买家账户创建许可授权，以使用该许可证。以下脚本使用测试卖家账户凭证。

   ```
   read -p 'AWS Account for test buyer: ' TEST_BUYER_ACCOUNT_ID
   read -p 'License entitlements: ' ENTITLEMENTS
   
   # TEST_SELLER_ACCOUNT_ID="109876543210"
   # ENTITLEMENTS="{\"Name\": \"ByData\",\"MaxCount\": 1000,\"Overage\":true,\"Unit\": \"Gigabits\",\"AllowCheckIn\": true}"
   
   # Create License
   
   NOW=$(date +"%Y-%m-%dT00:00:00+00:00")
   
   PRODUCT_NAME="My awesome product"
   PRODUCT_SKU="c97b7825-44c4-4f42-b025-12baa4c171e0"
   
   LICENSE_BENEFICIARY=" arn:aws:iam::$TEST_BUYER_ACCOUNT_ID:root "
   LICENSE_ISSUER_NAME="test-seller"
   LICENSE_NAME="test-seller-license"
   
   CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367"
   CONSUMPTION_TTL=180
   CONSUMPTION_RENEW_TYPE="None"
   
   HOME_REGION="us-east-1"
   
   LICENSE_ARN=$(aws license-manager create-license --license-name "$LICENSE_NAME" --product-name "$PRODUCT_NAME" --product-sku "$PRODUCT_SKU" --issuer Name="$LICENSE_ISSUER_NAME" --home-region "$HOME_REGION" --validity Begin="$NOW" --entitlements "$ENTITLEMENTS" --beneficiary "$LICENSE_BENEFICIARY" --consumption-configuration RenewType="$CONSUMPTION_RENEW_TYPE",ProvisionalConfiguration={MaxTimeToLiveInMinutes=$CONSUMPTION_TTL} --client-token "$CLIENT_TOKEN" | jq -r ".LicenseArn" )
   
   echo "License arn: $LICENSE_ARN"
   
   # Create Grant
   
   GRANT_TOKEN="e9a14140-4fca-4219-8230-57511a6ea6"
   GRANT_NAME="test-grant"
   
   GRANT_ARN=$(aws license-manager create-grant --grant-name "$GRANT_NAME" --license-arn "$LICENSE_ARN" --principals "$LICENSE_BENEFICIARY" --home-region "$HOME_REGION" --client-token "$GRANT_TOKEN" --allowed-operations "CheckoutLicense" "CheckInLicense" "ExtendConsumptionLicense" "CreateToken" | jq -r ".GrantArn")
   
   echo "Grant arn: $GRANT_ARN"
   ```

1. 采用之前定义的密钥格式，使用许可证令牌和 IAM 角色创建 Kubernetes 密钥。使用 License Manager `CreateToken` API 操作生成许可证令牌。然后，使用 IAM `CreateRole` API 操作创建具有权限和信任策略的 IAM 角色。请参阅以下示例脚本中的示例。以下脚本使用测试买家账户凭证。

   ```
   read -p 'AWS Account for test license: ' TEST_ACCOUNT_ID
   read -p 'License Arn' LICENSE_ARN
   # Create IAM Role
   ROLE_NAME="AWSLicenseManagerConsumptionTestRole"
   ROLE_DESCRIPTION="Role to test AWS License Manager integration on-prem"
   ROLE_POLICY_ARN="arn:aws:iam::aws:policy/service-role/AWSLicenseManagerConsumptionPolicy"
   ROLE_TRUST_POLICY="{\"Version\": \"2012-10-17\",\"Statement\": [{ \"Effect\":\"Allow\", \"Principal\": { \"Federated\": \"openid-license-manager.amazonaws.com\" }, \"Action\": \"sts:AssumeRoleWithWebIdentity\",\"Condition\": { \"ForAnyValue:StringLike\": { \"openid-license-manager.amazonaws.com:amr\": \"aws:license-manager:token-issuer-account-id:${TEST_ACCOUNT_ID}\" }}}]}"
   ROLE_SESSION_DURATION=3600
   
   ROLE_ARN=$(aws iam create-role --role-name "$ROLE_NAME" --description "$ROLE_DESCRIPTION" --assume-role-policy-document "$ROLE_TRUST_POLICY" --max-session-duration $ROLE_SESSION_DURATION | jq ".Role" | jq -r ".Arn")
   
   aws iam attach-role-policy --role-name "$ROLE_NAME" --policy-arn "$ROLE_POLICY_ARN"
   
   echo "Role arn: $ROLE_ARN"
   
   # Create Token
   CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367"
   
   TOKEN=$(aws license-manager create-token --license-arn $LICENSE_ARN --role-arns $ROLE_ARN --client-token $CLIENT_TOKEN | jq '.Token')
   
   echo "License access token: $TOKEN"c
   ```

1. 设置托管在外部的任何 Kubernetes 集群。 AWS使用它来测试容器应用程序是否可以从其他环境连接到 AWS License Manager API， AWS 以及自定义凭证提供程序是否已很好地集成到应用程序中。

1. 将之前生成的许可证令牌和 IAM 角色部署到本地 Kubernetes 集群中。

   ```
   kubectl create secret generic "awsmp-license-access-config" \
   --from-literal=license_token=${TOKEN} \
   --from-literal=iam_role=${ROLE_ARN}
   ```

1. 使用密钥名称作为输入通过 Helm 部署应用程序，并验证应用程序是否可以调用 License Manager API 操作来执行权利检查。有关 Helm 和部署规范的更改，请参阅[将 “容器随处 AWS Marketplace 可用” 产品与 License Manager 集成](#containers-anywhere-integrate-with-LM)中的步骤 9。

## 在 Amazon EKS 上测试 License Manager 集成
<a name="container-testing-LM-integration-EKS"></a>

您还可以在 Amazon EKS 上测试 License Manager 集成。测试以确保应用程序可以在没有许可证配置密钥的情况下调用 License Manager API 操作。此外，请确保服务账户可用于设置服务账户的 IAM 角色 (IRSA)，并为应用程序提供相关凭证。

**要在 Amazon EKS 上测试 License Manager 集成，请执行以下操作：**

1. 使用所需权利在测试卖家账户中创建测试许可证。请参阅 [CreateLicense API 参考](https://docs.aws.amazon.com/license-manager/latest/APIReference/API_CreateLicense.html)来设置您的测试许可证，或者使用以下脚本创建测试许可证，然后向测试买家账户创建使用许可证的许可授权。以下脚本使用测试卖家账户凭证。

   ```
   read -p 'AWS Account for test buyer: ' TEST_BUYER_ACCOUNT_ID
   read -p 'License entitlements: ' ENTITLEMENTS
   
   # TEST_SELLER_ACCOUNT_ID="109876543210"
   # ENTITLEMENTS="{\"Name\": \"ByData\",\"MaxCount\": 1000,\"Overage\": true,\"Unit\": \"Gigabits\",\"AllowCheckIn\": true}"
   
   # Create License
   
   NOW=$(date +"%Y-%m-%dT00:00:00+00:00")
   
   PRODUCT_NAME="My awesome product"
   PRODUCT_SKU="c97b7825-44c4-4f42-b025-12baa4c171e0"
   
   LICENSE_BENEFICIARY=" arn:aws:iam::$TEST_BUYER_ACCOUNT_ID:root "
   LICENSE_ISSUER_NAME="test-seller"
   LICENSE_NAME="test-seller-license"
   
   CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367"
   CONSUMPTION_TTL=180
   CONSUMPTION_RENEW_TYPE="None"
   
   HOME_REGION="us-east-1"
   
   LICENSE_ARN=$(aws license-manager create-license --license-name "$LICENSE_NAME" --product-name "$PRODUCT_NAME" --product-sku "$PRODUCT_SKU" --issuer Name="$LICENSE_ISSUER_NAME" --home-region "$HOME_REGION" --validity Begin="$NOW" --entitlements "$ENTITLEMENTS" --beneficiary "$LICENSE_BENEFICIARY" --consumption-configuration RenewType="$CONSUMPTION_RENEW_TYPE",ProvisionalConfiguration={MaxTimeToLiveInMinutes=$CONSUMPTION_TTL} --client-token "$CLIENT_TOKEN" | jq -r ".LicenseArn" )
   
   echo "License arn: $LICENSE_ARN"
   
   # Create Grant
   
   GRANT_TOKEN="e9a14140-4fca-4219-8230-57511a6ea6"
   GRANT_NAME="test-grant"
   
   GRANT_ARN=$(aws license-manager create-grant --grant-name "$GRANT_NAME" --license-arn "$LICENSE_ARN" --principals "$LICENSE_BENEFICIARY" --home-region "$HOME_REGION" --client-token "$GRANT_TOKEN" --allowed-operations "CheckoutLicense" "CheckInLicense" "ExtendConsumptionLicense" "CreateToken" | jq -r ".GrantArn")
   
   echo "Grant arn: $GRANT_ARN"
   ```

1. 创建包含所需配置的测试 Amazon EKS 集群，或运行以下命令以使用默认配置。

   ```
   aws ec2 create-key-pair --region us-west-2 --key-name eks-key-pair
   ```

   ```
   eksctl create cluster \
   --name awsmp-eks-test-example \
   --region us-west-2 \
   --with-oidc \
   --ssh-access \
   --ssh-public-key eks-key-pair
   ```

1. 为现有集群创建服务账户并将其与 IAM 角色关联。以下命令将使用 `AWSLicenseManagerConsumptionPolicy` 创建 IAM 角色。然后，该命令将其附加到应在其中部署 License Manager 集成映像的 Amazon EKS 集群的 `test_sa` 服务账户。这样一来，服务帐号可以获得相应的凭证来调用 License Manager API 操作。

   ```
   eksctl create iamserviceaccount \
   --name test_sa \
   --namespace test_namespace \
   --cluster awsmp-eks-test-example \
   --attach-policy-arn "arn:aws:iam::aws:policy/service-role/AWSLicenseManagerConsumptionPolicy" \
   --approve \
   --override-existing-serviceaccounts
   ```

1. 在服务账户中通过 Helm 部署应用程序，该账户通过前面的命令与 IAM 角色关联。验证应用程序是否可以调用 License Manager API 操作来执行权利检查。

## 使用 License Manager 管理浮动许可证特权
<a name="container-LM-floating-license"></a>

使用浮动许可证，当用户登录应用程序时，将从可用许可证池中消耗许可证。用户注销后，许可证将重新添加到可用许可证池中。

对于浮动许可证，应用程序使用 `CheckoutLicense` API 操作在使用资源时从权利池中签出权利。`CheckoutLicense` API 操作的响应包括许可证消耗令牌，该令牌是签出的唯一标识符。许可证消耗令牌可以对已签出的权利执行其他操作，例如将其签回许可证池或延长签出时间。

当资源不再使用时，应用程序会使用 `CheckInLicense` API 操作将权利签回池中。

```
aws license-manager check-in-license \
--license-consumption-token "f1603b3c1f574b7284db84a9e771ee12"
```

如果许可证未能签回池中，例如，如果应用程序在操作过程中崩溃，则权利将在 60 分钟后自动签回池中。因此，如果资源的使用时间超过 60 分钟，则最佳实操是将权利保留在资源池之外。为此，只要资源正在使用，就可以使用 `ExtendLicenseConsumption` API 操作。

```
aws license-manager extend-license-consumption \
--license-consumption-token "f1603b3c1f574b7284db84a9e771ee12"
```

## 针对本地部署集成 License Manager 的最佳实操
<a name="container-LM-best-practices-on-prem"></a>

本地环境中的容器应用程序部署可能会遇到不可靠的出站网络访问。使用以下最佳实操来增加弹性，以避免因互联网连接不畅导致的潜在问题而导致买家的服务中断：
+ **充分重试** — 临时网络问题可能会使您的应用程序无法连接到。 AWS License Manager实现最长 30 分钟的重试，并关闭指数级回退。这可以帮助避免短期中断或网络问题。
+ **避免硬限制** – 部署在联网集群中的应用程序可以定期检查许可证，以确定由于升级或续订而导致的任何更改。由于出站访问不可靠，应用程序可能无法识别这些更改。应用程序应尽可能避免因无法通过 License Manager 检查许可证而中断向买家提供的服务。当许可证到期时，应用程序可能会依赖免费试用或开源体验，并且无法检查许可证是否有效。
+ **通知客户** - 使用缓存许可证时，许可证的任何更改（包括续订或升级）都不会自动反映在正在运行的工作负载上。通知您的客户（他们必须暂时允许对应用程序进行出站访问），以便应用程序可以更新其缓存的许可证。例如，通过应用程序本身或通过其文档通知客户。同样，当回退到较低的功能集时，请通知客户其权利已用尽或许可证已过期。然后，他们可以选择升级或续订。

## `LicenseManagerCredentialsProvider` - Java 实现。
<a name="container-license-manager-cred-provider-java"></a>

`LicenseCredentialsProvider`通过添加`LicenseManagerTokenCredentialsProvider`扩展 AWS SDK 的默认凭证提供程序链，以供本地使用。

**`LicenseCredentialsProvider`**

```
package com.amazon.awsmp.license;

import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.internal.LazyAwsCredentialsProvider;
import software.amazon.awssdk.utils.SdkAutoCloseable;

public class LicenseCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable {
    private static final LicenseCredentialsProvider CREDENTIALS_PROVIDER = new LicenseCredentialsProvider();
    private final LazyAwsCredentialsProvider providerChain;

    private LicenseCredentialsProvider() {
        this.providerChain = createChain();
    }

    public static LicenseCredentialsProvider create() {
        return CREDENTIALS_PROVIDER;
    }

    @Override
    public AwsCredentials resolveCredentials() {
        return this.providerChain.resolveCredentials();
    }

    @Override
    public void close() {
        this.providerChain.close();
    }

    private LazyAwsCredentialsProvider createChain() {
        return LazyAwsCredentialsProvider.create(() -> {
            AwsCredentialsProvider[] credentialsProviders = new AwsCredentialsProvider[]{
                    DefaultCredentialsProvider.create(),
                    LicenseManagerTokenCredentialsProvider.create()};

            return AwsCredentialsProviderChain.builder().reuseLastProviderEnabled(true)
                    .credentialsProviders(credentialsProviders).build();
        });
    }
}
```

**`LicenseManagerTokenCredentialsProvider`**

`LicenseManagerTokenCredentialsProvider` 通过在本地环境中使用 License Manager OIDC 颁发的身份令牌来提供凭证。必须在应用程序类路径中包含 `LicenseCredentialsProvider` 的源代码。

```
package com.amazon.awsmp.license;

import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.retry.RetryPolicyContext;
import software.amazon.awssdk.core.retry.conditions.OrRetryCondition;
import software.amazon.awssdk.core.retry.conditions.RetryCondition;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
import software.amazon.awssdk.services.licensemanager.LicenseManagerClient;
import software.amazon.awssdk.services.licensemanager.model.GetAccessTokenRequest;
import software.amazon.awssdk.services.licensemanager.model.GetAccessTokenResponse;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithWebIdentityCredentialsProvider;
import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest;
import software.amazon.awssdk.services.sts.model.IdpCommunicationErrorException;
import software.amazon.awssdk.utils.IoUtils;
import software.amazon.awssdk.utils.SdkAutoCloseable;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.SystemSetting;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.function.Supplier;

public class LicenseManagerTokenCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable {

    private final StsAssumeRoleWithWebIdentityCredentialsProvider credentialsProvider;
    private final RuntimeException loadException;

    private Path licenseAccessTokenFile;
    private String roleArn;
    private String roleSessionName;
    private StsClient stsClient;
    private LicenseManagerClient lmClient;

    public static LicenseManagerTokenCredentialsProvider create() {
        return new Builder().build();
    }

    @Override
    public AwsCredentials resolveCredentials() {
        if (this.loadException != null) {
            throw this.loadException;
        }
        return this.credentialsProvider.resolveCredentials();
    }

    @Override
    public void close() {
        IoUtils.closeQuietly(this.credentialsProvider, null);
        IoUtils.closeQuietly(this.stsClient, null);
        IoUtils.closeIfCloseable(this.lmClient, null);
    }

    private LicenseManagerTokenCredentialsProvider(Builder builder) {
        StsAssumeRoleWithWebIdentityCredentialsProvider credentialsProvider = null;
        RuntimeException loadException = null;

        try {
            this.licenseAccessTokenFile = Paths.get(StringUtils.trim(LicenseSystemSetting.AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE.getStringValueOrThrow()));
            this.roleArn = SdkSystemSetting.AWS_ROLE_ARN.getStringValueOrThrow();
            this.roleSessionName = SdkSystemSetting.AWS_ROLE_SESSION_NAME.getStringValue().orElse("aws-sdk-java-" + System.currentTimeMillis());
            this.stsClient = builder.stsClient != null ? builder.stsClient : StsClientFactory.create();
            this.lmClient = builder.lmClient != null ? builder.lmClient : LicenseManagerClientFactory.create();

            AssumeRoleWithWebIdentityRequest request = AssumeRoleWithWebIdentityRequest.builder()
                    .roleArn(this.roleArn).roleSessionName(this.roleSessionName).build();

            Supplier<AssumeRoleWithWebIdentityRequest> supplier = new AssumeRoleRequestSupplier(request,
                    this.licenseAccessTokenFile, this.lmClient);

            credentialsProvider = StsAssumeRoleWithWebIdentityCredentialsProvider.builder()
                    .stsClient(this.stsClient).refreshRequest(supplier).build();
        } catch (RuntimeException ex) {
            loadException = ex;
        }

        this.credentialsProvider = credentialsProvider;
        this.loadException = loadException;
    }

    public static final class Builder {
        private Path licenseAccessTokenFile;
        private String roleArn;
        private String roleSessionName;
        private StsClient stsClient;
        private LicenseManagerClient lmClient;

        public LicenseManagerTokenCredentialsProvider build() {
            return new LicenseManagerTokenCredentialsProvider(this);
        }

        public LicenseManagerTokenCredentialsProvider.Builder licenseAccessTokenFile(Path licenseAccessTokenFile) {
            this.licenseAccessTokenFile = licenseAccessTokenFile;
            return this;
        }

        public LicenseManagerTokenCredentialsProvider.Builder roleArn(String roleArn) {
            this.roleArn = roleArn;
            return this;
        }

        public LicenseManagerTokenCredentialsProvider.Builder roleSessionName(String roleSessionName) {
            this.roleSessionName = roleSessionName;
            return this;
        }

        public LicenseManagerTokenCredentialsProvider.Builder stsClient(StsClient stsClient) {
            this.stsClient = stsClient;
            return this;
        }

        public LicenseManagerTokenCredentialsProvider.Builder lmClient(LicenseManagerClient lmClient) {
            this.lmClient = lmClient;
            return this;
        }
    }

    private static final class AssumeRoleRequestSupplier implements Supplier {
        private final LicenseManagerClient lmClient;
        private final AssumeRoleWithWebIdentityRequest request;
        private final Path webIdentityRefreshTokenFile;

        AssumeRoleRequestSupplier(final AssumeRoleWithWebIdentityRequest request,
                                                 final Path webIdentityRefreshTokenFile,
                                                 final LicenseManagerClient lmClient) {
            this.lmClient = lmClient;
            this.request = request;
            this.webIdentityRefreshTokenFile = webIdentityRefreshTokenFile;
        }

        public AssumeRoleWithWebIdentityRequest get() {
            return this.request.toBuilder()
                    .webIdentityToken(getIdentityToken())
                    .build();
        }

        private String getIdentityToken() {
            return refreshIdToken(readRefreshToken(this.webIdentityRefreshTokenFile));
        }

        private String readRefreshToken(Path file) {
            try (InputStream webIdentityRefreshTokenStream = Files.newInputStream(file)) {
                return IoUtils.toUtf8String(webIdentityRefreshTokenStream);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

        private String refreshIdToken(String licenseRefreshToken) {
            final GetAccessTokenRequest request = GetAccessTokenRequest.builder()
                    .token(licenseRefreshToken)
                    .build();

            GetAccessTokenResponse response = this.lmClient.getAccessToken(request);
            return response.accessToken();
        }
    }

    private static final class LicenseManagerClientFactory {
        private static final Duration DEFAULT_API_TIMEOUT = Duration.ofSeconds(30);
        private static final Duration DEFAULT_API_ATTEMPT_TIMEOUT = Duration.ofSeconds(10);

        public static LicenseManagerClient create() {
            return getLicenseManagerClient();
        }

        private static LicenseManagerClient getLicenseManagerClient() {
            ClientOverrideConfiguration configuration = ClientOverrideConfiguration.builder()
                    .apiCallTimeout(DEFAULT_API_TIMEOUT)
                    .apiCallAttemptTimeout(DEFAULT_API_ATTEMPT_TIMEOUT)
                    .build();

            LicenseManagerClient client = LicenseManagerClient.builder()
                    .region(configureLicenseManagerRegion())
                    .credentialsProvider(AnonymousCredentialsProvider.create())
                    .overrideConfiguration(configuration).build();
            return client;
        }

        private static Region configureLicenseManagerRegion() {
            Region defaultRegion = Region.US_EAST_1;

            Region region;
            try {
                region = (new DefaultAwsRegionProviderChain()).getRegion();
            } catch (RuntimeException ex) {
                region = defaultRegion;
            }
            return region;
        }
    }

    private static final class StsClientFactory {
        private static final Duration DEFAULT_API_TIMEOUT = Duration.ofSeconds(30);
        private static final Duration DEFAULT_API_ATTEMPT_TIMEOUT = Duration.ofSeconds(10);

        public static StsClient create() {
            return getStsClient();
        }

        private static StsClient getStsClient() {
            OrRetryCondition retryCondition = OrRetryCondition.create(new StsRetryCondition(),
                    RetryCondition.defaultRetryCondition());

            ClientOverrideConfiguration configuration = ClientOverrideConfiguration.builder()
                    .apiCallTimeout(DEFAULT_API_TIMEOUT)
                    .apiCallAttemptTimeout(DEFAULT_API_ATTEMPT_TIMEOUT)
                    .retryPolicy(r -> r.retryCondition(retryCondition))
                    .build();

            return StsClient.builder()
                    .region(configureStsRegion())
                    .credentialsProvider(AnonymousCredentialsProvider.create())
                    .overrideConfiguration(configuration).build();
        }

        private static Region configureStsRegion() {
            Region defaultRegion = Region.US_EAST_1;
            Region stsRegion;
            try {
                stsRegion = (new DefaultAwsRegionProviderChain()).getRegion();
            } catch (RuntimeException ex) {
                stsRegion = defaultRegion;
            }
            return stsRegion;
        }

        private static final class StsRetryCondition implements RetryCondition {
            public boolean shouldRetry(RetryPolicyContext context) {
                return context.exception() instanceof IdpCommunicationErrorException;
            }
        }
    }

    private enum LicenseSystemSetting implements SystemSetting {
        AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE("aws.webIdentityRefreshTokenFile");

        private String systemProperty;
        private String defaultValue = null;

        LicenseSystemSetting(String systemProperty) {
            this.systemProperty = systemProperty;
        }

        @Override
        public String property() {
            return this.systemProperty;
        }

        @Override
        public String environmentVariable() {
            return this.name();
        }

        @Override
        public String defaultValue() {
            return this.defaultValue;
        }
    }
}
```

## `LicenseManagerCredentialsProvider` - `Golang` 实现。
<a name="container-license-manager-cred-provider-golang"></a>

**`LicenseCredentialsProvider`**

`LicenseCredentialsProvider`通过添加`LicenseManagerTokenCredentialsProvider`扩展 AWS SDK 的默认凭证提供程序链，以供本地使用。

```
package lib

import (
	"context"
	"fmt"
	"sync"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
)

// LicenseCredentialsProvider is the custom credential provider that can retrieve valid temporary aws credentials
type LicenseCredentialsProvider struct {
	fallBackProvider   aws.CredentialsProvider
	mux                sync.RWMutex
	licenseCredentials aws.Credentials
	err                error
}

// NewLicenseCredentialsProvider method will create a LicenseCredentialProvider Object which contains valid temporary aws credentials
func NewLicenseCredentialsProvider() (*LicenseCredentialsProvider, error) {
	licenseCredentialProvider := &LicenseCredentialsProvider{}
	fallBackProvider, err := createCredentialProvider()
	if err != nil {
		return licenseCredentialProvider, fmt.Errorf("failed to create LicenseCredentialsProvider, %w", err)
	}
	licenseCredentialProvider.fallBackProvider = fallBackProvider
	return licenseCredentialProvider, nil
}

// Retrieve method will retrieve temporary aws credentials from the credential provider
func (l *LicenseCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
	l.mux.RLock()
	defer l.mux.RUnlock()
	l.licenseCredentials, l.err = l.fallBackProvider.Retrieve(ctx)
	return l.licenseCredentials, l.err
}

func createCredentialProvider() (aws.CredentialsProvider, error) {
	// LoadDefaultConfig will examine all "default" credential providers
	ctx := context.TODO()
	cfg, err := config.LoadDefaultConfig(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to create FallBackProvider, %w", err)
	}

	var useFallbackProvider bool
	if cfg.Credentials != nil {
		if _, err := cfg.Credentials.Retrieve(ctx); err != nil {
			// If the "default" credentials provider cannot retrieve credentials, enable fallback to customCredentialsProvider.
			useFallbackProvider = true
		}
	} else {
		useFallbackProvider = true
	}

	if useFallbackProvider {
		customProvider, err := newLicenseManagerTokenCredentialsProvider()
		if err != nil {
			return cfg.Credentials, fmt.Errorf("failed to create fallBackProvider, %w", err)
		}
		// wrap up customProvider with CredentialsCache to enable caching
		cfg.Credentials = aws.NewCredentialsCache(customProvider)
	}
	return cfg.Credentials, nil
}
```

**`LicenseManagerTokenCredentialsProvider`**

`LicenseManagerTokenCredentialsProvider` 通过在本地环境中使用 License Manager OIDC 颁发的身份令牌来提供凭证。必须在应用程序类路径中包含 `LicenseCredentialsProvider` 的源代码。

```
package lib

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/sts"
)

const awsRefreshTokenFilePathEnvVar = "AWS_LICENSE_ACCESS_FILE"

// licenseManagerTokenCredentialsProvider defines and contains StsAssumeRoleWithWebIdentityProvider
type licenseManagerTokenCredentialsProvider struct {
	stsCredentialProvider *stsAssumeRoleWithWebIdentityProvider
	mux                   sync.RWMutex
	licenseCredentials    aws.Credentials
	err                   error
}

// Retrieve method will retrieve credentials from credential provider.
// Make this method public to make this provider satisfies CredentialProvider interface
func (a *licenseManagerTokenCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
	a.mux.RLock()
	defer a.mux.RUnlock()
	a.licenseCredentials, a.err = a.stsCredentialProvider.Retrieve(ctx)
	return a.licenseCredentials, a.err
}

// newLicenseManagerTokenCredentialsProvider will create and return a LicenseManagerTokenCredentialsProvider Object which wraps up stsAssumeRoleWithWebIdentityProvider
func newLicenseManagerTokenCredentialsProvider() (*licenseManagerTokenCredentialsProvider, error) {
	// 1. Retrieve variables From yaml environment
	envConfig, err := config.NewEnvConfig()
	if err != nil {
		return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err)
	}
	roleArn := envConfig.RoleARN
	var roleSessionName string
	if envConfig.RoleSessionName == "" {
		roleSessionName = fmt.Sprintf("aws-sdk-go-v2-%v", time.Now().UnixNano())
	} else {
		roleSessionName = envConfig.RoleSessionName
	}
	tokenFilePath := os.Getenv(awsRefreshTokenFilePathEnvVar)
	b, err := ioutil.ReadFile(tokenFilePath)
	if err != nil {
		return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err)
	}
	refreshToken := aws.String(string(b))

	// 2. Create stsClient
	cfg, err := config.LoadDefaultConfig(context.TODO())
	if err != nil {
		return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err)
	}
	stsClient := sts.NewFromConfig(cfg, func(o *sts.Options) {
		o.Region = configureStsClientRegion(cfg.Region)
		o.Credentials = aws.AnonymousCredentials{}
	})

	// 3. Configure StsAssumeRoleWithWebIdentityProvider
	stsCredentialProvider := newStsAssumeRoleWithWebIdentityProvider(stsClient, roleArn, roleSessionName, refreshToken)

	// 4. Build and return
	return &licenseManagerTokenCredentialsProvider{
		stsCredentialProvider: stsCredentialProvider,
	}, nil
}

func configureStsClientRegion(configRegion string) string {
	defaultRegion := "us-east-1"
	if configRegion == "" {
		return defaultRegion
	} else {
		return configRegion
	}
}
```