If, like me, you have come from a LAMP (Linux, Apache, MySQL and PHP) background, you are probably used to vhosts, mod_rewrite and various .conf files. These are some of the core concepts for setting up Apache to serve a PHP application, and are a cornerstone of LAMP DevOps.
Recently, I’ve started to explore the Python / Django ecosystem, and one of the major differences is the move away from Apache, towards Nginx. As well as this shift, a raft of other technologies become useful with Django, and, crucially, there are some changes in architecture. In this post, I’ll run down some of the core technologies, and compare them to the LAMP stack architecture.
Before we start on the software, there are a couple of new protocols and concepts that need to be introduced. I’m going to use the analogy of a restaurant to help explain these concepts.
- Forward proxy: A proxy is simply an entity that sits between two systems that want to communicate with each other, and passes information back and forth. For websites, the two systems trying to communicate are the user (client) and the application (server). When people talk about proxy servers on computer networks, they usually mean forward proxy servers, which deal with sending client requests to the public internet. Most corporate networks have some form of forward proxy, otherwise every individual worker would need their own internet connection. You could think of ordering takeout from a restaurant. You would probably all look at the take-out menu and tell one person what you want for dinner. That person then calls the restaurant, and places the order. This is much easier than everyone ordering their food with separate phone calls. In this case, the person making the phone call is the forward proxy.
- Reverse proxy: If a forward proxy sends client requests out to the public internet, a reverse proxy is a system that takes requests from the public internet, and forwards them to a web application. Forward and reverse proxies are essentially the same concepts, except forward proxies collect outgoing client requests and send them to a specified destination, while reverse proxies collect incoming client requests and forward them to the correct part of an application. Reverse proxies can be used to speed up web pages by caching commonly used files. This means that the request can be dealt with without having to contact the application server at all. An example might be a logo image, that is displayed on every page, although to make pages load even faster, the client’s browser might store the logo. For our restaurant analogy, a reverse proxy would be a cashier in a fast food restaurant. This person can take multiple orders, pass the complicated bit of the order to the chef (e.g. the burger) and server the simple bits, such as fries and soft drinks himself.
- Load balancer: A load balancer is a specialised reverse proxy that is used to speed up webservers. The idea is that a single web server is a single point of failure, and has a limited capacity to serve webpages. This problem can be overcome by setting up more than one server, and putting a reverse proxy server in front of them. The reverse proxy can then allocate incoming requests equally between the available servers, so an individual server isn’t overloaded. Load balancers have several other advantages, such as simplifying updates and providing a degree of protection from software and hardware problems. Continuing our fast-food analogy, a load balancer might be the restaurant manager, standing in front of several cashiers, allocating customers to various queues. This minimises waiting time for individual customers, and helps to ensure that individual cashiers aren’t overworked.
- Application server: An application server is a piece of software that allows your code to run, and produces the correct output, ready to be sent to the user. In a fast food restaurant, the application server would be the chef. Ideally, you don’t want the chef serving fries or drinks, as these are simple requests that are a waste of the chef’s time. For websites, simple tasks are serving things like images and pdfs. For my application, Gunicorn is my application server of choice.
- Web server: A web server is a system that receives internet requests, and converts them into application requests. It then receives the application output, and passes it back to the client. In a fast-food restaurant, the web server would be the cashier. The cashier splits up the order, passes the complicated part to the chef, and handles the simple parts. I’m going to use Nginx as the web server.
- WSGI: This stands for Web Server Gateway Interface. This is a set of rules that allow web applications and web servers to talk to each other. It is specifically written for Python. For a fast food restaurant, a client might request a bacon-double cheeseburger with no onion, and the cashier might press the button marked ‘BDC’ and then another marked ‘-O’. The translation of ‘bacon double cheeseburger with no onions’ to ‘BDC-O’ was probably defined by head office, and is the same across all branches of the fast food restaurant. The list of burger names, acronyms and modifiers is equivalent to WSGI.
- Processes, threads and workers: These nouns describe the various ways that applications can handle requests. They have slightly different meanings in different contexts, but generally:
- A process is something created by the kernel (OS) to carry out a task. A process can be created by many types of applications - not just web server.
- A worker is a discrete task management process, internal to a piece of software. A web server can therefore create a ‘worker process’.
- A thread is specific task request, typically delegated from a worker process.
- Fork & pre-fork: Fork refers to a Unix command which clones an existing process. For a web server, there may be a master process, which is then forked for each new connection. Pre-forking is the idea of saving time by creating a pool of empty processes, ready for new connections. This means each new connection can skip the ‘fork’ step, and move straight to the execute step.
Now we have some concepts established, I’m going to discuss my selected components in more detail.
Gunicorn (A portmanteau of ‘Green Unicorn’) is a version of a Ruby HTTP server called Unicorn, ported over to Python. It is designed to do one thing well - that is to serve dynamic content to clients as fast as possible. The way Gunicorn serves content fast is by focussing all of its efforts on processing requests from a few clients, to the exclusion of all others. This is called “blocking input/output (I/O)”, and the clients requests are processed by Gunicorn ’workers’. This can cause problems, because sometimes clients cannot provide input fast enough - maybe because their internet connection is slow. In this case, the Gunicorn worker is hanging around, waiting for input, rather than processing the next client in the queue. The other part of Gunicorn’s speciality is serving dynamic content. This means that it is not optimised serving static content. See the discussion of Nginx and how that system is optimised for static content delivery.
Nginx is a lightweight web server, with an architecture that maximises speed. It does this with an ‘event-based’ architecture. This means
- Cannot interpret code
- No per directory configurations
- Nginx modules must be compiled into the code
- Each worker can have thousands of HTTP connections
- Nginx is good at
- serving static files
- offloading slow HTTP connections
- Nginx is not good at:
- Providing a flexible system (e.g. 3rd party modules)
Comparison with Apache
- Apache is flexible (extensible) as it has many modules
- Has interpreters
- Modules can be turned on and off dynamically, because there is one connection per process. New processes can be created with different settings (e.g. modules)
- .htaccess files for per directory configuration
- A new worker process is created for each new connection. Each client can have several simultaneous connections, kept open with HTTP ‘keepalive’.
- In pre-fork mode, worker processes are remade, in anticipation of new connections. This means processes are ready and waiting when required.
- Apache can work with Python applications, by using the Apache module ‘mod_wsgi’
- Apache has 2 multi-processing modules - worker & event.
Apache is great - it has loads of modules, loads of people using in and had been using in production environments for a long time. But it can be a bit bloated, and there are some new kids on the block that can do things faster. For me, these new things mean Nginx and Gunicorn. These split the job of serving content into two parts - talking to the application and talking to the user. The connection to the user can be slow, and get interrupted, so it’s necessary to have a robust connection. That’s what Nginx is good at. The application can be complicated, so the application server is a nearby, feeding it with input as fast as possible, and serving the output. That’s what Gunicorn is good at. It’s a little more work to setup, but for my chosen stack - Python / Django - it’s worth ascending the learning curve.