본문 바로가기
Projects/청하-청년을 위한 커뮤니티 서비스

[청하] 28. GCP 설정 (feat. GCS 설정 및 스프링부트 연동)

by Lpromotion 2024. 10. 19.

GCP로 서버 이전 및 GCS로 스토리지 변경하게 된 이유

NCP 우분투 운영체제가 지원 종료되어 클라우드 서비스를 GCP(Google Cloud Platform)로 이전하기로 결정했다.

기존 NCP 환경에서 운영 중인 서버들을 GCP로 마이그레이션하고, 기존의 Object Storage 데이터도GCS(Google Cloud Storage)로 이전하는 작업을 수행했다.

 

1. GCP 설정

프로젝트는 팀원이 이미 생성했고, 내 계정이 IAM 으로 프로젝트에 추가되어 있다.

 

2. Google Cloud Storage (GCS) 설정

Google Cloud Storage (GCS)는 클라우드 기반의 객체 저장소 서비스로, 여기에 기존 Object Storage 데이터를 마이그레이션할 것이다.

 

2.1. GCS 버킷 생성

GCS 콘솔에서 새 버킷 생성

  1. Storage > 버킷 만들기
  2. 버킷 이름, 위치, 저장소 클래스 설정
REF
Spring boot 로 구글 클라우드 저장소(GCS) 에 파일 업로드 하기
SpringBoot 프로젝트에서 GCS로 이미지 업로드 (Google Cloud Storage)(GCP)

 

 

2.2. 버킷 설정 및 권한 부여

버킷의 액세스 제어 설정

  1. 생성한 버킷을 선택한 후, “권한”으로 이동. “액세스 권한 부여” 선택.


  2. 버킷의 모든 객체들을 모든 사용자들에게 공개된 상태로 변경하기 위해 아래와 같이 설정한다.

필요한 권한 부여

  1. IAM 및 관리자 > 서비스 계정


  2. 역할을 추가하는데 setIamPolicy 권한이 필요해서 팀원에게 권한 부여 요청을 하고 진행했다.

key 파일 생성

  1. 생성한 서비스 계정에서 “키 관리”로 이동.
  2. 키 추가 > Json

 

생성된 키 파일은 저장해둔다.

프로젝트의 resource 폴더 안에 저장되어야 한다.

 

3. 스프링부트 애플리케이션 설정

3.1. GCS 의존성 추가

`build.gradle` 파일에 GCS 의존성 추가

// Google Cloud Storage (GCS)
implementation 'org.springframework.cloud:spring-cloud-gcp-starter:1.2.8.RELEASE'
implementation 'org.springframework.cloud:spring-cloud-gcp-storage:1.2.8.RELEASE'

 

3.2. 애플리케이션 설정 파일 업데이트

`application.yml` 파일에 GCS 설정 추가

위에서 만든 json key file이 resource 폴더 안에 있어야 함.

spring:
    cloud:
    gcp:
      storage:
        credentials:
          location: ${GCS_CREDENTIALS_LOCATION}
        project-id: ${GCP_PROJECT_ID}
        bucket: ${GCS_BUCKET_NAME}
  • `location`: JSON 키의 경로
  • `project-id`: JSON key 파일에 있는 project-id
  • `bucket`: Cloud Storage 콘솔 들어가면 나오는 버킷 이름

 

3.3. GCS 클라이언트 설정

GcsConfig

@Configuration
public class GcsConfig {
    @Value("${spring.cloud.gcp.storage.credentials.location}")
    private String gcpCredentialsLocation;

    @Value("${spring.cloud.gcp.storage.project-id}")
    private String projectId;

    @Bean
    public Storage storage() throws IOException {
        GoogleCredentials credentials;
        if (gcpCredentialsLocation.startsWith("classpath:")) {
            // 클래스패스에서 자격 증명 파일 로드
            Resource resource = new ClassPathResource(gcpCredentialsLocation.substring(10));
            InputStream inputStream = resource.getInputStream();
            credentials = GoogleCredentials.fromStream(inputStream);
        } else {
            // 기본 자격 증명 사용
            credentials = GoogleCredentials.getApplicationDefault();
        }
        // GCS 클라이언트 생성 및 반환
        return StorageOptions.newBuilder()
                .setProjectId(projectId)
                .setCredentials(credentials)
                .build()
                .getService();
    }
}

GCS 클라이언트 초기화 및 설정 코드 작성하는 클래스이다.

  • `@Value` 어노테이션을 사용하여 GCP 자격 증명 파일 위치와 프로젝트 ID를 주입받는다.
  • `GoogleCredentials.fromStream()` 을 사용해 자격 증명을 로드한다.
  • `StorageOptions.newBuilder()` 를 통해 GCS 클라이언트를 구성한다.
  • `Storage` 빈을 생성해 애플리케이션 전체에서 사용할 수 있도록 한다.

 

4. 파일 업로드 기능 수정

4.1. PostServce - 게시글 이미지 등록 & 삭제

@Transactional
private void uploadImages(Long postId, List imageFiles) {
    Post post = getPostById(postId);

    int idx = 0;
    for (MultipartFile file : imageFiles) {
        // 파일 이름 생성
        String fileName = idx + "_" + file.getOriginalFilename();
        String blobName = "postImage/" + postId + "/" + fileName;
        String fileUrl = "<https://storage.googleapis.com/>" + bucketName + "/" + blobName;

        try {
            // GCS에 파일 업로드
            BlobId blobId = BlobId.of(bucketName, blobName);
            BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
                    .setContentType(file.getContentType())
                    .build();
            storage.create(blobInfo, file.getBytes());

            // 데이터베이스에 이미지 정보 저장
            imageRepository.save(Image.builder()
                    .post(post)
                    .url(fileUrl)
                    .build());
        } catch (Exception e) {
            throw new CommonException(ErrorCode.POST_ERROR);
        }

        idx++;
    }
}

@Transactional
private void deleteImages(Post post) {
    List imageUrls = imageRepository.findUrlsByPost(post);
    List blobIdsToDelete = new ArrayList<>();

    for (String imageUrl : imageUrls) {
        // URL에서 blobName 추출
        String blobName = imageUrl.substring(imageUrl.indexOf(bucketName) + bucketName.length() + 1);
        blobIdsToDelete.add(BlobId.of(bucketName, blobName));
    }

    // GCS에서 이미지 일괄 삭제
    storage.delete(blobIdsToDelete);
}
  • AmazonS3 대신 Storage 객체를 주입받아 사용한다.
  • 이미지 업로드 시 BlobId와 BlobInfo를 사용해 GCS 파일에 저장한다.
  • 이미지 삭제 시 BlobId 리스트를 생성해 한 번에 여러 이미지를 삭제할 수 있도록 했다.

 

4.2. UserServce - 사용자 프로필 이미지 등록 & 삭제

@Transactional
public void uploadProfileImage(Long userId, MultipartFile file, User user) {
    try {
        String fileUrl;
        String blobName;
        if (user.getProfileImage() != null && !user.getProfileImage().equals("default.png")) {
            // 기존 프로필 이미지 삭제
            String oldBlobName = user.getProfileImage().substring(user.getProfileImage().indexOf(bucketName) + bucketName.length() + 1);
            storage.delete(BlobId.of(bucketName, oldBlobName));

            // 새 이미지 파일명 생성
            int lastIndex = oldBlobName.lastIndexOf("/") + 1;
            String folderPath = oldBlobName.substring(0, lastIndex);
            int newIndex = Integer.parseInt(oldBlobName.substring(lastIndex)) + 1;
            blobName = folderPath + newIndex;
        } else {
            blobName = "userProfile/" + userId + "/1";
        }

        fileUrl = "<https://storage.googleapis.com/>" + bucketName + "/" + blobName;

        // GCS에 새 이미지 업로드
        BlobId blobId = BlobId.of(bucketName, blobName);
        BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
                .setContentType(file.getContentType())
                .build();

        storage.create(blobInfo, file.getBytes());
        user.updateProfileImage(fileUrl);
    } catch (Exception e) {
        throw new CommonException(ErrorCode.FILE_UPLOAD_ERROR);
    }
}

@Transactional
public String deleteProfileImage(Long userId) {
    User user =
            userRepository.findById(userId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_USER));
    
    // GCS에서 프로필 이미지 삭제
    String blobName = user.getProfileImage().substring(user.getProfileImage().indexOf(bucketName) + bucketName.length() + 1);
    storage.delete(BlobId.of(bucketName, blobName));
    
    // 사용자 프로필 이미지를 기본 이미지로 재설
    user.updateProfileImage("default.png");
    return user.getProfileImage();
}
  • AmazonS3 대신 Storage 객체를 주입받아 사용한다.
  • 프로필 이미지 업로드 시 기존 이미지를 삭제하고, 새 이미지를 업로드한다.
  • 이미지 URL 생성 방식을 GCS 형식으로 변경했다.
  • 프로필 이미지 삭제 시 GCS의 delete 메서드를 사용한다.

 

5. API 응답 예시

5.1. 게시글 이미지 등록 & 삭제 테스트

 

게시글 등록 요청 시 이미지가 잘 등록되는 것을 확인했다.

 

5.2. 사용자 프로필 이미지 등록 & 삭제 테스트

반응형

댓글