AWS SDK for Java (CloudWatchLogsAsyncClient 사용법)
<필요사항>
- AWS ECS상 워크로드 중 특정서비스의 로그를 수집/가공 하여 통계 정보를 제공
- 기존에 사용중이던 AWS SDK S3 와 호환성
<개요>
- https://docs.aws.amazon.com/cloudwatch/index.html
- AWS는 Cloudwatch 내에서 특정 로그그룹에 대하여 쿼리할 수 있는 툴 (Insight) 를 제공하고 있다.
- 해당 기능을 Web, SDK등 다양한 형태로 제공하고 있으며 Java SDK를 사용했다.
<내용>
- Java용 AWS SDK는 크게 버전 1.x , 2.x 가 있으며 특히 2.x 부터 비동기 NonBlocking 및 CompletableFuture를 제공하고 있기 때문에 신규 개발의 경우 가급적 2.x 를 권장하고 있다.
- 현재 서비스에서 S3용 1.x SDK를 이미 사용중이고 CloudWatchLogs는 2.x SDK를 사용할 예정이기 때문에 Mig 를 하던지 두 가지 버전을 병행하던지 선택한다.
- 기존 기능에 큰 문제가 없기 때문에 일단 병행하기로 결정
<환경설정 및 변경사항>
1. pom.xml
- groupId 변경
ver 1.x | ver 2.x |
com.amazonaws | software.amazon.aws.sdk |
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.762</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cloudwatchlogs</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>
<version>1.12.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.16.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. @Configuration
public BasicAWSCredentials awsCredentials(){
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return awsCreds;
}
@Bean
public AmazonS3 amazonS3Client(){
AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
.withRegion("ap-northeast-2")
.withCredentials(new AWSStaticCredentialsProvider(this.awsCredentials()))
.build();
return amazonS3;
}
- 기존에는 BasicAWSCredentials 를 사용하고 있었으며 Region 이 String 으로 사용되고 있어서 오타의 위험이 있다.
@Configuration
public class AmazonCloudWatchLogsConfig {
@Value("${spring.cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${spring.cloud.aws.credentials.secret-key}")
private String secretKey;
public AwsBasicCredentials awsBasicCredentials(){
AwsBasicCredentials awsCreds = AwsBasicCredentials.create(accessKey, secretKey);
return awsCreds;
}
@Bean
public CloudWatchLogsAsyncClient cloudWatchLogsAsyncClient(){
CloudWatchLogsAsyncClient cloudWatchLogsAsyncClient = CloudWatchLogsAsyncClient.builder()
.region(Region.AP_NORTHEAST_2)
.credentialsProvider(StaticCredentialsProvider.create(this.awsBasicCredentials()))
.build();
return cloudWatchLogsAsyncClient;
}
- Ver2에서는 AwsBasicCredentials로 변경이 되었으며, new 를 사용하지 않고 create를 사용한다.
- Region 설정이 enum type으로 변경되어 사용자의 실수를 막아준다.
- 또한 필요에 따라서 HttpClient를 Netty로 변경할 수 도 있다.
<AWS SDK를 사용하여 AWSCloudWatchLog를 활용하는 방법>
- SDK에서 사용할 IAM을 사전에 생성할 필요가 있다.
- Log를 조회하는 것은 크게 2단계로 나누어진다.
a. startQuery
StartQueryResponse ret = cloudWatchLogsAsyncClient.startQuery(StartQueryRequest.builder()
.endTime(endTime)
.startTime(startTime)
.limit(n)
.logGroupName("")
.queryString(query)
.build()
).join();
쿼리 수행을 마치고 나서 결과값으로 unique한 queryId를 돌려주는데 이 값을 사용하여 쿼리결과를 조회한다.
b. getQueryResults
GetQueryResultsResponse queryReponse = cloudWatchLogsAsyncClient.getQueryResults(GetQueryResultsRequest.builder().queryId(queryId).build()).join();
queryId를 통해서 GetQueryResultsResponse 를 얻어올 수 있다.
여기서 주의해야 할 것은 GetQueryResultsResponse 내에는 QueryStatus가 존재하며 Running, Scheduled등 일 경우 전체결과가 조회되지 않을 수 있다는 점이다.
처음 작업할때 ComletableFuture의 join을 호출하는 시점에 모든 결과가 있을것으로 예상하여 원하는 값이 나오지 않아서 고민했었다.
c. Async방식
위에서 언급한 것 처럼 AsyncClient를 사용할 경우 CompletableFuture를 return하도록 되어있기 때문에 callback을 작성하여 불필요한 대기를 최소화할 수 것으로 예상했다.
하지만 구조자체가 응답으로 queryId를 받아와야 하고, 또 쿼리 결과를 조회할때에도 Complete 상태에 이르러야 완벽한 결과값이 세팅되는 점을 감안한다면 해당 API를 사용하는 유저시나리오는 Async-blocking에 가깝다.
<결론>
- 해당 기능을 통해서 AWS Cloudwatch 에 수집되고 있는 로그그룹에 접근하여 일정시간 동안 많이 입력된 키워드, 로그인한 사용자, requestUri정보 등을 집계하여 API로 제공중이다.
- 쿼리 사용시 주의해야할 점은 Scan하는 Log의 양만큼 비용을 지불하기 때문에 startTime, endTime을 적절하게 조정해야 한다.