docker로 올린 내 tomcat, 왜 배포 후 404 에러가 날까? (war, 컨텍스트, 방화벽)

2025-01-15

문제 상황

회사에서 프로젝트를 사내 온프레미스 서버에 배포하는 업무를 맡게 되었다. 요구사항은 다음과 같다.

  • Vue.js로 구성된 프론트엔드(FE) 프로젝트는 a 서버에 nginx로 배포한다.
  • Spring으로 구성된 백엔드(BE) 프로젝트는 b 서버에 Docker로 배포한다.
  • MySQL로 구성된 데이터베이스b 서버에 Docker로 배포한다. (해당 포스트에서는 다루지 않는다.)

구성한 환경은 다음과 같다.

  • 프론트 서버는 포트 8087을 사용한다.
  • 백엔드 서버는 8080 포트에서 서비스를 제공하며, 0.0.0.0:3310::3310 포트로 리다이렉션되어 Docker에서 실행된다.

모두 정상적으로 배포한 후, 프론트 서버에서 API를 호출했는데, 아래와 같이 404 에러가 발생한다.

<!doctype html>
<html lang="en">
<head>
  <title>HTTP Status 404 – Not Found</title>
  <style type="text/css">
    body {font-family:Tahoma,Arial,sans-serif;}
    h1, h2, h3, b {color:white;background-color:#525D76;}
    h1 {font-size:22px;}
    h2 {font-size:16px;}
    h3 {font-size:14px;}
    p {font-size:12px;}
    a {color:black;}
    .line {height:1px;background-color:#525D76;border:none;}
  </style>
</head>
<body>
  <h1>HTTP Status 404 – Not Found</h1>
  <hr class="line" />
  <p><b>Type</b> Status Report</p>
  <p><b>Description</b> The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.</p>
  <hr class="line" />
  <h3>Apache Tomcat/8.5.100</h3>
</body>
</html>


원인 추측 1: 방화벽의 문제인가?

먼저, 프론트 서버와 백엔드 서버에서 HTTP 요청 테스트를 진행해 보았다.

  1. 프론트 서버에서 HTTP 요청 테스트
curl http://<백엔드 서버 내부 IP>:3310/api/test

실패!

  1. 백엔드 서버에서 HTTP 요청 테스트
docker exec -it <backend-container-id> /bin/bash
curl http://<백엔드 서버 내부 IP>:3310/api/test

역시 실패!

방화벽 확인 전, 통신 흐름을 정리해 보았다.

  1. 사용자가 프론트 서버에 접속 → 요청이 8087 포트로 들어옴.
  2. nginx가 해당 요청을 백엔드 서버의 3310 포트로 전달.
  3. 백엔드 서버는 8080 포트에서 서비스하고 있기 때문에, 3310 포트로 전달된 요청을 8080 포트로 라우팅하여 처리.

따라서 확인해야 할 방화벽 설정은 다음과 같다.

  1. 프론트 서버에서 8087 포트가 열려 있는지 확인.
  2. 백엔드 서버에서 33108080 포트가 열려 있는지 확인.

방화벽 설정 확인 후, 방화벽을 열어주기 위한 방법은 다음과 같다.

  1. 프론트 서버 방화벽 확인
sudo ufw status
sudo ufw allow 8087/tcp
  1. 백엔드 서버 방화벽 확인
sudo ufw allow 3310/tcp
sudo ufw allow 8080/tcp
  1. 다시 프론트 서버에서 HTTP 요청 테스트
curl http://<백엔드 서버 내부 IP>:3310/api/test

여전히 404 에러가 발생함을 확인할 수 있었다.


원인 추측 2: 컨텍스트의 문제인가? (해결!)

만약 WAR 파일이 webapps/test-directory.war라는 이름이라면, 컨텍스트명test-directory가 된다. Tomcat 서버가 애플리케이션을 webapps/test-directory 경로로 배포했다면, 해당 API의 경로는 localhost:8080/test-directory/api/test로 지정된다는 의미이다.

그렇다면, 해당 백엔드 서버에서 배포된 WAR 파일의 경로를 알아보자.

docker exec -it <backend-container-id> /bin/bash
ls -l /usr/local/tomcat/webapps

유레카! 여기서 WAR 파일의 이름이 test-directory-x.x.x.war로 되어 있는 것을 알 수 있었다.

그럼 정말로 디렉토리의 경로가 API의 기본 경로가 될까?

docker exec -it <backend-container-id> /bin/bash
curl http://<백엔드 서버 내부 IP>:3310/test-directory-x.x.x/api/test

정상적으로 응답을 받을 수 있었다.

컨텍스트 경로를 올바르게 처리하는 방법

선택지는 두 가지가 있다.

  1. 프론트 서버에서 API를 호출할 때, prefixtest-directory-x.x.x 경로를 붙여준다.
  2. 배포되는 WAR의 명칭을 바꿔준다.

첫 번째 방법은 nginxconfig 파일에 한 줄만 추가해주면 되는 간단한 작업이었다. 그렇기 때문에 큰 고민 없이 첫 번째 방법으로 컨텍스트 경로를 처리하기로 했다.

server {
    listen 8087;

    # 생략

    location /api/ {
        rewrite ^/api(/.*)$ /test-directory-x.x.x$1 break;

        # 생략
    }
}

최종 테스트

마지막으로, 프론트 서버에서 HTTP 요청을 보내 해당 API가 정상적으로 응답하는지 테스트해 보았다.

curl http://<백엔드 서버 내부 IP>:3310/api/test

드디어 404가 아닌 200으로 정상 응답을 받았다!


결론

처음으로 web, was, db를 모두 혼자 구성하는 경험을 통해 네트워크, DevOps, CS 공부의 필요성을 절실히 느꼈다. Docker에 대한 막연한 두려움도 줄어들었고, 리눅스 명령어에 익숙해지기도 했다. 기억하자! Tomcat에서 WAR 파일을 기본적으로 /webapps 디렉토리에 배포하면, WAR 파일의 이름에 따라 컨텍스트 경로가 자동으로 설정된다!