diff --git a/CHANGES.rst b/CHANGES.rst index ae1fb8e34a2..773ba99bda8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -64,7 +64,7 @@ CHANGES - Raise `ClientDisconnectedError` to `FlowControlStreamReader` read function if `ClientSession` object is closed by client when reading data. #1323 -- +- Document deployment without `Gunicorn` #1120 - diff --git a/docs/deployment.rst b/docs/deployment.rst new file mode 100644 index 00000000000..134d76c26eb --- /dev/null +++ b/docs/deployment.rst @@ -0,0 +1,308 @@ +========================= +aiohttp server deployment +========================= + +There are several options for aiohttp server deployment: + +* Standalone server + +* Running a pool of backend servers behind of :term:`nginx`, HAProxy + or other *reverse proxy server* + +* Using :term:`gunicorn` behind of *reverse proxy* + +Every method has own benefits and disadvantages. + + +.. _aiohttp-deployment-standalone: + +Standalone +========== + +Just call :func:`aiohttp.web.run_app` function passing +:class:`aiohttp.web.Application` instance. + + +The method is very simple and could be the best solution in some +trivial cases. But it doesn't utilize all CPU cores. + +For running multiple aiohttp server instances use *reverse proxies*. + +.. _aiohttp-deployment-nginx-supervisord: + +Nginx+supervisord +================= + +Running aiohttp servers behind :term:`nginx` makes several advantages. + +At first, nginx is the perfect frontend server. It may prevent many +attacks based on malformed http protocol etc. + +Second, running several aiohttp instances behind nginx allows to +utilize all CPU cores. + +Third, nginx serves static files much faster than built-in aiohttp +static file support. + +But this way requires more complex configuration. + +Nginx configuration +-------------------- + +Here is short extraction about writing Nginx configuration file. +It doesn't cover all available Nginx options. + +For full reference read `Nginx tutorial +`_ and `official Nginx +documentation +`_. + +First configure HTTP server itself:: + + http { + server { + listen 80; + client_max_body_size 4G; + + server example.com; + + location / { + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_redirect off; + proxy_buffering off; + proxy_pass http://aiohttp; + } + + location /static { + # path for static files + root /path/to/app/static; + } + + } + } + +This config listens on port ``80`` for server named ``example.com`` +and redirects everything to ``aiohttp`` backend group. + +Also it serves static files from ``/path/to/app/static`` path as +``example.com/static``. + +Next we need to configure *aiohttp upstream group*:: + + http { + upstream aiohttp { + fail_timeout=0 means we always retry an upstream even if it failed + # to return a good HTTP response + + # TCP servers + server 127.0.0.1:8081 fail_timeout=0; + server 127.0.0.1:8082 fail_timeout=0; + server 127.0.0.1:8083 fail_timeout=0; + server 127.0.0.1:8084 fail_timeout=0; + } + } + +All HTTP requests for ``http://example.com`` except ones for +``http://example.com/static`` will be redirected to +``127.0.0.1:8081``, ``127.0.0.1:8082``, ``127.0.0.1:8083`` or +``127.0.0.1:8084`` *backend proxies*. + +By default Nginx uses round-robin algorithm for backend selection. + +.. note:: + + Nginx is not the only existing *reverse proxy server* but the most + popular one. Alternatives like HAProxy may be used as well. + +Supervisord +----------- + +After configuring Nginx we need to start our aiohttp backends. Better +to use some tool for starting them automatically after system reboot +or backend crash. + +There are very many ways to do it: Supervisord, Upstart, Systemd, +Gaffer, Circus, Runit etc. + +Here we'll use `Supervisord `_ for example:: + + [program:aiohttp_1] + cmd=/path/to/aiohttp_example.py 8081 + user=nobody + autostart=true + autorestart=true + + [program:aiohttp_2] + cmd=/path/to/aiohttp_example.py 8082 + user=nobody + autostart=true + autorestart=true + + [program:aiohttp_3] + cmd=/path/to/aiohttp_example.py 8083 + user=nobody + autostart=true + autorestart=true + + [program:aiohttp_4] + cmd=/path/to/aiohttp_example.py 8084 + user=nobody + autostart=true + autorestart=true + +The config will run four aiohttp server instances, ports are specified +by command line. + +aiohttp server +-------------- + +The last step is preparing aiohttp server for working with supervisord. + +Assuming we have properly configured :class:`aiohttp.web.Application` +and port is specified by command line the task is trivial:: + + # aiohttp_example.py + import argparse + from aiohttp import web + + parser = argparse.ArgumentParser(description="aiohttp server example") + parser.add_argument('port', type=int) + + + if __name__ == '__main__': + app = web.Application() + # configure app + + args = parser.parse_args() + web.run_app(app, port=args.port) + +For real use cases we perhaps need to configure other things like +logging etc. but it's out of scope of the topic. + + +.. _aiohttp-deployment-gunicorn: + +Nginx+Gunicorn +============== + +aiohttp can be deployed using `Gunicorn +`_, which is based on a +pre-fork worker model. Gunicorn launches your app as worker processes +for handling incoming requests. + +In opposite to deployment with :ref:`bare Nginx +` the solution doesn't need to +manually run several aiohttp processes and use tool like supervisord +for monitoring it. But nothing is for free: running aiohttp +application under gunicorn is slightly slower. + + +Prepare environment +------------------- + +You firstly need to setup your deployment environment. This example is +based on Ubuntu 14.04. + +Create a directory for your application:: + + >> mkdir myapp + >> cd myapp + +Ubuntu has a bug in pyenv, so to create virtualenv you need to do some +extra manipulation:: + + >> pyvenv-3.4 --without-pip venv + >> source venv/bin/activate + >> curl https://bootstrap.pypa.io/get-pip.py | python + >> deactivate + >> source venv/bin/activate + +Now that the virtual environment is ready, we'll proceed to install +aiohttp and gunicorn:: + + >> pip install gunicorn + >> pip install -e git+https://github.com/KeepSafe/aiohttp.git#egg=aiohttp + + +Application +----------- + +Lets write a simple application, which we will save to file. We'll +name this file *my_app_module.py*:: + + from aiohttp import web + + def index(request): + return web.Response(text="Welcome home!") + + + my_web_app = web.Application() + my_web_app.router.add_get('/', index) + + +Start Gunicorn +-------------- + +When `Running Gunicorn +`_, you provide the name +of the module, i.e. *my_app_module*, and the name of the app, +i.e. *my_web_app*, along with other `Gunicorn Settings +`_ provided as +command line flags or in your config file. + +In this case, we will use: + +* the *'--bind'* flag to set the server's socket address; +* the *'--worker-class'* flag to tell Gunicorn that we want to use a + custom worker subclass instead of one of the Gunicorn default worker + types; +* you may also want to use the *'--workers'* flag to tell Gunicorn how + many worker processes to use for handling requests. (See the + documentation for recommendations on `How Many Workers? + `_) + +The custom worker subclass is defined in +*aiohttp.worker.GunicornWebWorker* and should be used instead of the +*gaiohttp* worker provided by Gunicorn, which supports only +aiohttp.wsgi applications:: + + >> gunicorn my_app_module:my_web_app --bind localhost:8080 --worker-class aiohttp.worker.GunicornWebWorker + [2015-03-11 18:27:21 +0000] [1249] [INFO] Starting gunicorn 19.3.0 + [2015-03-11 18:27:21 +0000] [1249] [INFO] Listening at: http://127.0.0.1:8080 (1249) + [2015-03-11 18:27:21 +0000] [1249] [INFO] Using worker: aiohttp.worker.GunicornWebWorker + [2015-03-11 18:27:21 +0000] [1253] [INFO] Booting worker with pid: 1253 + +Gunicorn is now running and ready to serve requests to your app's +worker processes. + +.. note:: + + If you want to use an alternative asyncio event loop + `uvloop `_, you can use the + ``aiohttp.worker.GunicornUVLoopWebWorker`` worker class. + + +More information +---------------- + +The Gunicorn documentation recommends deploying Gunicorn behind an +Nginx proxy server. See the `official documentation +`_ for more +information about suggested nginx configuration. + + +Logging configuration +--------------------- + +``aiohttp`` and ``gunicorn`` use different format for specifying access log. + +By default aiohttp uses own defaults:: + + '%a %l %u %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i"' + +For more information please read :ref:`Format Specification for Accees +Log `. + +.. disqus:: + :title: aiohttp deployment with gunicorn diff --git a/docs/glossary.rst b/docs/glossary.rst index 95d213d9597..0288a848e8c 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -45,6 +45,13 @@ https://pypi.python.org/pypi/cchardet/ + gunicorn + + Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for + UNIX. + + http://gunicorn.org/ + keep-alive A technique for communicating between HTTP client and server @@ -54,6 +61,13 @@ It makes communication faster by getting rid of connection establishment for every request. + nginx + + Nginx [engine x] is an HTTP and reverse proxy server, a mail + proxy server, and a generic TCP/UDP proxy server. + + https://nginx.org/en/ + resource A concept reflects the HTTP **path**, every resource corresponds diff --git a/docs/gunicorn.rst b/docs/gunicorn.rst deleted file mode 100644 index 39a7c03fe68..00000000000 --- a/docs/gunicorn.rst +++ /dev/null @@ -1,118 +0,0 @@ -.. _deployment-using-gunicorn: - -Deployment using Gunicorn -========================= - -aiohttp can be deployed using `Gunicorn -`_, which is based on a -pre-fork worker model. Gunicorn launches your app as worker processes -for handling incoming requests. - -Prepare environment -------------------- - -You firstly need to setup your deployment environment. This example is -based on Ubuntu 14.04. - -Create a directory for your application:: - - >> mkdir myapp - >> cd myapp - -Ubuntu has a bug in pyenv, so to create virtualenv you need to do some -extra manipulation:: - - >> pyvenv-3.4 --without-pip venv - >> source venv/bin/activate - >> curl https://bootstrap.pypa.io/get-pip.py | python - >> deactivate - >> source venv/bin/activate - -Now that the virtual environment is ready, we'll proceed to install -aiohttp and gunicorn:: - - >> pip install gunicorn - >> pip install -e git+https://github.com/KeepSafe/aiohttp.git#egg=aiohttp - - -Application ------------ - -Lets write a simple application, which we will save to file. We'll -name this file *my_app_module.py*:: - - from aiohttp import web - - def index(request): - return web.Response(text="Welcome home!") - - - my_web_app = web.Application() - my_web_app.router.add_get('/', index) - - -Start Gunicorn --------------- - -When `Running Gunicorn -`_, you provide the name -of the module, i.e. *my_app_module*, and the name of the app, -i.e. *my_web_app*, along with other `Gunicorn Settings -`_ provided as -command line flags or in your config file. - -In this case, we will use: - -* the *'--bind'* flag to set the server's socket address; -* the *'--worker-class'* flag to tell Gunicorn that we want to use a - custom worker subclass instead of one of the Gunicorn default worker - types; -* you may also want to use the *'--workers'* flag to tell Gunicorn how - many worker processes to use for handling requests. (See the - documentation for recommendations on `How Many Workers? - `_) - -The custom worker subclass is defined in -*aiohttp.worker.GunicornWebWorker* and should be used instead of the -*gaiohttp* worker provided by Gunicorn, which supports only -aiohttp.wsgi applications:: - - >> gunicorn my_app_module:my_web_app --bind localhost:8080 --worker-class aiohttp.worker.GunicornWebWorker - [2015-03-11 18:27:21 +0000] [1249] [INFO] Starting gunicorn 19.3.0 - [2015-03-11 18:27:21 +0000] [1249] [INFO] Listening at: http://127.0.0.1:8080 (1249) - [2015-03-11 18:27:21 +0000] [1249] [INFO] Using worker: aiohttp.worker.GunicornWebWorker - [2015-03-11 18:27:21 +0000] [1253] [INFO] Booting worker with pid: 1253 - -Gunicorn is now running and ready to serve requests to your app's -worker processes. - -.. note:: - - If you want to use an alternative asyncio event loop - `uvloop `_, you can use the - ``aiohttp.worker.GunicornUVLoopWebWorker`` worker class. - - -More information ----------------- - -The Gunicorn documentation recommends deploying Gunicorn behind an -Nginx proxy server. See the `official documentation -`_ for more -information about suggested nginx configuration. - - -Logging configuration ---------------------- - -``aiohttp`` and ``gunicorn`` use different format for specifying access log. - -By default aiohttp uses own defaults:: - - '%a %l %u %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i"' - -For more information please read :ref:`Format Specification for Accees -Log `. - -.. disqus:: - :title: aiohttp deployment with gunicorn diff --git a/docs/index.rst b/docs/index.rst index a78a204c7c4..e531137f42f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -205,7 +205,7 @@ Contents api logging testing - gunicorn + deployment faq essays contributing diff --git a/docs/logging.rst b/docs/logging.rst index 342bcb6d9df..b6ad5ed1f51 100644 --- a/docs/logging.rst +++ b/docs/logging.rst @@ -95,7 +95,7 @@ Default access log format is:: .. note:: When `Gunicorn `_ is used for - :ref:`deployment ` its default access log format + :ref:`deployment ` its default access log format will be automatically replaced with the default aiohttp's access log format. If Gunicorn's option access_logformat_ is diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 8a5cb60a5b4..76a6d376d74 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -17,6 +17,8 @@ autogenerates autogeneration awaitable BaseEventLoop +backend +backends basename BasicAuth BodyPartReader @@ -61,6 +63,7 @@ epoll fallback filename finalizers +frontend getall gethostbyname github @@ -173,6 +176,10 @@ subpackage subprotocol subprotocols subtype +Supervisord +supervisord +Systemd +Runit Svetlov symlink symlinks