stack8s: Your First Python Serverless Function on Kubernetes: A Multi-Framework Guide (Knative, OpenFaaS, Nuclio & Fission)

· 6 min read
stack8s: Your First Python Serverless Function on Kubernetes: A Multi-Framework Guide (Knative, OpenFaaS, Nuclio & Fission)
Photo by Growtika / Unsplash

General Assumptions:

  • You have a Kubernetes cluster running on stack8s.
  • The respective serverless engine (Knative, OpenFaaS, Nuclio, Fission) is installed on your cluster.
  • You have the CLI for the respective engine installed locally (kn, faas-cli, nuctl, fission).
  • We'll assume an Ingress controller (like Nginx, Traefik, or the one bundled with the serverless engine like Kourier for Knative) is set up to expose services externally. If not, you might use NodePort or LoadBalancer service types and directly hit the IP/port of the gateway/router service. For simplicity, YOUR_CLUSTER_IP_OR_DOMAIN will represent this external access point.

1. Knative Serving

Knative Serving creates Kubernetes Services, Deployments, and (if you have a networking layer like Istio, Contour, or Kourier installed) Ingress-like resources (Route, Ingress) to expose your function.

Python "Hello World" (app.py):
Knative can run any container that serves HTTP. A common way with Python is to use a minimal web framework like Flask.

# app.py
from flask import Flask
import os

app = Flask(__name__)

@app.route('/')
def hello_world():
    return "Hello World from Knative!"

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8080))
    app.run(debug=True, host='0.0.0.0', port=port)

Dockerfile:

FROM python:3.9-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .

# Knative will set the PORT environment variable
ENV PORT 8080
EXPOSE 8080

CMD ["python", "app.py"]

requirements.txt:

Flask>=2.0

Deployment:

Deploy using kn CLI (assuming you are in the default namespace for simplicity):

kn service create hello-knative \
  --image your-registry/knative-hello-py \
  --port 8080 # The port your container listens on

Build and push your Docker image:

docker build -t your-registry/knative-hello-py .
docker push your-registry/knative-hello-py

API Endpoint Exposure:

Knative will create a URL for your service.

  • Example Endpoint: http://hello-knative.default.your-knative-domain.com

To find the URL:

kn service describe hello-knative

Look for the URL: field in the output. It will typically be something like:
http://hello-knative.default.your-knative-domain.com
(where your-knative-domain.com is configured during Knative installation, e.g., 1.2.3.4.nip.io if using nip.io for local testing with an IP).

Invocation:

curl http://hello-knative.default.your-knative-domain.com
# Expected Output: Hello World from Knative!

2. OpenFaaS

OpenFaaS uses an API Gateway to route requests to functions.

Python "Hello World" (handler.py for the python3-http template):

# handler.py
def handle(event, context):
    """
    handle a request to the function
    Args:
        event (obj): request body (if any)
        context (obj): invocation context
    """
    return {
        "statusCode": 200,
        "body": "Hello World from OpenFaaS!"
    }

(Note: For the simpler python3 template, handle(req) would just return the string.)

Stack file (hello.yml):

version: 1.0
provider:
  name: openfaas
  gateway: http://YOUR_OPENFAAS_GATEWAY_IP:8080 # Replace with your gateway
functions:
  hello-openfaas:
    lang: python3-http # Or python3 for simpler string return
    handler: ./ # Directory containing handler.py
    image: your-registry/hello-openfaas-py:latest
    # For python3-http, it expects a JSON serializable dict.
    # For python3, it expects a string.

Deployment:

Deploy:

# Make sure your OPENFAAS_URL is set or provide --gateway
# export OPENFAAS_URL=http://YOUR_OPENFAAS_GATEWAY_IP:8080
faas-cli deploy -f hello.yml

(Optional, if you want to build locally first)

faas-cli build -f hello.yml
faas-cli push -f hello.yml

API Endpoint Exposure:

The API Gateway exposes functions under the /function/ path.

  • Gateway URL: Find your OpenFaaS gateway service IP/port. If using kubectl port-forward svc/gateway -n openfaas 8080:8080, it's http://127.0.0.1:8080. If exposed via Ingress, it's http://YOUR_CLUSTER_IP_OR_DOMAIN/openfaas/.
  • Function Path: /function/<function-name>
  • Example Endpoint: http://YOUR_OPENFAAS_GATEWAY_IP:8080/function/hello-openfaas
    (If using a namespace, it might be .../function/hello-openfaas.namespace)

Invocation:

curl http://YOUR_OPENFAAS_GATEWAY_IP:8080/function/hello-openfaas
# Expected Output (for python3-http): {"statusCode": 200, "body": "Hello World from OpenFaaS!"}
# If using the simpler python3 template, the output would be: Hello World from OpenFaaS!

3. Nuclio

Nuclio deploys functions that can be triggered by HTTP. It has its own dashboard which can act as a gateway, or functions can be exposed via Kubernetes Ingress.

Python "Hello World" (helloworld.py):

# helloworld.py
def handler(context, event):
    return context.Response(body='Hello World from Nuclio!',
                            headers={},
                            content_type='text/plain',
                            status_code=200)

Function Configuration (function.yaml - optional, nuctl can infer):

apiVersion: nuclio.io/v1beta1
kind: Function
metadata:
  name: hello-nuclio
  namespace: nuclio # Or your preferred namespace
spec:
  runtime: python:3.9
  handler: helloworld:handler # filename:handler_function_name
  triggers:
    http:
      kind: http
      maxWorkers: 1
      # attributes:
      #   ingresses:
      #     my-ingress: # Rule name for an ingress
      #       host: my-func.example.com # if using ingress
      #       paths:
      #         - /hello

Deployment:

Use the nuctl CLI.

nuctl deploy hello-nuclio \
  --namespace nuclio \
  --path ./helloworld.py \
  --runtime python:3.9 \
  --handler helloworld:handler \
  --trigger http \
  --port 8080 # Port function listens on internally; Nuclio handles external routing

This command deploys the function and by default sets up an HTTP trigger.

API Endpoint Exposure:

Nuclio functions are typically invoked through the Nuclio dashboard/gateway service or an Ingress.

  • Default Path (if not using Ingress with custom paths):
    Nuclio functions deployed via the CLI in a project (default project is default) are often invoked via a path like:
    http://<nuclio-dashboard-or-ingress-ip>:<port>/<project-name>/<function-name>
    If you didn't specify a project, it might be implicitly in the default project within Nuclio.
    If the function is deployed into the nuclio Kubernetes namespace, and invoked directly via its service (less common for external access), the path might be simpler.However, a more common way is that nuctl deploy will also create a Kubernetes service for the function, e.g., svc/nuclio-hello-nuclio. If you have an Ingress controller, you'd create an Ingress rule pointing to this service.The nuctl get func hello-nuclio -n nuclio -o yaml command will show you the httpPort under status.
  • Invoking via nuctl (for testing):
    nuctl invoke hello-nuclio -n nuclio -m GET
    This uses the Nuclio API, not a direct HTTP endpoint necessarily, but good for testing.
  • Direct HTTP Invocation (Example assuming NodePort on the function's service):
    If Nuclio created a service for your function (e.g., nuclio-hello-nuclio) of type NodePort:
    1. kubectl get svc nuclio-hello-nuclio -n nuclio (Find the NodePort, e.g., 31234)
    2. http://YOUR_K8S_NODE_IP:31234

Typical Ingress Setup:
You'd create an Ingress resource:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-nuclio-ingress
  namespace: nuclio
spec:
  rules:
  - http:
      paths:
      - path: /nuclio/hello
        pathType: Prefix
        backend:
          service:
            name: nuclio-hello-nuclio # Service created by Nuclio for the function
            port:
              number: 8080 # The port the function's service listens on

Then the endpoint would be http://YOUR_CLUSTER_IP_OR_DOMAIN/nuclio/hello

Finding the Nuclio Dashboard/Gateway IP/Port:

kubectl get svc -n nuclio dashboard # Look for External IP or NodePort

Let's say it's YOUR_NUCLIO_DASHBOARD_IP:PORT.

Invocation (assuming Ingress like above):

curl http://YOUR_CLUSTER_IP_OR_DOMAIN/nuclio/hello
# Expected Output: Hello World from Nuclio!

4. Fission

Fission uses a Router component to direct HTTP requests to function pods.

Python "Hello World" (hello.py):

# hello.py
def main():
    return "Hello World from Fission!"

(Fission's Python environment typically expects a main function or a Flask app).

Deployment:

Create an HTTP trigger (route):

fission route create --name hello-route --method GET --url /hello-fission --function hello-fission
# Or fission httptrigger create --method GET --url /hello-fission --function hello-fission (newer CLI syntax)

Create the function:

fission function create --name hello-fission --env python --code hello.py

Ensure you have a Python environment in Fission (usually created by default):

fission env list
# If not, fission env create --name python --image fission/python-env

API Endpoint Exposure:

The Fission Router service handles incoming requests.

  • Function Path: This is defined by the --url parameter in fission route create.
  • Example Endpoint: http://YOUR_FISSION_ROUTER_IP:PORT/hello-fission

Finding the Fission Router IP/Port:

kubectl get svc router -n fission # Or your Fission installation namespace
# Look for External IP (if LoadBalancer) or NodePort

Let's say it's YOUR_FISSION_ROUTER_IP:PORT.

Invocation:

curl http://YOUR_FISSION_ROUTER_IP:PORT/hello-fission
# Expected Output: Hello World from Fission!

Summary of Endpoint Patterns:

  • Knative: http://<service-name>.<namespace>.<knative-domain>/[path-if-any] (highly dependent on networking layer config)
  • OpenFaaS: http://<gateway-ip>:<port>/function/<function-name>[.namespace]
  • Nuclio: http://<dashboard/ingress-ip>:<port>/<project>/<function-name> or custom Ingress path. Service-level access is also possible.
  • Fission: http://<router-ip>:<port>/<url-path-defined-in-trigger>

Remember that YOUR_..._IP and domain names are placeholders. You'll need to find the actual external IP addresses or configure DNS/Ingress for your specific Kubernetes and serverless engine setup. Using an Ingress controller with custom hostnames is the most common production approach.