What if you could take an existing web application and run it on a serverless platform with no changes? 🤔
Lots of existing (simple) stateless web applications are perfect candidates for serverless, but use web frameworks that don’t know how to integrate with those platforms. People have started to develop a number of custom plugins for those frameworks to try and bridge this gap.
These plugins can provide an easier learning curve for developers new to serverless. They can still use familiar web application frameworks whilst learning about the platforms. It also provides a path to “lift and shift” existing (simple) web applications to serverless platforms.
This approach relies on custom framework plugins being available, for every web app framework and serverless platform, which is not currently the case. Is there a better solution?
Recently, I’ve been experimenting with Apache OpenWhisk’s Docker support to prototype a different approach. This solution allows any web application to run on the platform, without needing bespoke framework plugins, with minimal changes. Sounds interesting? Read about how I did this below… 👍
Apache OpenWhisk Web Action HTTP Proxy
This project provides a static binary which proxies HTTP traffic from Apache OpenWhisk Web Actions to existing web applications. HTTP events received by the Web Action Proxy are forwarded as HTTP requests to the web application. HTTP responses from the web application are returned as Web Action responses.
Both the proxy and web application needed to be started inside the serverless runtime environment. The proxy uses port 8080 and the web application can use any other port. An environment variable or action parameter can be used to configure the local port to proxy.
Running both HTTP processes on the platform is possible due to custom runtime support in Apache OpenWhisk. This allows using custom Docker images as the runtime environment. Custom runtimes images can be built which include the proxy binary and (optionally) the web application source files.
Two different options are available for getting web application source files into the runtime environment.
- Build source files directly into the container image alongside proxy binary.
- Dynamically inject source files into container runtime during initialisation.
Building source files into the container is simpler and incurs lower cold-starts delays, but means source code will be publicly available on Docker Hub. Injecting source files through action zips means the public container image can exclude all private source files and secrets. The extra initialisation time for dynamic injection does increase cold-start delays.
Please note: This is an alpha-stage experiment! Don’t expect everything to work. This project is designed to run small simple stateless web applications on Apache OpenWhisk. Please don’t attempt to “lift ‘n’ shift” a huge stateful enterprise app server onto the platform!
Node.js + Express Example
The web application renders static HTML content for three routes (
/contact). CSS files and fonts are also served by the backend.
Use these steps to run this web application on Apache OpenWhisk using the Web Action Proxy…
- Clone project repo.
git clone https://github.com/jthomas/express_example
- Install project dependencies in the
- Bundle web application and libraries into zip file.
zip -r action.zip *
- Create the Web Action (using a custom runtime image) with the following command.
wsk action create --docker jamesthomas/generic_node_proxy --web true --main "npm start" -p "__ow_proxy_port" 3000 web_app action.zip
- Retrieve the Web Action URL for the new action.
wsk action get web_app --url
- Open the Web Action URL in a HTTP web browser. (Note: Web Action URLs must end with a forward-slash to work correctly, e.g.
If this works, the web application should load as above. Clicking links in the menu will navigate to different pages in the application.
custom runtime image
This example Web Action uses my own pre-built custom runtime image for Node.js web applications (
jamesthomas/generic_node_proxy). This was created from the following Dockerfile to support dynamic runtime injection of web application source files.
FROM node:10 ADD proxy /app/ WORKDIR /app EXPOSE 8080 CMD ./proxy
examples directory in the project repository for sample applications with build instructions for the following runtimes.
Usage & Configuration
Dynamic injection uses a custom runtime image with just the proxy binary and runtime dependencies. Web application source files are provided in the action zip file and extracted into the runtime upon initialisation. The proxy will start the app server during cold-starts.
Alternatively, source files for the web application can be included directly in the runtime image. The container start command will start both processes concurrently. No additional files are provided when creating the web action.
Please see the project documentation for more details on both these approaches, how to use them and configuration parameters.
This experiment is still in the alpha-stage and comes with many restrictions at the moment…
- HTTP request and responses sizes are limited to the maximum sizes allowed by Apache OpenWhisk for input parameters and activation results. This defaults to 1MB in the open-source project and 5MB on IBM Cloud Functions.
- Page links must use URLs with relative paths to the Web Action URL rather than the host root, e.g.
href="/home". This is due to the Web Actions being served from a sub-path of the platform (
/api/v1/web/<NAMESPACE>/default/<ACTION>) rather than the host root.
- Docker images will be pulled from the public registry on the first invocation. This will lead to long cold-start times for the first request after the action has been created. Large image sizes = longer delays. This only occurs on the first invocation.
- Web app startup times affect cold start times. The proxy blocks waiting for the web application to start before responding. This delay is included in each cold start. Concurrent HTTP requests from a web browser for static page assets will (initially) result in multiple cold starts.
- Web Sockets and other complex HTTP features, e.g. server-side events, cannot be supported.
- Web applications will run in ephemeral container environments that are paused between requests and destroyed without warning. This is not a traditional web application environment, e.g. running background tasks will not work.
Lots of things haven’t been tested. Don’t expect complex stateful web applications to work.
Being able to run existing web applications on serverless platforms opens up a huge opportunity for moving simple (and stateless) web application over to those platforms. These applications can then benefit from the scaling, cost and operational benefits serverless platforms provide.
Previous attempts to support traditional web applications on serverless platforms relied on custom framework plugins. This approach was limited by the availability of custom plugins for each web application framework and serverless platform.
Playing around with Apache OpenWhisk’s custom runtime support, I had an idea… could a generic HTTP proxy be used to support any framework without needing any plugins? This led to the Apache OpenWhisk Web Action HTTP Proxy project.
By building a custom runtime, the HTTP proxy and web application can both be started within the same serverless environment. HTTP events received by the Web Action Proxy are forwarded as HTTP requests to the web application. HTTP responses from the web application are returned as Web Action responses.
Web application sources files can be injected into the runtime environment during initialisation or built straight into the custom runtime image. No significant changes are required in the web application and it does not need custom framework plugins.
Apache OpenWhisk’s support for custom Docker runtimes opens up a huge range of opportunities for running more varied workloads on serverless platforms - and this is a great example of that!