Dockerfile를 사용해서 배포를 하다 보면,
도커에서 사용하는 JDK의 라이브러리로 인해 예기치 못한 에러를 경험하게 됩니다.

 

Dockerfile에서 사용하는 JDK 라이브러리는 보통 도커이미지의 용량을 최대한 줄이기 위해서 경량화된 버전을 사용하는데
기존 로컬 개발환경에 설치한 JDK버전에서 제외된 모듈로 인해서 발생하게 됩니다.

 

저의 경우,
팀에서 사용하는 로컬의 JDK와 배포시 도커파일이 사용하는 JDK버전이 다릅니다.

정확히는 버전이 다르다기 보다 패키징 옵션이 다르다고 해야겠네요.
( 이 부분은 지금 얘기할 이슈를 처리하면서 Docker와 로컬의 JDK버전을 동일하게 맞춰야 한다는 의견이 팀 내에서 논의되고 있습니다.)

로컬 개발환경 버전

$ java --version
openjdk 11.0.7 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.7+10, mixed mode)

 

도커 배포 버전

  • openjdk:11-jre-slim-buster

 

설정파일 (Dockerfile)

FROM openjdk:11-jre-slim-buster
...
...
ENV TZ=Asia/Seoul
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
...
...

ENTRYPOINT java -Xmx2048m -Xss512k -Dspring.profiles.active=production -jar /webapps/application.jar && bash

openJDK 11.0 버전대를 사용하는 것은 동일하지만 패키지가 다릅니다.
도커배포용은 패키지명에서도 알 수 있듯이 경량화된 버전입니다.

 

사실 이렇게 개발과 배포버전을 다른 게 사용하는 것이 좋은 방법은 아닙니다.
가능한 버전뿐만이 아니라 패키지까지 동일하게 사용하는 게 맞습니다.

 

하지만 개발환경에서 사용하는 OpenJDK 일반버전을 도커에서 사용하면 용량이 너무 커지게 됩니다.

그래서 보통 도커용-리눅스용-으로는 경량화 버전인 Alphine버전을 많이 사용합니다.

 

도커허브 OpenJDK 공식페이지
도커허브가 제공하는 공식이미지란

 

openjdk - Docker Hub

Supported tags and respective Dockerfile links Note: the description for this image is longer than the Hub length limit of 25000, so the "Supported tags" list has been trimmed to compensate. The full list can be found at https://github.com/docker-library/d

hub.docker.com

 

Official Images on Docker Hub

 

docs.docker.com

 

현재 도커에서 사용하는 패키지는 openjdk:11-jre-slim-buster 인데,
사실 도커에서 공식적으로 지원하는 슬림버전의 라이브러리는 아닙니다.

 

가능하면 도커에서 공식적으로 지원하는 라이브러리를 사용해야 하지만
운영상의 제약으로 어쩔 수 없이 사용하는 상황입니다.

 

이런 상황에서 발생한 이슈 중 하나를 공유합니다.

 

애플리케이션 내에 엑셀파일의 Parsing 및 Generation을 처리하기 위한 POI 라이브러리를 사용하면서 발생한 이슈인데요.
로컬에서는 정상적으로 엑셀파일 생성이 되었는데,
도커 배포 이후 엑셀파일 다운로드 시 아래의 예외가 발생했습니다.

...
...
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.ht.core.framework.common.filter.FilterAuth.doFilter(FilterAuth.java:224)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.ht.core.framework.common.filter.FilterCors.doFilter(FilterCors.java:34)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Unknown Source)
    at java.desktop/sun.font.FontManagerFactory$1.run(Unknown Source)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.desktop/sun.font.FontManagerFactory.getInstance(Unknown Source)
    at java.desktop/java.awt.Font.getFont2D(Unknown Source)
    at java.desktop/java.awt.Font.canDisplayUpTo(Unknown Source)
    at java.desktop/java.awt.font.TextLayout.singleFont(Unknown Source)
    at java.desktop/java.awt.font.TextLayout.<init>(Unknown Source)
    at org.apache.poi.ss.util.SheetUtil.getDefaultCharWidth(SheetUtil.java:274)
    at org.apache.poi.ss.util.SheetUtil.getColumnWidth(SheetUtil.java:249)
    at org.apache.poi.ss.util.SheetUtil.getColumnWidth(SheetUtil.java:234)
    at org.apache.poi.hssf.usermodel.HSSFSheet.autoSizeColumn(HSSFSheet.java:2165)
    at org.apache.poi.hssf.usermodel.HSSFSheet.autoSizeColumn(HSSFSheet.java:2147)
    ...
    ... 45 common frames omitted 

뜬금없이 Class Not Found 예외가 발생했는데요.
POI 가 엑셀 파일 생성 시 내부적으로 사용하는 java.awt 관련 API를 찾을 수 없어서 발생한 예외였습니다. 

 

잘못된 버전의 POI 라이브러리를 사용한 문제인지 아니면 비공식 슬림버전인 JDK를 사용한 게 문제인지는 좀 더 확인을 해 봐야 되는데요.

POI를 의심하는 건, 직전 JDK의 버전 업그레이드 작업이 있었기 때문입니다. ( 8 -> 11 )

우선은 빨리 해결방법을 찾는 게 우선이었습니다. 

 

로컬에서 테스트를 잘 끝내고, 배포도 정상적으로 되었는데
갑자기 서비스 운영 쪽에서 엑셀파일 다운로드가 안된다고 해서 적지 않게 당황을 했습니다.

 

해결방법

1. OpenJDK 변경 - 현재 운영환경상 꼭 해당 라이브러리를 사용해야 합니다.

2. 제외된 모듈 추가 - 이 방법으로 해결하였습니다.

  - Dockerfile에 아래와 같은 명령행을 추가합니다. java 실행 명령 전 추가해야 합니다.

RUN apt-get update; apt-get install -y fontconfig libfreetype6

3. 어플리케이션 Java 실행옵션 변경
  - 어플리케이션 실행 시  -Djava.awt.headless=true 옵션을 추가합니다.

  - 또는 환경변수에 추가를 해도 되고,  System.setProperty() 를 사용해서 지정해도 됩니다.

 


저는 2번 방법으로 해결했습니다.

 

혹시라도 이와 같은 예외가 갑자기 발생한다면,
당황하지 마시고 위와 같이 옵션을 추가하시면 됩니다.

 

당연히 근본적인 방법은 ‘로컬(개발)과 배포(상용)의 JDK 라이브러리(환경)를 통일한다’ 입니다.

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기