스프링부트 서버를 띄울 때 로컬에선 콘솔로 에러 로그를 확인하면 되지만, 원격 EC2에 서버를 띄우면 에러 로그 확인이 어려워집니다. 장애 대응 시 에러 로그를 바로 확인하는 것이 중요하기에, 이번 글에서는 에러 로그를 어떻게 하면 바로 확인할 수 있는지 알아보고자 합니다. 모든 설명은 EC2에 도커 컨테이너 기반으로 서버를 띄우고, 로깅 프레임워크로 Logback을 사용한다는 가정하에 진행하겠습니다.
Logback 적용 전
아직 Logback을 적용하지 않았다면 스프링부트 서버의 로그는 파일 형태가 아닌 표준 출력을 따를 것입니다. 따라서 docker logs 명령어를 통해 로그를 확인할 수 있습니다. docker logs 명령어는 컨테이너 내부에서 출력을 보여주는 명령어입니다. docker logs 컨테이너명 또는 컨테이너 ID를 입력하면 됩니다.
>> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be6643c84166 doonutmate/doonut-production-server:latest "java -jar /app.jar" 14 hours ago Up 14 hours 0.0.0.0:8081->8081/tcp, :::8081->8081/tcp production-server
34a328286f35 mysql/mysql-server:8.0 "/entrypoint.sh mysq…" 5 days ago Up 5 days (healthy) 33060-33061/tcp, 0.0.0.0:3307->3306/tcp, :::3307->3306/tcp doonut-devdb
>> docker logs production-server
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.5)
만약 마지막에 쌓인 로그의 일부분만 확인하고 싶다면 --tail 옵션을 사용하면 됩니다.
>> docker logs --tail 15 production-server
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'imageService': Injection of autowired dependencies failed
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:498)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1416)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:910)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:788)
... 27 common frames omitted
기본적으로 위와 같은 컨테이너 로그는 JSON 형태로 도커 내부에 저장됩니다. 저장되는 파일 경로는 다음과 같습니다.
/var/lib/docker/containers/${CONTAINER_ID}/${CONTAINER_ID}-json.log
cat 명령어를 통해 어떤 형태로 저장되는 지 확인할 수 있습니다. 정제되지 않은 JSON 형태이기에 실제 운영 환경에서 로그 확인 용으로 사용하기엔 무리가 있음을 알 수 있습니다.
>> cat /var/lib/docker/containers/be6643c8416684f82a30fcdcdfb302fdccb8d460f7dbf1ca085553e3e746fde1/be6643c8416684f82a30fcdcdfb302fdccb8d460f7dbf1ca085553e3e746fde1-json.log
{"log":"\n","stream":"stdout","time":"2024-02-16T15:09:24.40164305Z"}
{"log":" . ____ _ __ _ _\n","stream":"stdout","time":"2024-02-16T15:09:24.40326988Z"}
{"log":" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\\n","stream":"stdout","time":"2024-02-16T15:09:24.403278125Z"}
{"log":"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n","stream":"stdout","time":"2024-02-16T15:09:24.40328233Z"}
{"log":" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )\n","stream":"stdout","time":"2024-02-16T15:09:24.403286323Z"}
{"log":" ' |____| .__|_| |_|_| |_\\__, | / / / /\n","stream":"stdout","time":"2024-02-16T15:09:24.403289982Z"}
{"log":" =========|_|==============|___/=/_/_/_/\n","stream":"stdout","time":"2024-02-16T15:09:24.403293513Z"}
{"log":" :: Spring Boot :: (v3.1.5)\n","stream":"stdout","time":"2024-02-16T15:09:24.410990721Z"}
{"log":"\n","stream":"stdout","time":"2024-02-16T15:09:24.411146602Z"}
Logback 적용 후
Logback을 적용했다면 표준 출력이 아닌 파일 형태로 로그를 남기게 됩니다. 이때 도커 볼륨 설정을 하지 않았다면 로그는 컨테이너 내부에만 남게 됩니다.
이를 확인하기 위해 도커 컨테이너 내부로 한번 들어가보겠습니다.
>> docker exec -it be6643c84166 /bin/bash
## 도커 컨테이너 내부
>> tail -20f doonut-support/log/error/error-2024-02-16.log
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'cloud.aws.cloudfront.prefix' in value "${cloud.aws.cloudfront.prefix}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239)
at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:200)
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:918)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1358)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:764)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:747)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:492)
... 39 common frames omitted
도커 볼륨
이쯤에서 잠시 도커 이미지와 컨테이너 구조에 대해 짚고 넘어가겠습니다.
도커 이미지로 컨테이너를 생성하면 이미지는 읽기 전용이 되며, 컨테이너의 변경 사항만 별도로 저장해서 각 컨테이너의 정보를 보존합니다. 예를 들어, 위에서 생성했던 스프링부트 서버는 이미지를 통해 생성되었지만, 그 안에 남긴 로그 파일은 컨테이너가 갖고 있습니다.
이미지는 어떠한 경우로도 변경되지 않으며, 도커는 컨테이너 계층에 원본 이미지에서 변경된 사항을 저장합니다. 따라서 컨테이너를 삭제하면 컨테이너 계층에 저장된 로그 파일이 모두 삭제됩니다.
이를 방지하기 위해 컨테이너의 데이터를 영속적 데이터를 활용할 수 있는 방법이 몇가지 있습니다. 그중 가장 대표적인 방법은 바로 볼륨을 활용하는 것입니다.
볼륨을 활용하는 방법에는 3가지가 있습니다. 1) 호스트와 볼륨을 공유할 수도 있고, 2) 볼륨 컨테이너를 활용할 수도 있으며, 3) 도커과 관리하는 볼륨을 생성할 수도 있습니다.
이번 글에서는 도커과 관리하는 볼륨을 통해 로그 파일을 영속적으로 남기는 방향으로 진행하겠습니다.
도커 볼륨을 통한 로그 파일을 host에 남기기
도커에서 제공하는 볼륨 기능을 활용해 로그 파일을 보존할 수 있습니다.
이 방법을 사용하기 위해선 우선 docker volume create 명령어로 볼륨을 생성해야 합니다.
>> docker volume create --name dev-log
dev-log
docker volume ls 명령어로 방금 생성한 볼륨을 확인할 수 있습니다.
>> docker volume ls
DRIVER VOLUME NAME
local dev-log
도커 컴포즈를 사용하면 여러개의 컨테이너를 손쉽게 관리할 수 있습니다. docker-compose.yml 파일에 아래와 같이 source에 방금 생성한 볼륨의 이름을 target에는 컨테이너 내 로그 파일 위치를 작성합니다.
version: '3'
services:
web:
container_name: production-server
image: doonutmate/doonut-production-server:latest
networks:
- doonut_network
ports:
- 8081:8081
environment:
- "SPRING_PROFILES_ACTIVE=dev"
volumes:
- type: volume
source: dev-log
target: /doonut-support/log
volumes:
dev-log:
external: true
외부에서 생성한 볼륨을 이용하기 위해서는 마지막에 external: true 옵션을 적어주어야 합니다. 이 옵션을 설정하지 않으면 도커 컴포즈는 매번 새로운 볼륨을 생성합니다.
도커 볼륨의 위치를 알기 위해선 docker volume inspect 명령어를 사용해야 합니다.
>> docker volume inspect dev-log
[
{
"CreatedAt": "2024-02-11T14:03:08Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/dev-log/_data",
"Name": "dev-log",
"Options": null,
"Scope": "local"
}
]
Mountpoint의 위치를 들어가보면 컨테이너 내부에 작성된 로그 파일이 호스트의 파일에 영속적으로 보존된 것을 확인할 수 있습니다.
>> cd /var/lib/docker/volumes/dev-log/_data
>> ls -al
total 0
drwxr-xr-x. 5 root root 43 Feb 11 14:22 .
drwx-----x. 3 root root 19 Feb 11 14:03 ..
drwxr-xr-x. 2 root root 90 Feb 17 07:19 error
drwxr-xr-x. 2 root root 87 Feb 18 00:16 info
drwxr-xr-x. 2 root root 87 Feb 16 14:53 warn
마무리
이번 글에서는 도커 컨테이너 기반으로 스프링부트 서버를 띄웠을 때 로그를 어떻게 영속적으로 보존할지에 대해서 알아보았습니다. 도커 볼륨을 이용해 로그를 보존한다면 컨테이너가 삭제될 때에도 호스트에 남겨진 로그를 통해 로그를 확인할 수 있습니다.
참고 문헌
'Programming > Spring Boot' 카테고리의 다른 글
[SpringBoot] 스크롤 API (Offset vs Keyset-Filtering) (0) | 2024.03.31 |
---|---|
Circular view path [error] 원인과 해결방법 (0) | 2023.03.26 |
Lombok 정리 (0) | 2023.01.10 |
CORS 설정시 PUT, DELETE 요청하면 OPTIONS 403에러가 나는 경우 (0) | 2021.08.24 |