

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用 AWS Marketplace Metering Service 設定每小時計量
<a name="container-metering-registerusage"></a>

**注意**  
 對於 Amazon EKS 部署，您的軟體必須使用[服務帳戶 (IRSA) 的 IAM 角色](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)來簽署 API 操作的 [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 呼叫。不支援使用 [EKS Pod Identity](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) 角色來簽署 API 操作的 [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 呼叫。不支援使用節點角色或長期存取金鑰。

如果您的容器產品使用每小時每個任務或每個 Pod 定價，而不是自訂計量定價維度，則不需要定義自訂計量維度。您可以使用 AWS Marketplace Metering Service 搭配 中的容器產品進行每小時計量 AWS Marketplace。下列各節說明如何使用 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 與容器產品的詳細資訊，請參閱 [與賣方研討會的每小時計量實驗室整合](https://catalog.workshops.aws/mpseller/en-US/container/integrate-hourly)。 *AWS Marketplace *

**Topics**
+ [每小時計量先決條件](#hourly-metering-prereqs)
+ [測試 的整合 `RegisterUsage`](#testing-integration-for-registerusage)
+ [的錯誤處理 `RegisterUsage`](#hourly-metering-entitlement-error-handling)
+ [使用 整合您的容器產品與 AWS Marketplace Metering Service 適用於 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. 針對具有呼叫 所需 IAM 許可的任務或執行應用程式的 Pod，使用 AWS Identity and Access Management (IAM) 角色`RegisterUsage`。IAM 受管政策`AWSMarketplaceMeteringRegisterUsage`具有這些許可。如需政策的詳細資訊，請參閱《 *AWS 受管政策參考*》中的 [ AWSMarketplaceMeteringFullAccess](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSMarketplaceMeteringFullAccess.html)。

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`時 以外的例外狀況，您應該終止容器，以防止未經授權的使用。

以外的例外`ThrottlingException`狀況只會在初始呼叫 `RegisterUsage` API 操作時擲回。`CustomerNotSubscribedException` 即使客戶在任務或 Pod 仍在執行時取消訂閱，來自相同 Amazon ECS 任務或 Amazon EKS Pod 的後續呼叫也不會擲回。這些客戶在取消訂閱並追蹤其用量後，仍需支付執行中容器的費用。

下表說明 `RegisterUsage` API 操作可能擲回的錯誤。每個 AWS SDK 程式設計語言都有一組錯誤處理準則，如需其他資訊，請參閱這些準則。


|  **錯誤**  |  **Description**  | 
| --- | --- | 
|  InternalServiceErrorException  |  RegisterUsage 無法使用。 | 
|  CustomerNotEntitledException  |  客戶沒有產品的有效訂閱。 | 
|  InvalidProductCodeException  |  在請求中傳入ProductCode的值不存在。 | 
|  InvalidPublicKeyException  |  在請求中傳入PublicKeyVersion的值不存在。 | 
|  PlatformNotSupportedException  |  AWS Marketplace 不支援從基礎平台計量用量。僅 AWS Fargate 支援 Amazon ECS、Amazon EKS 和 。 | 
|  ThrottlingException  |  對 的呼叫RegisterUsage會受到調節。 | 
|  InvalidRegionException  |  RegisterUsage 必須在與啟動 Amazon ECS 任務或 Amazon EKS Pod AWS 區域 相同的 中呼叫 。這可防止容器在呼叫 時選擇區域 （例如 withRegion(“us-east-1”))RegisterUsage。 | 

# 使用 整合您的容器產品與 AWS Marketplace Metering Service 適用於 Java 的 AWS SDK
<a name="java-integration-example-registerusage"></a>

您可以使用 適用於 Java 的 AWS SDK 與 AWS Marketplace Metering Service 整合。軟體使用的持續計量由 自動處理 AWS Marketplace Metering Control Plane。除了呼叫`RegisterUsage`一次 開始計量軟體使用之外，您的軟體不需要執行任何計量特定動作。本主題提供使用 適用於 Java 的 AWS SDK 與 [AWS Marketplace Metering Service](https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/Welcome.html) `RegisterUsage`動作整合的範例實作。

`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 開發套件](https://aws.amazon.com/sdk-for-java/)。
**重要**  
 若要從 Amazon EKS 呼叫計量 APIs，您必須[使用支援的 AWS SDK，](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html)並在執行 Kubernetes 1.13 或更新版本的 Amazon EKS 叢集上執行。

1.  （選用） 如果您要與 `RegisterUsage`動作整合，而且想要執行數位簽章驗證，則需要在應用程式 classpath 中設定 [BouncyCastle](https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on) 簽章驗證程式庫。

   如果您想要使用 JSON Web Token (JWT)，您還必須在應用程式 classpath 中包含 [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 Pod 定義，參考與 整合的容器， AWS Marketplace 並參考您在步驟 7 中建立的 IAM 角色。如果您想要查看 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)。如需建立 Amazon EKS 叢集 （使用 Kubernetes 1.1.3.x 版或更新版本） 的詳細資訊，請參閱[建立 Amazon EKS 叢集](https://docs.aws.amazon.com/eks/latest/userguide/create_cluster.html)。

1. 在 us-east-1 中設定 Amazon ECS 或 Amazon EKS 叢集，並啟動您建立的 Amazon ECS 任務定義或 Amazon EKS Pod AWS 區域。只有在此測試程序期間，在產品上線之前，您必須使用此區域。

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 Metering Service 來呼叫 `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();
        }
    }
}
```