local 에서 S3 서버 대신 사용하기

local 에서 서버와 S3 연결하기

실서버에서 이미지 서버로 S3 를 사용해서 이전 포스팅에서 AWS S3 와 Spring Server 를 어떻게 연결하는지에 대해 알아봤습니다.

하지만 로컬에서도 실서버와 같은 이미지 서버를 사용할 수 없기에 대신해서 사용하는 것이 LocalStack 입니다.


localstack

https://www.localstack.cloud/

localstack 은 오픈소스로 만들어진 프레임워크입니다. localstack 을 이용하면 클라우드 환경을 로컬에서 직접 구현이 가능하며, 테스트 할 수 있습니다.


docker-compose

localstack 을 실행하기 위해서는 testContainer 과 도커를 이용할 수 있는데 프런트 작업을 하면서 자주 껐다 켰다 하면서 서버가 로딩되는 속도가 느려짐을 확인해서 도커로 localstack 을 띄우기로 하였습니다.

또한 도커로 localstack 을 띄우는데 본인의 경우에는 팀프로젝트라 docker-compose 를 작성해 팀원들과 공유하였습니다.

작성한 코드는 다음과 같습니다.

services:
  localstack:
    container_name: "localstack"
    image: localstack/localstack:0.14.3
    ports:
      - "4566:4566" # localstack port
      - "4572:4572" # aws s3 port
    environment:
      - SERVICES=s3
      - DEBUG=1
      - DATA_DIR=/tmp/localstack/data
      - DOCKER_SOCK=unix:///var/run/docker.sock
      - AWS_ACCESS_KEY_ID=test
      - AWS_SECRET_ACCESS_KEY=test
      - AWS_DEFAULT_REGION=ap-northeast-2
    volumes:
      - "./localstack:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

해당 코드의 Access_key 와 Secret_access_key 는 properties 에 적어줘야 하므로 기억해둡니다


docker-compose up -d 으로 실행한 뒤 docker ps 명령어로 세팅한 서비스가 정상 작동하고 있는지 확인합니다.

image




스프링 서버 세팅하기

implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.749'

//test
testImplementation 'cloud.localstack:localstack-utils:0.2.20'

Gradle 에 먼저 위와 같이 작성합니다.


## aws
cloud.aws.s3.bucket=test
cloud.aws.stack.auto=false
cloud.aws.region.static=ap-northeast-2
cloud.aws.credentials.access-key=test
cloud.aws.credentials.secret-key=test

그리고 다음과 같이 application-local.properties 에 작성합니다.

local 에 작성했기때문에 실행시 profile 에 local 이 들어가야 합니다.


@Profile(value = "local")
@Configuration
public class S3LocalConfig {

    @Value("${cloud.aws.s3.bucket}")
    private String bucket;

    @Value("${cloud.aws.region.static}")
    private String AWS_REGION;
    private final String AWS_ENDPOINT = "http://127.0.0.1:4566";

    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;
    @Value("${cloud.aws.credentials.secret-key}")
    private String secretKey;

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

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

        if (!amazonS3.doesBucketExistV2(bucket)) {
            amazonS3.createBucket(new CreateBucketRequest(bucket, AWS_REGION));
        }
        return amazonS3;
    }
}

그리고 S3LocalConfig 파일을 통해서 LocalStack 을 이용해 만든 S3 서버 정보를 빈으로 등록합니다.


@Service
@RequiredArgsConstructor
public class FileService {
    private final AmazonS3 amazonS3;
    @Value("${cloud.aws.s3.bucket}")
    private String bucket;

    public String upload(MultipartFile file) {
        File uploadFile = convert(file);
        String url = uploadToS3(uploadFile);
        return url;
    }

    public File convert(MultipartFile multipartFile) {
        File file = new File(multipartFile.getOriginalFilename());
        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(multipartFile.getBytes());
        } catch (IOException e) {
            throw new RuntimeException();
        }
        return file;
    }

    private String uploadToS3(File uploadFile) {
        try {
            amazonS3.putObject(bucket, uploadFile.getName(), uploadFile);
            return amazonS3.getUrl(bucket, uploadFile.getName()).toString();
        } catch (SdkClientException e) {
            throw e;
        } finally {
            removeNewFile(uploadFile);
        }
    }

    private void removeNewFile(File uploadFile) {
        uploadFile.delete();
    }
}

다음은 Service 코드인데 위에서 등록한 AmazonS3 빈을 여기서 불러와 사용하게 됩니다.


@RequiredArgsConstructor
@RequestMapping("/admin/file")
@RestController
public class S3Controller {
    private final FileService fileService;

    @PostMapping("/upload")
    public ApiResponse<String> upload(@RequestParam("file") MultipartFile file) {
        String url = fileService.upload(file);
        return ApiResponse.success(url);
    }

}

추가로 컨트롤러 코드입니다.




프로젝트 세팅하기 시리즈

프로젝트 세팅하기




참고

https://techblog.woowahan.com/2638/

Leave a comment