[Spring Boot] 배포할 때,GCP Credential 오류 해결하기

이정진·2024년 7월 10일
0

개발

목록 보기
16/21
post-thumbnail

로컬 환경에서는 정상적으로 작동하지만, 동일한 파일을 Actions Secrets로 등록하여 Workflow 실행 중에 주입해서 서버를 배포했을 때,json 파일을 찾지 못했다는 에러를 마주쳐, 이를 해결한 내용을 정리해보고자 한다.

GCP의 자격 증명 방식

먼저, GCP의 자격 증명 방식이다. GCP는 GCP 제품에 접근할 때 json 형식의 파일을 기반으로 자격 증명(Credential)을 생성해서 활용한다.
이 json 파일이 자격 증명을 위해 accesskeysecretKey를 발급받아 활용하는 AWS와 큰 차이점이라고 볼 수 있다.

로컬

로컬 환경에서 Spring Boot에서 GCP Credential을 생성할 때는 아래와 같이 작업할 수 있다.

서비스 계정의 json 파일은 이미 생성한 상황이라고 가정하고 정리한다.

json 파일의 위치

먼저 json 파일은 resoruces 경로 하단에 위치시킨다.

application.yml

spring:
  cloud:
    gcp:
      credentials:
        location: gcs-access-key.json
      storage:
        project-id: {프로젝트 ID}
        bucket: {버켓 이름}

GCPConfig

GCS에 이미지를 업로드하는 과정으로 예시를 들겠다.

@Configuration
public class GcsConfig {

    @Value("${spring.cloud.gcp.credentials.location}")
    private ClassPathResource gcpServiceAccountKey;

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

    @Bean
    public Storage storage() throws IOException {
        GoogleCredentials credentials = GoogleCredentials.fromStream(gcpServiceAccountKey.getInputStream());
        return StorageOptions.newBuilder()
                .setProjectId(projectId)
                .setCredentials(credentials)
                .build()
                .getService();
    }
}
  • 프로젝트를 빌드하면, 리소스 파일들은 CLASS_PATH에 위치하게 된다. 이 때, ClassPathResource 클래스를 사용하면 해당 위치에 있는 파일을 쉽게 가져올 수 있다.

오류가 발생했었던 배포

Workflow

# GCP Service Account Key
- name: Make GCP Service Account Key
  run : |
    cd ./src/main/resources
    touch gcp-access-key.json
    echo "${{ secrets.GCP_ACCESS_KEY }}" > ./gcp-access-key.json
  shell: bash

로컬에서는 별 문제가 발생하지 않았기에, 당연하게 배포 또한 별도의 작업 없이 Github Actions Secrets에 json 파일을 등록해놓고, 이를 workflow에서 주입받아서 jar 파일을 생성하도록 했었다.

오류

VM에서 서버가 실행될 때,Caused by: java.lang.IllegalArgumentException: no JSON input found 라는 json 타입이 아니라는 오류가 발생했었다.

Base64를 활용한 배포

이는 secrets에 json 파일 내부의 값을 그대로 복사 붙여넣기하여 넣었던 상황인데, 이로 인해 json 포맷이 아니라고 인식된다고 판단했다. 다양한 인코딩 방식이 있지만, 가장 쉬운 방식인 Base64를 활용해서 인코딩을 진행했다.

Base64로 인코딩하는 법

base64 < your-service-account-key.json | tr -d '\n'

해당 json 파일의 경로로 이동하여, 위 명령어와 같이 입력하면 Base64로 인코딩된 결과가 출력된다. 이를 Actions Secrets에 저장하면 된다.

위 명령어는 mac OS 기준이다. 타 OS는 해당 명령어가 정상적으로 동작하지 않을 수 있다. OS에 맞추어 아래의 명령어를 사용하면 된다.

Linux: base64 -w 0 < your-service-account-key.json
일반: cat your-service-account-key.json | base64 | tr -d '\n'

Workflow 수정

# GCP Service Account Key
- name: Make GCP Service Account Key
  run : |
    cd ./src/main/resources
    echo "${{ secrets.GCP_ACCESS_KEY_BASE64 }}" | openssl base64 -d -A > gcp-access-key.json
  shell: bash

다음으로 이를 디코딩하도록 Workflow를 수정하면 된다. OpenSSL이 대부분의 Unix 계열 시스템(Linux, macOS 등)에 기본적으로 설치되어 있어 호환성 이슈가 적기에 위와 같은 방식으로 디코딩을 진행했다.

json 파일이 정상적으로 들어갔는지 확인하기

나는 불안해서, 실제로 해당 json 파일이 정상적으로 들어갔는지 확인이 필요하다고 생각했다. 그래서 아래와 같이 코드를 변경해서 정보를 직접 확인했다.

@Configuration
public class GcsConfig {

    @Value("gcp-service-account-key.json")
    private ClassPathResource gcpServiceAccountKey;

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

    @Bean
    public Storage storage() throws IOException {
        System.out.println(readResourceAsString(gcpServiceAccountKey));
        GoogleCredentials credentials = GoogleCredentials.fromStream(gcpServiceAccountKey.getInputStream());
        return StorageOptions.newBuilder()
                .setProjectId(projectId)
                .setCredentials(credentials)
                .build()
                .getService();
    }

    private String readResourceAsString(ClassPathResource resource) throws IOException {
        try (InputStream inputStream = resource.getInputStream()) {
            return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
        }
    }
}

위와 같이 설정하면, Bean 어노테이션으로 인해 스프링 컨테이너로 들어가는 과정에서 정보를 출력하게 되어 서버 실행 시점에 바로 로그를 통해 확인해볼 수 있다.
임시적인 조치이므로 확인 이후에는 반드시 삭제해야 한다.


후기

솔직하게 말하자면 배포할 때, 오류가 발생할 것 같다는 예상을 하고 있었다.
(속으로는 제발 발생하지 않기를 기도했다...)
과릿의 인프라를 GCP로 마이그레이션하는 과정에서 Credential과 관련된 부분에서 위와 비슷한 오류를 마주했었기 때문이다. 당시에는 Jasypt을 활용하여 암호화/복호화하여 해결했었다. 큰 틀에서 보면, Base64로 인코딩/디코딩하는 방식과 Jasypt으로 암호화/복호화하는 방식은 유사하다고 생각한다. 그래도 이 기억을 바탕으로 빠르게 해결해서 시간 비용은 많이 줄여서 다행이라고 생각한다.

혹시 Jasypt을 활용해보고 싶으시다면 아래의 글을 참고해서 시도해보시면 좋을 것 같다.

0개의 댓글