AWS Marketplace 연동 가이드 | 01. AWS Marketplace Seller 계정 생성부터 x-amzn-marketplace-token 토큰 수신까지
AWS Marketplace 연동 가이드 | 02. AWS Marketplace Client 생성하기
AWS Marketplace 연동 가이드 | 03. ResolveCustomer
AWS Marketplace 연동 가이드 | 04. BatchMeterUsage
BatchMeterUsage
BatchMeterUsage는 AWS로 미터링 정보를 1시간마다 1번씩 전송해야합니다.
AWS MP로 미터링 정보를 전송하기 위해서는 아래의 과정이 필요합니다
- 미터링 정보 조회
- MeteringRawData
- AWS 미터링 단위로 변환하기 :
- MetringRawData → AwsBillingData
- AWS 미터링 정보 BatchMeterUsage API에 맞춰 변형하기 :
- AwsBillingData → AwsBillingRequestBuilder → List
- BatchMeterUsageRequest → BatchMeterUsage API 호출
- AWS로 과금 정보 전송
AWS 미터링 단위로 변환하기
AWS MP에 제품을 등록하면 AWS을 통해서 Billing 됩니다. 저희가 보내주는 metering 정보에 전적으로 의존해서 고객에게 과금이 됩니다. 만약 metering 정보를 실수로 더 보내거나 덜 보내는 경우, 고객에에 의도치않은 금액이 청구될 수 있습니다.
AWS MP 제품을 등록할 때 dimension이라는 단위를 입력해야합니다. 각 dimension 별로 사용량에 정보를 Unit으로 환산해서 전송하면 dimension 별로 과금액이 청구됩니다.
dimension에 대한 자세한 설명은 아래 링크를 확인해주세요.
2024.01.16 - [개발/AWS Marketplace] - AWS Marketplace SaaS Subscription 과금 모델이 적합한지 미리 검토하는 방법
AWS Marketplace에서는 1시간 단위의 과금 모델을 사용하고 있기 때문에, 기존에 존재하던 과금 방식 월 단위 과금이라면 변환 공식이 필요합니다. 아래의 예시에서 MeteringRawData 클래스는 월 단위 과금량을 담고 있고, AwsMeteringData 클래스는 시간 단위 과금량을 저장합니다.
/**
* @param rawData 매 시 0, 20, 40분마다 갱신되는 미터링 정보
* @param productType 매 정각마다 갱신되는 정보
*/
public AwsMeteringData(MeteringRawData rawData, String productType) {
this.pcode = rawData.getPcode();
this.time = DateUtil.getHourTrim(rawData.getTime());
this.productType = productType;
this.host = rawData.getHost();
this.cpu = rawData.getCpu();
this.urls = rawData.getUrls();
this.logs = getAwsLogUnit(rawData);
this.k8sContainers = rawData.getK8sContainers();
this.k8sAppContainers = rawData.getK8sAppContainers();
this.session = getAwsSessionUnit(rawData);
this.updateTime = new Date();
}
변환 공식은 연동하는 서비스마다 달라집니다. 하나만 예시를 들면 아래와 같이 변환 공식을 적용할 수 있습니다. 아래의 공식을 결정하실 떄에는 이미 존재하는 과금 정보가 어떻게 집계되는지를 아주 자세히 파악해보셔야 합니다.
/**
* 100만 로그 = 1 Log Unit = 0.04$ = 50원
* 25000 로그 = AWS 1 Unit = 0.001$ = 50/40 원
*
* '100만 Log Unit 당 50원'과 AWS MP 상에서 제공하는 최소 가격 단위가 0.001인점을 고려하여 25_000으로 나누어서 Unit을 전송함
*/
private int getAwsLogUnit(MeteringRawData rawData) {
// 매 시간 쌓여있는 총 로그 수 (보관기간이 반영되어 있음)
long allLogsExistsInYardbase = rawData.getLogs();
if (allLogsExistsInYardbase == 0) {
// 사용량이 없으면 과금하지 않음
return 0;
}
// AWS Marketplace는 매 시간 과금이기 때문에
// 현재 Yard에 남아있는 로그를 24시간으로 나누어 '1시간 평균 로그 양'에 대해서 AWS Log Unit을 계산해야
// AWS Marketplace를 사용하지 않는 과금 방식과 최대한 비슷한 과금량이 산정됨
long oneHourAvg = allLogsExistsInYardbase / HOURS_PER_DAY;
if (oneHourAvg < AWS_LOG_UNIT) {
// 로그를 하나라도 사용했으면 1 AWS LogUnit으로 과금
return 1;
} else {
// 반올림
return (int) Math.round((double) oneHourAvg / AWS_LOG_UNIT);
}
}
💡과금량은 Integer로 소수점을 지원하지 않습니다.
dimension 별 quantity는 Integer이기 때문에 소수점 아래 자리는 절삭됩니다. 이 부분을 반올림/올림 처리하면 사용자의 과금액이 그만큼 늘어납니다. 정답은 없고 사내 정책에 맞추어서 작성하면 됩니다. 아래는 AwsMeteringData 데이터로 변환한 뒤, 실제로 AWS MP에 과금량을 전송하는 BatchMeterUsage API의 RequestDTO입니다.
// BatchMeterUsage API에서 사용하는 포멧 package com.amazonaws.services.marketplacemetering.model; public class UsageRecord implements Serializable, Cloneable, StructuredPojo { private java.util.Date timestamp; private String customerIdentifier; private String dimension; private Integer quantity; }
AWS 미터링 정보 BatchMeterUsage API에 맞춰 변형하기
BatchMeterUsage에서 API는 BatchMeterUsageRequest 객체를 요청으로 받습니다. 이 객체는 아래와 같은 구조로 되어 있습니다.
이 요청 정보의 계층을 이해해보면 아래와 같습니다.
- 하나의 AWS MP 제품(productCode)을 여러개의 계정(customerIdentifier)에서 구독할 수 있습니다. 구매자 별로 과금량을 전송하는 것이 아니라, AWS MP 제품 별로 과금량을 전송해야 합니다.
- 각 계정(customerIdentifier) 별로 dimension 별 총 사용량(quantity)을 1시간(timestamp)마다 전송해야한다
- dimension 별 총 사용량(quantity)은 Tag 별 사용량(allocatedUsageQuantity)의 총합이다
연동해야하는 서비스는 아래와 같은 정책으로 미터링 정보를 생성하고 있습니다.
💡 혹시 아래의 설명이 이해가 잘 되지 않는다면
글 보다는 코드를 훑어보시는 것이 더 잘 이해되실 수 있습니다.
- 하나의 Account(customerIdentifier)이 여러 개의 Project를 생성할 수 있다
- Project가 생성되면 고유한 식별자가 추가된다(Tag)
- 미터링 정보(allocatedUsageQuantity)는 Project 별로 생성된다
- Project를 생성할 때 Project의 타입에 따라서 집계되는 dimension 정보가 1개 이상 지정되어 있다.
이를 요청 정보에 맵핑하면 아래와 같습니다.
이렇게 매핑 방식을 코드로 변환하면 다음과 같습니다.
BatchMeterUsageRequest → BatchMeterUsage API 호출하기
마지막 단계는 API를 호출하면 됩니다. 실패했을 경우를 대비해서 꼼꼼하게 작성하면 됩니다.
/**
* AWS Marketplace에 AWS 미터링 정보를 전송한다
*/
public void batchMeterUsage(BatchMeterUsageRequest request) throws WhaTapError {
int retry = 0;
List<UsageRecord> preprocess = request.getUsageRecords();
while (retry < conf.aws_marketplace_metring_retry_count) {
List<UsageRecord> unprocessed = doBatchMeterUsage(request.getProductCode(), preprocess);
if (unprocessed.isEmpty()) {
if (conf.debug_aws_marketplace) {
logger.info("[AWS MP] batchMeterUsage request success. productCode : {}", request.getProductCode());
}
return;
}
preprocess = unprocessed;
retry++;
}
// retry도 실패한 경우
int unprocessed = preprocess.size();
if (unprocessed > 0) {
String productCode = request.getProductCode();
for (UsageRecord usageRecord : request.getUsageRecords()) {
Date timestamp = usageRecord.getTimestamp();
String dimension = usageRecord.getDimension();
Integer quantity = usageRecord.getQuantity();
logger.error("[AWS MP] batchMeterUsage request failed. productCode : {}, timestamp : {}, dimension : {}, quantity : {}",
productCode, timestamp, dimension, quantity);
}
preprocess.forEach(record -> logger.error(record.toString()));
}
}
/**
* @param awsProductCode AWS Marketplace 상품 코드
* @param usageRecords 전송해야하는 미터링 정보
* @return 처리에 실패한 미터링 정보, retry 처리되어야 한다
*/
private List<UsageRecord> doBatchMeterUsage(String awsProductCode, List<UsageRecord> usageRecords) throws WhaTapError {
try {
BatchMeterUsageRequest request = new BatchMeterUsageRequest()
.withProductCode(awsProductCode)
.withUsageRecords(usageRecords);
BatchMeterUsageResult batchMeterUsageResult = getAwsMarketplaceClient().batchMeterUsage(request);
for (UsageRecordResult result : batchMeterUsageResult.getResults()) {
String meteringRecordId = result.getMeteringRecordId();
String status = result.getStatus();
UsageRecord usageRecord = result.getUsageRecord();
Date timestamp = usageRecord.getTimestamp();
String dimension = usageRecord.getDimension();
Integer quantity = usageRecord.getQuantity();
logger.info("[AWS MP] batchMeterUsageResult : status : {}, meteringRecordId : {}, timestamp : {}, dimension : {}, quantity : {}",
status, meteringRecordId, timestamp, dimension, quantity);
}
return batchMeterUsageResult.getUnprocessedRecords();
} catch (Exception e) {
logger.error("[AWS MP] " + e.getMessage());
return usageRecords;
}
}
'AWS > AWS Marketplace' 카테고리의 다른 글
AWS Marketplace 연동 가이드 | 03. ResolveCustomer (0) | 2024.01.17 |
---|---|
AWS Marketplace 연동 가이드 | 02. AWS Marketplace Client 생성하기 (0) | 2024.01.17 |
AWS Marketplace 연동 가이드 | 01. AWS Marketplace Seller 계정 생성부터 x-amzn-marketplace-token 토큰 수신까지 (0) | 2024.01.17 |
AWS Marketplace AssumeRole 적용하기 (0) | 2024.01.17 |
AWS API에서 AccessKey, SecretAccessKey 대신 Role ARN 사용하기 (0) | 2024.01.16 |