Fission is an open-source serverless framework for Kubernetes. You write a function, point it at an environment (a language runtime container), and Fission loads the code into a pre-warmed pod on the first request, so cold starts take around a hundred milliseconds instead of the seconds needed to schedule a new container. In this tutorial you will install Fission on a single-node k3s cluster on Ubuntu 24.04, create a Python environment, deploy functions, expose them over HTTP, run one on a schedule, and store everything as declarative specs.
Prerequisites
To follow this tutorial, you need:
- A server running Ubuntu 24.04 LTS on amd64 or arm64, for example a CubePath VPS, with at least 2 vCPUs, 4 GB of RAM and 20 GB of disk.
- A non-root user with
sudoprivileges. - Ports 22 and 80 reachable from your workstation.
Fission 1.27 requires Kubernetes 1.32 or newer. The current stable k3s release meets that requirement. Throughout the guide, replace your_server_ip with the public IP address of the server.
Step 1 - Installing k3s
k3s is a lightweight, certified Kubernetes distribution. You will disable its bundled Traefik ingress controller so that the Fission router can take port 80 on the node through k3s ServiceLB.
Download the install script and review it before running it:
curl -sfL https://get.k3s.io -o k3s-install.sh
less k3s-install.sh
sudo sh k3s-install.sh --disable traefik
Give your user its own copy of the cluster credentials:
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown "$USER":"$USER" ~/.kube/config
chmod 600 ~/.kube/config
echo 'export KUBECONFIG=$HOME/.kube/config' >> ~/.bashrc
source ~/.bashrc
Check that the node is ready:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
fission01 Ready control-plane,master 35s v1.34.1+k3s1
If UFW is active, allow SSH, HTTP and the k3s pod and service networks:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow from 10.42.0.0/16 to any
sudo ufw allow from 10.43.0.0/16 to any
Step 2 - Installing Helm
Fission is distributed as a Helm chart. Install Helm from its official snap package:
sudo snap install helm --classic
helm version
The last command prints the installed Helm version and confirms the binary is on your PATH.
Step 3 - Installing Fission
Fission keeps its control plane in the fission namespace. Create it and install the custom resource definitions for the release you are going to deploy, here 1.27.0 (check the Fission releases page for a newer one):
export FISSION_VERSION=v1.27.0
kubectl create namespace fission
kubectl create -k "github.com/fission/fission/crds/v1?ref=${FISSION_VERSION}"
The -k option lets kubectl fetch the CRDs directly from the Git repository, so git must be installed (it is on Ubuntu 24.04 by default).
Add the chart repository and install the fission-all chart. routerServiceType=LoadBalancer exposes the Fission router, which serves every HTTP trigger, on port 80 of the node:
helm repo add fission-charts https://fission.github.io/fission-charts/
helm repo update
helm install fission fission-charts/fission-all \
--version 1.27.0 \
--namespace fission \
--set routerServiceType=LoadBalancer
By default, Fission runs functions in the default namespace. Wait for the control plane to start:
kubectl wait --for=condition=Available deployment --all -n fission --timeout=300s
kubectl get svc router -n fission
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
router LoadBalancer 10.43.88.201 your_server_ip 80:31314/TCP 90s
Step 4 - Installing the Fission CLI
The fission CLI talks to the cluster through your kubeconfig. Download the binary for the same release:
curl -fsSLo fission https://github.com/fission/fission/releases/download/${FISSION_VERSION}/fission-${FISSION_VERSION}-linux-amd64
sudo install -m 0755 fission /usr/local/bin/fission
rm fission
On an ARM server, use fission-${FISSION_VERSION}-linux-arm64. Verify that the client and server versions match and that the core components are healthy:
fission version
fission check
fission-services
--------------------
√ executor is running fine
√ router is running fine
√ storagesvc is running fine
√ webhook is running fine
fission-version
--------------------
√ fission is up-to-date
Step 5 - Creating a Python environment
An environment is a language runtime image. For the default poolmgr executor, Fission keeps a pool of idle pods of that image running; when a function is called for the first time, one of those pods loads the function code and serves the request.
Create a Python environment with a pool of two pods and explicit resource limits:
fission env create --name python \
--image ghcr.io/fission/python-env \
--poolsize 2 \
--mincpu 50 --maxcpu 500 \
--minmemory 64 --maxmemory 256
CPU values are in millicores and memory in megabytes. List the environment and its warm pods:
fission env list
fission env pods --name python
The pods run in the default namespace, and their status should be Running before you continue.
Step 6 - Deploying your first function
A Python function for Fission is a module with a main() function. Create one:
mkdir -p ~/fission-demo && cd ~/fission-demo
nano hello.py
def main():
return "Hello from Fission!\n"
Upload it and run it through Fission without creating any route yet:
fission function create --name hello --env python --code hello.py
fission function test --name hello
Hello from Fission!
The Python environment is built on Flask, so you can read the request with flask.request and return a body, status code and headers. Create a function that echoes a JSON body:
nano echo.py
import json
from flask import request
def main():
payload = request.get_json(silent=True) or {}
body = {
"method": request.method,
"received": payload,
"name": payload.get("name", "stranger"),
}
return json.dumps(body), 200, {"Content-Type": "application/json"}
fission function create --name echo --env python --code echo.py
Step 7 - Exposing functions with HTTP triggers
An HTTP trigger maps a URL path and method on the Fission router to a function. Create one for each function:
fission httptrigger create --name hello --url /hello --method GET --function hello
fission httptrigger create --name echo --url /echo --method POST --function echo
fission httptrigger list
Call them through the router, which listens on port 80 of the server:
curl http://your_server_ip/hello
curl -X POST http://your_server_ip/echo \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'
Hello from Fission!
{"method": "POST", "received": {"name": "Alice"}, "name": "Alice"}
WarningThe router has no authentication, so every HTTP trigger you create is public. Put authentication in the function itself or in a reverse proxy in front of the router before exposing anything sensitive.
Step 8 - Choosing an executor and scaling
Fission has two executors, selected per function with --executortype:
| Executor | How it works | Best for |
|---|---|---|
poolmgr (default) | Takes a warm pod from the environment pool and loads the code into it. Idle pods are recycled after --idletimeout seconds. | Low, bursty traffic where fast cold starts matter |
newdeploy | Creates a Kubernetes Deployment and a HorizontalPodAutoscaler for the function. | Steady traffic that needs predictable scaling |
Deploy the echo function a second time with the newdeploy executor, keeping one replica always running and scaling up to five when average CPU usage passes 70%:
fission function create --name echo-scaled --env python --code echo.py \
--executortype newdeploy \
--minscale 1 --maxscale 5 --targetcpu 70 \
--mincpu 100 --maxcpu 500 --minmemory 64 --maxmemory 256
k3s includes metrics-server, so the autoscaler has CPU metrics to work with. Check the resources Fission created:
kubectl get deployments,hpa -n default
The output lists a deployment and an HPA whose names start with newdeploy-echo-scaled.
Step 9 - Running a function on a schedule
Time triggers call a function on a cron schedule, which makes Fission a replacement for small cron jobs. Unlike the classic crontab format, Fission cron expressions have six fields, starting with seconds, and also accept shortcuts such as @every 5m or @hourly.
Preview the next run times of an expression before using it. This one runs every day at 02:30:
fission timetrigger showschedule --cron "0 30 2 * * *" --round 3
Create a trigger that calls hello every minute:
fission timetrigger create --name hello-every-minute --function hello --cron "@every 1m"
fission timetrigger list
After a couple of minutes, check the function logs to confirm the invocations:
fission function log --name hello
Remove the trigger when you no longer need it:
fission timetrigger delete --name hello-every-minute
Step 10 - Storing functions as specs
Creating resources one command at a time is fine for testing, but for version control and CI/CD you want declarative files. Fission specs are YAML files that describe environments, functions and triggers; fission spec apply creates or updates them in the cluster.
Initialise a spec directory in your project and generate specs with the same commands you used before, adding --spec. Use new names, because spec apply refuses to take over resources that were created outside the specs:
cd ~/fission-demo
fission spec init
fission env create --spec --name py --image ghcr.io/fission/python-env --poolsize 2
fission function create --spec --name greet --env py --code hello.py
fission httptrigger create --spec --name greet --url /greet --method GET --function greet
The YAML files are now in specs/. Validate and apply them, then call the new route:
fission spec validate
fission spec apply --wait
curl http://your_server_ip/greet
Hello from Fission!
Commit the specs/ directory together with your code. A pipeline only has to run fission spec apply --wait on each push, and fission spec apply --delete removes cluster resources that were deleted from the specs.
Troubleshooting
fission function test times out. The environment pods are probably not running. Check them and look for image pull or scheduling errors:
fission env pods --name python
kubectl get pods -n default
kubectl describe pod pod_name -n default
The router returns 404. No trigger matches the path and method. Run fission httptrigger list and compare the URL and method with your request; curl uses GET unless you pass -X.
The router service shows EXTERNAL-IP <pending>. Port 80 is already taken on the node, usually by Traefik. Check with kubectl get pods -n kube-system | grep traefik and reinstall k3s with --disable traefik.
The function returns 500. Read the function logs, which include the Python traceback:
fission function log --name echo --detail
Conclusion
You installed Fission on k3s, created a Python environment with a warm pod pool, deployed functions behind HTTP routes, scaled one with the newdeploy executor, scheduled another with a time trigger, and captured the setup as specs you can apply from a pipeline. As next steps, add environments for other languages such as Node.js or Go, protect the router with a reverse proxy that handles TLS and authentication, and look at message queue triggers to consume events from Kafka.
