Skip to content

SlimFaas : The slimest and simplest Function As A Service

License

Notifications You must be signed in to change notification settings

AxaFrance/SlimFaas

Repository files navigation

SlimFaas : The slimest and simplest Function As A Service Continuous Integration Quality Gate Reliability Security Code Corevage

Docker SlimFaas Docker Image Size Docker Image Version

SlimFaas.png

Why use SlimFaas?

  • Scale to 0 after a period of inactivity (work with deployment and statefulset)
  • Synchronous HTTP calls
  • Asynchronous HTTP calls
    • Allows you to limit the number of parallel HTTP requests for each underlying function
  • Retry: 3 times with graduation: 2 seconds, 4 seconds, 8 seconds
  • Private and Public functions
    • Private functions can be accessed only by internal namespace http call from pods
  • Synchronous Publish/Subscribe internal events via HTTP calls to every replicas via HTTP without any use of specific drivers/libraries (Couple you application with SlimFaas)
  • Mind Changer: REST API that show the status of your functions and allow to wake up your infrastructure (Couple your application with Slimfaas)
    • Very useful to inform end users that your infrastructure is starting
  • Plug and Play: just deploy a standard pod
    • No impact on your current kubernetes manifests: just add an annotation to the pod you want to auto-scale
  • Very Slim and very Fast

slim-faas-ram-cpu.png

Getting Started with Kubernetes

To test SlimFaas on your local machine by using kubernetes with Docker Desktop, please use these commands:

git clone https://github.com/AxaFrance/slimfaas.git
cd slimfaas/demo
# Create slimfaas service account and pods
kubectl apply -f deployment-slimfaas.yml
# Expose SlimFaaS service as NodePort or Ingress
kubectl apply -f slimfaas-nodeport.yml
# or
# kubectl apply -f slimfaas-ingress.yml
# Install three instances of fibonacci functions
# fibonacci1, fibonacci2 and fibonacci3
kubectl apply -f deployment-functions.yml
# Install MySql
kubectl apply -f deployment-mysql.yml
# to run Single Page webapp demo (optional) on http://localhost:8000
docker run -p 8000:8000 --rm axaguildev/fibonacci-webapp:latest

Now, you can access your pod via SlimFaas proxy:

Synchronous way:

Asynchronous way:

Just wake up function:

List all function status:

  • http://localhost:30021/status-functions => [{"NumberReady":1,"numberRequested":1,PodType":"Deployment","Visibility":"Public","Name":"fibonacci1"}, {"NumberReady":1,"numberRequested":1,PodType":"Deployment","Visibility":"Public","Name":"fibonacci2"}, {"NumberReady":1,"numberRequested":1,PodType":"Deployment","Visibility":"Public","Name":"fibonacci3"}, {"NumberReady":2,"numberRequested":2,PodType":"Deployment","Visibility":"Private","Name":"fibonacci4"}]

Send event to every function replicas (which deployment subscribe to the event name) in synchronous way:

Single Page WebApp demo:

Enjoy slimfaas!!!!

Getting Started with docker-compose

To test slimfaas on your local machine by using kubernetes with Docker Desktop, please use these commands:

git clone https://github.com/AxaFrance/slimfaas.git
cd slimfaas
docker-compose up

Now, you can access your pod via SlimFaas proxy:

Enjoy slimfaas!!!!

How it works

SlimFaas act as an HTTP proxy with 2 modes:

Synchronous HTTP call

sync_http_call.PNG

Asynchronous HTTP call

async_http_call.PNG

Wake HTTP call

Synchronous Publish HTTP call (events) to every replicas

To publish the message to every replicas in "Ready" state of the function

publish_sync_call.png

How to install

  1. Add SlimFaas StatefulSet to your kubernetes cluster
  2. Add SlimFaas annotations to your pods
  3. Have fun!

sample-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fibonacci1
  namespace: slimfaas-demo
spec:
  selector:
    matchLabels:
      app: fibonacci1
  template:
    metadata:
      labels:
        app: fibonacci1
      annotations:
        # Just add SlimFaas annotation to your pods and that's it !
        SlimFaas/Function: "true"
        SlimFaas/ReplicasMin: "0"
        SlimFaas/ReplicasAtStart: "1"
        SlimFaas/DependsOn: "mysql,fibonacci2" # comma separated list of deployment or statefulset names
        SlimFaas/TimeoutSecondBeforeSetReplicasMin: "300"
        SlimFaas/NumberParallelRequest : "10"
        SlimFaas/Schedule: |
            {"TimeZoneID":"Europe/Paris","Default":{"WakeUp":["07:00"],"ScaleDownTimeout":[{"Time":"07:00","Value":3600},{"Time":"21:00","Value":60}]}}
        SlimFaas/SubscribeEvents: "Public:my-event-name1,Private:my-event-name2,my-event-name3" # comma separated list of event names
        SlimFaas/DefaultVisibility: "Public" # Public or Private (private can be accessed only by internal namespace https call from pods)
        SlimFaas/UrlsPathStartWithVisibility: "Private:/mypath/subPath,Private:/mysecondpath" # Public or Private (private can be accessed only by internal namespace https call from pods)
    spec:
      serviceAccountName: default
      containers:
        - name: fibonacci1
          image: docker.io/axaguildev/fibonacci:latest
          livenessProbe:
              httpGet:
                  path: /health
                  port: 5000
              initialDelaySeconds: 5
              periodSeconds: 5
              timeoutSeconds: 5
          resources:
            limits:
              memory: "96Mi"
              cpu: "50m"
            requests:
              memory: "96Mi"
              cpu: "10m"
          ports:
            - containerPort: 50004
---
apiVersion: v1
kind: Service
metadata:
    name: fibonacci1
    namespace: slimfaas-demo
spec:
    selector:
        app: fibonacci1
    ports:
        - port: 5000
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: slimfaas
  namespace: slimfaas-demo
spec:
  replicas: 3
  podManagementPolicy: Parallel
  serviceName: slimfaas
  selector:
    matchLabels:
      app: slimfaas
  template:
    metadata:
      labels:
        app: slimfaas
    spec:
      volumes:
        - name: slimfaas-volume
          emptyDir:
            sizeLimit: 10Mi
      serviceAccountName: admin # Use a service account with admin role
      containers:
        - name: slimfaas
          image: docker.io/axaguildev/slimfaas:latest
          livenessProbe:
            httpGet:
              path: /health
              port: 5000
            initialDelaySeconds: 3
            periodSeconds: 10
            timeoutSeconds: 8
            terminationGracePeriodSeconds: 30
          env:
            - name: BASE_FUNCTION_URL
              value: "http://{function_name}.{namespace}.svc.cluster.local:5000"
            - name: BASE_FUNCTION_POD_URL # require for publish route
              value: "http://{pod_ip}:5000"
            - name: BASE_SLIMDATA_URL
              value: "http://{pod_name}.slimfaas.{namespace}.svc.cluster.local:3262/"  # Don't expose this port, it can also be like "http://{pod_ip}:3262/" but if you can use DNS it's better
            - name: SLIMFAAS_PORTS
              value: "5000" # can be like "5000,6000,7000" if you want to expose more ports
            - name: NAMESPACE
              value: "default"
            - name: SLIMDATA_DIRECTORY
              value: "/database"
            # If you want to send event to an url which is not a SlimFaas function, you can use this env variable
            # use comma to separate event name and url, use => to separate event name and destination url.
            # urls are separated by ;
            #- name: SLIMFAAS_SUBSCRIBE_EVENTS
            #  value: "my-event-name1=>http://localhost:5002;http://localhost:5003,my-event-name2=>http://localhost:5002"
            # If you want to use just one pod for testing purpose, you can use this env variable
            #- name: SLIMDATA_CONFIGURATION
            #  value: |
            #      {"coldStart":"true"}
            # If you are not on kubernetes for example docker-compose, you can use this env variable, but you will lose auto-scale
            #- name: MOCK_KUBERNETES_FUNCTIONS
            #  value: "{"Functions":[{"Name":"fibonacci","NumberParallelRequest":1}],"Slimfaas":[{"Name":"slimfaas-1"}]}"

             # Configure CORS allowed Origins, default is *, you can use a comma separated list example: http://localhost:3000,http://localhost:3001
            #- name: SLIMFAAS_CORS_ALLOW_ORIGIN
            # Optional, longer is the delay, less CPU and RAM is used
            #- name : HISTORY_SYNCHRONISATION_WORKER_DELAY_MILLISECONDS
            #  value : "500" # default equivalent to 0,5 seconds
            # Optional, longer is the delay, less CPU and RAM is used
            #- name : REPLICAS_SYNCHRONISATION_WORKER_DELAY_MILLISECONDS
            #  value : "2000" # default equivalent to 2 seconds
            # Optional, longer is the delay, less CPU and RAM is used
            #- name : SLIM_WORKER_DELAY_MILLISECONDS
            #  value : "50" # default equivalent to 50 milliseconds
            # Optional, longer is the delay, less CPU and RAM is used
            #- name : SCALE_REPLICAS_WORKER_DELAY_MILLISECONDS
            #  value : "1000" # default equivalent to 1 seconds
            # Optional
            # name : TIME_MAXIMUM_WAIT_FOR_AT_LEAST_ONE_POD_STARTED_FOR_SYNC_FUNCTION
            # value : "10000" # default equivalent to 10 seconds
            # Optional
            # name : POD_SCALED_UP_BY_DEFAULT_WHEN_INFRASTRUCTURE_HAS_NEVER_CALLED
            # value : "false" # default equivalent to false
            # Optional
            # name : SLIMFAAS_ALLOW_UNSECURE_SSL
            # value : "false" # default equivalent to false
            # Optional
            # name: HEALTH_WORKER_DELAY_MILLISECONDS
            # value: "1000" # default equivalent to 1 seconds
            # Optional
            # name: HEALTH_WORKER_DELAY_TO_EXIT_SECONDS
            # value: "60" # default equivalent to 10 seconds

            # name : SLIMDATA_CONFIGURATION # represent SlimData internal configuration, more documentation here: https://dotnet.github.io/dotNext/features/cluster/raft.html
            # value : | #default values
            #    {
            #      "partitioning":"false",
            #      "lowerElectionTimeout":"150",
            #      "upperElectionTimeout":"300",
            #      "requestTimeout":"00:00:00.3000000",
            #      "rpcTimeout":"00:00:00.1500000",
            #      "coldStart":"false",
            #      "requestJournal:memoryLimit":"5",
            #      "requestJournal:expiration":"00:01:00",
            #      "heartbeatThreshold":"0.5",
            #   }
          volumeMounts:
            - name: slimfaas-volume
              mountPath: /database
          resources:
            limits:
              memory: "76Mi"
              cpu: "400m"
            requests:
              memory: "76Mi"
              cpu: "250m"
          ports:
            - containerPort: 5000
            - containerPort: 3262
  # You can use this section to define a persistent volume claim
  #volumeClaimTemplates:
  #- metadata:
  #    name: slimfaas-volume
  #  spec:
  #    accessModes: [ "ReadWriteOnce" ]
  #    storageClassName: managed-csi # or any other storage class available in your cluster
  #    volumeMode: Filesystem
  #    resources:
  #      requests:
  #        storage: 10Mi
---
apiVersion: v1
kind: Service
metadata:
    name: slimfaas
    namespace: slimfaas-demo
spec:
    selector:
        app: slimfaas
    ports:
        - name: "http"
          port: 80
          targetPort: 5000
        - name: "slimdata"
          port: 3262
          targetPort: 3262

Warning

Yours service name must be the same as the SlimFaas Deployment/StatefulSet name

SlimFaas Annotations with defaults values

  • SlimFaas/Function: "true"
    • Activate SlimFaas on this pod, so your pod will be auto-scaled
  • SlimFaas/ReplicasMin: "0"
    • Scale down to this value after a period of inactivity
  • SlimFaas/ReplicasAtStart: "1"
    • Scale up to this value at start
  • SlimFaas/TimeoutSecondBeforeSetReplicasMin: "300"
    • Scale down to SlimFaas/ReplicasMin after this period of inactivity in seconds
  • SlimFaas/NumberParallelRequest : "10"
    • Limit the number of parallel HTTP requests for each underlying function
  • SlimFaas/DependsOn : ""
    • Comma separated list of deployment names or statefulset names
    • Pods will be scaled up only if all pods in this list are in ready state with the minimum number of replicas superior or equal to ReplicasAtStart
    • This property is useful if you want to scale up your pods only if your database is ready for example
  • SlimFaas/SubscribeEvents : ""
    • Comma separated list of event names to license the function to receive events. example: "Public:my-event-name1,Private:my-event-name2,my-event-name3"
    • "Public:" or "Private:" are prefix that set the event visibility, if not set, "SlimFaas/DefaultVisibility" is used
  • SlimFaas/DefaultVisibility : "Public"
    • Public or Private (private can be accessed only by internal namespace https call from pods)
  • SlimFaas/PathsStartWithVisibility : ""
    • Comma separated list of path prefixed by the default visibility. example: "Private:/mypath/subpath,Public:/mypath2"
    • "Public:" or "Private:" are prefix that set the path visibility, if not set, "SlimFaas/DefaultVisibility" is used
  • SlimFaas/ExcludeDeploymentsFromVisibilityPrivate : ""
    • Comma separated list of deployment names or statefulset names
    • Message from that pods will be considered as public. It is useful if you want to exclude some pods from the private visibility, for example for a backend for frontend.
  • SlimFaas/Schedule : json configuration
    • Allows you to define a schedule for your functions. If you want to wake up your infrastructure at 07:00 or for example scale down after 60 seconds of inactivity after 07:00 and scale down after 10 seconds of inactivity after 21:00. Time zones are defined as IANA time zones. The full list is available here
{
  "TimeZoneID":"Europe/Paris", # Time Zone ID can be found here: https://nodatime.org/TimeZones
  "Default":{
    "WakeUp":["07:00"], // Wake up your infrastructure at 07:00
    "ScaleDownTimeout":[
              {"Time":"07:00","Value":20}, // Scale down after 20 seconds of inactivity after 07:00
              {"Time":"21:00","Value":10} // Scale down after 10 seconds of inactivity after 21:00
            ]
  }
}

Why SlimFaas?

We used OpenFaas for a long time and we love it. But we encountered many OpenFaas issues:

  • Kubernetes scripts are tightly coupled to OpenFaas syntax
  • OpenFaas pro is too expensive for our projects
  • OpenFaas needs to be installed on a dedicated namespace and configuration was intricate
  • OpenFaas monitoring was not compatible with our monitoring solution
  • It requires to configure well NATS for managing fail-over
  • Queue configuration is not easy
  • The aggressive removing of old images from docker.io by OpenFaas team in April 2023 got us some production issues

We would like to use Knative but:

  • We cannot use it because of some internal constraints and security issues.

So we decided to create SlimFaas to have a quick and simple replacement proxy solution that can expose Prometheus metrics. Now we have a solution not coupled to anything. SlimFaas is simple, light, fast and plug and play!

How it works ?

Instead of creating many pods, SlimFaas use internally many workers in the same pod:

  • SlimWorker: Manage asynchronous HTTP requests calls to underlying functions
  • SlimDataSynchronizationWorker: Allow to make possible scale up and down SlimData Raft nodes
  • HistorySynchronisationWorker: Manage history of HTTP requests between the pod and kubernetes
  • ReplicasSynchronizationWorker: Manage replicas synchronization between the pod and kubernetes
  • ReplicasScaleWorker: If master, then scale up and down kubernetes pods

SlimData is a simple redis like database included inside SlimFaas executable. It is based on Raft (https://raft.github.io/) algorithm offered by awesome dotNext library (https://github.com/dotnet/dotNext). By default, SlimData use a second HTTP port 3262 to expose its API. Don't expose it and keep it internal.

SlimFaas requires at least 3 nodes in production. 2 nodes are required to keep the database in a consistent state.

slimdata.PNG

If you want to use just one pod for testing purpose, you can use this env variable:

  • SLIMDATA_CONFIGURATION: '{"coldStart":"true"}'

This will allow to start a pod alone as a leader. SlimFaas can to scale up and down by using classic Horizontal Pod Autoscaler (HPA).

Build with .NET

Why .NET?

Videos

What Next?

  1. Retry pattern fully controllable with dead letter queue
  2. Scale up dynamically from SlimFaas in Async Mode
  3. Scale up dynamically from SlimFaas in Sync Mode (Copy KEDA syntax)
  4. Aggregate all swaggers from functions in one exposed by SlimFaas
  5. New pod to "Wake up" from External Source (example: Kafka, etc.) using https://cloudevents.io/ as standard https://github.com/cloudevents/sdk-csharp
  6. Continue Optimization