로컬 환경에서는 정상적으로 작동하지만, 동일한 파일을 Actions Secrets로 등록하여 Workflow 실행 중에 주입해서 서버를 배포했을 때,json 파일을 찾지 못했다는 에러를 마주쳐, 이를 해결한 내용을 정리해보고자 한다.
먼저, GCP의 자격 증명 방식이다. GCP는 GCP 제품에 접근할 때 json 형식의 파일을 기반으로 자격 증명(Credential)을 생성해서 활용한다.
이 json 파일이 자격 증명을 위해 accesskey와 secretKey를 발급받아 활용하는 AWS와 큰 차이점이라고 볼 수 있다.
로컬 환경에서 Spring Boot에서 GCP Credential을 생성할 때는 아래와 같이 작업할 수 있다.
서비스 계정의 json 파일은 이미 생성한 상황이라고 가정하고 정리한다.
먼저 json 파일은 resoruces 경로 하단에 위치시킨다.
spring:
cloud:
gcp:
credentials:
location: gcs-access-key.json
storage:
project-id: {프로젝트 ID}
bucket: {버켓 이름}
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();
}
}
# 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 타입이 아니라는 오류가 발생했었다.
이는 secrets에 json 파일 내부의 값을 그대로 복사 붙여넣기하여 넣었던 상황인데, 이로 인해 json 포맷이 아니라고 인식된다고 판단했다. 다양한 인코딩 방식이 있지만, 가장 쉬운 방식인 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'
# 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 파일이 정상적으로 들어갔는지 확인이 필요하다고 생각했다. 그래서 아래와 같이 코드를 변경해서 정보를 직접 확인했다.
@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을 활용해보고 싶으시다면 아래의 글을 참고해서 시도해보시면 좋을 것 같다.