2018년 6월 29일 금요일

(AWS) Apache(Web Server)와 Tomcat 연동하기

 Apache Web Server를 Front에 두고 그 뒤에 Tomcat을 연동해서 서비스를 하는 형태는 실제 상용 서비스에서 많이 적용하는 형태이이다. 지난 포스팅에서 Apache Web Server 설치 방법Tomcat의 설치 방법을 다루었으므로 이번 포스팅은 두 서버를 연동하는 법을 설명하도록 하겠다.

 브라우저를 띄워서 현재 두 서버가 설치된 EC2 인스턴스의 Public IP로 연결을 해보면 80 포트는 Apache의 테스트 페이지가 보일 것이고 8080 포트는 Tomcat의 테스트 페이지가 보일 것이다. 이제 두 서버의 연동 작업을 완료하고 나면 80포트로 접속했을 때 Tomcat의 테스트 페이지가 보이게 된다.

 Apache와 Tomcat을 연동할 때는 mod_jk 라는 Tomcat Connector를 사용한다. Tomcat 테스트 페이지 하단 왼쪽의 Tomcat Connectors 링크를 눌러보자.


 링크를 따라 들어가면 아래 그림과 같은 페이지가 보인다. Binary 배포의 링크를 눌러보면 알겠지만 Binary 형태의 배포는 Windows만 지원한다. 지금은 Amazon Linux에 설치할 것이므로 Source 형태로 받아서 빌드할 것이다. tar.gz 형태로 배포하는 파일을 받도록 하자.


 로컬에 Source 파일을 받았으면 EC2 인스턴스에 복사해넣고 빌드를 해야 하므로 아래 명령으로 파일을 서버로 복사해 넣도록 한다.

$ scp -i aws_keypair.pem tomcat-connectors-1.2.xx-src.tar.gz ec2-user@{Public IP}:~/

 복사가 되었으면 이제 SSH로 EC2 인스턴스에 접속을 하도록 하자. 접속이 되었으면 자신의 home 폴더에 바로 위에서 복사한 파일이 있을 것이다. 아래 명령으로 압축을 풀어놓도록 한다.

$ tar xvfz tomcat-connectors-1.2.xx-src.tar.gz

 이제 mod_jk를 빌드해야 하지만 Amazon Linux 2 AMI로 생성한 인스턴스에는 gcc 패키지가 설치되어있지 않다. mod_jk를 빌드하는데 반드시 필요하므로 먼저 gcc 패키지를 설치한다. 아래 명령으로 gcc 패키지를 설치할 수 있다.

$ sudo yum install gcc

 그 다음 mod_jk를 빌드하려면 httpd-devel 패키지가 설치 되어있어야 한다. 만약 설치하지 않았다면 지난 포스팅을 참고하길 바란다. 아래 명령으로 설치할 수 있다.

$ sudo yum install httpd-devel

 httpd-devel 패키지를 설치하면 apxs가 설치된다. 다음 명령으로 apxs의 위치를 알아보자.

$ which apxs
/usr/bin/apxs

 화면에 출력된 apxs의 위치를 잘 기억한 후 소스 폴더 안의 native 폴더로 이동하여 빌드 & 설치를 해보도록 하자. 아래 명령을 실행하도록 한다.

$ cd tomcat-connectors-1.2.xx-src
$ cd native
$ ./configure --with-apxs=/usr/bin/apxs
$ make
$ sudo make install

 configure를 실행할 때 apxs의 위치를 정확히 지정하도록 하고 마지막 make install 명령은 root 권한으로 실행되어야 정상적으로 설치가 되므로 주의하도록 한다.

 별 문제가 없다면 mod_jk 모듈의 설치는 완료가 되었다. 홈폴더에 있는 mod_jk의 소스 파일들은 이제 삭제해도 무방하다.

 이제 mod_jk의 설치가 완료되었으니 Apache와 Tomcat의 설정을 수정할 차례이다. Tomcat쪽 설정을 먼저 보도록 하겠다. Tomcat의 기본 설치 상태에서 이미 AJP/1.3 요청은 8009 포트로 받도록 되어 있을 것이다. /etc/tomcat/server.xml 파일을 열어서 AJP/1.3 포로토콜을 처리하는 Connector 설정이 되어있는 지 확인을 해보고 안되어있으면 설정해 주도록 하자.

...
    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectport="8443">
...

 추가적인 설정이 되어있거나 다른 설정이 필요한 경우라면 위 예와 다를 수 있다. 그러한 경우라면 Connector 설정에 대해서는 어느 정도 잘 알고 있을 것이라고 생각된다. 위 예는 설치 직후의 디폴트 상태이므로 참고하기 바란다.

 Tomcat쪽 설정이 확인 및 수정되었으면 이제 Apache쪽 설정을 보도록 하자. Apache 설정 파일인 /etc/httpd/conf/httpd.conf을 열어서 아래 DSO 설정 부분을 살펴보자.

...

#
# Dynamic Shared Object (DSO) Support
#
# To be able to use the functionality of a module which was built as a DSO you
# have to place corresponding `LoadModule' lines at this location so the
# directives contained in it are actually available _before_ they are used.
# Statically compiled modules (those listed by `httpd -l') do not need
# to be loaded here.
#
# Example:
# LoadModule foo_module modules/mod_foo.so
#
Include conf.modules.d/*.conf

...

 conf.modules.d 폴더에 있는 모든 .conf 파일을 include 하고 있는 것을 확인할 수 있다. conf.modules.d 폴더에 mod_jk용 .conf 파일을 만들어서 넣어 놓으면 여기서 include 가 될 것이라는 걸 예상할 수 있다. 그러면 conf.modules.d 폴더로 이동하여 파일 목록을 보자. 여러 개의 .conf 파일과 README 파일이 있으므로 README 파일을 읽어보기 바란다.

This directory holds configuration files for the Apache HTTP Server;
any files in this directory which have the ".conf" extension will be
processed as httpd configuration files.  This directory contains
configuration fragments necessary only to load modules.
Administrators should use the directory "/etc/httpd/conf.d" to modify
the configuration of httpd, or any modules.

Files are processed in alphanumeric order.

 내용을 보면 conf.modules.d 폴더에 모듈용 .conf 파일을 넣어 놓으면 된다는 것과 alphanumeric 순서대로 처리가 된다는 것을 알 수 있다. mod_jk는 딱히 로딩순서가 중요하지는 않지만 언젠가 필요하다면 파일 이름을 적당히 조절해서 순서를 바꿀 수 있다는 것 정도만 알아두도록 하자. 실제로 conf.modules.d 폴더의 파일 목록을 보면 "00-", "01-", "10-" 등의 접두사를 사용해서 순서를 지정하고 있다는 것을 알 수 있다. 우리는 순서가 상관이 없으므로 그냥 알아보기 쉽도록 "mod_jk.conf" 라고 명명할 것이다.

 그럼 conf.modules.d 폴더에 mod_jk.conf 파일을 만들어서 아래 내용을 넣도록 하자.

# Load mod_jk module
LoadModule jk_module modules/mod_jk.so

# Where to find workers.properties
JkWorkersFile conf/workers.properties

# Where to put jk shared memory file
JkShmFile run/mod_jk.shm

# Where to put jk logs
JkLogFile logs/mod_jk.log

# Set the jk log level [debug/error/info]
JkLogLevel info

# Send all requests to worker named ajp13
JkMount /* ajp13worker

 주석을 보면 대충 어떤 내용인지 알 수 있겠지만 한 가지 짚고 넘어가야 할 게 있다. JkShmFile의 값으로 run/mod_jk.shm이 아니라 logs/mod_jk.shm을 설정하는 경우가 많은데 logs/는 selinux가 활성화된 상태에서는 shm파일을 access할 수 없기 때문에 Apache가 시작될 때 에러가 발생하며 시작되지 않는다. 그래서 반드시 run/mod_jk.shm으로 지정해야 한다. 그리고 JkWorkersFile, JkMount만 간단히 덧붙이자면 JkWorkersFile은 Apache의 worker 설정 파일을 지정한 것인데 아직 파일을 만들지는 않았으므로 아래로 계속 진행하면서 만들 예정이다. 그리고 JkMount는 모든 요청을 ajp13worker로 보낸다는 것인데 ajp13worker도 역시 worker 설정 파일의 내용에서 설정할 것이므로 차차 알게될 것이다.

 이제 workers.properties 파일을 만들 차례이다. 위에서 설명한 mod_jk.conf 파일에서 설정한 대로  /etc/httpd/conf 폴더로 가서 workers.properties 파일을 만들고 아래 내용을 넣도록 한다.

# the list of workers
worker.list=ajp13worker

worker.ajp13worker.type=ajp13
worker.ajp13worker.host=localhost
worker.ajp13worker.port=8009
worker.ajp13worker.connection_pool_timeout=600
worker.ajp13worker.socket_keepalive=1

 mod_jk.conf 파일의 마지막에 JkMount에서 ajp13worker 라고 설정했었는데 그 값이 바로 여기서 굵은 글씨로 표시한 값이다. 각 필드의 이름만 봐도 어떤 용도인지 알 수 있을 것이다.

 이제 모든 설정이 끝났다. 아래 명령으로 Apache를 재시작하자.

$ sudo service httpd restart

 재시작이 되고나면 브라우저를 띄워서 주소창에 EC2 인스턴스의 Public IP만 (포트는 없이) 넣고 페이지를 로딩해보자. 포트를 지정하지 않았으므로 기본 포트인 80 포트로 요청을 했을 것이다. 여기서 브라우저에는 Apache의 테스트 페이지가 아니라 Tomcat의 테스트 웹앱 페이지가 보여야 한다.

 Tomcat의 테스트 웹앱 페이지가 정상적으로 로딩이 되었다면 기본적인 연동은 끝났다. 이제 추가로 한 가지만 더 설정하도록 하자. mod_jk.conf 파일을 작성할 때 JkMount에 대해서 간단히 언급하고 지나갔었는데 여기서 좀 더 상세하게 설명을 하려고 한다.

 일반적으로 Apache와 Tomcat을 연동할 때 이미지 파일 같은 것들은 Tomcat까지 요청이 전달될 필요가 없고 바로 Apache에서 처리가 가능하다. 이런 정적(static)인 리소스들은 당연히 Apache 서버가 더 빠르고 효율적으로 처리할 수 있기 때문에 Tomcat으로 전달되지 않는게 더 빠르고 효율적이다. 하지만 mod_jk.conf 파일을 설정할 때 JkMount 설정에서 모든 요청을 ajp13worker로 보내도록 했기 때문에 지금은 모든 요청이 Tomcat으로 전달된다. 특정 파일에 대한 요청을 Tomcat으로 전달하지 않고 Apache에서 직접 처리하고 싶다면 어떻게 해야할까? 방법은 간단하다. 아래와 같이 mod_jk.conf 파일의 마지막에 JkUnMount 설정을 추가함으로써 특정 패턴의 url은 Tomcat으로 전달하지 않고 바로 Apache에서 처리가 되도록 설정할 수 있다.

...

# Send all requests to worker named ajp13worker
JkMount /* ajp13worker

# do not send image requests to ajp13worker
JkUnMount /*.gif ajp13worker
JkUnMount /*.jpg ajp13worker
JkUnMount /*.png ajp13worker
JkUnMount /*.svg ajp13worker

 짐작한 대로 .gif, .jpg, .png, .svg 파일들은 모두 Tomcat으로 요청하지 않도록 설정한 것이다. 이렇게 설정을 해 놓고 브라우저의 캐쉬를 모두 삭제한 후에 요청을 해보면 이미지 파일 요청들이 Tomcat으로 전달되지 않게된다. JkUnMount 설정 전과 후의 Tomcat의 access 로그를 살펴보면 아래와 같이 다르게 나온다는 걸 알 수 있다. 참고로 테스트 전에는 항상 캐쉬를 삭제해야 정확히 알 수 있다.

  • JkUnMount 설정 전

  • JkUnMount 설정 후


 JkUnMount를 설정하기 전에는 .png, .svg 파일들에 대한 요청이 Tomcat으로 모두 들어왔지만 설정 후에는 .png, .svg 파일들에 대한 요청은 모두 사라졌음을 알 수 있다. 자 그렇다면 페이지가 정상적으로 표시가 되었을까? 사실 요청이 Tomcat으로 전달되지는 않았지만 Apache에서도 제대로 처리하지 못하고 있기 때문에 페이지의 이미지들은 모두 표시되지 않고 있다. 아래 두 그림에서 빨간색 박스 부분을 비교해 보면 JkUnMount가 설정된 후에 로딩된 페이지는 로고나 배경 이미지가 모두 표시되지 않고 있다. (정확한 테스트를 위해서 항상 캐쉬를 삭제하고 테스트하길 바란다.)

 

 
<80포트 요청>                                                     <8080포트 요청>

 왼쪽의 이미지들은 80포트(Apache)를 통해서 요청한 것이고 오른쪽의 이미지들은 8080포트(Tomcat)로 직접 요청한 것이다. 주소창에 입력된 내용을 보면 알겠지만 위 쪽 페이지는 Tomcat 웹앱의 메인 페이지이고 아래 쪽의 이미지는 메인 페이지에서 Manager App 버튼을 클릭하고 들어가면 나오는 페이지이다. 80 포트를 통해서 요청한 페이지들은 이미지가 표시되지 않고 있다.

 이렇게 이미지들이 표시되지 않는 이유는 요청된 이미지들의 실제 경로가 Apache의 DocumentRoot가 아닌 Tomcat의 webapps 경로안에 위치하고 있기 때문에 Apache에서 요청된 이미지 파일들을 찾을 수 없기 때문이다. 현재 Apache의 DocumentRoot는 "/var/www/html"이고 Tomcat의 webapps 경로는 "/var/lib/tomcat/webapps"이기 때문에 Tomcat의 관리용 웹앱 페이지의 이미지들을 DocumentRoot에서 찾으려고 시도하다가 404 처리를 해버리는 것이다.

 그렇다면 이 문제를 해결하기 위해서는 어떻게 해야할까? 이 문제에 대한 해결책은 두 가지가 있다. 짐작한대로 가장 간단한 방법은 Apache의 DocumentRoot를 tomcat의 webapps 경로로 바꿔주는 것이다. Apache의 설정 파일인 /etc/httpd/conf/httpd.conf 파일을 열어서 아래 그림과 같이 "/var/www/html"로 설정된 부분을 "/var/lib/tomcat/webapps"로 바꾸고 브라우저에서 캐쉬를 삭제 후 다시 시도해보도록 하자.

# httpd.conf

...
DocumentRoot "/var/lib/tomcat/webapps"

...

<Directory "/var/lib/tomcat/webapps">
  ...
</Directory>
...

 위와 같이 수정 후 서버를 재시작하고 브라우저의 캐쉬를 삭제한 후 페이지를 다시 로딩해보면 아래와 같이 나온다.

 

 
<80 포트>                                                         <8080 포트>

 모든 이미지가 정상적으로 표시될 것이라는 예상과 달리 뭔가 잘못된 것 같다. 80포트를 통해서 요청된 메인 페이지의 이미지가 여전히 정상적으로 표시되지 않고 있다. Manager App이나 Server Status 등의 웹앱에서 표시되는 이미지는 모두 잘 표시가 되는데 메인 페이지용 웹앱의 이미지들은 모두 표시가 되지 않는다. 좀 더 자세히 관찰해보면 이미지의 요청 URL에서 Path가 root(/)인 이미지들은 표시가 되지 않는다는 것을 알 수 있다. Manager App의 경우 /manager 경로를 이용하고 있기 때문에 모든 이미지가 정상적으로 표시가 된다. 이런 현상이 나오는 이유는 Tomcat의 webapps 폴더의 내용을 보면 알 수 있다.


 위 그림을 보면 webapps 폴더 안에는 ROOT라는 폴더가 보인다. Tomcat은 기본적으로 웹앱의 context 경로가 root인 경우에 webapps 폴더 아래 "ROOT" 폴더를 두고 그 경로를 root로 사용한다. 즉 webapps 폴더 안에 있어야할 이미지들이 webapps/ROOT 안에 있기 때문에 Apache에서는 찾지 못하고 404 처리를 해버리는 것이다. webapps/ROOT 안에 있는 이미지 파일들을 webapps 폴더로 옮기면 모두 정상적으로 출력될 것이다.

 root 경로에 대한 문제는 Apache 설정을 변경하는 방법으로는 해결책이 없다. 하지만 대부분의 경우 이미지같은 정적인 리소스를 root에 모아놓고 관리하는 경우는 없을 것이다. 대부분 image, css, js 등의 하위 폴더를 두고 따로 모아서 관리하거나 하나의 webapp을 위한 하위폴더를 두고 그 안에 각종 리소스들도 같이 관리하기 때문에 이 문제는 대부분 자연스럽게 피해갈 수 있다. 그럼에도 불구하고 이런 문제가 있음을 잘 기억두고 프로젝트를 시작할 때 이 문제를 감안해서 각종 경로를 설계하는 게 좋을 것이다.

 그럼 이제 Apache의 DocumentRoot를 재정의하는 방법말고 이 문제를 해결하는 두 번째 방법을 알아보자. 두 번째 방법은 mod_jk 설정에서 JkAutoAlias를 지정하는 방법이다. Apache의 httpd.conf 파일을 열어서 DocumentRoot 설정(/var/www/html)을 다시 원래대로 복구하고 mod_jk의 설정 파일(mod_jk.conf)의 마지막에 아래 내용을 추가하도록 하자.

...

# Automatically Alias webapp context directories into the Apache document space.
JkAutoAlias /var/lib/tomcat/webapps

<Directory "/var/lib/tomcat/webapps">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

 추가된 내용을 간단히 설명하도록 하겠다. JkAutoAlias 지시자는 지정된 webapps 경로를 Apache의 Document 공간으로 인식하도록 하는 기능을 한다. 그래서 JkAutoAlias에 Tomcat webapps 폴더를 지정하면 Tomcat으로 전달되지 않는 요청들을 Apache의 DocumentRoot가 아닌 Tomcat의 webapps 폴더에서도 찾게되는 것이다.

 그리고 <Directory> 설정을 추가하는 이유는 Apache의 httpd.conf 파일에서 DocumentRoot에 지정된 폴더를 제외하고 모든 파일 시스템을 접근하지 못하도록 설정하고 있기 때문이다. 이 설정을 추가하지 않으면 JkAutoAlias를 지정했다 하더라도 403 Forbidden 에러를 반환하게 된다. 인터넷의 많은 글들을 보면 JkAutoAlias만 지정하고 <Directory> 설정에 대한 언급은 없는 경우가 대부분인데 반드시 <Directory> 설정을 추가해주어야 정상동작하므로 꼭 추가해주길 바란다. 일단 <Directory> 설정 옵션은 DocumentRoot용 설정과 같은 값으로 해주면 된다. 나중에 수정이 필요하다면 그 때 맞춰서 바꿔주면 된다.

 작업을 완료했으면 이제 아파치를 재시작하고 다시 브라우저의 캐쉬를 비우고 다시 페이지들을 요청해 보도록 하자. 페이지의 이미지가 모두 잘 표시되는가? 아마도 DocumentRoot를 재정의하는 방법과 똑같이 메인 페이지의 이미지들은 표시되지 않았을 것이다. 화면 캡쳐는 어차피 위의 이미지들과 똑같기 때문에 하지 않았다.

 root에 있는 이미지 요청들을 처리하지 못하고 404를 리턴하는 문제가 여전히 존재하지만 원인은 약간 다르다. 이 방법으로는 첫 번째 방법과는 달리 webapps/ROOT에 있는 이미지를 webapps 폴더로 옮겨놓아도 이미지를 찾지 못한다. 해결 방법은 webapps/ROOT에 있는 이미지들을 DocumentRoot(/var/www/html)로 옮겨놓는 것이다. root에 있는 리소스를 요청하면 DocumentRoot만 탐색을 하고 webapps 폴더는 탐색하지 않기 때문에 나오는 현상인데 이것이 의도된 것인지 버그인지는 모르겠다. 하지만 이것도 첫 번째 방법과 마찬가지 이유로 자연스럽게 피해갈 수 있는 문제이다.

 이 방법은 Apache의 원래 DocumentRoot 설정을 유지하면서 mod_jk를 통하는 요청에 대해서만 Tomcat의 webapps 폴더를 이용하는 설정이 가능하기 때문에 Apache의 DocumentRoot를 직접 변경하는 방법보다는 더 세련된 방법이며 필자가 선호하는 방법이다.

 독자분들께서 여기서 설명한 일련의 문제점과 원인 및 해결방법에 대해서 제대로 이해했다면 어떤 식으로 설정을 하든 적절한 해법을 마련할 수 있을 것으로 생각된다. 하지만 심각하게 고민해야 할 것이 하나 있다. 정적 리소스들만 Apache에게 서비스를 하도록 설정하는 건 실제 적용을 다시 생각해봐야 할 정도록 까다로운 작업들을 야기시킨다. 직접 해보면 알겠지만 내 경험상으로는 War 파일에 포함된 이미지 같은 정적 리소스들을 따로 복사해서 적절한 위치로 옮기는 작업이 거의 항상 필요했다. 물론 소규모의 프로젝트에서는 그렇지 않을 수도 있지만 억지로 맞추다 보면 또 다른 제약 사항들이 생기게 되고 결국 그냥 Tomcat에서 모두 처리하도록 하는 게 답이라는 생각을 하게 된다. 개발이 완료되어 더 이상의 배포가 없다면 모를까 배포할 때 마다 손이 많이 가게 되고 유지보수하는 동안 신경쓸게 많아져서 유지보수 전담 인력이 생겨야 할 정도로 피곤한 프로젝트가 될 것이다. Apache와 Tomcat이 물리적으로 분리되어 다른 장비에 설치가 되는 환경이라면 더 말할 것도 없이 정말 피곤해진다. 어떤 게 더 나은 선택인지는 여러 분의 판단에 맡기도록 하겠다.




댓글 5개:

  1. 정말 너무 좋은글 감사합니다. 보고 많은 도움이 됐습니다.

    근데 지금 저도 aws로 서버로 구성을 하고 있는데, 저는 물리서버가 두개입니다. 웹서버하나 따로 와스따로.

    지금 저의 문제는 웹서버와 와스가 아이피로는 연동인 되고 있는 상황인데,

    도메인을 따서 연결을 해야하는데, 도메인으로 연결을 하면 버츄얼호스트를 수정해야 해서 수정을 하고 잇는 중에, 자꾸 다큐먼트루트 영역에 톰캣주소를 입력해야하는데, 톰캣이 완전 다른 서버 아이피인데... 이럴때는 어떻게 하는게 좋을지 의견을 좀 여쭙고 싶습니다 ㅜㅜㅜ

    답글삭제
  2. 도메인 구성을 하려면 AWS 내 route53 으로 도메인 연동 구성을 하시면 편합니다.

    답글삭제
  3. 작성자가 댓글을 삭제했습니다.

    답글삭제
  4. Service Unavailable
    The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.

    이런 오류가 나오는데 혹시 도움 받을 수 있을까요?

    답글삭제
    답글
    1. https://bono915.tistory.com/entry/Tomcat-Apache-Service-Unavailable-error-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95

      삭제