LocalStack 로컬 환경에 구축하기

공병주(Chris)·2023년 4월 19일
0
post-thumbnail

2023 글로벌미디어학부 졸업작품 Dandi를 개발 과정에, LocalStack이 구축된 환경을 개선한 기록입니다.

과거 S3 통합 테스트를 위해서 LocalStack을 구축했습니다

방식은 ApplicationContext가 로드될 때, Localstack Container를 init되고 Application Context가 종료될 때 Localstack Container가 destroy되도록 했습니다.

그때는, 개발을 하는 동안 Localstack의 Container가 로컬 환경에 계속 구동해야 한다는 것이 싫었습니다. Intellij도 무거워서 컴퓨터가 힘들어하는데 Docker Container 하나도 같이 띄워놓기가 부담스러웠습니다.

그렇게, 개발을 진행하던 중에 위 방식은 상당히 비효율적이라는 것을 느꼈습니다.

프로젝트의 크기가 큰 것도 아닌데, Application을 구동하는데 18s~20s가 걸렸습니다. 또한, 기능을 구현하고 리팩토링을 하는 과정에서 수도 없이 많은 횟수의 테스트 코드를 실행시키는데 @SpringBootTest에서 ApplicationContext를 load하는 시간이 너무 오래걸렸습니다.

ApplicationContext가 로드되는 시점에 Localstack Container를 init하는 시간이 너무 오래 걸리기 때문이었습니다.

따라서, Loca환경에 Localstack Container를 구축해둬야겠다는 생각을 했습니다.

services:
  localstack:
    image: localstack/localstack
    ports:
      - "4566:4566"
      - "4571:4571"
    environment:
      - SERVICES=s3
      - DEBUG=1
      - DATA_DIR=/tmp/localstack/data
      - AWS_ACCESS_KEY_ID=localstack-access-key
      - AWS_SECRET_ACCESS_KEY=localstack-secret-key
      - AWS_REGION=us-east-1
    volumes:
      - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
      - ./data:/opt/data

위 docker-compose.yml 파일으로 container를 구동했습니다.

위 설정 파일에서 적은 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION 값과 동일한 값과 localhost:4566 주소로 Localstack을 Bean으로 등록할 수 있었습니다.

@Configuration
@Profile({"local", "test"})
public class LocalStackConfig {

    private final String bucketName;

    public LocalStackConfig(@Value("${cloud.aws.s3.bucket-name}") String bucketName) {
        this.bucketName = bucketName;
    }

    private static final String AWS_REGION = US_EAST_1.getName();
    private static final String AWS_ENDPOINT = "http://localhost:4566";

    private static final String LOCAL_STACK_ACCESS_KEY = "localstack-access-key";
    private static final String LOCAL_STACK_SECRET_KEY = "localstack-secret-key";

    @Bean
    public AmazonS3 amazonS3() {
        AwsClientBuilder.EndpointConfiguration endpoint =
                new AwsClientBuilder.EndpointConfiguration(AWS_ENDPOINT, AWS_REGION);
        BasicAWSCredentials credentials = new BasicAWSCredentials(LOCAL_STACK_ACCESS_KEY, LOCAL_STACK_SECRET_KEY);

        AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
                .withEndpointConfiguration(endpoint)
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .build();
        amazonS3.createBucket(bucketName);
        return amazonS3;
    }
}

위와 같은 방식으로 ApplicationContext가 load 되는 시간을 훨씬 빠르게 만들 수 있었습니다.

Spring이 load되는데 걸리는 시간이 18s~20s에서 6~8s로 줄어들었습니다.

또한, 현재 모든 테스트에서 SpringBootTest가 2개 있었는데, 전체 테스트 시간도 대폭 줄일 수 있었습니다.

CI에서는 어떻게?

이전에는 CI 과정에서 Local 환경과 동일하게 ApplicationContext가 load될 때, java 코드를 통해 LocalStack 컨테이너를 구축하고 ApplicationContext가 종료될 때 LocalStack을 내렸는데요.

CI에서도 테스트 속도 향상을 기대하며 workflow에서 localstack을 구축하도록 했습니다. 하지만, 위 방식으로 CI에서 Build Gradle의 속도는 이전과 비슷했습니다.

다만, CI 환경 구축을 위한 코드가 java 객체가 아닌 ci script에서 전적으로 관리된다는 이점이 있다고 생각해서 ci script에서 localstack을 구축하는 방식을 채택했습니다.

jobs:
  build:
    runs-on: ubuntu-latest

    services:
      localstack:
        image: localstack/localstack
        env:
          SERVICES: s3
          DEFAULT_REGION: us-east-1
          DATA_DIR: /tmp/localstack/data
          AWS_ACCESS_KEY_ID: localstack-access-key
          AWS_SECRET_ACCESS_KEY: localstack-secret-key
          AWS_REGION: us-east-1
        ports:
          - 4566:4566
        options: >-
          --name=localstack
          --health-cmd="curl -sS 127.0.0.1:4566 || exit 1"
          --health-interval=5s
          --health-timeout=5s
          --health-retries=3

UnknownHostException

Local 환경의 Localstack에 연결해 AmazonS3를 Bean으로 등록했는데, amazonS3 (SdkClientException: Unable to execute HTTP request: dandi-img.localhost)로 인한 java.net.UnknownHostException이 발생했습니다.

host가 localhost 아닌 dandi-img.localhost와 같이 되어있었습니다.

이 문제는 권남님의 localstack wiki를 보고 해결할 수 있었습니다.

AwsClientBuilder로 AmazonS3 객체를 생성할 때, .withPathStyleAccessEnabled(true) 를 설정해줘야하는데요. 그 이유는 Java SDK에서 위 설정을 해주지 않으면 S3의 접속 주소가 bucketname.localhost와 같이 설정되기 때문입니다.

AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
                .withEndpointConfiguration(endpoint)
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withPathStyleAccessEnabled(true)
                .build();

따라서, 위 처럼 .withPathStyleAccessEnabled(true)를 추가해서 해결할 수 있었습니다.

0개의 댓글