Removed demo, documentations & updated Readme (#2411)

This commit is contained in:
Atulpriya Sharma
2022-04-15 17:55:56 +05:30
committed by GitHub
parent 8442e21621
commit 2d150e79fb
44 changed files with 1 additions and 2264 deletions
-200
View File
@@ -1,200 +0,0 @@
# A high-level view of the internals of Fission.
## How it works
Fission is a FaaS -- users create functions (source level), register
them with Fission using a CLI, and associate functions with triggers.
Fission wraps those functions into a service and runs them on
Kubernetes on demand.
Here's an overview of the services that make up Fission.
## Components
Core Components:
* Controller
* Executor
* Environment Container (language-specific)
* Router
* Builder Manager
* Storage Service
Optional Components:
* Logger
* Kubewatcher
* Message Queue Trigger
* Timer
Third-party components:
* InfluxDB: To store function logs.
* Prometheus: For metric collection and canary deployment.
* NATS Streaming: For message queue trigger. (Kafka, Azure are not included in charts deployment.)
## Core Components
### Controller
The controller contains CRUD APIs for functions, triggers, environments,
Kubernetes event watches, etc. This is the component that the client talks to.
All fission resources are stored in kubernetes CRDs. It needs to be able to
talk to kubernetes API service.
### Executor
The executor has two simple APIs; the router calls both these endpoints.
* GetFunctionService takes function metadata and dispatches the corresponding executor
type to get the address of a service/pod and returns it to the router.
* TapService lets executor know a service/pod is being used; if it's not
called for a few minutes the pod(s) backing the service are killed.
It now supports two different executor types:
* PoolManager
* NewDeploy
These two executor types have different strategies to launch, specialize, and manage pod(s).
You should choose one of the executor types wisely based on the scenario.
#### PoolManager
PoolManager manages pools of generic containers and function containers.
PoolManager watches the environment CRD changes and eagerly creates generic pools
for environments. It uses Kubernetes deployments to do that. The
environment container runs in a pod with the 'fetcher' container.
Fetcher is a straightforward utility that downloads a URL sent to it
and saves it at a configured location (shared volume).
The implementation chooses a generic pod from the pool, relabels it to
"orphan". The pod from the deployment invokes fetcher to copy the function
into the pod and hits the specialize endpoint on the environment container.
This causes the function to be loaded. The pod is now specific to that
function. This function pod is cached; it's cleaned up if it's unused for a few minutes.
PoolManager selects a generic pod from the warm pool, specializes it,
and recycles the pod if there are no further requests to the function after a few minutes.
It makes PoolManager suitable for functions that are short-living
and requires a short cold start time [1].
However, PoolManager only selects one pod per function, which is not
suitable for serving massive traffic. In such cases, you should consider
using NewDeploy as executor type of function.
[1] The cold start time depends on the package size of the function. If it's
a snippet of code, the cold start time usually is less then 100ms.
#### NewDeploy
NewDeploy creates deployment, service, and HPA for functions in order to handle
massive traffic.
NewDeploy watches the function CRD changes and creates a Kubernetes deployment,
service, and HPA for a function. NewDeploy will scale the replicas of a function
deployment to the minimum feasible scale setting, if the minimum scale setting of
a function is greater than 0. The 'fetcher' inside the pod uses a URL in the
JSON payload, which is attached as a parameter to start fetcher, to download the
function package instead of waiting for calls from NewDeploy.
When a function experiences a traffic spike, the service helps to distribute the requests to
pods belonging to the function for better workload distribution and lower latency. Also,
the HPA scales the replicas of the deployment based on the conditions set by the user.
This approach though increases the cold time of a function, but also makes NewDeploy
suitable for functions designed to serve massive traffic.
### Environment Container
Environment containers run user-defined functions and are language-specific.
Each environment container must contain an HTTP server and a loader for functions.
The pool manager deploys the environment container into a pod with fetcher
(fetcher is a simple utility that can fetch an HTTP URL to a file at a
configured location). This pod forms a "generic pod" because it can
be loaded with any function in that coding language.
When the pool manager needs to create a service for a function, it calls
fetcher to fetch the function. Fetcher downloads the function into a
volume shared between fetcher and this environment container. Poolmgr
then requests the container to load the function.
### Router
The router forwards HTTP requests to function pods. If there's no
running service for a function, it requests one from executor, while
holding on to the request; the router will forward the request to
the pod once the function service is ready.
The router is the only stateless component and can be scaled up if needed, according to
load.
### Builder Manager
The builder manager watches the package & environments CRD changes and manages
the builds of function source code. Once an environment that contains a builder
image is created, the builder manager will then creates the Kubernetes service
and deployment under the fission-builder namespace to start the environment
builder. And once a package that contains a source archive is created, the
builder manager talks to the environment builder to build the function's source
archive into a deploy archive for function deployment.
After the build, the builder manager asks Builder to upload the deploy archive to the
Storage Service once the build succeeded, and updates the package status attached with build logs.
### Storage Service
The storage service is the home for all archives of packages with sizes larger than 256KB.
The Builder pulls the source archive from the storage service and uploads deploy archive to it.
The fetcher inside the function pod also pulls the deploy archive for function specialization.
## Optional Components
### Logger
Logger is deployed as DaemonSet to help to forward function logs to a centralized
database service for log persistence. Currently, only InfluxDB is supported to store logs.
Following is a diagram describe how log service works:
1. Logger watches pod changes and creates a symlink to the container log if the pod runs on the same node.
2. Fluentd reads logs from symlink and pipes them to InfluxDB
3. `fission function logs ...` retrieve event logs from InfluxDB with optional log filter
4. Logger removes the symlink if the pod no longer exists.
### Kubewatcher
Kubewatcher watches the Kubernetes API and invokes functions
associated with watches, sending the watch event to the function.
The controller keeps track of the user's requested watches and associated
functions. Kubewatcher watches the API based on these requests; when
a watch event occurs, it serializes the object and calls the function
via the router.
While a few simple retries are done, there isn't yet a reliable
message bus between Kubewatcher and the function. Work for this is
tracked in issue #64.
### Message Queue Trigger
A message queue trigger binds a message queue topic to a function:
Events from that topic cause the function to be invoked with the
message as the body of the request. The trigger may also contain a
response topic: if specified, the function's output is sent to this
response.
Here's a diagram of the components:
![Message queue trigger Diagram](https://user-images.githubusercontent.com/202578/27012344-9457cb24-4f00-11e7-8d6b-926ff01637b3.jpg)
### Timer
The timer works like kubernetes CronJob but instead of creating a pod to do the task,
it sends a request to router to invoke the function. It's suitable for the background tasks that
need to executor periodically.
The timer works like a Kubernetes CronJob, but instead of creating a
pod to do the task, it sends a request to the router to invoke the
function. It is suitable for background tasks that need to execute periodically.
-55
View File
@@ -1,55 +0,0 @@
# BUILDING MULTI-ARCHITECTURE IMAGES
While the Docker images for Fission can be built by running:
```
make image
```
the stable images are build for multiple architectures (and automatically pushed to the repository) by
```
make images-multiarch
```
However, there are several options and requirements for building these images, which are explained here.
## BUILDERS
We use `docker buildx` to build images for multiple architectures. At the moment, this is considered an
experimental feature by Docker, and as such it requires access to experimental features to be enabled
in the daemon.json file.
By default, this will use BuildKit and QEMU emulation support to build the image for each architecture
locally. However, for faster building, it is possible to use native nodes to build each architecture,
either simple remote Docker instances or a Kubernetes cluster. For information on how to set this up,
please see the blog article here:
<https://www.docker.com/blog/multi-arch-images/>
And/or the command reference here:
<https://docs.docker.com/engine/reference/commandline/buildx_create/>
## BUILD OPTIONS
### PLATFORMS
The PLATFORMS variable can be set to a comma-separated list of platforms for which images should be
built when running the multi-arch build. If unset, it defaults to _linux/amd64,linux/arm64,linux/arm_.
### REPOSITORY SELECTION
By default, the multi-arch build will automatically try to push the resulting images to the fission/*
repositories, under the dev tag; i.e. fission/fission-bundle:dev, fission/fetcher:dev, and
fission/builder:dev. However, for convenient, this can be customized by setting the REPO and TAG
environment variables to set a different repository prefix and tag for the images.
For example:
```
REPO=randomdev TAG=test make images-multiarch
```
would build and push the images randomdev/fission-bundle:test, randomdev/fetcher:test, and
randomdev/builder:test . Private Docker repositories can be used in the same manner.
-4
View File
@@ -1,4 +0,0 @@
Fission Document
=================
* Please visit [here](https://fission.io/docs) for fission documentation.
-106
View File
@@ -1,106 +0,0 @@
# Fission Roadmap
## Function features ([area-func](https://github.com/fission/fission/labels/area-func))
- Secrets, configmaps, env vars
- Volumes
- Mem requests and limits
- Function exec time deadline
- Expose a regular K8s Service for a function
## Fission API ([area-api](https://github.com/fission/fission/labels/area-api))
- CRD-based controller
- API authentication
- Aggregated API server
## Development Workflows ([area-dev](https://github.com/fission/fission/labels/area-dev))
- Function Versioning
- Versioning for a group of functions
- Rolling upgrades
- for one function
- for multiple functions
- for functions + other kubernetes deployments
- Unit testing
## API Gateway / Ingress features ([area-ingress](https://github.com/fission/fission/labels/area-ingress))
- Function authn hooks
- K8s Ingress flag
- Bundle an ingress controller?
## Workflows and Function composition ([area-composition](https://github.com/fission/fission/labels/area-composition))
- Simple Composition - Sync
- Simple Composition - Async
- Hooks (pre, post, on-error)
- Workflows
- Testing in the presence of function composition
## Events ([area-events](https://github.com/fission/fission/labels/area-events))
- NATS
- Kafka
- AWS: SNS, SQS
- Google PubSub
- RabbitMQ
- Other event sources
- Bundle an event queue (NATS streaming, most probably)
## Operability ([area-ops](https://github.com/fission/fission/labels/area-ops))
### Fission Install/Upgrade ([area-install](https://github.com/fission/fission/labels/area-install))
- Helm Installer for Fission
- Helm installation for fission functions
- CLI installer/upgrader? ("fission upgrade")
- Upgrade checker/reminder (like minikube does)
- CLI auto-upgrader
### Function observation ([area-observe](https://github.com/fission/fission/labels/area-observe))
- Function Logging
- Tracing -- Opentracing
- Metrics -- Prometheus
- Function exception tracking
- Logging function load errors
- Tracing fission overheads for each function
## Function Security
- Function isolation (is authz hook sufficient or do we need something like mutual TLS?)
- Function service accounts
## UX, especially for beginners ([area-ux](https://github.com/fission/fission/labels/area-ux))
- Fission CLI should include a tutorial
- Fission CLI should have a way to drop you into the UI
- Eliminate FISSION_URL, just use kube client to find fission url. Also useful to grab credentials.
## Documentation ([area-doc](https://github.com/fission/fission/labels/area-doc))
- Installation guide improvements
- Troubleshooting guide for common problems
- FAQ
- Performance overview
- Render docs nicely to fission.io
## Web UI (tracked separately in the fission-ui repo)
## Performance and Scalability ([area-perf](https://github.com/fission/fission/labels/area-perf))
- Autoscaling
- Cold-start optimization -- optimistically choose from pool, save about ~20msec
- Cold-start optimization -- preload funcs in fetcher
- Cold-start optimization -- preload libraries in envs (v2) -- mem vs. speed tradeoff
## Function extensibility ([area-ext](https://github.com/fission/fission/labels/area-ext))
- Env v2: easy addition of dependencies etc.
- Integration with Service Broker
## Multi-area stuff
- Execution strategies: cold-start pool vs create-pod-on-cold-start -- one size doesn't fit all, at least with current tech; abstract over execution strategies according to requirements
-66
View File
@@ -1,66 +0,0 @@
# Multi-tenancy in Fission
Multi-tenancy in Fission allows users to create Fission objects, i.e functions, packages, environments and triggers in different namespaces.
It mandates that a function reference secrets, configmaps and its package (if explicitly referenced during function create/update operation) to be present in the same namespace as the function.
This allows user separation and prevents in-advertent access to sensitive data of other users sharing the same cluster.
However, users are allowed and encouraged to share environments to ensure optimal utilization of cluster resources. To achieve this, users can create all the necessary environments in a ns, say ns1 and then go on to create functions in different namespaces and refer to env in ns1.
Users that prefer complete isolation can create their env, functions in the same ns.
## Roles and privileges
1. Cluster-Admin Role : Fission's services need cluster-admin privileges to monitor, create, update and delete resources across namespaces.
2. Package-getter Role : This role has privileges to do a get, watch and list on fission package objects.
3. Secret-Configmap-getter Role : This role has privileges to do a get, watch and list on secrets and configmaps.
## Service Accounts
1. fission-fetcher
This SA is created in every namespace that a user creates runtime environments in.
Also created in function namespaces where a user creates functions that use NewDeploy executor backend.
2. fission-builder
This SA is created in every namespace that a user creates builder environments in.
## Role-bindings
1. Package-getter-binding
Every time a user creates a package explicitly in a namespace, this role binding is created in package's namespace (which is also function's namespace). This grants package-getter role to fission-fetcher SA present in the referenced environment's namespace.
If the package is a source package, then, fission-builder SA present in the environment namespace is also added to this role binding.
Next when the user creates a function in the same namespace, if the function's executor type is newdeploy, then, the fission-fetcher SA present in function namespace is also added to the same role binding.
Note : For functions that have executor type poolmgr, the env pods are created in the namespace that env object is created. Whereas, for those functions that have executor type New deploy mgr, the function pods are created in the namespace that function object is created in.
This is because, poolmgr allows env sharing and optimal resource utilization. so generic env pools are created in a different namespace and all functions that prefer sharing this env pool can reference these pools.
If users require strict isolation, they can either create functions with new deploy backend, or, create envs in different namespaces and not share them across functions.
2. Secret-Configmap-getter-binding
Every time a user creates a function in a namespace, Secret-Configmap-getter-binding is created in the same namespace, granting secret-configmap-getter role to fission-fetcher SA present in the referenced environment's namespace in case the executor type is poolmgr.
If the executor type is newdeploymgr, then the same role binding is created in the same namespace as the function, granting the same secret-configmap-getter role to fission-fetcher SA present in the function namespace.
## Examples
1. create a generic python runtime env in ns 1 and function with poolmgr executor type in ns 2 that references it.
```bash
$ fission env create --name python --image fission/python-env --envns ns1
$ fission function create --name func1 --env python --code hello.py --fns ns2
```
2. create a builder and runtime environment in ns3, a source pkg in ns3 and a function referring to this src pkg also in ns3. (for complete isolation, all objects are in ns3)
```bash
$ fission env create --name python-builder-env --builder fission/python-builder --image fission/python-env --ns3
$ fission package create --src src-pkg.zip --env python-builder-env --buildcmd "./build.sh" --pkgns ns3
$ fission fn create --name func3 --fns ns3 --pkg $pkg --entrypoint "user.main"
```
## Note
1. To maintain backward compatibility, fission objects that are created without the ns flags are created in default namespace. Also, the run time env pods in such a case will continue to live in fission-function ns and builder env pods in fission-builder ns
2. Since all envs in a namespace have the same fission-fetcher SA mounted in them, even though multiple envs are created in a namespace and referenced by functions in different namespaces, the SA will have privileges to view those function's secrets if any.
3. Similarly, if there are multiple functions in different namespaces but all sharing an env in one namespace, the fission-fetcher SA in that namespace will have privileges to see all of their secrets.
-24
View File
@@ -1,24 +0,0 @@
# Programming Model
This document describes the programming model for fission functions.
## Time Limits
By default, there is no time limit on fission functions.
Idle running instances may be killed at any time (usually after the
default idle timeout of 10 minutes, but this is configurable).
## HTTP Triggers
Functions triggered over HTTP receive the HTTP request object in the
context. The request's query string, POST body, etc. can be retrieved
from this object. The interface is language-specific: see [TODO] for
documentation on the context object in each environment.
## Kubernetes Watch Event Triggers
Kubernetes watches can be used to trigger functions. These functions
receive the Kubernetes watch.Event object in JSON-serialized form.
-91
View File
@@ -1,91 +0,0 @@
# Annotations
Annotations are used by the core Kubernetes system and to even larger extent by projects such as Istio Ingress Controllers and Prometheus and such.
Users want to add annotations to some objects such as ingress (https://github.com/fission/fission/issues/989).
To enable the users to use annotations, here are some thoughts and ideas:
## Defining annotations
Annotations can be defined fairly easily in the spec file for any object as part of metadata.
``` yaml
apiVersion: fission.io/v1
kind: HTTPTrigger
metadata:
creationTimestamp: null
name: spectest
namespace: default
annotations:
test-anno: some-test-value
spec:
createingress: true
```
These annotations can be merged to target object using a merging mechanism - so that additional annotations put by Fission can also be preserved.
## Considerations
- More often than not the annotations are needed by a Kubernetes objects created by one of the function CRDs/controllers. For example ingress created by route needs the annotation and not the route object itself.
### Implementation 1
- Most annotations use a convention which we can use to determine if an annotation is meant for an ingress object or to be applied on a pod.
For example. look at annotations:
|Annotation name| Description|
|:-------------|:-------------|
|`prometheus.io/scrape`| Prometheus - applied to pod|
|`sidecar.istio.io/inject`|Istio - applied to pod|
|`helm.sh/hook`| Used by helm to apply to pods/jobs|
|`traefik.ingress.kubernetes.io/app-root`|Used by Trafeik ingress controller, applied to ingress|
|`nginx.ingress.kubernetes.io/add-base-url`|Used by Nginx ingress controller, applied to ingress|
So we can write a simple logic - to check if a annotation is applicable for an ingress and based on that apply or not apply annotations to ingress.
### Implementation 2
- One of the side effects is that the annotations will still stay on the source CRD object - for example annotation will stay on the httptrigger as well as the ingress object. This can cause problems in certain cases where something like Prometheus uses annotations to scrape objects. So instead we wrap the annotations needed by an object into another annotation name. This also solves problem of having to guess which annotations to apply to which object.
```yaml
apiVersion: fission.io/v1
kind: HTTPTrigger
metadata:
creationTimestamp: null
name: spectest
namespace: default
annotations:
ingress-annotations: '{"nginx.ingress.kubernetes.io/add-base-url": "true", "nginx.ingress.kubernetes.io/app-root": "somevalue"}'
```
This is specifically important because for example newdeploy function will create a deployment, service and HPA and all three might have different set of annotations.
```yaml
annotations:
service-annotations: '{"service.annotation1": "somevalue"}'
deployment-annotations: '{"deploy.annotation1": "somevalue"}'
```
### Implementation 3
Based on discussion in the team there is a additional option of adding a explicit field in the spec to hold the annotations. For now this assumes that we are only considering HTTPTriggers for annotations and not other objects such as Functions.
```
HTTPTriggerSpec struct {
Host string `json:"host"`
RelativeURL string `json:"relativeurl"`
CreateIngress bool `json:"createingress"`
Method string `json:"method"`
FunctionReference FunctionReference `json:"functionref"`
Annotations map[string]string `json:annotations`
}
```
## Final thoughts
- The implementation idea 2 & 3 look better than 1. The third option involves HTTPTrigger Spec change.
- For both (2) & (3) - if in future we have to implement annotations for Functions etc. we will have to consider the fact that a function will in turn create 3 objects (Service, Pod & HPA) and annotations for all three would need to be accommodated.
-101
View File
@@ -1,101 +0,0 @@
# Continuous Integration and Delivery of Fission functions
This document outlines a simple CI/CD process for Fission functions which can be extended to any CI/CD tool. Before we start, some level setting for terminology used as the terms are used rather broadly in industry.
## Continuous Integration
CI is made up of a series of broad areas:
- The first step is to compile the source code and convert into artifact which is pushed to artifact repository. Traditionally this has been building a Py or wheel package (Python) or Jar file (Java) but as containers became mainstream the container image became the package. The traditional artifact repositories were replaced by the Docker registries.
- Execution and reporting of unit testing has been a crucial part of the CI cycle and is done after the source code can be compiled successfully.
- Running a static/dynamic code analyzer is the next step in continuous integration. The static code analysis is usually used to measure and report quality metrics and dynamic code scanning/analysis for security.
In the draft version of this proposal we will only consider the source to artifact conversion part and will not dive into unit testing or code scanning/analysis cycles of the CI.
## Continuous Delivery
CD is also composed of a few broad areas focusing on different aspects:
- After CI cycle completes successfully - deploying the artifact to a Dev/Staging environment so that it can be tested by developers and QA teams.
- Once the tests & teams have verified that a function works, the same function should be promoted from Dev/Stage to higher/production environment. The number of environment that a organization maintains varies but the idea of promotion from one environment to higher environment does exist. There are very few organizations who deploy the newer versions of functions directly in production with a A/B setup but that is as of this writing is an exception and not the norm.
- Another aspect of promoting from one environment to higher environment is the configuration for both environments will be different. For ex. the DB connection string will be different for each environment. Or the "maxscale" property for production environment could be higher than that for Dev. The ability to store all these environment specific configurations in some sort of system (Github for normal values and some sort of KMS for sensitive data) and being able to combine the logic and configurations for each environment when deploying is important.
Beyond these points there are integration/automation points such as being able to call a test suite after deployment is done etc. but we will skip for now for brevity.
## 1 Fission specs in a container
Let's start with a simple Fission function which uses specs. A typical directory structure looks like below:
```
.
├── multifile
│   ├── README.md
│   ├── __init__.py
│   ├── main.py
│   ├── message.txt
│   └── readfile.py
└── specs
├── README
├── env-python.yaml
├── fission-deployment-config.yaml
└── function-pyz.yaml
```
Irrespective of if source code that needs to be built or not, there is a simple command to fire to update the function, which will build (if applicable) and deploy the function:
```
$ fission spec apply
```
If we look at this from CI/CD perspective this process requires:
1. Source code & specs
2. A github push event when any one of the two change
3. Fission CLI
4. Kubernetes Config so that the apply command can be run
So if we build a container - which has the above requirements met as installed software (Ex. Fission & Kubectl CLI) or available as environment variable (Github pull token or Kubeconfig), the container can be used as part of CI workflow in any tool such as - Jenkins, Argo, Github Actions, GitLab etc.
The idea is to build a generic container with Fission CLI, Kubernetes CLI and a way to read Github token and Kubeconfig from env variable/mounted files and being able to run `fission spec apply` command.
### 1.1
Instead of building a container in previous section - the same can be achieved by a function. The Github webhook can call a function endpoint which in turn can execute the process similar to inside the container.
## 2 Environment Configurations
There are use cases and reasons to have environment configuration different for each environment such as Dev/Staging etc. Let's assume that we want to vary the `maxscale` in functions and `DB_CONNECTION` in environment
```
spec:
InvokeStrategy:
ExecutionStrategy:
ExecutorType: newdeploy
MaxScale: 2 // <-- Varies based on environment deployed in
MinScale: 1
```
```
container:
env:
- name: DB_CONNECTION
value: "http://database.url" // <-- Varies based on environment deployed in
```
Without changing anything in Fission spec it is possible to change these things from environment to environment and some of strategies used by people are:
1. Generate and maintain specs for each environment. This is not a best practice as it leads to drift in code and configuration between environment over time.
2. Use placeholder variables (i.e. $DB_CONNECTION_VALUE) and replace them for each environment before deploying. This is better in the sense that you are combining changes specific to each environment with spec code but is still a work around sort of.
For environment specific configurations, it is possible to use some sort of templates or overlay mechanism. One of interesting projects using overlays is [Kustomize](https://github.com/kubernetes-sigs/kustomize). In any case as of today the fission spec command does not have a way to use template or modify values using overlay and it is worth exploring this approach for fission spec.
## 3 Promotion from one environment to another
This necessarily does not fall in the area of Fission per se but it would be fairly easy to build a pipeline in the the tool used for CI/CD if we have container mentioned in (1) and even work around mentioned in (2).
## Action Items
As a first step it would be good to build a simple container mentioned in (1) and use it in various tools to understand the value it adds and any unknowns. The next steps would be to build a full end to end pipeline from source to production.
-128
View File
@@ -1,128 +0,0 @@
# Fission CLI Extensibility
### Approach
To sum up the approach: git-style plugins.
Plugins are named with the fission prefix `fission-*`. When fission is invoked with an undefined/non-core subcommand
is called (like `fission foo`). Fission will look in the PATH
**Installing Fission**
```bash
# The same as before
$ curl -Lo fission https://github.com/fission/fission/releases/download/0.7.2/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
```
**Installing Fission Workflows**
```bash
# The same process as Fission cli itself
$ curl -Lo fission https://github.com/fission/fission-workflows/releases/download/0.4.0/fission-workflows-osx && chmod +x fission-workflows && sudo mv fission-workflows /usr/local/bin/
```
**Invoking Fission Workflows**
```bash
$ fission workflows invocation get b1278e802a
# Which is equivalent to:
$ fission-workflows invocation get b1278e802a
```
General flow:
1. fission does not recognize `workflows` subcommand
2. fission checks the path for a binary called `fission-workflows`
3. fission finds the binary.
4. fission invokes the `fission-workflows`, passing the remainder of the arguments.
**Discoverability: fission --help**
```bash
$ fission --help
USAGE:
fission [global options] command [command options] [arguments...]
VERSION:
0.6.0
COMMANDS:
function, fn Create, update and manage functions
httptrigger, ht, route Manage HTTP triggers (routes) for functions
timetrigger, tt, timer Manage Time triggers (timers) for functions
mqtrigger, mqt, messagequeue Manage message queue triggers for functions
environment, env Manage environments
watch, w Manage watches
package, pkg Manage packages
spec, specs Manage a declarative app specification
upgrade Upgrade tool from fission v0.1
tpr2crd Migrate tool for TPR to CRD
help, h Shows a list of commands or help for one command
PLUGINS:
workflows, wf Inspect and manage workflow executions
ui Start the user interface
GLOBAL OPTIONS:
--server value Fission server URL (default: "http://127.0.0.1:65356")
--help, -h show help
--version, -v print the version
```
Of course Fission needs to be able to find all plugins for this. There are several ways in which we can provide discoverability. The simplest one is for Fission to look in the path for all binaries starting with the `fission-*` prefix. Optionally, fission could invoke a specific command on the subcommand to get info about the plugin (such as version, help text, aliases...)
With Fission Workflows this info would look something like this:
```bash
$ fission-workflows --plugin
name: workflows
version: 0.4.0
help: Inspect and manage workflow executions
```
The idea is that this plugin info is all completely optional.
If it is not available, we simply degrade the results to user.
This way users/we can easily prototype or add plugins without having to worry about adhering to some interface.
**List version**
```bash
$ fission --version
client:
fission: 0.8.0
fission-workflows: 0.4.0
server:
fission: 0.8.1
fission-workflows: 0.3.0
```
Again, versioning info for fission-workflows is taken from the plugin info of the commands.
Note: a related issue is to have some more formalized plugin support/discoverability on the server-side,
but that is out of the scope of this issue.
### Other (optional) extensions and notes
- Like git we could setup a preferred binary path, where fission looks first when searching for the subcommand.
This could optionally be defined with a `FISSION_EXEC_PATH`.
- With the current approach we cannot have aliases for commands---fission will not be able to find fission-workflows
when the user calls `fission wf`. This might be UX issue, with these long path names. One option is let the user fix
it themselves by symlinking `fission-wf` to `fission-workflows`; using the plugin info Fission can recognize and
merge aliases together.
- To help detect versioning conflicts (old version of fission, too new version of fission workflows). We could add
a `requires` field to the fission-workflows plugin info. Then we could throw a warning or error, when two out of sync
versions are being used.
- To avoid unhelpful errors to the user when they have not installed a plugin, we could add a heuristic to check
`https://github.com/fission/SUBCOMMAND` to see if the subcommand might be an uninstalled plugin.
OR, we could lookup a simple text file that contains common plugins `https://github.com/fission/fission/plugins.txt`
and list them as suggestions to the user. OR we could of course just default to a bit help text that says something
like `unknown subcommand 'foo'. If this is a plugin, ensure that it is present on your PATH`.
---
### Motivation
The proposed approach is to use the git-based plugin system for now. Reasons for this approach over a sophisticated,
integrated plugin-based approach:
- It is low effort to implement.
- It is easy to extend with minimal to no required interface.
- The binaries remain standalone, allowing users to separate them if needed and make independent development on the
binaries easy.
Limitations of the proposed approach:
- The user still has to do some work, adding binaries to the PATH; ensuring that permissions are correct; ensuring
that the binary is executable; how to deal with duplicate binaries on the PATH. All this makes this approach assume
basic/intermediate knowledge of the OS from the user.
- I have to admit: I am not entirely sure if this approach requires any changes for Windows. Probably not.
- Upgrading fission with many plugins could be cumbersome, as you would need to upgrade each binary one by one.
Improving this is probably best left to future work.
The more heavyweight solution solves some of these limitations to an extent, but these do not way up to the increased
development and maintenance cost IMO. If needed we could explore this option (or some hybrid option) in the future.
-120
View File
@@ -1,120 +0,0 @@
# Environment V2 Fission-Environment API
Fission Environments are the language-specific component of fission.
They must satisfy the interface in this spec.
## Meta
This is version 2.0-alpha of the Fission-Environment API.
(It's unstable and may change without warning until 2.0-beta.)
## Overview
Fission V2 Environments consist of:
* Metadata
* A runtime image
* A builder image (optional)
* Function Interface Specification
* User Documentation
* Examples
### Metadata
See EnvironmentSpec, Runtime, and Builder in types.go.
## Runtime Image
An environment runtime image is a docker container image. It must run
a server that has two jobs:
(a) Loading a "function" from a file path on demand
(b) Invoking that function on request
### Function Loading
The environment must expose a single HTTP endpoint (at the port and
URL specified in the metadata) that loads a function. The function
load request is a JSON-serialized `FunctionLoadRequest`.
The function load request contains a filepath to load the function
from. Fission does not define in any way the contents of the path; it
is completely environment-dependent. It may be a single file, or a
directory (in case of deployment packages).
The load request may contain an EntryPoint. If it does, the loader
must interpret this; usually it's the name of a function in a module
or package containing multiple functions. If there is no entry point,
the environment must use a default; again, the value of this default
is environment-specific.
The load request may contain a URL. If it does, requests to that URL
should be routed to the function. It defaults to "/".
### Function Invocation
Functions are invoked on HTTP request to the server. The port for the
request on the runtime container is defined in the Runtime metadata,
and the URL for the request is specified in the FunctionLoadRequest.
The interface of the function is environment specific; the environment
must come with a spec for this interface.
## Builder
The builder is a container image that contains tools to build a
function from source. The source may be a single file or a directory
of files.
The builder container is invoked with the specified command, with the
following params:
1. File path of the source
2. File path where the output should go
3. Other env or function-specific params passed by the user
The first two parameters are file paths, and all remaining params are
environment-specific.
The output of the builder should be something that the runtime can
load and run -- there should be no intermediate steps that need user
intervention.
### Errors
## Function Interface Spec
The function interface spec is a document that specifies the interface
of functions and their semantics. It must specify:
* How functions are invoked (sync, async)
* How the request context is provided to the function (URL, headers, request type, request body)
* Function logging
* Semantics of function errors and exceptions
## Documentation
The docs should contain everything necessary to use the environment:
* How to add it to a fission cluster
* How to write and build functions for this environment (link to the interface spec)
* How to modify and rebuild the environment itself
## Examples
Suggested examples to provide:
* A simple "Hello world"
* A function that demonstrates use of the request context: url params,
request headers, request body
* A function that does logging
* A multi-file function package
* A function with dependencies
* Functions with shared code
## Compatibility with v1
V1 environment images can be used as v2 environment runtime images.
-327
View File
@@ -1,327 +0,0 @@
# Fission Environments Redesign
As Fission supports more languages and reaches a wider set of use
cases, it's time to ask how well the current Environments design is
holding up.
## Environments V1: What we learned
Environments V1 is very simple idea: an environments is one Docker
image with an HTTP server + dynamic loader for that language; it's run
in a pod with a language-agnostic sidecar (fetcher) that downloads and
saves the function into a volume shared with the language-specific
container.
### Pros:
* Abstracted away images.
* Very fast cold start
* No image registry to manage (neither for the user nor for fission
implementation)
* Relatively small amount of language specific code. (python env is <
100 lines)
### Cons:
* Doesn't work well for compiled languages
* Users have to rebuild the image to add dependencies
* Only one file supported
* Errors in loading are not surfaced properly. It is especially
annoying to wait until runtime to see a syntax error that could have
been caught on function upload.
* Starting a Pod without knowing the functions has its limitations: we
can't set CPU/memory limits, we can't mount volumes (persistent
volumes, secrets, configmaps). We also can't change the namespace
the Pod is in.
* Not great for a large code base
* Some people want to operate at the image level but still get the
on-demand execution semantics of FaaS. This is a cost-optimization
use case.
### Discussion
Early feedback shows that almost evey user ends up rebuilding images
to add some dependencies. Some sort of automated dependency resolution
would be very nice to have and improve the development workflow. In
other words, just attach a package.json(nodejs) or
requirements.txt(python) with a function, and fission will do the
rest. There's also the possibility of supporting buildpacks (simple
zipfiles), a la AWS Lambda.
Though we can support compiled languages by doing the compilation
inside the cold-start, that's not a great solution because: (a)
compile errors would be reported at runtime, and (b) because the
overhead of compilation doesn't really need to be inside the
cold-start latency.
Non-trivial functions will need multiple files. That also helps for
common code across functions. So we need a way for the user to define
a function as a collection of code with an entry point.
Finally, Docker images remain the most flexible way to package an app.
Today, users can always rebuild an environment image to include
anything they want. But those images must still run a server that
implements fission-environment interface (i.e. the specialize
endpoint). So perhaps there could be a way for users to say "don't
use environments, I've already packaged up my function, here it is".
## Environment V2 Requirements
Roughly in order of priority:
0. Retain the simplicity of the simple use cases. First user
experience should remain trivial -- write a function, map a URL,
done.
1. Support compiled languages. Support error reporting on function
upload rather than cold start.
2. Support functions as a collection of files rather than just one
file.
3. Support automated environment-specific dependency resolution.
(#3 may end up having the same solution as #1. You could think of
gathering deps as a "compilation" of package.json,
requirements.txt, etc.)
4. Support functions as images.
### User stories
#### Environment Creation
V1 Environments were just an image. V2 Environments will be a yaml
file with the following properties:
* Run time image (required)
* Version (required)
* Builder image (optional)
* Build invocation command (required if builder image specified)
* File name extension(s) (optional)
The version will be used to distinguish V2 environments from V1.
```
$ cat golang.yaml
type: Environment
metadata:
name: go
spec:
runtimeImage: fission/go-runtime
builderImage: fission/go-builder
buildCommand:
- "/build.sh"
fileExtentions:
- go
$ fission env create -f golang.yaml
```
#### Function creation for compiled languages
User writes a function in a compiled language, for example Go.
```
$ fission function create --code blah.go
<compilation errors>
<user edits file>
$ $EDITOR blah.go
<fixes errors>
$ fission function update --code blah.go
<success>
$ fission route ... # routes work as usual
```
This same user story applies to interpreted languages too, where the
"compilation" step can be used to check for syntax errors.
#### Compiled language, without using fission builds
User compiles their function locally, resulting in a set of one or
more binaries. The user packages these up as a zip file, creating a
"deployment package".
```
$ fission function create --deployment-package foo.zip
$ fission route ... # routes work as usual
```
In this use case, fission is no longer operating at the source
level. Builds are left to the user and fission only sees the
deployment package package.
#### Collections of source files
The user can create a source package -- a set of source files in a
zip.
```
$ fission function create --source-package foo.zip
```
This workflow works similarly to providing a single source file.
In addition, fission CLI could support automatic creation of source packages, e.g.
```
$ fission function create --source-files *.js
```
This is purely client-side "syntactic sugar" -- the CLI creates the
source package instead of the user having to do it manually. It
doesn't change semantics; the source package is still handled as one
object.
#### Handling Dependencies
The source package of a function can contain dependency specs.
Fission framework proper does not treat this spec in any special way;
it's just another file in the source package. These will be
interpreted by the environment builder.
```
$ fission function create --source-files *.js --source-files package.json
```
In this case, the CLI will create a source package containing the JS
files and package.json. The NodeJS environment builder will create a
deployment package out of these files. The runtime environment will
load and run the deployment package.
#### V1 Compatibility
V1 Environments will continue to be supported. Existing commands will
continue to work. V1 environments won't support newer features like
builds, source and deployment packages, etc.
### Implementation
#### Environment Type
The environment type has a set of new properties: version, runtime
image, builder image, build command, file extension(s).
#### Function Type
The function type has new properties: source package, deployment
package. The literal code string continues to be supported, but it
will have a specified size limit, say 512KB.
#### Source and Binary Packages
A package is just a zip file. It's contents are opaque to fission:
the meaning of its contents is defined by the environment. Fission's
job is to manage the storage and delivery of the package into build
and runtime environments.
#### Storage Service
The storage service will have an HTTP API to upload and download
files. It can store the packages on a persistent volume or as objects
in cloud storage services such as S3.
Storage service has a garbage collection API endpoint. When invoked,
it will remove all packages that are not referenced from any function.
#### Fetcher
Fetcher gets some new responsibilities:
1. It must now also handle zip/unzip of packages
2. It must know how to upload to the storage service (so it's not
exactly "fetcher" any more, but...)
#### Runtime Environment Interface
The V2 runtime environment interface is very similar to V1
environments. Environments must support a dynamic loader and have an
HTTP server that forwards requests to the loaded module.
The differences:
* V2 runtimes must support loading a deployment package. Fetcher is
responsible for unzipping a deployment package, but interpretation
of the contents is up to the environment's code. For example, it
may have to include the directory where the deployment package is
unzipped in its module load path.
[TODO any other differences?]
#### Buildmgr
A new service that will manage builds. Its design is similar to
poolmgr, except it is triggered on creation or update of a function,
rather than HTTP requests.
Buildmgr creates a builder deployment+service for each environment.
Pods in this deployment run the environment's build container, and
fetcher, with a shared volume between the two containers.
When a function is created or updated with a source package, buildmgr
notices this and triggers a build. First, it calls fetcher to
download the source package into a shared volume with the build
container. It then invokes the builder by running the build
invocation command in the build container. Next, it calls fetcher to
package up the output of the builder and store the built package into
the the storage service.
Finally, it updates the function object in the controller API with a
reference to the built package.
[We can collapse this workflow into one request into the builder
service, which would make it easier to scale up the builder
deployment; if we used multiple requests we'd need some sort of
affinity rule, but k8s services only support IP based affinity.]
#### Poolmgr
Poolmgr remains relatively unchanged. Instead of constructing URLs for
function metadata, it uses the deployment package URL in the function
object.
#### CLI
Client libraries and CLI have to deal with the new properties in
functions and environments.
The CLI will now talk to both storage service and controller. When a
function is created, the user can specify the function in one of 3
ways:
1. One source file, same as v1.
2. A source package (or a set of source files, which is turned into a
source package by the CLI)
3. A deployment package
If the file is specified as a source file, fission CLI will use the
code literal if it's under the size limit; otherwise it should use the
storage service. This will allow users to use fission deployments
with no storage service, but with a size limit on functions.
For the case of a source or deployment package, the CLI first does an
upload to the storage service, then creates a function object with a
reference to the uploaded package.
-47
View File
@@ -1,47 +0,0 @@
# Fission Pool manager
Fission's currently uses a pool of running "environments" and specialized them for execution of a function. This design served the cold start use cases well but this is not the only strategy for creation and execution of functions. For example requirements for a new execution backend have been discussed in https://github.com/fission/fission/issues/193. This document aims to discuss the currently under development "newdeploy" backend and related thoughts
# Executor
A new layer - executor now sits between the router and actual backends are responsible for all of heavy lifting for execution of functions. Executor layer is responsible for accepting requests from router and checking with cache before calling on a backend for execution of a function.
# Backend
A backend is responsible for execution of a function - which can involve provisioning appropriate objects in Kubernetes. So with the new design Pool manager becomes one of the backends. As of this writing there are two backends which are described as:
### Pool Manager Backend
Pool manager backend uses a pool of environment pods and specialized them when a function is invoked. The specialized pods are cleaned up if not in use after a few minutes. More details on Pool manager can be found here: https://github.com/fission/fission/blob/5c470735185b980c1f7987921db360e91c65573b/Documentation/Architecture.md
### New Deploy Backend
New Deploy backend create a Kubernetes deployment, a Kubernetes Service for a given function. It additionally creates a HorizontalPodAutoscaler if scale parameters are provided. The creation of deployment and service can be eager or lazy based on input.
### Execution Strategy
While this is still a WIP, parameters that affect execution behavior of function are based on `InvokeStrategy`. A invoke strategy defines the `strategyType` and actual strategy parameters encapsulated in the strategy object.
```
InvokeStrategy struct {
ExecutionStrategy ExecutionStrategy
StrategyType StrategyType
}
```
For example in above case the strategy type is `ExecutionStrategy` and the corresponding parameters are listed below.
```
ExecutionStrategy struct {
Backend BackendType
MinScale int
MaxScale int
EagerCreation bool
}
```
In future there could be more strategies for different use cases.
## Dispatch to backend
As of now one of the backends is chosen based on a simple flag in `ExecutionStrategy`. In future there might be a intelligent/hybrid ways of choosing a backend. For example initial requests of a function could be served from a pool manager while later scaling could be served by a NewDeploy backend
-67
View File
@@ -1,67 +0,0 @@
# Java Environment : Design & considerations
This document documents the design and thoughts that lead to design of Java environment. Before we dive deeper, some important points:
- When we say Java, we really mean JVM. That does not mean that all languages will work seamlessly, so support will be added gradually based on validation. Some of popular languages as of today are:
- Scala
- Groovy
- Kotlin (Server side with Spring)
- In Java there are a few prominent frameworks which have a ecosystem of their own (See list below). How these framework fit in the environment will be detailed later, but it is important to understand their place in ecosystem and design for it.
- A large percentage of enterprise developers use [Spring framework](https://spring.io/) as has been shown by multiple surveys
- Reactive has taken up recently with data intensive operations [Reactive extensions for JVM](https://github.com/ReactiveX/RxJava)
- [Spark](http://sparkjava.com/) is a micro web framework
A draft implementation of the Java environment design is in the branch [java_env_alpha](https://github.com/fission/fission/tree/java_env_alpha). Also a earlier implementation based on Vort.x framework [can be found here](https://github.com/tobias/fission-java-env/)
## Function interface
The goal is here is to minimize the lock in for the user into any framework as much as possible. Java 8 introduced an interface called ```Function``` which could be a great fit here. The user has to implement the ```Function<T, R>``` class and to meet the contract implement the apply method:
```
public class HelloWorld implements Function<T, R> {
public R apply(T str) {
```
Now - the T & R could be different things and we discuss some options below:
### Body in request and response
From the early implementation in the branch mentioned above, the environment extracts the body and send it as a JSON string. The JSON then can be transformed into the appropriate object by the function.
This works well, but has one major limitation: the function does not get access to other things like headers etc. The same thing applies to the response: function can send the body but looses control over status code etc.
### HttpServletRequest and HttpServletResponse
It is possible to send the [HttpServeletRequest](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html) request object as it is to the function class but then the interface becomes a bit too low level. For example the function user has to retrieve the body of request using ```getInputStream``` which gives raw input stream and needs additional work.
Also most enterprise applications today use a framework of some sort for web applications instead of dealing with the raw HttpServlet
### Custom/Context Object
A custom object which encapsulates all needed fields etc. can be used to pass the data from environment to the function. But this means the user has to import a Fission object/library for this object in the application code.
This approach has been taken in the implementation done earlier for a Java environment in Fission and [object interface can be found here](https://github.com/tobias/fission-java-env/blob/master/src/main/java/io/fission/api/Context.java). Related discussion is in the [issue](https://github.com/fission/fission/issues/91)
AWS Lambda also uses a context object, but the purpose is very different, [details of context object here](https://docs.aws.amazon.com/lambda/latest/dg/java-context-object.html).
### Spring's HttpEntity
If we have to depend on a class/library, it is probably better to depend on a class which is part of ecosystem. So instead of using the low level interface of Servlet, we can use [HttpEntity's subclasses RequestEntity and ResponseEntity](https://docs.spring.io/spring/docs/5.0.5.RELEASE/javadoc-api/org/springframework/http/HttpEntity.html). This ensures that the function user is not locked in the Fission object contract, but also gets the full access to request/response object.
The Spring cloud function project also discusses the issue of not having access to other things in request and [related issues are here](https://github.com/spring-cloud/spring-cloud-function/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+header)
### Thoughts
- If we only intend to pass request/response object to function - then using HttpEntity might be a good choice
- If there is a need for additional exchange of information between the environment and function execution in future, then a custom/context object is a better option. We can wrap the HttpEntity's fields and additional fields in the custom context object
## Environment Design
JVM environment design is based on Spring boot and Spring MVC frameworks. The details can be found in branch, but here are some key points:
- All classes in the function and dependent classes are loaded into JVM. Which means the user should supply the uber/fat jar for execution.
- The entrypoint class is specified by the user as ```entrypoint``` flag on the class. The method is by convention (```apply``` as per the Function interface contract)
-33
View File
@@ -1,33 +0,0 @@
# Profiling Fission with Pprof
Fission uses [net/pprof](https://pkg.go.dev/net/http/pprof) for profiling the code across Fission components.
It would be helpful in identifying performance bottlenecks.
To enable profiling, just set `pprof.enabled` to `true` while installing Fission helm chart.
## Pprof data of component pod
Do port forwarding to port 6060 of the pod,
```sh
kubectl port-forward pod/executor-668dfd7c89-2b2ff 6060:6060
```
Run different commands to get or analyze pprof data,
```sh
go tool pprof http://localhost:6060/debug/pprof/flamegraph
go tool pprof http://localhost:6060/debug/pprof/profile\?seconds\=60
```
You can also analyze with binary to get correct references of source,
```sh
# Download binary from pod
kubectl cp fission/executor-668dfd7c89-2b2ff:/fission-bundle fission-bundle
go tool pprof -http ":49816" fission-bundle http://localhost:49513/debug/pprof
```
You can also download pprof data and visualize/analyze with different compatible tools.
-62
View File
@@ -1,62 +0,0 @@
# Fission Support Tool
Fission now has rich functionality supported by multiple services, however, it brings the complexity of troubleshooting.
This proposal tends to give a picture of fission support tool that can help both user and developer to locate the problem in short time.
To achieve this, the support tool will dump related kubernetes objects, fission resources and pod logs from the given cluster.
# Functionality
## Environment Information Collection
Before troubleshooting, some of the basic information is needed to give others an overview of kubernetes/fission user test with so that we can locate the problem in short time.
* Fission version
* Client/Server version
* Kubernetes cluster version
* Cluster version (i.e v1.9.7-gke.0)
* Running environment (i.e GKE, AKS and minikube)
* Nodes version and other information
## Service Logs Collection
The component logs and the logs of interaction between components are important for people to understand what really happened in cluster. Following are components need to collect logs from.
* All fission component pods
* Function pods
* Builder pods
* Environment pods
## Object dumping
Fission is deeply coupled with Kubernetes, most of the objects are created and maintained by it. There is two major type of objects need to be dumped from kubernetes:
* K8S objects
* CRD resources
All objects should be dumped into a readable file format. It will be great if people can reproduce similar environment with these files.
## Information upload
Upload dump files to the specific backend server for support channel to analysis
# CLI Interface
```
$ fission support collect
NAME:
fission support collect - Collect pod logs, fission resources and related kubernetes objects for troubleshooting
USAGE:
fission support collect [command options] [arguments...]
OPTIONS:
--dumpdir value Directory to save dump kubernetes objects and fission resources (default: "fission-dump")
--fissionns value Namespace of fission installation (default: "fission")
--builderns value Namespace of fission package builder (default: "fission-builder")
--funcns value Namespace of fission function pod (default: "fission-function")
```
# Thoughts?
1. What to do with sensitive objects like secrets and configmap? Ignore the dump for such objects?
2. The functionality is necessary but not listed above?
-209
View File
@@ -1,209 +0,0 @@
# Testing Proposal
This proposal was initially started as a upgrade testing proposal but soon problems that were posed resulted in a bigger proposal.
### Kinds of testing
Most of current integration tests are CLI driven. Fission CLI is used to test execute various test cases. In future we would have to also focus on API level testing as a UI is built for Fission.
## Needs & patterns
This section only explains the problems/best practices without going into tooling and language used for implementation.
### Separating the test & data
Separating the tests from test data has two aspects - one is separation of concerns and second is scaling the tests without touching the test logic. The test data is a simple data structure which holds all information and test can take data and execute the logic.
As an example today we test "Hello world" for nodejs environment with a simple hello.js like this:
```
fission env create --name nodejs --image fission/node-env
fission fn create --name $fn --env nodejs --code $ROOT/examples/nodejs/hello.js
fission route create --function $fn --url /$fn --method GET
response=$(curl http://$FISSION_ROUTER/$fn)
```
The variables here are environment image, function code & route URL.
If tomorrow if we had to scale this test for all environments, we will have to repeat ourselves. (Violate DRY principle). Instead of that if we encapsulate the test setup & test in a simple function:
```
test_hello_env(envImage, codePath, routeURL){
}
```
And feed it with a dictionary which has all possible combination of tests:
```
{
node: {"fission/node-env", "test/hello.js", "/hellonode"},
python: {"fission/python-env", "test/hello.py", "/hellopy"},
golang: {"fission/go-env", "test/hello.go", "/hellogo"},
binary: {"fission/binary-env", "test/hello.sh", "/hellobinary"},
}
```
This would achieve a few things:
- Separate the test execution logic from the data it needs clearly.
- For adding new kind of environments, you just need to add one more entry into data structure.
Testing all environments may not be most apt example for this, but there can be potential use cases like this.
### Separating the test & setup/teardown
When we run a test there are typically three distinct phases:
- Setup (Create env, fn, route)
- Test (Curl the function)
- Cleanup (Delete fn, route & env)
It should be possible to separate the before test and after test parts from actual tests at two levels:
- Each test
- A whole test suite
The ability to have clean and separate before and after blocks, apart from separation of concerns, enables:
- Running a suite of tests for same setup (See tagging for suite of tests)
### Tagging tests & running a selection
Over a period of time as tests grow, there will be unit, smoke, integration, performance, soak tests and so on. Ability to run a particular test suite only or a combination of them makes it easy to run for specific purpose.
### Measuring test times
[Good to have, not a must] Measuring time for tests and reporting somewhere helps over time to monitor trends. Although this job is better done by performance/benchmark tests so it is not a strict requirement
### Cleaner Logging
It would be good to have cleaner/relevant logging as part of build & test. For example something that Ginkgo framework does is it shows error logs only for failed tests.
### Tests in Parallel
It would be good to be able to run tests in parallel.
## Evaluating the tools/alternatives
### BATS
Bash Automated Testing System is like a enhanced version of bash with support for @test tags and before and after steps & ability to skip tests etc. While it enhances the bash to certain extent, the overall improvement is only marginal.
```
#!/usr/bin/env bats
@test "addition using bc" {
result="$(echo 2+2 | bc)"
[ "$result" -eq 4 ]
}
$ bats addition.bats
✓ addition using bc
✓ addition using dc
2 tests, 0 failures
```
#### Links
- Bats repo: https://github.com/sstephenson/bats
- Runc uses Bats https://github.com/opencontainers/runc/tree/master/tests/integration
### Go Test
The testing package of Go also is quite feature rich for most of the use cases we need. Go 1.7 onwards there is support for setup & teardown parts and parallelism etc.
#### Go - Testing package
- Support for setup and teardown based on https://golang.org/pkg/testing/#hdr-Main
- Go testing already supports and has examples of table driven tests (Separating test & data), measuring test times and parallel tests
#### Shell execution: Go's Exec Library
GO provides a built in Exec library for working with CLI commands. The package seems good enough for us to work, though a few working examples will help decide better
https://golang.org/pkg/os/exec
### Using CLI package
Currently we build a CLI and then execute the tests. The tests basically call one of functions from CLI package. If we decide to use a go lang based framework, then we can import the CLI package and then call those functions by providing them context. This is as good as calling the Fission from CLI, with added benefit of programmability of Go language.
```
func TestSomething(t *testing.T) {
// Build the Cli context with flags etc.
ctx := cli.Context{}
// Pass the ctx to create function
fnCreate(ctx)
}
```
Some of benefits of using above pattern are:
- We can build a small framework around above core where we can pass various flag combinations etc. and exercise all flags in great detail
- We can use rest of Go testing library and other libraries to build matchers, looping, parallelism etc.
- It allows us to exercise the logic in CLI as well as validate the API at the same time.
### Ginkgo & Gomega
Ginkgo is a BDD framework which works with Gomega matcher library. I will state relevant portions of these two frameworks which can be utilized:
From Ginkgo:
- Global `BeforeSuite` and after `AfterSuite` can be used to have global setup and tear down phases
- For tests `BeforeEach` and `AfterEach` and more such variants to do before and after test tasks.
From Gomega:
Gomega is a matcher library but the `gexec` library makes it really easy to interact with OS execution environment. Some working examples:
- Build and cleanup the Fission CLI before & after the tests
```
var fissionCli string
BeforeSuite(func() {
var err error
fissionCli, err = gexec.Build("github.com/fission/fission")
Ω(err).ShouldNot(HaveOccurred())
})
AfterSuite(func() {
gexec.CleanupBuildArtifacts()
})
```
- Following will run Fission commands with Fission CLI and print error if there is one (verbosity is configurable)
```
command := exec.Command(fissionCli, "fission env create --name nodejs --image fission/node-env")
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Ω(err).ShouldNot(HaveOccurred())
```
- Use Fission CLI's output to validate test results
```
Eventually(session.Out).Should(gbytes.Say("hello [A-Za-z], world"))
```
#### Gomega Matchers
- Gomega provides quite a few built in matchers - so you don't have to code those small usual checks, for example:
```
Ω(ACTUAL).Should(BeTrue()) // The output should be true
Ω(ACTUAL).Should(BeAnExistingFile()) // The file should already exist
```
There are many more matchers which cane be found here: http://onsi.github.io/gomega/#provided-matchers
- We can build custom matchers in Go language for reusable logic.
#### Links
Ginkgo: http://onsi.github.io/ginkgo/
Gomega: http://onsi.github.io/gomega/
## Thoughts & Next actions
Based on the discussion with team, here are current thoughts and next action items:
### Thoughts
- As far as possible we should stick to Go's built in testing package
- Ginkgo's cleaner logging feature (Onlu log if there are errors) - is very useful. We can decide to incorporate this in future.
- Gomega's (gexec)[http://onsi.github.io/gomega/#gexec-testing-external-processes] is really neat and some matchers can be used if necessary
### Action items
- How will upgrade test for Fission fit in the framework?
- How will migration of tests happen over time:
- Aim is to keep existing tests around so that enough validation is in place
- May be migrate one test at a time
- How much of current setup etc. will move into framework? For example it is clear that helm commands should be part of test framework as part of setup/teardown. But other sections may or may not be. A RCA needs to be done to analyze and come up with clear demarcation.
## References
- EngineYard uses BATS: https://www.engineyard.com/blog/bats-test-command-line-tools
- AWS CLI Tests, written in Python (CLI itself is also in Python): https://github.com/aws/aws-cli/tree/develop/tests
- Kubernetes uses Ginkgo and Gomega extensively: https://github.com/kubernetes/kubernetes/search?l=Go&q=onsi&type=
- Hashicorp's Mitchell's talk on Advanced testing with go talks about some good patterns to use: https://www.youtube.com/watch?v=8hQG7QlcLBk
+1
View File
@@ -101,6 +101,7 @@ aggregation &mdash; also helps with ops on your Fission deployment.
- Understand [Fission Concepts](https://fission.io/docs/concepts/).
- See the [installation guide](https://fission.io/docs/installation/) for installing and running Fission.
- You can learn more about Fission and get started from [Fission Docs](https://fission.io/docs).
- To see Fission in action, check out the [Fission Examples Repo](https://github.com/fission/examples).
- See the [troubleshooting guide](https://fission.io/docs/trouble-shooting/) for debugging your functions and Fission installation.
## Contributing
@@ -1,4 +0,0 @@
#!/bin/bash
kubectl delete canaryconfig canary-2
kubectl delete httptrigger route-fail
-23
View File
@@ -1,23 +0,0 @@
#!/bin/bash
# this script is useful to demo canary deployment when the latest function starts receiving 100% of the traffic
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
desc "Function version-1"
run "fission function get --name fn1-v6"
desc "Function version-2"
run "fission function get --name fn1-v7"
desc "Create a route \(HTTP trigger\) the version-1 of the function with weight 100% and version-2 with weight 0%"
run "fission route create --name route-fail --method GET --url /fail --function fn1-v6 --weight 100 --function fn1-v7 --weight 0"
desc "Create a canary config to gradually increment the weight of version-2 by a step of 20 every 1 minute"
run "fission canary-config create --name canary-2 --newfunction fn1-v7 --oldfunction fn1-v6 --httptrigger route-fail --increment-step 30 --increment-interval 30s --failure-threshold 10"
desc "Fire requests to the route"
run "ab -n 10000 -c 1 http://$FISSION_ROUTER/fail"
@@ -1,11 +0,0 @@
#!/bin/bash
kubectl delete canaryconfig canary-1
kubectl delete httptrigger route-canary
fission fn delete --name func-v1
fission fn delete --name func-v2
fission package delete --orphan
# bug in canary config cache when you re-create a config with the same name, restart controller
kubectl -n fission get pod -l application=fission-api -o name | xargs -n1 kubectl -n fission delete
@@ -1,7 +0,0 @@
module.exports = async function(context) {
return {
status: 200,
body: "This is Version One!\n"
};
}
@@ -1,7 +0,0 @@
module.exports = async function(context) {
return {
status: 200,
body: "This is Version Two!\n"
};
}
-37
View File
@@ -1,37 +0,0 @@
#!/bin/bash
# this script is useful to demo canary deployment when the latest function starts receiving 100% of the traffic
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
desc "Kubernetes cluster"
run "kubectl get nodes"
desc "Fission installed"
run "kubectl --namespace fission get deployment"
clear
desc "NodeJS environment pods"
run "kubectl --namespace fission-function get pod -l environmentName=nodejs"
# set up functions
fission fn create --name func-v1 --env nodejs --code func-v1.js
desc "Function version 1"
run "fission function get --name func-v1"
fission fn create --name func-v2 --env nodejs --code func-v2.js
desc "Function version 2"
run "fission function get --name func-v2"
desc "Create a route \(HTTP trigger\) the version-1 of the function with weight 100% and version-2 with weight 0%"
run "fission route create --name route-canary --method GET --url /canary --function func-v2 --weight 0 --function func-v1 --weight 100"
desc "Start sending requests to the route"
run_bg "hey -n 100000 -c 1 http://$FISSION_ROUTER/canary"
desc "Create a canary config: with an increment of 10 percent, every 1 minute, rolling back if 10% of requests fail"
run "fission canary-config create --name canary-1 --newfunction func-v2 --oldfunction func-v1 --httptrigger route-canary --increment-step 10 --increment-interval 30s --failure-threshold 10"
-4
View File
@@ -1,4 +0,0 @@
#!/bin/bash
fission fn delete --name hello
fission route delete --name $(fission route list|grep hello|cut -f1 -d' ')
-7
View File
@@ -1,7 +0,0 @@
module.exports = async function(context) {
return {
status: 200,
body: "This is Version One!\n"
};
}
-7
View File
@@ -1,7 +0,0 @@
module.exports = async function(context) {
return {
status: 200,
body: "This is Version Two!\n"
};
}
-7
View File
@@ -1,7 +0,0 @@
module.exports = async function(context) {
return {
status: 200,
body: "Hello, world!\n"
};
}
-108
View File
@@ -1,108 +0,0 @@
#!/bin/bash
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
#
# General setup intro
#
desc "Our Kubernetes cluster"
run "kubectl get nodes"
desc "Fission is installed"
run "kubectl --namespace fission get deployment"
clear
#
# Hello world, environments, cold starts
#
desc "Hello world function"
run "cat hello.js"
desc "Add the NodeJS environment to fission"
run "fission env create --name nodejs --image fission/node-env"
desc "NodeJS environment pods"
run "kubectl --namespace fission-function get pod -l environmentName=nodejs"
desc "Upload our function to fission"
run "fission function create --name hello --env nodejs --code hello.js"
desc "Set up a route (aka HTTP Trigger) for the function"
run "fission route create --method GET --url /hello --function hello"
sleep 2
desc "Finally, run the function"
run "time -p curl http://$FISSION_ROUTER/hello"
desc "Run the function again"
run "time -p curl http://$FISSION_ROUTER/hello"
run "time -p curl http://$FISSION_ROUTER/hello"
desc "The pod is now labeled by the function name"
run "kubectl --namespace fission-function get pod -l functionName=hello"
#
# Declarative Specs.
#
# Use go for this one to show off the fact that we can do builds.
#
clear
pushd declarative
desc "Declaratively specified app"
run "ls"
desc "Declaratively specified app"
run "ls specs"
desc "Validate configs"
run "fission spec validate"
desc "Deploy application, wait for build to finish"
run "fission spec apply --wait"
desc "Invoke our new function"
run "fission function test --name hello-go"
#
# Live Reload (still in the same app)
#
clear
popd
#
# Canary Deployments
#
clear
# set up functions
fission fn create --name func-v1 --env nodejs --code func-v1.js
desc "Function version 1"
run "fission function get --name func-v1"
fission fn create --name func-v2 --env nodejs --code func-v2.js
desc "Function version 2"
run "fission function get --name func-v2"
desc "Create a route \(HTTP trigger\) the version-1 of the function with weight 100% and version-2 with weight 0%"
run "fission route create --name route-canary --method GET --url /canary --function func-v2 --weight 0 --function func-v1 --weight 100"
desc "Start sending requests to the route"
run_bg "hey -n 100000 -c 1 http://$FISSION_ROUTER/canary"
desc "Create a canary config: with an increment of 10 percent, every 1 minute, rolling back if 10% of requests fail"
run "fission canary-config create --name canary-1 --newfunction func-v2 --oldfunction func-v1 --httptrigger route-canary --increment-step 10 --increment-interval 30s --failure-threshold 10"
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
desc "one"
run "sleep 30"
desc "two"
run "echo hi"
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
fission spec destroy
# this one is generated in run.sh
rm specs/function-hello-go.yaml
rm *.~?~
-15
View File
@@ -1,15 +0,0 @@
package main
import (
"log"
"net/http"
)
// Handler is the entry point for this fission function
func Handler(w http.ResponseWriter, r *http.Request) { //nolint:golint,unused,deadcode
msg := "Hello, CNCF Webinar!\n"
_, err := w.Write([]byte(msg))
if err != nil {
log.Fatal(err)
}
}
-24
View File
@@ -1,24 +0,0 @@
#!/bin/bash
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
desc "Declaratively specified app"
run "ls specs"
desc "Generate initial YAML, so we don't have to write it by hand"
run "fission function create --spec --name hello-go --env go --src hello.go --entrypoint Handler"
desc "Generated function YAML"
run "tail -25 specs/function-hello-go.yaml"
desc "Deploy on cluster, and wait for build result"
run "fission spec apply --wait"
desc "Invoke the function"
run "fission function test --name hello-go"
desc "Live-reload: auto build + deploy on save"
run "fission spec apply --watch"
-42
View File
@@ -1,42 +0,0 @@
Fission Specs
=============
This is a set of specifications for a Fission app. This includes functions,
environments, and triggers; we collectively call these things "resources".
How to use these specs
----------------------
These specs are handled with the 'fission spec' command. See 'fission spec --help'.
'fission spec apply' will "apply" all resources specified in this directory to your
cluster. That means it checks what resources exist on your cluster, what resources are
specified in the specs directory, and reconciles the difference by creating, updating or
deleting resources on the cluster.
'fission spec apply' will also package up your source code (or compiled binaries) and
upload the archives to the cluster if needed. It uses 'ArchiveUploadSpec' resources in
this directory to figure out which files to archive.
You can use 'fission spec apply --watch' to watch for file changes and continuously keep
the cluster updated.
You can add YAMLs to this directory by writing them manually, but it's easier to generate
them. Use 'fission function create --spec' to generate a function spec,
'fission environment create --spec' to generate an environment spec, and so on.
You can edit any of the files in this directory, except 'fission-deployment-config.yaml',
which contains a UID that you should never change. To apply your changes simply use
'fission spec apply'.
fission-deployment-config.yaml
------------------------------
fission-deployment-config.yaml contains a UID. This UID is what fission uses to correlate
resources on the cluster to resources in this directory.
All resources created by 'fission spec apply' are annotated with this UID. Resources on
the cluster that are _not_ annotated with this UID are never modified or deleted by
fission.
@@ -1,7 +0,0 @@
# This file is generated by the 'fission spec init' command.
# See the README in this directory for background and usage information.
# Do not edit the UID below: that will break 'fission spec apply'
apiVersion: fission.io/v1
kind: DeploymentConfig
name: declarative-specs
uid: e20fe7b4-b012-4ae0-98bd-0f968d21d397
@@ -1,51 +0,0 @@
include:
- hello.go
kind: ArchiveUploadSpec
name: hello-go
---
apiVersion: fission.io/v1
kind: Package
metadata:
creationTimestamp: null
name: hello-go-xfxy
namespace: default
spec:
deployment:
checksum: {}
environment:
name: go
namespace: default
source:
checksum: {}
type: url
url: archive://hello-go
status:
buildstatus: pending
---
apiVersion: fission.io/v1
kind: Function
metadata:
creationTimestamp: null
name: hello-go
namespace: default
spec:
InvokeStrategy:
ExecutionStrategy:
ExecutorType: poolmgr
MaxScale: 1
MinScale: 0
TargetCPUPercent: 80
StrategyType: execution
configmaps: null
environment:
name: go
namespace: default
package:
functionName: Handler
packageref:
name: hello-go-xfxy
namespace: default
resources: {}
secrets: null
-16
View File
@@ -1,16 +0,0 @@
#
# Handles POST /guestbook -- adds item to guestbook
#
from flask import request, redirect
import redis
# Connect to redis.
redisConnection = redis.StrictRedis(host='redis.guestbook', port=6379, db=0)
def main():
# Read the item from POST params, add it to redis, and redirect
# back to the list
item = request.form['text']
redisConnection.rpush('guestbook', item)
return redirect('/guestbook', code=303)
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
kubectl delete ns guestbook
fission fn delete --name guestbook-add
fission fn delete --name guestbook-get
fission route list|grep guestbook|cut -f1 -d' '|xargs -n1 fission route delete --name
-28
View File
@@ -1,28 +0,0 @@
#
# Handles GET /guestbook -- returns a list of items in the guestbook
# with a form to add more.
#
from flask import current_app, escape
import redis
# Connect to redis. This is run only when this file is loaded; as
# long as the pod is alive, the connection is reused.
redisConnection = redis.StrictRedis(host='redis.guestbook', port=6379, db=0)
def main():
messages = redisConnection.lrange('guestbook', 0, -1)
items = [("<li>%s</li>" % escape(m.decode('utf-8'))) for m in messages]
ul = "<ul>%s</ul>" % "\n".join(items)
return """
<html><body style="font-family:sans-serif;font-size:2rem;padding:40px">
<h1>Guestbook</h1>
<form action="/guestbook" method="POST">
<input type="text" name="text">
<button type="submit">Add</button>
</form>
<hr/>
%s
</body></html>
""" % ul
-45
View File
@@ -1,45 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: guestbook
labels:
name: guestbook
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: redis
name: redis
namespace: guestbook
spec:
replicas: 1
selector:
matchLabels:
run: redis
template:
metadata:
labels:
run: redis
spec:
containers:
- image: redis
name: redis
---
apiVersion: v1
kind: Service
metadata:
labels:
run: redis
name: redis
namespace: guestbook
spec:
selector:
run: redis
type: ClusterIP
ports:
- port: 6379
protocol: TCP
targetPort: 6379
-18
View File
@@ -1,18 +0,0 @@
#!/bin/bash
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
desc "Deploy redis"
run "kubectl create -f redis.yaml"
desc "Ensure fission has a python environment"
run "fission env create --name python --image fission/python-env"
desc "Register fission functions"
run "fission function create --name guestbook-get --env python --code get.py --url /guestbook --method GET"
run "fission function create --name guestbook-add --env python --code add.py --url /guestbook --method POST"
echo "http://$FISSION_ROUTER/guestbook"
-4
View File
@@ -1,4 +0,0 @@
#!/bin/bash
fission fn delete --name hello
fission route delete --name $(fission route list|grep hello|cut -f1 -d' ')
-7
View File
@@ -1,7 +0,0 @@
module.exports = async function(context) {
return {
status: 200,
body: "Hello, world!\n"
};
}
-40
View File
@@ -1,40 +0,0 @@
#!/bin/bash
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
desc "Kubernetes cluster"
run "kubectl get nodes"
desc "Fission installed"
run "kubectl --namespace fission get deployment"
clear
desc "Hello world function"
run "cat hello.js"
desc "Add NodeJS environment to fission"
run "fission env create --name nodejs --image fission/node-env"
desc "NodeJS environment pods"
run "kubectl --namespace fission-function get pod -l environmentName=nodejs"
desc "Upload a function to fission"
run "fission function create --name hello --env nodejs --code hello.js"
desc "Set up a route \(HTTP trigger\) for the function"
run "fission route create --method GET --url /hello --function hello"
sleep 2
desc "Finally, run the function"
run "time -p curl http://$FISSION_ROUTER/hello"
desc "Run the function again"
run "time -p curl http://$FISSION_ROUTER/hello"
run "time -p curl http://$FISSION_ROUTER/hello"
desc "The pod is now labeled by the function name"
run "kubectl --namespace fission-function get pod -l functionName=hello"
-76
View File
@@ -1,76 +0,0 @@
#!/bin/bash
# Copyright 2016 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
readonly reset=$(tput sgr0)
readonly green=$(tput bold; tput setaf 2)
readonly yellow=$(tput bold; tput setaf 3)
readonly blue=$(tput bold; tput setaf 6)
readonly timeout=$(if [ "$(uname)" == "Darwin" ]; then echo "1"; else echo "0.1"; fi)
function desc() {
maybe_first_prompt
echo "$blue# $@$reset"
prompt
}
function prompt() {
echo -n "$yellow\$ $reset"
}
started=""
function maybe_first_prompt() {
if [ -z "$started" ]; then
prompt
started=true
fi
}
function run() {
maybe_first_prompt
rate=25
if [ -n "$DEMO_RUN_FAST" ]; then
rate=1000
fi
echo "$green$1$reset" | pv -qL $rate
if [ -n "$DEMO_RUN_FAST" ]; then
sleep 0.5
fi
$1
echo
echo -n ">"
read -s
echo -e "\b "
}
function run_bg() {
maybe_first_prompt
rate=25
if [ -n "$DEMO_RUN_FAST" ]; then
rate=1000
fi
echo "$green$1$reset" | pv -qL $rate
if [ -n "$DEMO_RUN_FAST" ]; then
sleep 0.5
fi
$1 &
echo
echo -n ">"
read -s
echo -e "\b "
}
SSH_NODE=$(kubectl get nodes | tail -1 | cut -f1 -d' ')