

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

# 使用 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();
        }
    }
}
```