In a recent blog post we covered how you can use the oc rsync --watch command to perform live synchronisation of code changes between a local filesystem and a container. This worked by virtue of the oc client monitoring the local filesystem directory for file changes and when they were detected copying the changed files to the running container, replacing the version of the file in the container.

If the container was running a web server, or other web application which was able to automatically detect the updated file and use it, you could use this mechanism to perform live code updates in more or less real time. This was achieved only by making changes on your local computer, without having to re-build the application image, or manually upload changes each time.

One scenario where this may not work is where the building of the application image reorganised the original source code so that it didn't match the layout of the original in the local code repository, or a compilation process was performed as part of the build process. In the case of a different filesystem layout, you would need to first download a copy of the directories you want to work on from the container to your local filesystem, then set up the file synchronisation using that copy as the source.

As much as oc rsync is useful for transferring files in and out of containers, it also is a mechanism specific to OpenShift and doesn't have support in third-party client tools users may be familiar with, for uploading and downloading files to or from other machines.

In this post we will start looking at how other more standard protocols might be used with OpenShift to handle file transfer. First up, we will look at the WebDav protocol and using it as an alternative for handling live code updates to a running application.

Web Distributed Authoring and Versioning (WebDAV)

WebDAV is an extension of HTTP that allows clients to create, change, and move documents on a remote server. Although it relies on HTTP as the transport mechanism for communicating with a server, major operating systems support the protocol as a backend for mounted filesystems. This means that one can connect to a WebDav server and then work directly with files hosted by the server as if they were present in your local file system.

In the context of the example application in our prior blog post where we used oc rsync --watch to perform live synchronisation of changes, if we could use WebDav to mount the original files from the running container on our local machine, we could achieve the same result without needing to run a separate synchronisation process.

To achieve this we will need a WebDav server to also be running in our container. In the case of our prior example, which was a Python web application implemented using the Django web framework, it was being hosted using mod-wsgi-express, which uses the Apache HTTPD server. Luckily for us, the Apache HTTPD server supports being used as a WebDav server.

Let's move on to seeing how a WebDav enabled application could be deployed and used and then we can look under the covers at how the Apache HTTPD server is configured to enable WebDav access.

Deploying Our Sample Web Application

To deploy our sample web application and make it accessible from the command line we can use oc new-app to create the build and deploy it, and then use oc create route to expose it and make it public.

oc new-app openshiftkatacoda/blog-django-py --name blog
oc create route edge --service blog

We use oc create route rather than oc expose, and we use edge as the option to oc create route, as we want to ensure we are using a secure connection when accessing the site. This is because we don't want others to be able to spy on our source code when we are accessing it.

Visiting the URL for the web application, we are presented with:

In preparation for making changes to the code, we also need to enable automatic reloading of the application code when changes are detected. This is done by setting the environment variable MOD_WSGI_RELOAD_ON_CHANGES to signal to mod_wsgi-express that it should monitor for code changes.

oc set env dc/blog MOD_WSGI_RELOAD_ON_CHANGES=true

Previously we would modify a local copy of the code files and rely on oc rsync --watch to upload the changes. This time we want to use WebDav.

Create a Password File to Authenticate Users

Before we enable WebDav access in the Apache HTTPD server, we need to set up a password file to control access. This is because we don't want everyone being able to access the source code over WebDav.

To create a password file we run the command:

htdigest -c webdav.htdigest openshift-evangelists/blog-django-py grumpy

The web application, in this case, has been set up to use HTTP Digest authentication. We create the password file webdav.htdigest where the authentication realm the application is using is openshift-evangelists/blog-django-py. We are going to add a user called grumpy. The htdigest command will prompt us to enter a password for the user.

Adding password for grumpy in realm openshift-evangelists/blog-django-py.
New password:
Re-type new password:

We now need to make this password file available to the application to authenticate users. For this we will use an OpenShift secret. This can be done using the oc create secret command.

$ oc create secret generic blog-webdav-secrets --from-file=.htdigest=webdav.htdigest
secret "blog-webdav-secrets" created

Querying the result, the command has created a secret of type Opaque, where the contents of the password file are saved under the key .htdigest.

$ oc describe secret blog-webdav-secrets
Name: blog-webdav-secrets
Namespace: python
Labels: <none>
Annotations: <none>

Type: Opaque

Data
====
.htdigest: 77 bytes

Having created the secret, we need to mount it into the application container at the location where it is expected. The application, in this case, is expecting a .htdigest file with the user passwords to be located in the directory /opt/app-root/secrets/webdav.

To mount the secret at that location, we run the command:

oc set volume dc/blog --add --secret-name blog-webdav-secrets --mount-path /opt/app-root/secrets/webdav

Note that we declare the mount path as /opt/app-root/secrets/webdav. Because the secret has data in it which is referenced by keys, the data associated with a key then gets stored in a file within that directory, with name corresponding to the key. Our password file will therefore appear inside of the container as /opt/app-root/secrets/webdav/.htdigest.

We are now ready to turn on WebDav within our application. With the way our sample web application has been set up, we can do this by setting the MOD_WSGI_ENABLE_WEBDAV environment variable.

oc set env dc/blog MOD_WSGI_ENABLE_WEBDAV=true

Mounting WebDav as a Local File System

The next step is to mount as a local file system, the source code from our application in the remote container over the WebDav protocol which we have enabled.

On MacOS X we can do this by first creating a directory onto which to mount the file system.

mkdir my-blog-site

Next we run the mount_webdav command. The options this command takes are:

usage: mount_webdav [-i] [-s] [-S] [-o options] [-v <volume name>]
<WebDAV_URL> node

We will need to supply the -i option to ensure that it will prompt us for our user credentials. We then need to supply the URL for the WebDav endpoint.

The URL for our web application is:

https://blog-python.b9ad.pro-us-east-1.openshiftapps.com

The WebDav endpoint is at the /webdav/ sub URL for the same site. The mount_webdav command we run is therefore:

mount_webdav -i https://blog-python.b9ad.pro-us-east-1.openshiftapps.com/webdav/ my-blog-site

We will be prompted for our username and password, for which we supply what we used when we ran the htdigest command previously.

Username: grumpy
Password:

If we list the contents of the my-blog-site directory we will now be able to see the code for our application which resides in the container.

$ ls -l my-blog-site/
total 134
-rwx------ 1 graham staff 430 10 Oct 13:32 Dockerfile
-rwx------ 1 graham staff 7861 10 Oct 13:32 README.md
-rwx------ 1 graham staff 548 10 Oct 13:32 app.sh
drwx------ 1 graham staff 2048 10 Oct 13:38 blog
drwx------ 1 graham staff 2048 10 Oct 13:32 configs
-rwx------ 1 graham staff 39936 10 Oct 13:34 db.sqlite3
drwx------ 1 graham staff 2048 10 Oct 13:32 htdocs
drwx------ 1 graham staff 2048 10 Oct 13:32 katacoda
-rwx------ 1 graham staff 806 10 Oct 13:32 manage.py
drwx------ 1 graham staff 2048 10 Oct 13:32 media
-rwx------ 1 graham staff 832 10 Oct 13:32 posts.json
-rwx------ 1 graham staff 65 10 Oct 13:32 requirements.txt
drwx------ 1 graham staff 2048 10 Oct 13:32 scripts
drwx------ 1 graham staff 2048 10 Oct 13:32 static
drwx------ 1 graham staff 2048 10 Oct 13:32 templates

Performing Live Updates to the Code

To demonstrate that it is actually the files from the container and that we can make changes and affect the running application, we can run:

echo "BLOG_BANNER_COLOR = 'green'" >> my-blog-site/blog/context_processors.py

Refresh the home page for our web application in the browser, and we find that the banner colour has changed to green.

Configuring mod_wsgi-express to Enable WebDav

This ability to mount the files from the container as a local file system was possible because the mod_wsgi-express server we used to host our Python web application was using the Apache HTTPD server.

The changes needed to the app.sh file which ran mod_wsgi-express to get it to work was to add --include configs/webdav.conf as an additional option.

if [ x"$MOD_WSGI_ENABLE_WEBDAV" != x"" ]; then
ARGS="$ARGS --include configs/webdav.conf"
fi

exec python manage.py runmodwsgi $ARGS

The configs/webdav.conf file contains the additional configuration for the Apache HTTPD server as shown below:

<IfModule !dav_module>
LoadModule dav_module '${MOD_WSGI_MODULES_DIRECTORY}/mod_dav.so'
</IfModule>

<IfModule !dav_fs_module>
LoadModule dav_fs_module '${MOD_WSGI_MODULES_DIRECTORY}/mod_dav_fs.so'
</IfModule>

<IfModule !auth_digest_module>
LoadModule auth_digest_module '${MOD_WSGI_MODULES_DIRECTORY}/mod_auth_digest.so'
</IfModule>

<IfModule !authn_file_module>
LoadModule authn_file_module '${MOD_WSGI_MODULES_DIRECTORY}/mod_authn_file.so'
</IfModule>

<IfModule !authz_user_module>
LoadModule authz_user_module '${MOD_WSGI_MODULES_DIRECTORY}/mod_authz_user.so'
</IfModule>

Alias /webdav/ /opt/app-root/src/

DavLockDB /opt/app-root/DavLock

<Directory /opt/app-root/src>
DAV on

AuthType Digest
AuthName openshift-evangelists/blog-django-py
AuthDigestDomain /webdav/
AuthDigestProvider file
AuthUserFile /opt/app-root/secrets/webdav/.htdigest

Require valid-user
</Directory>

This loaded in the additional Apache modules required to support WebDav and use Digest authentication to control access. We then mapped the /webdav/ sub URL to the location of the application source code.

Enabling WebDav Support when Using PHP

Updating the application code was possible as our Python web application was hosted using mod_wsgi-express, and we could use the WebDav support of the Apache HTTPD server. Further, Python is a dynamic scripting language, so it wasn't necessary to recompile code or re-link the application, we just needed the application to automatically detect the changed files and reload itself as necessary.

Of the other programming languages that OpenShift supports, PHP is also both a dynamic scripting language and is hosted using the Apache HTTPD server. It is, therefore, possible to enable WebDav access when using OpenShift's PHP S2I builder. This can be done by adding an executable script .s2i/bin/run to your source code repository which contains:

#!/bin/bash

set -x
set -eo pipefail

# Enable WebDav access if authentication realm set and user database exists.

if [ x"$WEBDAV_AUTHENTICATION_REALM" != x"" ]; then
if [ -f /opt/app-root/secrets/webdav/.htdigest ]; then
cat > /opt/app-root/etc/conf.d/90-webdav.conf << !
<IfModule !dav_module>
LoadModule dav_module modules/mod_dav.so'
</IfModule>

<IfModule !dav_fs_module>
LoadModule dav_fs_module modules/mod_dav_fs.so'
</IfModule>

<IfModule !auth_digest_module>
LoadModule auth_digest_module modules/mod_auth_digest.so'
</IfModule>

<IfModule !authn_file_module>
LoadModule authn_file_module modules/mod_authn_file.so'
</IfModule>

<IfModule !authz_user_module>
LoadModule authz_user_module modules/mod_authz_user.so'
</IfModule>

DavLockDB /opt/app-root/DavLock

Alias /webdav/ /opt/app-root/src/

<Location /webdav/>
DAV on

ForceType text/plain
DirectoryIndex disabled

AuthType Digest
AuthName "$WEBDAV_AUTHENTICATION_REALM"
AuthDigestDomain /webdav/
AuthDigestProvider file
AuthUserFile /opt/app-root/secrets/webdav/.htdigest

Require valid-user
</Location>
!
fi
fi

# Execute the original run script, replacing this script as current process.

exec /usr/libexec/s2i/run

Just prior to starting up the Apache HTTPD server, this script will generate an additional configuration file to be included at the end of the existing Apache configuration. The configuration file will be used as the PHP S2I builder has been set up to include all additional Apache configuration files found in the /opt/app-root/etc/conf.d directory.

As the configuration is only being activated when needed, you will need to set an environment variable WEBDAV_AUTHENTICATION_REALM to be the name of the authentication realm, as well as create and mount the secret with the user password database for WebDav access into the container for the application, as was explained above. If both these steps are not done, WebDav access will not be activated.

To demonstrate this in action with PHP, we have updated the WordPress quick start which was described in a prior post to have WebDav support. You can find the WordPress quick start, along with further details of enabling WebDav access when using it, in the code repository for the WordPress quick start at:

Performance and Local Code Development

The WebDav protocol makes use of HTTP as the underlying transport protocol. For this reason, access to the application code when mounted as a local filesystem is not as performant as if the files were actually local. You should keep this in mind, especially when the application you are accessing via WebDav is remote and you have a slow Internet connection.

As a means to perform live code updates when developing a web application deployed locally using Minishift, the performance is more than adequate and can be a useful tool to streamline development as it can avoid the need to always be re-building an application image.

In using this method for local development, just keep in mind that the container file system is ephemeral and if the container is restarted, you will lose your changes. You may, therefore, want to set up the deployment so that the application code resides in a persistent volume. This is actually how the WordPress quick start mentioned above is setup.

One final warning. The WebDav server that Apache runs works on the assumption that all updates to files will then be through WebDav. So do be a bit careful about modifying files from within the container at the same time as being worked on via WebDav, as this could cause an inconsistency between what is on the file system and what the WebDav server may have cached.

Adding a WebDav Server as a Sidecar Container

In order to be able to access the source code for the application which was built into the container during the building of the application image, the WebDav server needed to run in the same container as the application.

If the files you need to access are not in the application image itself, but stored in a persistent volume, a variation on this theme that could be used is to add a sidecar container to an application which runs a WebDav server. The sidecar container could then mount the same persistent volume the application is using and expose it over the WebDav protocol. This would then give you the ability to mount the persistent volume on your local computer and work directly on the files. We will try and revisit this topic of running a WebDav server as a sidecar container in a future post.