톰캣은 HTTP 서버 기능을 내장하고 있고 Java NIO 를 사용하는 커넥터를 구현하는 등 전보다 많은 성능 향상이 이루어졌지만 톰캣 단독으로 사용보다는 아파치 웹 서버등 별도의 웹 서버와 연계하여 사용하고 있다.

이렇게 사용하는 이유는 여러 가지가 있지만 주요 이유는 다음과 같다.

 

 

정적 컨텐츠 서비스 효율이 뛰어남

일반적으로는 웹 서버가 이미지 파일이나 동영상등 정적 컨텐츠를 제공하는데 더 성능이 뛰어난 것으로 알려져 있다.

주의할 점은 특정 상황(톰캣에 APR native 와 sendFile 사용등)에서는 톰캣이 정적 컨텐츠 처리도 더 빠른 경우도 있으므로 Apache Benchmark 나 jMeter 등의 성능 측정 도구로 직접 서비스 사이트를 측정해 볼 필요가 있다.

 

유연한 클러스터링

하드웨어 기반의 부하 분산 장비(L4)가 없어도  아파치 웹 서버와 연계하여 사용하면 여러 대의 아파치 웹 서버와 톰캣을 클러스터로 손쉽게 구성하여 유연하게 서비스할 수 있다. 아파치 웹서버의 톰캣 연계 모듈은 특정 톰캣 인스턴스가 응답이 없으면 클러스터에서 제외하고 서비스하므로 한 대의 서버에 장애가 생겨도 정상적으로 서비스가 가능하다. 

 

모듈 기반의 확장성

아파치 웹 서버는 다양한 모듈이 제공되므로 서버의 기능을 확장할 수 있으며 mod_headers, mod_rewrite 등의 다양한 모듈을 사용하면 유연한 서비스 제공이 가능하다.

 

가상 호스트(Virtual Host)

톰캣도 Host 구문으로 가상 호스트를 사용할 수 있지만 아파치 웹 서버보다는 설정과 관리가 번거롭다. 아파치 웹 서버를 사용하면 하나의 서버에 여러 개의 가상 호스트를 설정하여 가상 호스트별로(예: app1.example.com, app2.example.com) 은 톰캣에서 처리하고 특정 확장자(.php)나 가상 호스트는(예:wordpress.example.com) 는 PHP 기반의 서비스를 제공하도록 설정할 수 있다.

 

보안

웹 서버가 필요한 가장 중요한 이유중 하나이다.

먼저 권한 부분을 보자. 유닉스의 설계상 1024 이하의 포트를 사용하려면 루트 사용자여야 가능하다. 그러므로 톰캣을 루트 사용자로 구동해야 하며 이는 보안상 많은 문제를 야기할 수 있다. 그렇다고 1024 이후의 포트를 사용하면 클라이언트들은 서비스 URL과 서비스 포트 번호를 외워야 하는 어려움이 있다. 

아파치 웹 서버는 루트로 구동해도 자식 프로세스를 fork 한후에 apache 그룹과 계정으로 전환한다.

 

톰캣은 특성상 MySQL, 오라클, MariaDB 등 DB 서버와 같이 사용하게 되며 DB 서버에는 기업의 중요한 정보가 보관되므로 외부에서 연결할 수 없게 격리된 내부 네트워크 환경을 사용한다.

웹 서버는 외부에 서비스를 제공해야 하므로 DMZ 라고 불리우는 외부망과 내부망 사이에 위치해야 하며 톰캣을 웹 서버로 사용한다면 DMZ 의 톰캣에서 DB 서버로 바로 연결을 허용해야 하므로 2차 방화벽이 존재해도 의미가 없다.

이런 구성에서는 WAS 가 해킹당하면 WAS 에 있는 DB 서버 설정을 보면 DB 서버의 연결 정보를 알수 있고 연결이 가능하므로 데이타까지 유출될수 있다.

 

아파치 웹 서버를 DMZ 영역에 두고 서비스하면 위와 같은 우려가 없어진다. 웹 서버는 오로지 톰캣에만 연결할 수 있으므로 웹 서버가 해킹당해도 DB 서버까지 침입하기가 힘들다.

SELinux 까지 사용한다면 아파치 웹 서버는 오로지 8080, 8009 포트만 접근할 수 있으므로 톰캣에 서비스 용도외에는 연결 자체가 불가능해지므로 웹 서버가 공격받아도 2차 피해를 최소화할 수 있다.

 

연계 방식

크게 mod_jk 와 mod_proxy 두 개의 연계 방식이 있다.두 가지 방식 모두 장단점이 있으며 환경이나 필요 여부에 따라 골라서 선택하면 된다.

먼저 특징을 정리한 다음 표를 보자

mod_proxy는 톰캣의 HTTP 커넥터와 연계되며 아파치에 기본 탑재된 모듈이므로 별도의 설치가 필요없고 HTTP 프락시 이므로 WAS 의 종류에 구애받지 않는 장정이 있으며 mod_jk 보다는 설정이 간편한 편이다.

 

mod_jk 는 톰캣의 AJP 커넥터와 연계되는 모듈로 톰캣 전용 바이너리 프로토콜인 AJP 를 사용하므로 mod_proxy 보다 속도가 빠른 편이다.

하지만 설정이 mod_proxy 보다 어렵고 별도의 모듈을 컴파일해서 설치해야 하며 톰캣 전용 프로토콜이므로 WAS 가 다른 제품으로 변경되면 사용이 불가능하다.

 

다음 장에서 다룰 sonatype nexus 의 경우 jetty 라는 WAS 를 내장하고 있으므로 mod_jk 로 연계할 수 없으며 mod_proxy 를 사용해야 한다.

아파치의 모듈과 톰캣의 커넥터와 연결 관계는 아래의 그림과 같으며 보통 하나의 연결 방식을 사용하므로 사용하지 않는 아파치 모듈과 톰캣의 커넥터는 주석 처리하여도 정상 동작에는 문제가 없다.

 

이 책에서는 두 가지 방법 모두를 설명하지만 어플리케이션 서버와 연계시에는 mod_proxy 를 사용하고 설정할 것이다.

mod_jk 로 연계

mod_jk 설치

yum 으로는 제공되지 않으므로 이미 컴파일된 바이너리를 구하거나 소스를 받아서 컴파일해서 설치해야 한다.

컴파일된 바이너리는 책의 부록을 참고하고 소스를 받아서 설치하는 방법을 알아 보자.

  1. 먼저 컴파일러와 웹 서버 개발 패키지를 설치해야 한다. 다음 명령어로 설치하자.

    yum install gcc gcc-c++ httpd-devel

  2. tomcat 홈페이지에서 connector 를 배포하는 곳(http://tomcat.apache.org/connectors-doc/)에서 최신 소스를 다운 받는다.

  3. 압축을 해제하고 폴더로 이동한다.

    tar zxvf tomcat-connector*

    cd tomcat-connec*src

  4. native 폴더로 이동한 후에 Makefile 을 생성하기 위해 autoconf 스크립트를 실행한다. 실행시 아파치 익스텐션 설치를 도와주는 유틸리티인 apxs  의 절대 경로를 설정해 주어야 정상적으로 컴파일이 가능하다.

    cd native

    ./configure --with-apxs=/usr/sbin/apxs

     

    config.status: creating apache-2.0/Makefile.apxs
    config.status: creating common/Makefile
    config.status: creating common/list.mk
    config.status: creating common/jk_types.h
    config.status: creating common/config.h
    config.status: common/config.h is unchanged
    config.status: executing depfiles commands

     

  5. make 를 실행하여 컴파일을 수행한다.
  6. 정상적으로 컴파일이 완료되었으면 make install 로 설치를 진행한다.
  7. 제대로 설치되었다면 /etc/httpd/modules/ 밑에 mod_jk.so 라는 파일이 생성될 것이다. SELinux 보안 정책에 의거해 아파치 웹서버는 모듈에 httpd_modules_t 보안 컨텍스트가 있는 모듈만 로딩할 수 있다. 정상 설치라면 저 컨텍스트가 적용되어 있겠지만 다시 한 번 확인해보고 없으면 restorecon 으로 컨텍스트를 복구하자.

    # ls -lZ /etc/httpd/modules/mod_jk.so 


    -rwxr-xr-x. root root unconfined_u:object_r:httpd_modules_t:s0 /etc/httpd/modules/mod_jk.so

     

mod_jk 설정

이제 아파치 mod_jk 설정을 변경하여 톰캣과 연동해 보자.

  1. 아파치의 설정 파일이 있는 경로인 /etc/httpd 로 이동한다.

    cd /etc/httpd

  2. 선호하는 에디터로 conf/httpd.conf 파일을 연다.
  3. LoadModule 항목을 찾아서 다음 내용을 추가하고 저장한다. (까만 부분이 추가할 부분이다.)

    LoadModule version_module modules/mod_version.so
    LoadModule jk_module modules/mod_jk.so

  4. conf.d/mod_jk.conf 을 에디터로 연 후에 다음 내용을 추가하고 저장한다.

    <IfModule mod_jk.c>
      # Where to find workers.properties
      JkWorkersFile conf/workers_jk.properties
      
      # Where to put jk shared memory
      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
      
      # Select the timestamp log format
      JkLogStampFormat "[%a %b %d %H:%M:%S %Y] " 
    </IfModule>
    CODE
  5. conf/workers_jk.properties 을 에디터로 열어서 커넥터 연결 설정을 추가한다.

    worker.list=server1, server2
    ## 톰캣의 AJP 커넥터가 사용하는 포트를 지정한다.
    worker.server1.port=8009
    ## 톰캣을 구동하는 서버의 도메인 또는 ip 를 지정한다.
    worker.server1.host=server1.example.com
    worker.server1.type=ajp13
    worker.server1.lbfactor=1
    worker.server2.port=9009
    ## 톰캣을 구동하는 서버의 도메인 또는 ip 를 지정한다.
    worker.server2.host=server1.example.com
    worker.server2.type=ajp13
    worker.server2.lbfactor=1
    CODE

     worker.list
      # 여러 개의 워커가 있을 경우 콤마를 구분자로 적어준다. 

  6. 가상 호스트 설정에 사용할 mod_jk 워커와 URL 패턴을 기술해 준다.

    <VirtualHost *:80>
        ServerName tomcat.example.com
        ServerAdmin webmaster@example.com
    	# 모든 요청을 server1 워커에게 보낸다.
        JkMount /* server1
    	# /app2/로 시작되는 모든 요청은 server2 워커에게 보낸다.
        JkMount /app2 server2
        JkMount /app2/* server2
    	# URL 이 /exclude 로 시작되는 요청은 톰캣에 보내지 않는다. 이럴 경우 DocumentRoot 에 설정된 경로에서 컨텐츠를 찾아서 처리하게 된다.
        JkUnMount /exclude/* server1
        JkUnMount /*.php server1
        ErrorLog logs/tomcat.example.com-error_log
        CustomLog logs/tomcat.example.com-access_log common
    </VirtualHost>
    CODE

     

    JkMount 
    워커에 요청을 보낼 URL 패턴을 기술한다. 한 가상 호스트에 여러 번 기술할 수 있으며 여러 워커를 혼용하여 사용 가능하다.
    JkUnMount - 워커에 요청하지 않을 URL 패턴을 기술한다. 한 가상 호스트에 여러 번 기술할 수 있다. 

  7. httpd 서비스를 재구동하여 설정을 반영한다.

    service httpd restart

  8. 이제 톰캣과 연결된 가상호스트에 연결하면 톰캣의 초기 화면이 보인다면 제대로 설정된 것이다. 

 

mod_proxy 연계

mod_proxy 는 아파치 웹서버에 기본 포함되어 있는 모듈로 포워드 프락시(Forward proxy)와 리버스 프락시(Reverse proxy) 역할을 수행하는 모듈이다.

두가지 방식의 프락시에 대해서 간단하게 살펴 보도록 하자.

 

포워드 프락시

 

포워드 프락시는 그림처럼 사용자가  특정 사이트(example.com)에 연결시 사용자 PC 가 직접 연결하는게 아니라 포워드 프록시 서버가 요청을 받아서 사이트에 연결하고 그 결과를 클라이언트에 전달(forward) 해 준다.

포워드 프록시는 대개 캐슁 기능이 있으므로 자주 사용되는 컨텐츠라면 월등한 성능 향상을 가져올 수 있으며  사용자는 정해진 사이트만 연결할수 있는등 웹 사용 환경을 제한할수 있으므로 기업 환경등에서 많이 사용한다

 

리버스 프락시


 

클라이언트가 example.com 웹 서버에 데이타를 요청하면 Reverse Proxy는 이 요청을 받아서 내부 서버에서 데이타를 받은후에 이 데이타를 클라이언트에 전달하게 된다.

보통 기업의 네트워크 환경은 DMZ 라고 하는 내부 네트워크와 외부 네트워크 사이에 위치하는 구간이 존재하며 이 구간에는 메일 서버, 웹 서버, FTP 서버등 외부 서비스를 제공하는 서버가 위치하게 된다.

WAS 나 DB 는 내부망에 위치하므로 DMZ 에 위치시키면 보안에 매우 취약해 지므로 프락시 서버를 두고 프락시가 내부 서비스와 통신해서 결과를 클라이언트에게 제공하는 방식으로 서비스를 하게 된다.

이런 방식으로 동작할 때 리버스 프락시라고 한다.

리버스 프락시는 HTTP 방식으로 WAS 와 연결하므로 WAS 종류가 무엇이든 상관없이 웹 서버와 연계 할 수 있다.

 

설정

mod_proxy 는 RHEL 에서는 기본 모듈이며 자동으로 켜져 있으므로 별도의 설치가 필요없고 사용할 가상 호스트에 바로 설정만 해주면 된다.

SELinux 주의 사항

톰캣의 HTTP 커넥터가 사용하는 기본 포트인 8080 은 SELinux 정책중 http_cache_port_t 보안 컨텍스트에 할당되어 있고 http_cache_port_t 는 아파치 웹 서버가 연결하지 못 하므로 mod_proxy 사용시 문제가 발생한다. 다음 명령어로 8080 을 http_port_t 로 변경해 주자.

# semanage port -m -p tcp -t http_port_t  8080

 

  1. 가상 호스트 설정 파일(conf/http-vhost.conf)을 에디터로 열고 다음 내용을 추가한다.

    <VirtualHost *:80>
    	ServerName tomcat.example.com
    	ServerAdmin webmaster@example.com
    
    	ProxyRequests Off
    	ProxyPreserveHost On
    	<Proxy *>        
    		Order deny,allow
            Allow from all
    	</Proxy>
    
    	ProxyPass / http://server1.example.com:8080/
    	ProxyPassReverse / http://server1.example.com:8080/
        <Location />
            Order allow,deny
            Allow from all
        </Location>
    
    	ErrorLog logs/tomcat.example.com-error_log
    	CustomLog logs/tomcat.example.com-access_log common
    </VirtualHost>
    CODE
    • ProxyRequests - 포워드 프락시로 사용할 경우 On 으로 리버스 프락시로 사용할 경우 Off 로 설정한다.
    • ProxyPreserveHost - HTTP 호스트가 받은 HTTP 요청을 프락시 요청시 사용한다. 리버스 일때 On 으로 설정해야 한다.

    • ProxyPass - 가장 중요한 키워드로 프락시에 연결할 URL 을 기술한다. 웹서버의 가상 호스트이름이 tomcat.example.com 이므로 위의 예는 http://tomcat.example.com 로 클라이언트가 요청시 아파치는 http://server1.example.com:8080/ 에 연결하게 된다. 
      만약 ProxyPass /mirror/foo  http://server1.example.com:8080/ 로 설정시 클라이언트는 http://tomcat.example.com/mirror/foo 로 연결해야 리버스 프락시에 연결할 수 있다. 
    • ProxyPassReverse - WAS 가 redirect HTTP 응답을 보냈을 경우  Location, Content-Location HTTP 헤더를 수정하여 클라이언트에 전달한다. 러비스 프락시가 이 헤더를 수정하지 않으면 클라이언트는 redirect 시 제대로 연결할 수 없으므로 꼭 설정해야 한다.

     

  2. 웹서버를 재구동하여 설정을 반영하자. 

    service httpd restart

  3. 이제 톰캣과 연결된 가상호스트에 연결하면 톰캣의 초기 화면이 보인다면 제대로 설정된 것이다. 

톰캣이 https 로 동작할 때 mod_proxy 로 연결할 경우 아파치 웹서버의 VirtualHost 에 다음 설정이 있어야 리버스 프록시가 동작한다.

SSLProxyEngine on

 

8080 포트 닫기

어느 방식이든 연계가 끝났다면 톰캣의 포트인 8080 을 방화벽에서 열 필요가 없다. 전절에서 열은 포트를 닫아 보자.

  1. iptables 설정 파일인 /etc/sysconfig/iptables 을 에디터로 연다. 
  2. 설정 파일에서 8080 이 기술된 라인(까만색 표시)를 삭제하고 저장한다.

    -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 8080 -j ACCEPT

  3. 저장하고 방화벽 규칙을 반영한다.

    service iptables restart

  4. 브라우저로 8080 포트로 연결하여 차단 되었는지 확인한다. 


manager context 보안 적용

전절에서 익힌 아파치 웹서버를 사용하면 URL 이나 디렉터리별로 접근 권한을 설정할 수 있다. 이제 톰캣과 연계되었으므로 manager, host-manager 에 Location 지시자를 이용하여 접근 통제를 걸어 보자.

아파치의 설정 파일을 열고 다음 내용을 추가하자.

<Location /manager>
	Order deny,allow
Deny from all
Allow from 127.0.0.1 192.168.0.3 192.168.152.0/24
</Location>
  • Order deny,allow - 접근 제어를 거부, 허용 순서로 지정한다.
  • Deny from all - 모든 클라이언트의 연결을 거부한다.
  • Allow from 127.0.0.1 192.168.0.3  192.168.152.0/24 - localhost 와 192.168.0.3 그리고 192.168.152 대역을 가진 IP 에서만 연결을 허용한다.

이제 웹서버를 재구동하고 manager URL 에 연결하면 403 Forbidden 에러가 발생하는 것을 확인할 수 있다.

host-manager 에 대해서도 마찬가지로 Location 지시자로 접근 통제를 걸 수 있다.

 

ProxyPass 로 설정한 톰캣의 URL 이 다를 경우 Location 지시자 뒤의 /manager 를 변경된 URL 로 수정해야 한다.

예로 ProxyPass /abc http://localhost:8080/ 로 지정했을 경우 Location 도 /abc/manager 로 변경해야 한다.

 

SELinux 설정

아파치와 톰캣을 연계할 경우 기본 포트가 아닌 톰캣과 연결할 경우 SELinux 설정을 수정해야 하는 것을 잊는 것이다. SELinux 정책에 위반되면 다음과 같은 로그가 audit.log 에 남게 된다.

 audit2why < /var/log/audit/audit.log

type=AVC msg=audit(1380255768.480:232202): avc:  denied  { name_connect } for  pid=27935 comm="httpd" dest=9080 scontext=unconfined_u:system_r:httpd_t:s0 tcontext=system_u:object_r:transproxy_port_t:s0 tclass=tcp_socket

        Was caused by:
        The boolean httpd_can_network_connect was set incorrectly.
        Description:
        Allow HTTPD scripts and modules to connect to the network using TCP.
        Allow access by executing:
        # setsebool -P httpd_can_network_connect 1

위 로그 내용은 httpd 가 9080 포트에 연결하려고 해서 차단했다는 의미이다. 

흔히 하는 실수이나 서비스에 영향을 줄 수 있으므로 잊지 말고 SELinux 설정을 수정하자.

  1. 현재 등록된 http

    semanage port -l |grep http_port_t

     

    http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000

     

  2. 아파치가 연결할 수 있는 포트는 위에 출력된 포트이다. 연결하려는 톰캣의 포트(예: 9080)가 위의 포트가 없다면 SELinux 정책에 추가해 주어야 한다.

    semanage port -a -p tcp -t http_port_t 9080

     

  3. 아파치 웹 서버를 재구동한후에 브라우저로 연결하여 정상 동작 여부를 확인해 본다.

 

마치며

톰캣은 NIO 지원, 메모리 누수 검출/예방, 보안 기능 강화등 많은 개선이 이루어졌다. 

톰캣에 대한 인식도 가벼운 서블릿/JSP 컨테이너에서 상용 서비스에 사용해도 손색없는 자바 어플리케이션 서버로 거듭나고 있다.

개발하려는 자바 웹 어플리케이션이 J2EE 기반이 아니고 스프링등의 경량화된 프레임웍이라면 톰캣은 훌륭한 WAS 의 후보가 될 수 있다.

 

이미 톰캣에 친숙한 독자들도 많을테지만 익숙하지 않은 독자들을 위해 톰캣의 기본적인 사용법과 웹서버 연계에 대해서 알아 보았다.

이제 2부에서는 앞에서 익힌 지식을 바탕으로 리눅스에 여러 어플리케이션을 설치하여 SW 개발의 핵심 인프라로 활용하는 방법에 대해서 알아 보자.