배치

아무 인자 없이 보틀의 run() 함수를 호출하면 8080 포트로 로컬 개발 서버를 띄운다. 같은 호스트에 있으면 http://localhost:8080/을 통해 응용에 접근해서 테스트할 수 있다.

바깥 세상에서도 응용을 쓸 수 있게 하려면 서버가 리슨할 인터페이스 IP를 지정하거나 (예: run(host='192.168.0.1')) 서버가 모든 인터페이스에 리슨하게 하면 (예: run(host='0.0.0.0')) 된다. 리슨할 포트도 비슷하게 바꿀 수 있는데, 1024 아래 포트를 쓰려면 root 내지 관리자 권한이 필요하다. 80 포트가 HTTP 서버 표준 포트다.

run(host='0.0.0.0', port=80) # 모든 인터페이스에서 HTTP 요청 받기

서버 선택지

기본 내장 서버는 wsgiref WSGI 서버를 기반으로 한 것이다. 스레드 안 쓰는 이 HTTP 서버가 개발 및 운용 초기에는 충분할 수 있지만 서버 부하가 커지면 성능 병목이 될 수 있다. 그 병목을 없앨 수 있는 방법이 세 가지 있다.

  • 다중 스레드거나 비동기인 다른 서버 쓰기.

  • 서버 프로세스를 여러 개 띄우고 로드 밸런서로 부하 나누기.

  • 둘 다 하기.

다중 스레드 서버는 ‘전통적’ 방법이다. 매우 견고하고 충분히 빠르며 관리하기 쉽다. 단점으로는, 동시에 처리할 수 있는 연결 수가 제한돼 있고 “전역 인터프리터 락(GIL)” 때문에 CPU 코어를 한 개만 활용한다. 대부분 응용에선 어차피 네트워크 IO에 대기하며 대부분의 시간을 보내기 때문에 문제가 되지 않지만 CPU 집약적인 작업(예: 이미지 처리)은 속도가 떨어질 수 있다.

비동기 서버는 아주 빠르고 실질적으로 무한대의 동시 연결을 처리할 수 있으며 관리하기 쉽지만 좀 까다로울 수 있다. 그 잠재력을 완전히 이용하려면 그에 맞도록 응용을 설계해야 하며 해당 서버의 개념들을 이해하고 있어야 한다.

다중 프로세스(포크) 서버는 GIL의 제약을 받지 않아서 CPU 코어를 여러 개 활용할 수 있지만 그 때문에 서버 인스턴스 간 통신 비용이 커진다. 프로세스들 사이에 상태를 공유하려면 데이터베이스나 외부 메시지 큐가 필요하다. 아니면 아예 공유하는 상태가 필요치 않도록 응용을 설계해야 한다. 구성하는 것 역시 좀 복잡하지만 잘 작성된 튜토리얼들이 있다.

서버 백엔드 바꾸기

성능을 높이는 가장 쉬운 방법은 pastecherrypy 같은 다중 스레드 서버 라이브러리를 설치해서 단일 스레드 서버 대신 쓰게 하는 것이다.

bottle.run(server='paste')

보틀에는 많이 쓰는 여러 WSGI 서버들을 위한 어댑터가 딸려 있어서 구성이 자동으로 이뤄진다. 다음이 그 중 일부다.

이름

홈페이지

설명

cgi

CGI 스크립트 실행

flup

flup

FastCGI 프로세스로 실행

gae

gae

구글 앱 엔진 도입 위한 헬퍼

wsgiref

wsgiref

기본 단일 스레드 서버

cherrypy

cherrypy

다중 스레드, 매우 안정적

paste

paste

다중 스레드, 안정적, 경험으로 확인

rocket

rocket

다중 스레드

waitress

waitress

다중 스레드, Pyramid에서 사용

gunicorn

gunicorn

사전 포크, 일부는 C로 작성

eventlet

eventlet

WSGI 지원하는 비동기 프레임워크

gevent

gevent

비동기 (greenlet)

diesel

diesel

비동기 (greenlet)

fapws3

fapws3

비동기 (네트워크 쪽만), C로 작성

tornado

tornado

비동기, Facebook 일부에서 사용

twisted

twisted

비동기, 충분히 검증됐지만… 뒤틀렸음

meinheld

meinheld

비동기, 일부는 C로 작성

bjoern

bjoern

비동기, 매우 빠르고 C로 작성

auto

사용 가능한 서버 어댑터를 자동으로 선택

전체 목록은 server_names를 통해 얻을 수 있다.

원하는 서버 어댑터가 없거나 서버 구성을 좀 더 제어해야 한다면 서버를 직접 띄우고 싶을 수 있다. 서버 문서에서 WSGI 응용을 실행하는 방법을 찾아 보면 된다. 다음 예는 paste를 띄우는 방법이다.

application = bottle.default_app()
from paste import httpserver
httpserver.serve(application, host='0.0.0.0', port=80)

Apache mod_wsgi

보틀 내에서 HTTP 서버를 돌리지 말고 mod_wsgi를 쓰는 Apache 서버에 보틀 응용을 붙일 수도 있다.

application 객체를 제공하는 app.wsgi 파일만 만들어 주면 된다. mod_wsgi에서 그 객체를 써서 응용을 띄우는데 WSGI 호환 파이썬 콜러블이어야 한다.

/var/www/yourapp/app.wsgi 파일:

# 상대 경로가 (그리고 템플릿 탐색이) 동작하도록 작업 디렉터리 바꾸기
os.chdir(os.path.dirname(__file__))

import bottle
# ... 여기서 보틀 응용을 구성 또는 임포트 ...
# bottle.run()을 mod_wsgi과 함께 사용하지 말 것
application = bottle.default_app()

Apache 설정은 다음처럼 된다.

<VirtualHost *>
    ServerName example.com

    WSGIDaemonProcess yourapp user=www-data group=www-data processes=1 threads=5
    WSGIScriptAlias / /var/www/yourapp/app.wsgi

    <Directory /var/www/yourapp>
        WSGIProcessGroup yourapp
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>

구글 앱 엔진

New in version 0.9.

서버 어댑터 gae를 써서 구글 앱 엔진에서 응용을 돌릴 수 있다. HTTP 서버를 새로 시작하지 않는다는 점에서 cgi 어댑터와 비슷하지만 거기 더해서 구글 앱 엔진에 맞게 응용을 준비 및 최적화하고 그 API에 맞게 만들어 준다.

bottle.run(server='gae') # host나 port 설정 필요 없음

정적 파일은 GAE에서 직접 제공하게 하는 게 좋다. 다음은 app.yaml 예시다.

application: myapp
version: 1
runtime: python
api_version: 1

handlers:
- url: /static
  static_dir: static

- url: /.*
  script: myapp.py

로드 밸런서 (수동 구성)

파이썬 프로세스 하나는 가용 CPU 코어가 몇 개든 한 번에 한 CPU만 활용할 수 있다. 이때 가능한 기법은 독립된 파이썬 프로세스들로 부하를 분산해서 CPU 코어를 모두 활용하는 것이다.

보틀 응용 서버를 하나만 띄우는 게 아니라 사용 가능한 CPU 코어마다 로컬 포트를 다르게 해서 (localhost:8080, 8081, 8082, …) 한 인스턴스씩 실행한다. 원하는 대로 서버 어댑터를 고를 수 있고 비동기 어댑터도 쓸 수 있다. 다음으로 고성능 로드 밸런서가 역방향 프록시 역할을 하며 각 요청을 무작위 포트로 전달하고, 그래서 동작 중인 모든 백엔드로 부하를 분산시킨다. 이렇게 하면 모든 CPU 코어를 쓸 수 있고 심지어 여러 물리적 서버로 부하를 나눌 수도 있다.

아주 빠른 로드 밸런서로 Pound가 있지만 많이 쓰는 웹 서버 대부분에도 그런 일을 잘 해 주는 프록시 모듈이 있다.

Pound 예시:

ListenHTTP
    Address 0.0.0.0
    Port    80

    Service
        BackEnd
            Address 127.0.0.1
            Port    8080
        End
        BackEnd
            Address 127.0.0.1
            Port    8081
        End
    End
End

Apache 예시:

<Proxy balancer://mycluster>
BalancerMember http://192.168.1.50:80
BalancerMember http://192.168.1.51:80
</Proxy>
ProxyPass / balancer://mycluster

Lighttpd 예시:

server.modules += ( "mod_proxy" )
proxy.server = (
    "" => (
        "wsgi1" => ( "host" => "127.0.0.1", "port" => 8080 ),
        "wsgi2" => ( "host" => "127.0.0.1", "port" => 8081 )
    )
)

옛 시절의 CGI

CGI 서버는 요청마다 새 프로세스를 시작한다. 이로 인한 오버헤드가 크지만 때로는, 특히 저렴한 호스팅 환경에선 유일한 선택지일 수 있다. 서버 어댑터 cgi는 실제 CGI 서버를 띄우는 게 아니라 보틀 응용을 유효한 CGI 응용으로 바꿔 준다.

bottle.run(server='cgi')