Today, Google announced Cloud Run, a product that is really beautiful in its simplicity:
1) You write an service/app/function/whatever that listens for HTTP requests on
2) You write a
Dockerfile that inherits from any base image, installs any necessary dependencies, and starts your service;
3) You deploy the image to Cloud Run, and it scales on demand (even to zero).
Basically, it's "serverless Docker containers as a service" and it's exactly what I think the PaaS/FaaS development community could use right now, for a number of reasons:
The most idiomatic runtime possible
There's no potential for vendor-specific weirdness here. Really, you can't get more idiomatic than using
FROM python:3.7, or whatever other base image you want to use.
Customizability without paying for idle virtual machines
If you have specific workloads that don't fit within the bounds of the most "common" runtimes usually provided, before now your best option was to spin up a custom VM which won't scale to zero and will spend a lot of time sitting idle. With Cloud Run, you can use the
Dockerfile to customize your runtime to your heart's content.
Containerization means portability
Defining your runtime with a standard
Dockerfile means you can take it anywhere—even locally—which means that you're not locked into a specific vendor, and that local development of your application is built right in without needing to do anything special.
These are all great, and they'll certainly help developers do great things...
...But what if you wanted to do something really silly with it instead?
When I first started playing with Cloud Run, I really wanted to test the limits of what I could do with it. Could I really run any runtime I wanted? Could I run a super modern Python 3.8.0a3 alpha release? (Yes). Well, what about a "vintage" version of Python? Could I deploy a "Hello World" app running on Python 1.x to Cloud run? 🤔
The first problem was finding a base image for Python 1.x. The official Python Docker images only publish images for 2.7 and up, so no luck there. I couldn't find it anywhere else, so it looked like I'd have to build the base image from scratch myself.
Building the base image from scratch meant that I had to:
a) Find the old source code for a Python 1.x version;
b) Get it to compile on a modern distribution's base image;
c) Publish this new base image.
Finding the source was slightly challenging. The source for modern Python releases is published on https://www.python.org/downloads/source/, but this only goes back as far as Python 2.0.1, released June 22, 2001.
Luckily, I found https://legacy.python.org/download/releases/src/, which includes source releases as far back as Python 1.0.1, which was released just over 25 years ago on February 15th, 1994.
I decided to attempt to target the latest Ubuntu distribution, and amazingly, with just some small patches, I was able to get Python 1.0.1, 1.1, 1.2 and 1.3 to compile under Ubuntu 18.04.
I published these resulting builds as Docker images under the
vintage-python repository, so this means you can now start a vintage Python REPL whenever you want:
$ docker run -it dustingram/vintage-python:1.0.1 Python 1.0.1 (Feb 23 2019) Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam >>> print "Hello world!" Hello world!
It also means I can do
FROM dustingram/vintage-python:1.0.1 in my
Dockerfile, which is a huge step towards having my silly runtime.
I had my heart set on Python 1.0.1, but after realizing that Python didn't ship with an HTTP server until Python 1.3, I decided to use that instead of attempting to backport the HTTP server to an older version.
In Python 1.3, I was able to write this:
import sys from BaseHTTPServer import HTTPServer from SimpleHTTPServer import SimpleHTTPRequestHandler class MyHandler(SimpleHTTPRequestHandler): protocol_version = "HTTP/1.1" def do_GET(self): self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write("Hello from Python " + sys.version) if __name__ == '__main__': print('Running...') HTTPServer(('0.0.0.0', 8080), MyHandler).serve_forever()
This sets up a basic HTTP/1.1 server on port 8080 that responds to every request with the given message. I really wanted to prove that this was a vintage version of Python serving the request, so I included the
sys.version along with the response.
After saving that as
app.py, all that was left to do was write the
Dockerfile. Since my app doesn't have any dependencies, the
Dockerfile is pretty close to the
Dockerfile in the Cloud Run quickstart
FROM dustingram/vintage-python:1.3 # Copy local code to the container image. ENV APP_HOME /app WORKDIR $APP_HOME COPY . . # Service must listen to $PORT environment variable. # This default value facilitates local development. ENV PORT 8080 # Run the web service on container startup. CMD ["python", "app.py"]
Here, we inherit from my vintage Python image, copy in my
app.py to the container, set the
PORT environment variable (for local development), and then start the server by running
With Cloud Run, there's a two step deploy:
Build and submit your image with Cloud Build:
$ gcloud builds submit --tag gcr.io/my_project_id/helloworld
Then, deploy the submitted image to Cloud Run:
$ gcloud beta run deploy --image gcr.io/my_project_id/helloworld
And then it was deployed!
See it in action here: https://helloworld-bpzdnjbrsq-uc.a.run.app/
Cloud Run is definitely flexible enough to run any runtime I could throw at it -- even one of the oldest versions of Python. Obviously I wouldn't seriously encourage anyone to use Python 1.x in production: any version past it's EOL is essentially unmaintained and severely feature-limited.
From here, you can: