배치¶
아무 인자 없이 보틀의 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 코어를 여러 개 활용할 수 있지만 그 때문에 서버 인스턴스 간 통신 비용이 커진다. 프로세스들 사이에 상태를 공유하려면 데이터베이스나 외부 메시지 큐가 필요하다. 아니면 아예 공유하는 상태가 필요치 않도록 응용을 설계해야 한다. 구성하는 것 역시 좀 복잡하지만 잘 작성된 튜토리얼들이 있다.
서버 백엔드 바꾸기¶
성능을 높이는 가장 쉬운 방법은 paste나 cherrypy 같은 다중 스레드 서버 라이브러리를 설치해서 단일 스레드 서버 대신 쓰게 하는 것이다.
bottle.run(server='paste')
보틀에는 많이 쓰는 여러 WSGI 서버들을 위한 어댑터가 딸려 있어서 구성이 자동으로 이뤄진다. 다음이 그 중 일부다.
이름 |
홈페이지 |
설명 |
---|---|---|
cgi |
CGI 스크립트 실행 |
|
flup |
FastCGI 프로세스로 실행 |
|
gae |
구글 앱 엔진 도입 위한 헬퍼 |
|
wsgiref |
기본 단일 스레드 서버 |
|
cherrypy |
다중 스레드, 매우 안정적 |
|
paste |
다중 스레드, 안정적, 경험으로 확인 |
|
rocket |
다중 스레드 |
|
waitress |
다중 스레드, Pyramid에서 사용 |
|
gunicorn |
사전 포크, 일부는 C로 작성 |
|
eventlet |
WSGI 지원하는 비동기 프레임워크 |
|
gevent |
비동기 (greenlet) |
|
diesel |
비동기 (greenlet) |
|
fapws3 |
비동기 (네트워크 쪽만), C로 작성 |
|
tornado |
비동기, Facebook 일부에서 사용 |
|
twisted |
비동기, 충분히 검증됐지만… 뒤틀렸음 |
|
meinheld |
비동기, 일부는 C로 작성 |
|
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')