The WSGI (Web Server Gateway Interface) specification for hosting Python web applications was created in 2003. Measured in Internet time, it is ancient. The oldest main stream implementation of the WSGI specification is mod_wsgi, for the Apache HTTPD server and it is 10 years old.
WSGI is starting to be regarded as not up to the job, with technologies such as HTTP/2, web sockets and async dispatching being the way forward. Reality is that WSGI will be around for quite some time yet and for the majority of use cases is more than adequate.
The real problem is not that we need to move to these new technologies, but that we aren't using the current WSGI servers to their best advantage. Moving to a new set of technologies will not necessarily make things better and will only create a new set of problems you have to solve.
As one of the oldest WSGI server implementations, Apache and mod_wsgi may be regarded as boring and not cool, but it is still the most stable option for hosting WSGI applications available. It also hasn't been sitting still, with a considerable amount of development work being done on mod_wsgi in the last few years to make it even more robust and easier to use in a development environment as well as production, including in containerised environments.
In this talk you will learn about many features of mod_wsgi which you probably didn't even know existed, features which can help towards ensuring your Python web application deployment performs to its best, is secure, and has a low maintenance burden.
1. Secrets of
a WSGI master.
Graham Dumpleton
Graham.Dumpleton@gmail.com
@GrahamDumpleton
https://www.slideshare.net/GrahamDumpleton/secrets-of-a-wsgi-master
2. What is WSGI?
Web Browser
Web Browser
Web Browser
Web Server
HTTP
HTTP
HTTP
File System
(Static Files)
Python
Web Application
WSGI
WSGI == Web Server Gateway Interface (PEP 3333)
3. WSGI is a specification for an
Application Programming Interface
WSGI is NOT a wire protocol
WSGI is NOT an implementation
of any anything
22. Friends don’t let friends
use Python without a
Python virtual environment
23. A better container image #1
FROM python:3
RUN apt-get update &&
apt-get install -y --no-install-recommends apache2 apache2-dev locales &&
apt-get clean &&
rm -r /var/lib/apt/lists/*
RUN adduser --disabled-password --gecos "Warp Drive" --uid 1001
--gid 0 --home /opt/app-root warpdrive &&
chmod g+w /etc/passwd
RUN echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen
ENV LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8
PATH=/opt/app-root/bin:$PATH
HOME=/opt/app-root
Create non root user
24. A better container image #2
RUN pip install --no-cache-dir virtualenv &&
virtualenv /opt/app-root &&
. /opt/app-root/bin/activate &&
pip install --no-cache-dir warpdrive &&
warpdrive fixup /opt/app-root
WORKDIR /opt/app-root
COPY . /opt/app-root/src
RUN warpdrive fixup /opt/app-root/src
USER 1001
RUN warpdrive build &&
warpdrive fixup /opt/app-root
EXPOSE 8080
CMD [ "warpdrive", "start" ]
Create a Python
virtual environment
Use warpdrive,
it's magicUse non
root user
Non
privileged
port
25. Running as non root
$ docker run mypyapp warpdrive exec id
uid=1001(warpdrive) gid=0(root) groups=0(root)
$ docker run -u 100001 mypyapp warpdrive exec id
uid=100001(warpdrive) gid=0(root) groups=0(root)
Default to assigned non root user
Can be run as arbitrary high user ID
26. Same tools for development
$ warpdrive project mypyapp
(warpdrive+mypyapp) $ warpdrive build
(warpdrive+mypyapp) $ warpdrive start
https://pypi.python.org/pypi/warpdrive
27. Generate image with no Dockerfile
$ warpdrive image mypyapp
$ docker run --rm -p 80:8080 mypyapp
36. Failed application loading
• startup-timeout=15
Defines the maximum number of seconds allowed to pass waiting to
see if a WSGI script file can be loaded successfully by a daemon
process. When the timeout is passed, the process will be restarted.
37. Connection timeouts
• connect-timeout=15
Defines the maximum amount of time for an Apache child process to wait trying to get a
successful connection to the mod_wsgi daemon processes. This defaults to 15 seconds.
• socket-timeout=60
Defines the timeout on individual reads/writes on the socket connection between the Apache
child processes and the mod_wsgi daemon processes. If not specified, the number of seconds
specified by the Apache Timeout directive will be used instead. See also response-socket-
timeout if need to control this only for writing back content of the response.
• queue-timeout=45
Defines the timeout on how long to wait for a mod_wsgi daemon process to accept a request for
processing. Not enabled by default.
38. Time triggered restart
• request-timeout=60
Defines the maximum number of seconds that a request is allowed
to run before the daemon process is restarted.
• inactivity-timeout=0
Defines the maximum number of seconds allowed to pass before
the daemon process is shutdown and restarted when the daemon
process has entered an idle state. To restart on stuck requests use
request-timeout instead.
39. Request Monitoring
import mod_wsgi
def event_handler(name, **kwargs):
if name == 'request_started':
...
elif name == 'request_finished':
environ = kwargs['request_environ']
response_time = kwargs.get('response_time')
cpu_user_time = kwargs.get('cpu_user_time')
cpu_system_time = kwargs.get('cpu_system_time')
...
elif name == 'request_exception':
...
mod_wsgi.subscribe_events(event_handler)