[TIL] 도커 환경에서 파일 경로를 못찾는 문제 해결하기

Loopy·2025년 2월 16일
0

삽질기록

목록 보기
31/31
post-thumbnail

문제 상황

로컬에서는 파일의 경로를 찾지만, 도커 환경에서는 Spring Boot 컨테이너에서 파일 경로 못찾는 에러가 발생했다.

Caused by: java.io.FileNotFoundException: src/main/resources/logback-spring.xml (No such file or directory)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
	at ch.qos.logback.core.joran.GenericXMLConfigurator.doConfigure(GenericXMLConfigurator.java:92)

문제 원인

1. 파일이 JAR 내부에 포함되지 않았을 가능성

우선 src/main/resources/ 내부에 있는 파일들은 Jar 파일의 BOOT-INF/classes/ 에 위치한다. 따라서 혹시나 빌드 과정에서 파일이 포함되지 않았는지 확인해보자.

나와 같은 경우는 스프링 부트 컨테이너 내부로 접속해서, JAR 파일 압축을 풀어 확인해봤더니 logback-spring.xml 이 위치하고 있었다.

sudo docker exec -it {컨테이너 ID} /bin/bash

2. 컨테이너 내부에서 잘못된 경로로 접근을 하는 경우

1번에서 파일이 잘 있는 것을 확인했으니, 컨테이너에서 제대로 인식을 하지 못하는 원인만 남아있었다. 근데 곰곰히 생각해보니 완전 기본적인 것을 놓치고 있었던 것이다.

File 클래스는 파일 시스템 내의 절대 경로를 path로 전달해야 정상적으로 인식할 수 있다. 따라서, 파일 시스템 경로를 참조하는 경우가 아니라면 일반적으로 프로젝트 루트 디렉터리 기준 경로 또는 classpath:logback-xml 과 같은 클래스패스 경로를 통해 파일을 참조할 수 있다.

🔗 classpath: 접두사
JVM의 클래스패스에서 리소스를 찾을 때(ex) ResourceLoader, @Value ) 사용한다. 도커 컨테이너에서는 JAR 내부의 파일을 직접 참조하는 게 일반적이기 때문에, OS 시스템에 독립적인 classpath: 을 사용하는게 더 권장된다.

하지만 당연히 logback-spring.xml 파일은 압축된 JAR 파일 내부에 포함된 리소스기 때문에, 파일 시스템 상에 직접 존재하지 않아 파일 경로를 찾지 못한다는 오류가 났던 것이다.

해결 방법

그렇다면 우리는 도커를 사용하고 있으니, 빌드 과정에서 해당 파일을 도커 컨테이너의 파일 시스템으로 복사해주면 문제를 해결할 수 있다.

🔗 컨테이너 파일 시스템

도커 파일 시스템은 크게 컨테이너 레이어와 이미지 레이어로 나누어진다.

  • 컨테이너 레이어 : docker run 이후 만들어지는 Read-Write 레이어
  • 이미지 레이어 : docker build 시 만들어지는 Only Read 레이어이다.

이때 도커에서는 Copy-On-Write 전략에 의해, 쓰기 작업은 상위 컨테이너 레이어로 복사되어 이루어진다. 따라서 하나의 이미지로부터 복수의 컨테이너를 실행시켜도 정상적으로 동작하게 된다.

실제 컨테이너 사이즈를 출력했을 때 나오는 크기는 컨테이너 레이어의 크기를 의미한다.

sudo docker ps -s  -- size 출력

  • Size : 컨테이너 레이어의 데이터의 크기
  • Virtual Size : 컨테이너 레이어 + 이미지 레이어 데이터 크기

🔗 빌드 과정에서 복사하기

깃허브 액션을 통해서 배포를 하고 있었기 때문에, CI 서버 내에 파일을 업로드
할 때 logback-spring.xml 을 추가해주었다.

      - name: Build with Gradle
        run: SPRING_PROFILES_ACTIVE=test ./gradlew :${{ env.MODULE_NAME }}:clean :${{ env.MODULE_NAME }}:build
        shell: bash
        
      - name: Upload build artifact (JAR and Dockerfile)
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: |
            ./${{ env.MODULE_NAME }}/build/libs/*.jar
            ./${{ env.MODULE_NAME }}/build/resources/main/logback-spring.xml
            ./${{ env.MODULE_NAME }}/*.Dockerfile

그리고 DockerFile에 COPY 명령어를 통해 CI 서버에 있던 logback-spring.xml 파일을 도커 이미지의 config/ 디렉토리로 복사하도록 했다.

FROM eclipse-temurin:17-jdk-jammy
...
COPY build/resources/main/logback-spring.xml config/logback-spring.xml
ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul",  "-jar", "app.jar", "--spring.profiles.active=${PROFILE}"]
  • COPY : 도커 이미지 빌드 시, 해당 파일을 이미지 내부로 포함시킨다.

즉 아까 위에서 언급한 파일 시스템을 참고해보면, 다음과 같이 복사 과정이 일어나게 된다.

  1. 이미지 빌드 단계에서 logback-spring.xml 파일이 새로운 이미지 레이어에 저장된다.
  2. 도커 컨테이너 실행 시, 이미지에서 읽기 전용 파일 시스템을 로드한다.
  3. 만약 파일을 수정하면 컨테이너 레이어에서 만들어진 새로운 복사본에서 수정 작업이 일어난다.

참고 자료
https://docs.docker.com/reference/dockerfile/#copy
https://docs.docker.com/get-started/docker-concepts/building-images/understanding-image-layers/#create-a-base-image

profile
개인용으로 공부하는 공간입니다. 피드백 환영합니다 🙂

0개의 댓글

관련 채용 정보