Hacks by Ruddra

Deploy Django App in Sub Directory Using Openshift

Deploy Django App in Sub Directory Using Openshift

The Problem

When you are using Openshift, you will be using routes to expose a route from a service. Let’s say you want to expose a path in example.com/dummy. When you do that for a django application(without any reverse proxy server), it usually becomes a problem, because the sub directory does not work well with Django’s urls. You will probably face error 404 page not found.

If you are not using NGINX/Apache or any other reverse proxy server to serve the django application, you will not be able to set X-SCRIPT-NAME. To me, it does not make sense to use reverse proxy in pods when Openshift provides routes(or ingress in kubernetes) using NGINX/HA-Proxy(depending on your setup).

In this situation, I have used the following solution:

Solution

Disable Namespace Ownership Check in Router

You need to patch the router in Openshift before starting any step(from Openshift 3.9). By disabling Ownership check, you can allow a service to use a xyz.com/abc even if there is another service taking xyz.com. You can do it like this(from documentation):

oc adm router ... --disable-namespace-ownership-check=true
oc env dc/router ROUTER_DISABLE_NAMESPACE_OWNERSHIP_CHECK=true

FYI: this also works in OKD as well.

Set FORCE_SCRIPT_NAME

I have set FORCE_SCRIPT_NAME in settings.py. Usually I fetched the value from environment varibles, which I have set in the Deployment Config:

FORCE_SCRIPT_NAME = os.environ.get('DJANGO_FORCE_SCRIPT_NAME', '/subdirectory')  # without trailing slash

Using FORCE_SCRIPT_NAME will configure django’s url in path /subdirectory. But it is not sufficient to serve the django application in sub path, as SCRIPT_NAME in requests are not updated, also it will show some erratic behaviour such as showing double entry of /subpath in url, 404 Page not found issues etc. To fix this, lets go to the second step:

Update wsgi.py

Lets update the wsgi.py file like this:

import os
from django.core.wsgi import get_wsgi_application
from django.conf import settings


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dummy.settings')
_application = get_wsgi_application()

# Here is the important part
def application(environ, start_response):
    script_name = getattr(settings, 'FORCE_SCRIPT_NAME', None)
    if script_name:
        environ['SCRIPT_NAME'] = script_name
        path_info = environ['PATH_INFO']
        if path_info.startswith(script_name):
            environ['PATH_INFO'] = path_info[len(script_name):]

    scheme = environ.get('HTTP_X_SCHEME', '')
    if scheme:
        environ['wsgi.url_scheme'] = scheme

    return _application(environ, start_response)

In above code, we are getting the value of script_name from FORCE_SCRIPT_NAME, then putting that value in wsgi environ(environment variable).

Big Thanks To

I got the implementation idea from this stackoverflow answer. Big thanks to @whp.

That is it for today, hope it helps. If you have a better approach to resolve this problem please make a comment in comment section below.