Doc updates

This commit is contained in:
Soam Vasani
2017-01-23 19:11:37 -08:00
parent 8339e120ce
commit 000b74d4ad
5 changed files with 251 additions and 100 deletions
+73 -67
View File
@@ -1,107 +1,113 @@
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
==========
Language-neutral components:
* Controller
* Container Pool Manager
* Container Specializer
* Router
* controller
* poolmgr
* router
* kubewatcher
Language-specific components:
* Language Build Container
* Language Run Container
* Environment container
Controller
----------
Function and Trigger CRUD APIs. APIs to watch for changes are also
included (useful for other components that cache state).
See api/swagger.json for API details.
The controller contains CRUD APIs for functions, http triggers,
environments, Kubernetes event watches. This is the component that
the client talks to.
This is the only stateful component. It needs to be configured with a
URL to an etcd cluster and a path to a persistent volume. The volume
will be used to store the functions' source code.
is used to store the functions' source code. Etcd is used as the DB.
Etcd is used as the DB.
[Work to extend to other storage backends is planned, see issue #83.]
Pool Manager
------------
Container Pool Manager
----------------------
Poolmgr manages pools of generic containers and function containers.
Manage pool of generic containers.
It has a simple API; both these endpoints are called by the router.
Probably use K8s RCs. Can we Use labels to move pods from one rc to
another? What about jobs?
* GetFunctionService takes function metadata and returns the address
of a service.
* TapService lets poolmgr know a service is being used; if it's not
called for a few minutes the pod(s) backing the service are killed.
Container Specializer
---------------------
Poolmgr watches the controller API 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 very simple utility that downloads a URL sent to it and
saves it at a configured location.
Inputs: a running generic language run container, a user function,
optionally an http trigger URL.
Calls Language Run Container and sets up Router to point to it.
GetFunctionService "specializes" a pod. The implementation chooses a
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 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.
Router
------
- Cache trigger -> container instance mapping; implement cache miss and expiration.
The router forwards HTTP requests to function pods. If there's no
running service for a function, it requests one from poolmgr, while
holding on to the request; when the function's service is ready it
forwards the request.
- Invoke Specializer, setup up k8s API
The router is stateless and can be scaled up if needed, according to
load.
- Forward requests
Kubewatcher
-----------
The Router is stateless -- it can be scaled or killed at any time.
Kubewatcher watches the Kubernetes API and invokes functions
associated with watches, sending the watch event to the function.
There's a lot of functionality overlap with K8S Ingress Controllers.
We should clearly use Ingress and Ingress Controllers in some way.
It's not exactly clear at the moment how -- should make a whole new
Ingress Controller perhaps based on the contrib/nginx
The controller keeps track of 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.
Autoscaler
----------
This autoscales the language run containers that are backing a
trigger.
What metrics this is based on is TBD.
- Number of requests/sec
- "Backlog" -- number of outstanding requests not yet started -- how to measure this?
- Change in turn-around time?
Language Build Container
------------------------
* The Language Build Container is a container that is invoked for a
build. It takes one user-created function and outputs something
that can be run by the corresponding Language Run Container.
* The Build Container must implement the Language Build Container
interface.
Language Run Container
----------------------
* The Language Run Container is the container in which user functions
run.
* The Run Container is started without the user function. It must
start as a "Generic Container". It must implement the
"specialization interface". In short, it must implement an HTTP
server that can receive a piece of code, verify its signature, and
map it to an HTTP endpoint. See
Documentation/specs/LanguageRunContainerSpec.md for details.
Environment Container
---------------------
Environment containers run user-defined functions. Environment
containers are language specific. They must contain an HTTP server
and a loader for functions.
Poolmgr 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.
When poolmgr 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.
+11 -18
View File
@@ -1,22 +1,15 @@
_Function_: A _function_ is the smallest unit of a program in Fission.
It's a program with an function as an entry point -- it doesn't have
to be just one function.
_Function_: A fission function is something that's mapped to a
_trigger_ and run on demand. Though we call it a "function", this is
a bit imprecise, since it's actually a module with an function as an
entry point -- it doesn't have to be just one function.
_Trigger_: Triggers are what cause functions to be called. For
example the HTTP trigger causes functions to be called on HTTP
requests.
example, an HTTP trigger causes functions to be called on HTTP
requests. Kubernetes Watch triggers cause functions to be called when
a Kubernetes watch changes. Future triggers will include message
queues, timers, storage systems, etc.
_Instance_: Fission creates and runs servers containing these
functions. The running server is called an _instance_. You normally
don't need to worry about instances: their management is completely
automatic and transparent to you. However, certain configuration
settings allow you to tweak the behaviour of instances -- for example,
you can configure the strategy for destroying unused instances.
_Generic Instance_: An instance with no user code in it. The process
of including the user code to a Generic Instance is _Specialization_,
and the resulting instance is a _Specialized Instance_.
_Specialized Instance_: See _General Instance_.
_Environments_: Environments are the language-specific parts of
Fission. Environment containers wrap the user's function and present
a common interface to the rest of the fission framework.
-15
View File
@@ -1,15 +0,0 @@
Fission is functions-as-a-service for Kubernetes.
You write some functions, and setup some way for them to be invoked
("triggers"). Triggers can be HTTP requests (HTTP Triggers) or timers
(Time Triggers).
Topics
======
Testing
-------
Function Updates
----------------
+167
View File
@@ -0,0 +1,167 @@
# 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.
* 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 dependecies. Some sort of automated dependecy 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 possiblity 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 shoud 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 dependecy 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
#### Compiled language
User writes a function in Go.
```
$ fission function create --code blah.go
<compilation errors>
<user edits file>
$ $EDITOR blah.go
<fixes errors>
$ fission function update --code blah.go
<success>
```
(Or perhaps we could have a `fission function check --env x --code y`
which just does compilation, without creating a function object?
Useful for integration into IDEs. Basically, just an on-demand
builder. Useful when you don't wanna setup anything on your laptop.)
This same user story applies to interpreted languages too, where the
"compilation" step can be used to check for syntax errors.
#### Collections of files
We should probably have a manifest in YAML/JSON/etc syntax for
specifying a function. We could also use that YAML to let users
specify the function's environment, resource requirements, etc.
```
$ fission funcion create -f blah.yaml
$ cat blah.yaml
type: Function
metadata:
name: ...
environment: ...
files:
- foo.py
- bar.py
- baz/*.py
```
The yaml file could specify a list of files. The fission client would
deal with packaging up this set of files and uploading the package.
#### Handling Dependencies
A manifest could point at a function's dependency spec. The
Environment's "builder" container could then fetch these deps.
So a NodeJS function manifest could contain a reference to
package.json. A "builder" container in the NodeJS environment would
then run npm on that function.
``` $ cat func.yaml
type: Function
metadata: ...
environment: ...
dependencies: package.json
$ fission function create -f func.yaml
```