BoxBoat Blog
Service updates, customer stories, and tips and tricks for effective DevOps
Kubeless FastAPI Runtime
by John Hooks | Tuesday, Sep 28, 2021 | Kubernetes Serverless
Recently, one of our customers required a serverless offering for Kubernetes. After looking through multiple solutions, we settled on Kubeless. It gave us a good mix of compliance by allowing us to select only certain runtimes, but also the flexibility to create our own runtimes.
Overview
By default, Kubeless uses Bottle with Python as its web framework. This is great when simplicity is needed. You can just pass in a request and function context getting whatever you need from that request item. Here's a simple example:
def handle(event, context):
return event["data"]
You can also access the request directly at event["extensions"]["request"]
. So if you need a specific header call event["extensions"]["request"].headers.get("X-My-Header")
. Along with the body in [event.data](http://event.data)
you get these other properties as well:
event:
data:
mykey: "my value"
event-id:
event-time:
event-namespace:
extensions:
request:
response:
While this is initially great for simplicity, our client already Settled on FastAPI, and they wanted the same simplicity you can get out of the box with Bottle. FastAPI gives you OpenAPI documentation automatically, integrations with Pydantic for API contract enforcement, and much more. All of these advantages would be non-existent with the default Bottle framework. So we took the time to write a FastAPI based runtime for Kubeless. Now we get documentation and easy API enforcement with Pydantic. The downside to using FastAPI for a runtime is the code is a bit longer. Most of the code is for the documentation that is generated through FastAPI's automated OpenAPI generator.
Developer Flow
FastAPI uses function decorators to define the method for the handler, we need a way to determine what the handler is expecting. So in our runtime, you just begin your function with the method type and an underscore (e.g. get_
, post_
, put_
, delete_
). The BaseModels are also based on the method type (e.g. GetResponse
, PostResponse
, GetRequest
, PutRequest
, etc). A large reason for having a request/response model per method is because with FastAPI GET requests are not allowed to receive a request body.
We also have a dataclass for our parameters. This is a dataclass and not a BaseModel because of a limitation with FastAPI where BaseModels cannot be used for parameter definitions. To declare your params, just define a dataclass named Params
with the names of the params and their types (Query
, Header
, Cookie
, Path
).
Kubeless also has a Custom Resource Definition (CRD) for their functions. You can define the function above as a Kubernetes Kind vs using the kubeless cli tool to deploy the function in the cluster. This allows you to easily version the functions in Git and deploy them with a pipeline.
Simple Get Example
from fastapi import HTTPException, Query, Header
from pydantic import BaseModel
from dataclasses import dataclass
FUNCTION_NAME="mytest"
FUNCTION_VERSION="1.0.0"
FUNCTION_SUMMARY="A basic function"
FUNCTION_RESPONSE_DESC="Some get response information"
class GetResponse(BaseModel):
details: dict
@dataclassclass Params:
request_id: str = Header(None)
my_param: str = Query(None)
def get_handler(event, context):
try:
data ={
"request_id": event.headers["request-id"],
"param": event.query_params["my_param"]
}
except Exception:
raise HTTPException(status_code=400, detail="request id and my_param must be set")
try:
res = GetResponse(details=data)
except Exception:
raise HTTPException(status_code=500, detail="something went wrong")
return res
Generated Documentation from Example
Security
The default image Kubeless uses for their runtimes is a custom, minimal Debian image. After scanning, it had quite a few vulnerabilities. It was also around 500MB in size. This would never fly with our client.
What we ended up doing was using Python's Alpine image. This substantially shrunk our runtime container size to ~73MB and removed every single vulnerability.
Compliance
As I mentioned, our client desires strong compliance. Kubeless fits this bill by only using images defined in a configmap. We can limit the existing public runtimes to disable undesired languages and versions of languages. Kubeless uses an init container to inject the application into the runtime image with the handler. This was desired over how other solutions like OpenFaaS work since those serverless runtimes are built into an image and then deployed which makes container image compliance more difficult.
More Information
For more information you can view the project on GitHub here: https://github.com/boxboat/kubeless-fastapi
This includes more examples, and instructions on configuring Kubeless to include this image as a runtime option.