Live is streaming live. Watch now.

Hello Python, My Old Friend: Revisiting Python in a Cloud-Native Climate

Brian McClain August 11, 2020

For quite a while now, I’ve kept an eye on RedMonk’s programming language rankings — which track the usage of all programming languages based on data from GitHub, as well as discussion about them on Stack Overflow —— to get insight into the various language trends. In the January 2020 update, something interesting happened: Python reached No. 2 on the list, taking over Java.

As RedMonk pointed out, “[T]he numerical ranking is substantially less relevant than the language’s tier or grouping. In many cases, one spot on the list is not distinguishable from the next.” However, it’s still interesting, especially as Python has continued to hold the No. 2 spot into the latest ranking after spending approximately seven years ranked third or fourth.

RedMonk isn’t alone in its findings, either. GitHub’s report, The State of the Octoverse also ranked Python as the second most-popular language used on that website, just behind JavaScript. Not only that, it also found Python remains among the top 10 fastest-growing languages in the community, despite already having a foothold with developers. In the JetBrains Python Developers Survey in 2019, it found that one of the most popular things developers use Python for is web development, with Flask and Django fighting for the top web framework.

At one time Python was my main language of choice. For the past few years, I’ve primarily been a Java developer, and a Ruby developer before that. Since the time that Python was my go-to, the landscape of software development has changed significantly. Microservices-based architecture continued to gain popularity at mach speed and the adoption of serverless began. Perhaps most significantly, we saw the rise of Kubernetes.

So now that Java’s been bumped out of the top spot for more than six months running, it seems like an especially good time to revisit how to build an application in Python.

Building the Application

For the purposes of this post, I wanted to keep the application simple. I chose a two-tier application: a backend app that serves up a list of items in an inventory, and a frontend app to display the inventory. Both applications were written using Flask, a lightweight web application framework for Python. Flask provides a lot to make developing web apps in Python easy, with features such as routing, response templates, logging, session management, and more. For this app, we’re using it just for basic request routing and template rendering.

If you’ve written in Python before, much of the code should look pretty standard. The backend application has one endpoint served at / , which returns a JSON array of objects. The frontend app has a single endpoint, also at /, which requests the lists of items from the backend and passes them to a template that it renders. One thing that should be pointed out is the getBackendURI() function in the app.py file for the frontend. This function is called when the frontend needs to know where to request data from the backend:

CONFIG_PATH = "/config/config.cfg"

def getBackendURI():
    if os.path.exists(CONFIG_PATH):
        app.config.from_pyfile(CONFIG_PATH)
        return app.config["BACKEND_URI"]
    return "http://localhost:8082"

This method reads the contents of /config/config.cfg to know where to send requests when reaching out to the backend. If the file doesn’t exist, it sends requests to localhost. You may notice that this file doesn’t exist anywhere in the code; that’s because we’ll be generating it when we deploy our application to Kubernetes. Be sure to keep that in mind.

Packaging the Application

Speaking of deploying to Kubernetes, now we need to package our code into containers. We could write a Dockerfile ourselves, but lucky for us, Cloud Native Buildpacks can build the container automatically! If you’re new to using Cloud Native Buildpacks with Python, I recommend reading this guide from my teammate Ben Wilcock which explains how to get started.

Since I just use Docker Hub as my container registry, you can use the following commands yourself so long as you replace my username with your own. While we’re in the directory containing the python-shop-backend code, let’s build and push a container image for it:

$ pack build brianmmcclain/python-shop-backend  --builder gcr.io/buildpacks/builder:v1
…
Successfully built image brianmmcclain/python-shop-backend

$ docker push brianmmcclain/python-shop-backend

In the above example, I’ve used the pack CLI to build a container from my code, telling it to use a specific builder. In this case, the builder can tell that we’re building a container for a Python application, so it pulls in the dependencies with pip by reading the requirements.txt file. It knows how to run the application, thanks to the Procfile.

Next, we’ll do the same thing in the directory containing the python-shop-frontend code:

$ pack build brianmmcclain/python-shop-frontend  --builder gcr.io/buildpacks/builder:v1
…
Successfully built image brianmmcclain/python-shop-frontend

$ docker push brianmmcclain/python-shop-frontend

Just as we saw with the backend code, pack builds the container, then we push it up to Docker Hub. Now all that’s left is to run it!

Deploying to Kubernetes

In the python-shop-frontend codebase, I’ve included a file named deploy.yaml, which contains all of the resources we need to deploy to Kubernetes. In my case, I’m just deploying to Minikube, but you can use other solutions if you’d like. You can apply this YAML to Kubernetes with the kubectl apply command:

$ kubectl apply -f deploy.yaml
deployment.apps/python-shop-backend created
service/python-shop-backend-service created
configmap/python-shop-frontend-config created
deployment.apps/python-shop-frontend created
service/python-shop-frontend-service created

You should see a couple of deployments created: one for the frontend and one for the backend. We’ve also created two services for each deployment to attach to. Finally, we’ve created a ConfigMap named python-shop-frontend-config. Remember when we were looking at the code and noticed a reference to a file at /config/config.cfg? Here’s where that data lives. Take a look at this ConfigMap definition:

kind: ConfigMap
apiVersion: v1
metadata:
  name: python-shop-frontend-config
data:
  config.cfg: |
        BACKEND_URI="http://python-shop-backend-service:8080"

Here’s where we’ve defined that BACKEND_URI will point to the python-shop-backend-service service in port 8080. We then reference this ConfigMap in the Deployment for the frontend:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-shop-frontend
  labels:
    app: python-shop-frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python-shop-frontend
  template:
    metadata:
      labels:
        app: python-shop-frontend
    spec:
      containers:
      - name: python-shop-frontend
        image: brianmmcclain/python-shop-frontend:latest
        volumeMounts:
        - name: python-shop-frontend-config-volume
          mountPath: /config
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
      volumes:
      - name: python-shop-frontend-config-volume
        configMap:
          name: python-shop-frontend-config

Notice the volume defined in the volumes section named python-shop-frontend-config-volume, which references the python-shop-frontend-config ConfigMap. In the container specification, we then mount this volume to the container running the frontend at /config:

  spec:
      containers:
      - name: python-shop-frontend
        image: brianmmcclain/python-shop-frontend:latest
        volumeMounts:
        - name: python-shop-frontend-config-volume
          mountPath: /config

With this configuration, a file is created from the ConfigMap named config.cfg and mounted at /config, which our application reads to know the value of BACKEND_URI. By configuring our code this way, if the address of the backend ever changes we can update the ConfigMap without having to restart our frontend container.

So What’s the Verdict?

In putting together this post, I was reminded of what an enjoyable experience it is to write in Python. I was also very happy to see how easily we can use it to leverage more modern technologies, such as Kubernetes and Cloud Native Buildpacks. Indeed, Python is lightweight, and can result in fairly slim container images. Flask, meanwhile, proves to be a great framework for developing modern applications.

Of course, there would be a lot more to consider if we were writing a production application, such as logging, metrics, a proper web server to back our code, and much more. But it makes sense that Python has had such a hold on the software community, and it’s exciting to see that it’s only getting stronger. If, like me, you want to learn more, I suggest checking out some of the other content that Ben has been making, such as running Python in production.