Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Run Absolutely Everything* With uWSGI

Run Absolutely Everything* With uWSGI

A 2017 ConFoo presentation about uWSGI, the application server framework.

While its name may seem to imply that it's Python-only, uWSGI is a combination application server, proxy, process manager/monitor, async queue, cross-language RPC framework, and more.

We'll take a look at the broad features that make uWSGI an interesting option as an application server, and then show you how to concurrently serve Python, PHP, Ruby, Perl, Go, Lua, and Clojure applications, and use the built-in RPC, queue and in-memory cache.

Joël Perras

March 08, 2017
Tweet

More Decks by Joël Perras

Other Decks in Technology

Transcript

  1. OUTLINE 1. Hello 2. What is uWSGI? Why do we

    need it? 3. Structure/Organization 4. Languages 5. Configuration 6. Extra Extra
  2. HELLO Joël Perras Web stuff @ Fictive Kin Distributed company,

    ~25 people, 9-ish timezones Primarily Python, but have Ruby/Erlang/PHP/JVM in production Client services & in-house application development
  3. PEP333 (2003) PEP3333 (2010) WSGI -> uWSGI Web Server Gateway

    Interface. Low level interface for mediating web servers (Apache/ nginx/etc.) and web applications. Originally Python-only. Extended to support much more.
  4. uWSGI is more than an app server. “The uWSGI project

    has ISPs and PaaS (that is, the hosting market) as the main target.” Not your typical application server: - Proxy - Process manager - Load balancer - Router - Schedule/cron runner - Queue - Cache - RPC framework - Static file server
  5. uWSGI is more than an app server. - Binary protocol

    (uwsgi, lowercase!) - Extensible via request plugins to support multiple languages/ platforms, including Docker. - Emperor/vassal configuration for massive instance management/monitoring. - Open source, with commercial support.
  6. uWSGI It’s possible to use uWSGI to fulfill the roles

    of nearly all the entities involved. However, a typical installation will have it act between the web server and the application callables. Flow — Basic web server uWSGI app instances static assets
  7. uWSGI The core is composed of: - configuration - processes

    management - sockets creation - monitoring - logging - shared memory areas - IPC - cluster membership Flow — Basic core Lang 2 Lang 3 Lang 4 Lang 5 Gateways APP APP APP APP Loop Engines Vassals Lang 1
  8. Languages - Python - Ruby - Lua - Perl -

    PHP - Go - Erlang - JVM - Mono ASP.NET - V8 (JavaScript)
  9. Platforms - Linux 2.6/3.x - Free/Net/Open/DragonFly BSDs - Windows Cygwin

    - Mac OSX - OpenSolaris, Solaris >= 10 - NexentaOS - SmartOS - OpenIndiana - OmniOS - Debian/kFreeBSD - GNU/Hurd
  10. Bonus - Docker - Concurrently serve multiple versions of same

    language, e.g. Ruby 2.1.10 and 2.4.0 - Coroutines/fibres - External demonized (or not) services! - Memcached - Celery - PostgreSQL - etc.
  11. WSGI Python Application 1 [uwsgi] 2 http = :9090 3

    wsgi-file = path/to/app.py 4 processes = 4 5 threads = 2 1 def application(env, start_response): 2 start_response('200 OK', [('Content-Type','text/html')]) 3 return [b"Hello World"] Config
  12. Flask Python Application 1 [uwsgi] 2 socket = 127.0.0.1:3031 3

    wsgi-file = app.py 4 callable = app 6 processes = 4 6 threads = 2 Config 1 from flask import Flask 2 3 app = Flask(__name__) 4 5 @app.route('/') 6 def index(): 7 return “Hello there"
  13. Django Application 1 [uwsgi] 2 chdir=/path/to/your/project 3 module=mysite.wsgi:application 4 master=True

    5 pidfile=/tmp/project-master.pid 6 vacuum=True 7 max-requests=5000 8 daemonize=/var/log/uwsgi/yourproject.log This assumes you have a top-level project package named mysite, and within it a module mysite/ wsgi.py that contains a WSGI application object.
  14. Django Application 1 uwsgi --chdir=/path/to/your/project \ 2 --module=mysite.wsgi:application \ 3

    --env DJANGO_SETTINGS_MODULE=mysite.settings \ 4 --master --pidfile=/tmp/project-master.pid \ 5 --socket=127.0.0.1:49152 \ # can also be a file 6 --processes=5 \ # number of worker processes 7 --uid=1000 --gid=2000 \ # if root, uwsgi can drop privileges 8 --harakiri=20 \ # respawn processes taking more than 20 seconds 9 --max-requests=5000 \ # respawn processes after serving 5000 requests 10 --vacuum \ # clear environment on exit 11 --home=/path/to/virtual/env \ # optional path to a virtualenv 12 --daemonize=/var/log/uwsgi/yourproject.log # background the process
  15. Ruby/Rack Application Need to build uWSGI with Ruby/Rack support, first.

    1 make rack 2 UWSGI_PROFILE=rack make 3 make PROFILE=rack 4 python uwsgiconfig.py --build rack
  16. Ruby/Rack Application Config 1 class App 2 3 def call(environ)

    4 [200, {'Content-Type' => 'text/html'}, ['Hello']] 5 end 6 7 end 8 9 run App.new 1 [uwsgi] 2 socket = 127.0.0.1:3031 3 rack = app.ru 4 processes = 4 5 master = true
  17. Sinatra Ruby Application Config 1 require 'sinatra' 2 3 get

    '/hi' do 4 "Hello World" 5 end 6 7 run Sinatra::Application 1 [uwsgi] 2 socket = 127.0.0.1:3031 3 rack = config.ru 4 master = true 5 processes = 4 6 lazy-apps = true
  18. Bundler’ized Ruby Application 1 [uwsgi] 2 socket = 127.0.0.1:3031 3

    rack = config.ru 4 master = true 5 processes = 4 6 lazy-apps = true 7 rbrequire = rubygems 8 rbrequire = bundler/setup 9 env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
  19. Bundler + RVM 1 [uwsgi] 2 socket = 127.0.0.1:3031 3

    rack = config.ru 4 master = true 5 processes = 4 6 lazy-apps = true 7 rbrequire = rubygems 8 rbrequire = bundler/setup 9 env = BUNDLE_GEMFILE=<path_to_your_Gemfile> 10 gemset = ruby-2.0@foobar
  20. Java/JVM 1 public class Foobar { 2 static void main()

    { 3 4 // create an anonymous function 5 uwsgi.RpcFunction rpc_func = new uwsgi.RpcFunction() { 6 public String function(String... args) { 7 return "Hello World"; 8 } 9 }; 10 11 // register it in the uWSGI RPC subsystem 12 uwsgi.register_rpc("hello", rpc_func); 13 } 14 }
  21. Java/JVM 1 import uwsgi 2 3 def application(environ, start_response): 4

    start_response('200 OK', [('Content-Type','text/html')]) 5 yield "<h1>" 6 yield uwsgi.call('hello') 7 yield "</h1>" Necessary uWSGI bridge for RPC call. 1 [uwsgi] 2 plugins = python,jvm 3 http = :9090 4 wsgi-file = myapp.py 5 jvm-classpath = /opt/uwsgi/lib/uwsgi.jar 6 jvm-main-class = Foobar Config
  22. Go via GCCGO 1 package main 2 3 import "uwsgi"

    4 import "net/http" 5 import "fmt" 6 7 8 9 func viewHandler(w http.ResponseWriter, r *http.Request) { 10 fmt.Fprintf(w, "<h1>Hello World</h1>") 11 } 12 13 func main() { 14 http.HandleFunc("/view/", viewHandler) 15 uwsgi.Run() 16 }
  23. Go via GCCGO 1 gcc -fPIC -shared -o myapp.so myapp.go

    Build as shared library. 1 [uwsgi] 2 http-socket = :9090 3 http-socket-modifier1 = 11 4 go-load = ./myapp.so Config Goroutines work!
  24. PHP (with nginx frontend) Config 1 [uwsgi] 2 plugins =

    php 3 socket = 127.0.0.1:3030 4 chown-socket = www-data:www-data 5 uid = phpapp 6 gid = phpapp 7 master = True 8 processes = 4 1 <?php echo "hello there"; ?> 1 location ~ \.php { 2 root /your_document_root; 3 include uwsgi_params; 4 uwsgi_modifier1 14; 5 uwsgi_pass 127.0.0.1:3030; 6 } Nginx
  25. Lua Application, with coroutines! 1 function hello(wsapi_env) 2 local headers

    = { ["Content-type"] = "text/html" } 3 4 local function hello_text() 5 coroutine.yield("<html><body>") 6 coroutine.yield("<p>Hello Wsapi!</p>") 7 coroutine.yield("<p>PATH_INFO: " .. wsapi_env.PATH_INFO .. "</p>") 8 coroutine.yield("<p>SCRIPT_NAME: " .. wsapi_env.SCRIPT_NAME .. "</p>") 9 for i=0, 10000, 1 do 10 coroutine.yield(i .. "<br/>") 11 end 12 coroutine.yield("</body></html>") 13 end 14 15 return 200, headers, coroutine.wrap(hello_text) 16 end 17 18 return hello
  26. Lua Application, with coroutines! 1 [uwsgi] 2 plugins = lua

    3 socket = 127.0.0.1:3031 4 lua = hello.lua 5 async = 8 6 ; run the gc every 100 requests 7 lua-gc-freq = 100 Config
  27. Bonus Configuration Options - lazy-apps - `for…endfor` iteration - if-env

    - if-exists - harakiri - zerg - route-if-not - Emperor/Tyrant mode - subscribe-to - … hundreds more
  28. Bonus Configuration Options Build your own uWSGI binary, with your

    application embedded. en build it with its own configuration file embedded within it as well. And then build it with your static assets, HTML, templates, etc.!
  29. Config 1 [uwsgi] 2 ; a couple of placeholder 3

    django_projects_dir = /var/www/apps 4 my_project = foobar 5 ; chdir to app project dir and set pythonpath 6 chdir = %(django_projects_dir)/%(my_project) 7 pythonpath = %(django_projects_dir) 8 ; load django 9 module = django.core.handlers:WSGIHandler() 10 env = DJANGO_SETTINGS_MODULE=%(my_project).settings 11 ; enable master 12 master = true 13 ; 4 processes should be enough 14 processes = 4 15 ; enable the spooler (the mytasks dir must exist!) 16 spooler = %(chdir)/mytasks 17 ; load the task.py module 18 import = task 19 ; bind on a tcp socket 20 socket = 127.0.0.1:3031
  30. Python/Django 1 from uwsgidecorators import * 2 from django.contrib.sessions.models import

    Session 3 import os 4 5 @cron(40, 2, -1, -1, -1) 6 def clear_django_session(num): 7 print("it's 2:40 in the morning: clearing django sessions") 8 Session.objects.all().delete() 9 10 @spool 11 def encode_video(arguments): 12 os.system("ffmpeg -i \"%s\" image%%d.jpg" % arguments['filename'])
  31. Python/Django 1 from task import encode_video 2 3 def index(request):

    4 # launching video encoding 5 encode_video.spool(filename=request.POST['video_filename']) 6 return render_to_response('enqueued.html')
  32. Memcached! 1 [uwsgi] 2 master = true 3 socket =

    :3031 4 smart-attach-daemon = /tmp/memcached.pid memcached -p 11311 -d -P /tmp/memcached.pid
  33. Celery workers! 1 [uwsgi] 2 master = true 3 socket

    = :3031 4 smart-attach-daemon = /tmp/celery.pid celery -A tasks worker --pidfile=/tmp/celery.pid Celery beat! 1 [uwsgi] 2 master = true 3 socket = :3031 4 legion-mcast = mylegion 225.1.1.1:9191 90 bf-cbc:mysecret 5 legion-smart-attach-daemon = mylegion /tmp/celery- beat.pid celery beat --pidfile=/tmp/celery-beat.pid
  34. Who needs nginx?! 1 [uwsgi] 1 offload-threads = 6 2

    static-map = /static=/var/www/app/static 3 static-map = /media=/var/www/app/assets 4 static-expires = /var/www/app/static/* 3000000