Quite often, I’ve had to put webapps in read only mode during maintenance activities like migrating databases, moving web servers, etc. During such windows, users are allowed to browse and read their existing data, but no write operations are allowed as they may lead to data inconsistency.
Most major frameworks have some sort of maintenance mode (including Django and Rails) which restricts the activity that a user is allowed to perform. However, I wanted a generic pattern that can be used across various platforms. Nginx being the awesome go-to tool that it is, allows one to leverage the power of Lua scripting with endless possibilities. The method described below relies on Nginx with a couple of plugins which make it easy to configure and extend.
So without further ado, here is gridlock
- Nginx - all awesome reverse proxy
- lua-nginx-module - for embedding Lua scripts in our Nginx config
- Redis - to store configuration
- lua-rest-redis - reach our Redis configuration from Nginx
A easier way to get these in one shot is to install the openresty bundle, which comes with the required modules and is way easier to install.
The technique is simple - we use redis to set various keys, which are picked up by the Lua script. If the readonly mode is set, we then block all HTTP requests which modify a resource. So anything other than a GET and HEAD is blocked. You can set an option to determine how Nginx blocks the resource - either by throwing a 403 Forbidden error, or by setting a header for your app server to handle it however you deem fit. A custom header
X-READONLY is set to true so that the app can handle it accordingly.
The following redis keys control the behavior of the gatekeeper:
SET gridlock.readonly true- Turn on readonly mode
SET gridlock.readonly.mode redirect|forbid- if
forbid, nginx will exit with a 403 error. If
redirect, you can set a common error page to redirect to
SET gridlock.readonly.redirect_url- Define the redirect url for your app. If this is not set, and the mode is set to
redirect, we will redirect to the referer of the original request
LPUSH gridlock.readonly.allowed- A list of URLs that you want to explicitly allow. These are for exceptions like login forms which you would still want to function
Once you have all the settings in place, you just flip the switch by setting
gridlock.readonly to true. The changes are live instantly without a reload! Once you maintainance window is done, simply set it to false, or delete the key. Zero downtime switching FTW!
The code for the script and a simple Node.js app are available in the repo.
Although I’ve tested it out internally, absolutely no guarantees offered! If this breaks or burns your server, you are on your own.
Currently, redis is hit everytime a request comes into Nginx. This obviously doesn’t work for high traffic sites. Figure out a way to use other lifecycle methods to periodically update and cache the settings.