Kubernetes Serverless: fission.io

Published: Sep 10, 2020 by Isaac Johnson

Following last week’s post on Knative, I thought I should check out one of the other popular serverless framework for k8s, fission.io.  Fission, unlike KNative just focuses on the code.  This means you are not deploying ‘serverless’ containers, rather, pushing code like one might do with AWS Lambda.  While the core is written in Go, it supports many ‘environments’ including NodejS, Python, Ruby, Go, PHP, Bash and pretty much anything else that runs on Linux.

Getting Started

Let’s create a resource group and service principal

$ az group create --name idjfssnrg --location centralus
{
  "id": "/subscriptions/2d07208f-3cda-40cf-b54c-f28a6e8b0bf6/resourceGroups/idjfssnrg",
  "location": "centralus",
  "managedBy": null,
  "name": "idjfssnrg",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}

Creating the SP:

$ az ad sp create-for-rbac -n idjfssnrg --skip-assignment --output json > my_sp_fs_aks.json
Changing "idjfssnrg" to a valid URI of "http://idjfssnrg", which is the required format used for service principal names

$ export SP_PASS=`cat my_sp_fs_aks.json | jq -r .password`
$ export SP_ID=`cat my_sp_fs_aks.json | jq -r .appId`

Next, we can create the K8s cluster

$ az aks create --resource-group idjfssnrg --name idjfssnaks --location centralus --node-cou
nt 3 --enable-cluster-autoscaler --min-count 2 --max-count 4 --generate-ssh-keys --network-plugin azure --network-policy azure --service-principal $SP_ID --client-secret $SP_PASS
{
  "aadProfile": null,
  "addonProfiles": {
    "kubedashboard": {
      "config": null,
      "enabled": true,
      "identity": null
    }
  },
  "agentPoolProfiles": [
    {
      "availabilityZones": null,
      "count": 3,
      "enableAutoScaling": true,
      "enableNodePublicIp": null,
      "maxCount": 4,
      "maxPods": 30,
      "minCount": 2,
      "name": "nodepool1",
      "nodeLabels": null,
      "nodeTaints": null,
      "orchestratorVersion": "1.16.13",
      "osDiskSizeGb": 128,
      "osType": "Linux",
      "provisioningState": "Succeeded",
      "scaleSetEvictionPolicy": null,
      "scaleSetPriority": null,
      "tags": null,
      "type": "VirtualMachineScaleSets",
      "vmSize": "Standard_DS2_v2",
      "vnetSubnetId": null
    }
  ],
  "apiServerAccessProfile": null,
  "dnsPrefix": "idjfssnaks-idjfssnrg-70b42e",
  "enablePodSecurityPolicy": null,
  "enableRbac": true,
  "fqdn": "idjfssnaks-idjfssnrg-70b42e-f6790523.hcp.centralus.azmk8s.io",
  "id": "/subscriptions/2d07208f-3cda-40cf-b54c-f28a6e8b0bf6/resourcegroups/idjfssnrg/providers/Microsoft.ContainerService/managedClusters/idjfssnaks",
  "identity": null,
  "identityProfile": null,
  "kubernetesVersion": "1.16.13",
  "linuxProfile": {
    "adminUsername": "azureuser",
    "ssh": {
      "publicKeys": [
        {
          "keyData": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHZ3iOnMMLkiltuikXSjqudfCHmQvIjBGMOuGk6wedwG8Xai3uv0M/X3Z2LS6Ac8tComKEKg7Zje2KFBnvBJvU5JqkTwNHnmp682tXf15EYgn4tB7MDz5DUARpcUXJbYfUg8yPUDveYHw8PEm1n+1MvLJN0ftvdORG5CQQEl/m7jErbJJQI70xg7C8/HG5GmJpIQjDl7UVsJANKab/2/bbUlG1Sqp4cQ/LwxKxQ6/QK/HVauxDkudoTkFLqukLWVjHvNZD37MC/wygSsEVYF+yrkNJySlNbMk4ZNmMwva1yLX8Shhr8G4wWe8QI9Ska8B0keSIu8fzRWxXAv2gB3xB"
        }
      ]
    }
  },
  "location": "centralus",
  "maxAgentPools": 10,
  "name": "idjfssnaks",
  "networkProfile": {
    "dnsServiceIp": "10.0.0.10",
    "dockerBridgeCidr": "172.17.0.1/16",
    "loadBalancerProfile": {
      "allocatedOutboundPorts": null,
      "effectiveOutboundIps": [
        {
          "id": "/subscriptions/2d07208f-3cda-40cf-b54c-f28a6e8b0bf6/resourceGroups/MC_idjfssnrg_idjfssnaks_centralus/providers/Microsoft.Network/publicIPAddresses/40e09eb5-0971-4d0c-bdcb-24b873ea8d3c",
          "resourceGroup": "MC_idjfssnrg_idjfssnaks_centralus"
        }
      ],
      "idleTimeoutInMinutes": null,
      "managedOutboundIps": {
        "count": 1
      },
      "outboundIpPrefixes": null,
      "outboundIps": null
    },
    "loadBalancerSku": "Standard",
    "networkPlugin": "azure",
    "networkPolicy": "azure",
    "outboundType": "loadBalancer",
    "podCidr": null,
    "serviceCidr": "10.0.0.0/16"
  },
  "nodeResourceGroup": "MC_idjfssnrg_idjfssnaks_centralus",
  "privateFqdn": null,
  "provisioningState": "Succeeded",
  "resourceGroup": "idjfssnrg",
  "servicePrincipalProfile": {
    "clientId": "f969704b-b745-45b3-9f7f-5af97fe49cfa",
    "secret": null
  },
  "tags": null,
  "type": "Microsoft.ContainerService/ManagedClusters",
  "windowsProfile": {
    "adminPassword": null,
    "adminUsername": "azureuser"
  }
}

Verification

$ rm ~/.kube/config && az aks get-credentials -n idjfssnaks -g idjfssnrg --admin
Merged "idjfssnaks-admin" as current context in /home/builder/.kube/config
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
aks-nodepool1-46349218-vmss000000 Ready agent 3m34s v1.16.13
aks-nodepool1-46349218-vmss000001 Ready agent 3m38s v1.16.13
aks-nodepool1-46349218-vmss000002 Ready agent 3m36s v1.16.13

Lastly, as I’ve mentioned before, I dance between helm and kubectl versions so let’s verify we have helm 3 and a recent kubectl:

$ helm version
version.BuildInfo{Version:"v3.2.3", GitCommit:"8f832046e258e2cb800894579b1b3b50c2d83492", GitTreeState:"clean", GoVersion:"go1.13.12"}
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.3", GitCommit:"b3cbbae08ec52a7fc73d334838e18d17e8512749", GitTreeState:"clean", BuildDate:"2019-11-13T11:23:11Z", GoVersion:"go1.12.12", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.13", GitCommit:"1da71a35d52fa82847fd61c3db20c4f95d283977", GitTreeState:"clean", BuildDate:"2020-07-15T21:59:26Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}

Setting up Fission

Next we need to create a namespace and install Fission.io from the Helm chart.

$ export FISSION_NAMESPACE="fission"
$ kubectl create namespace $FISSION_NAMESPACE
namespace/fission created
$ helm install --namespace $FISSION_NAMESPACE --name-template fission \
> https://github.com/fission/fission/releases/download/1.10.0/fission-all-1.10.0.tgz
NAME: fission
LAST DEPLOYED: Thu Sep 3 18:45:11 2020
NAMESPACE: fission
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Install the client CLI.

Mac:
  $ curl -Lo fission https://github.com/fission/fission/releases/download/1.10.0/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/

Linux:
  $ curl -Lo fission https://github.com/fission/fission/releases/download/1.10.0/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/

Windows:
  For Windows, you can use the linux binary on WSL. Or you can download this windows executable: https://github.com/fission/fission/releases/download/1.10.0/fission-cli-windows.exe

2. You're ready to use Fission!

  # Create an environment
  $ fission env create --name nodejs --image fission/node-env

  # Get a hello world
  $ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js

  # Register this function with Fission
  $ fission function create --name hello --env nodejs --code hello.js

  # Run this function
  $ fission function test --name hello
  Hello, world!

Following those instructions, let’s install the fission CLI

$ curl -Lo fission https://github.com/fission/fission/releases/download/1.10.0/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/

Validation

We can verify fission is running by checking the version of the client and server component.

$ fission version
client:
  fission/core:
    BuildDate: "2020-06-29T16:26:41Z"
    GitCommit: e85d77bbce719fb898084c0ddb4ba37b199bf667
    Version: 1.10.0
server:
  fission/core:
    BuildDate: "2020-06-29T16:26:41Z"
    GitCommit: e85d77bbce719fb898084c0ddb4ba37b199bf667
    Version: 1.10.0

Hello World

Let’s start with the hello world example.

One of the key concepts to understand is that you need to create an “environment” first. This tells fission what type of base container you are running for particular code.

Create a NodeJS Fission 1.10 env:

$ fission env create --name nodejs --image fission/node-env:1.10.0
environment 'nodejs' created

Next we can download the hello world js (or you can copy from below):

$ curl -LO https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js
  % Total % Received % Xferd Average Speed Time Time Time Current
                                 Dload Upload Total Spent Left Speed
100 119 100 119 0 0 237 0 --:--:-- --:--:-- --:--:-- 237
$ cat hello.js

module.exports = async function(context) {
    return {
        status: 200,
        body: "hello, world!\n"
    };
}

Now we “create a function” to deploy the code. For a simple js file, this works just fine

$ fission function create --name hello --env nodejs --code hello.js
Package 'hello-a2d6a378-3852-43d1-9546-9985dd7e9b20' created
function 'hello' created
$ fission function test --name hello
hello, world!

We can also package a folder of NodeJS code.

Here I cloned their git repo and used some examples: https://github.com/fission/fission

When we zip some contents and deploy, we are creating a “package”.  To refer to a function within a “package”, we’ll reference the package name on our function create step.

Creating a package:

/fission/fission/examples$ zip -jr nodejs.zip nodejs/
  adding: multi-entry.js (deflated 54%)
  adding: kubeEventsSlack.js (deflated 54%)
  adding: package.json (deflated 49%)
  adding: README.md (deflated 62%)
  adding: README_V1.md (deflated 60%)
  adding: index.js (stored 0%)
  adding: hello.js (deflated 18%)
  adding: hello-callback.js (deflated 13%)
  adding: weather.js (deflated 57%)
/fission/fission/examples$ fission pkg create --sourcearchive nodejs.zip --env nodeenv
Warning: --name will be soon marked as required flag, see 'help' for details
Package 'nodejs-zip-ipzv' created

We can now install functions referencing that package (nodejs-zip-ipzv)

/fission/fission/examples$ fission fn create --name index --pkg nodejs-zip-ipzv
Warning: Function's environment is different than package's environment, package's environment will be used for creating function
function 'index' created

And while we can test on the command line still:

/fission/fission/examples$ fission fn test --name index
hello, world!

Exposing Functions

We can also expose it through the “router”

/fission/fission/examples$ fission route create --method GET --url /hello-index --function index
Warning: --name will be soon marked as required flag, see 'help' for details
trigger 'bd3e48d9-aaf4-4db4-bf80-ab296bfa47b7' created

You could now setup DNS to the router pod if you wanted

Summary:

At this point, this is a good introduction to Fission.io.  I did attempt to secure TLS on the Nginx Ingress (using this guide) but could not seem to get it to work with LetsEncrypt.  I do plan to circle back.  

However, I could really see a use for scheduled cleanup tasks.  I have seen in the past examples of creating persistent containers with crontabs for service work in clusters.  Instead, one could launch a function with a “timer”. From docs:

$ fission timer create --name minute --function hello --cron "@every 1m"
trigger 'minute' created
Current Server Time: 2019-12-17T08:33:43Z
Next 1 invocation: 2019-12-17T08:34:43Z

They also have a native Message Queue trigger that supports standards like Kafka.

getting-started k8s serverless fission-io

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes