Kubernetes native applications are those that leverage Kubernetes-specific API objects, such as Batch/Jobs, Pods and the like, not only as a means of deploying an application, but rather as a means of building the application itself. This architectural approach offers numerous advantages, such as the ability to run application processes asynchronously and to allow the Kubernetes scheduler to distribute those processes efficiently across your full cluster’s resources.
However, while developing such an application, your individual services can only be effectively written and debugged in a running Kubernetes cluster, as they rely on the Kubernetes API and any additional cluster resources your architecture includes.
Historically, this would have required you to update your code locally, commit your changes, wait for any relevant CI process to complete, build and push a container image, pull the image into your Kubernetes cluster and only then would you be able to see if your code runs as expected.
Velocity dramatically simplifies and speeds up the development workflow for Kubernetes native applications by allowing you to simply write and debug code in your local IDE, which it then automatically syncs to your cluster, so you can effectively develop directly in your cluster while working from your local machine.
Velocity automatically sync local code changes to a running cluster.
This post will walk you through the process of developing a Kubernetes native application, and it will also demonstrate how much more easily and efficiently such applications can be developed with Velocity.
Today we'll build a video frame stabilizer written in Python, which will require a significant amount of compute time and resources to process the videos we'll be uploading. To keep the application responsive while this video processing workflow is being executed, we'll use Kubernetes Batch/Jobs to allow the Kubernetes controller to schedule this processing workflow asynchronously and in a way that maximizes our cluster's compute resources.
That is, rather than running the video processing workflow in our “core” FastAPI application, which would cause the API to bog down with any significant traffic load, the FastAPI application will call the Kubernetes API and request the creation of a Batch/Job that will run the video processing workflow as an independent and isolated workflow. This way, the Kubernetes scheduler can spread the running of this distinct workflow across the full cluster's resources, and the FastAPI application will be able to handle additional incoming requests during the video processing workflow.
The full project is available in GitHub.
To start, let's spin up a local cluster with Minikube. Because the video processing workflow is a bit resource intensive, let's also increase the default memory and CPUs of our cluster, like so:
Above, after starting the cluster, we have enabled the kong add-on and started a tunnel session in order to later deploy a Kubernetes Ingress, so we can then send requests from outside of the cluster.
Want to learn more about minikube? Check out our tutorial https://www.youtube.com/watch?v=xiyWacQca2c
Next, we'll create a virtual env locally to make it simple to manage our dependencies as we work.
Let's create the “core” FastAPI application which will allow us to upload videos, store them in MongoDB's GridFS, and then stream the videos to the browser. First, we'll install our Python dependencies, and add those packages to our requirements.txt:
And now, let's define the API in a file called main.py:
Above, we've defined a simple FastAPI application that connects to MongoDB at startup, and that then exposes two HTTP endpoints — upload and stream. The upload endpoint accepts a POST request that contains a video file as form data, which it then writes to MongoDB. The stream endpoint accepts a GET request with the name of a file that has previously been uploaded, and then it streams the file back to the browser.
Notice that to stream the video, we are returning a FastAPI streaming response, and within that, we are reading the data in chunks asynchronously from MongoDB, which allows us to stream data to the browser as it is read from Mongo.
Next, we'll need to containerize the application we just wrote above with the following Dockerfile.
Then we can run the following to build and push the image:
Now, to deploy the app to Kubernetes, we'll need to write two files which will define the MongoDB deployment and service, the FastAPI deployment and service, and the ingress that will route traffic to the FastAPI app.
Finally, with the above files ready, we can deploy the application by running the following:
Now that the app is up and running, we can upload a video to our FastAPI app, and it will be stored in MongoDB by its filename, like so:
NOTE: you'll need to update the above file path to point at a video you have on your local machine.
Successfully uploaded wobbly.mov
View it in your browser at http://localhost/api/stream/wobbly.mov
And then, we will see something like the above, with a link to stream the video to the browser!
Install Velocity from the JetBrains Marketplace, and then click the “run” button next to the default run configuration, “Setup Velocity.”
Then define a Velocity run configuration as follows:
Click “Next” and then “Create” and you'll see a Velocity session's output in the Velocity console:
Now, let's update our deployed application to include the Kubernetes SDK for Python, which we'll use to create the Batch/Job described above.
Next, let's write the logic that will call the Kubernetes API in a class called CreateProcessVideoJob with the Python SDK for Kubernetes. This base_api.py file will include a Kubernetes Job manifest defined with the Python SDK for Kubernetes, which in turn will contain the container image batch-process-image:latest that we’ll define in just a bit.
Notice that above, we've defined all the sections that you would see in a standard YAML manifest for a Batch/Job, but we've done so in Python.
And now, let's import the above module — base_api.py, and update the upload function so that it will instantiate the CreateProcessVideoJob class and then use it to call the Kubernetes API as a FastAPI background task:
Next, we'll need to define the video processing workflow, which will run as a container in the Batch/Job we're creating. For this, we'll define a Python class called ProcessVideo, which will take a filename as a parameter when we instantiate the class.
Create the following Dockerfile:
And then build the image in Minikube as follows:
Now, when you upload a video, you'll see the following error in the Velocity console:
This is the power of Velocity! You can develop Kubernetes native applications and see Kubernetes-specific tracebacks as if you were developing locally!
If you look closely, the above error is a 403 response from the Kubernetes API, which means that the FastAPI API is sending a request to the Kubernetes API, but the default Kubernetes Service Account (which is trying to execute the command the FastAPI app is sending) doesn't have permission to create the Job that it is trying to create. To fix this, we'll need to give the default Service Account permission to create a Batch/Job in the default namespace.
To do this, we'll need to create the following Role and RoleBinding. The Role we'll create will be called jobs-creator and it will specify the “default” namespace, the “batch” API group, the “jobs” resource type within that API group, and the actions or “verbs” that this Role will be able to carry out.
Then, we'll associate this Role with the default Service Account by creating a related RoleBinding called the jobs-creator-binding. Notice that this RoleBinding specifies the default Service Account and the Role we just defined — the “jobs-creator.”
We can create the above resources with kubectl like we did with the others above:
Now, upload another video, and you will see output similar to that shown below, as the FastAPI application will now have permission to create a Batch/Jobs API object in the default namespace!
Now, in the Minikube dashboard, you can track the progress of the video processing job.
The running Batch/Job in the above screenshot is our video being processed! When this Batch/Job completes, you'll be able to navigate to the link that was returned from the Curl upload — i.e. http://localhost/api/stream/<video_basename>_stabilized.mp4 to stream the stabilized video!
Kubernetes native applications are those that are not only deployed in Kubernetes, but rather are built from Kubernetes API objects. Above, we saw how this application design approach can allow compute-intensive processes to run as independent and asynchronous workflows via Batch/Jobs, and we saw how Velocity can dramatically simplify the process of developing complex workflows such as this within a running Kubernetes cluster.
Without Velocity, we would have had to update our local code, wait for all relevant CI processes to complete, build a container image, and deploy that new image to Kubernetes in order to see if our updated code worked. But with Velocity, we were able to simply develop the service while it was deployed to our cluster, and see our updated code running almost immediately.
Python class called ProcessVideo
Python class called ProcessVideo