[Feature request] Support Function Template
Description
To support 'FunctionTemplate/Function' or Function/FunctionRun in OpenFunction, so that user can reuse their Function with different parameters.
Similar concepts: 1. Argo WorkflowTemplate/Workflow: https://argoproj.github.io/argo-workflows/workflow-templates/#working-with-parameters 2. Tekton Task/TaskRun: https://tekton.dev/docs/pipelines/taskruns/#taskruns
Here I take 'FunctionTemplate/Function' as an example. 1. FunctionTemplate: define the function and parameters as a template 2. Function: run the FunctionTemplate and overwrite the parameters if provided, as well as other configurations like scaleOptions.
Benefits: 1. User can define FunctionTemplate and reuse it with different parameters 2. Function template -> Function -> ServerlessWorkflow. example
Additional context
Example:
# a function template
apiVersion: core.openfunction.io/v1beta1
kind: FunctionTemplate
metadata:
name: function-sample-template
spec:
Inputs:
parameters:
- name: func_name
value: "myfunction"
- name: func_msg
value: "Hello world!"
functionSpec:
version: "v1.0.0"
image: "openfunctiondev/v1beta1-sample:latest"
imageCredentials:
name: push-secret
port: 8080
build:
successfulBuildsHistoryLimit: 2
failedBuildsHistoryLimit: 3
timeout: 10m
builder: openfunction/builder-go:latest
env:
FUNC_NAME: "{{inputs.parameters.func_name}}"
FUNC_RETURN: "{{inputs.parameters.func_msg}}"
srcRepo:
url: "https://github.com/OpenFunction/samples.git"
sourceSubPath: "functions/Knative/hello-world-go"
serving:
template:
containers:
- name: function
imagePullPolicy: Always
runtime: "knative"
----------
# to run the function template with default value
apiVersion: core.openfunction.io/v1beta1
kind: Function
metadata:
name: function-sample-run
spec:
functionRef:
name: function-sample-template
# to run the function template with new value
apiVersion: core.openfunction.io/v1beta1
kind: Function
metadata:
name: function-sample-run
spec:
inputs:
parameters:
- name: func_name
value: "myfunction-new"
- name: func_msg
value: "Hello!"
functionRef:
name: function-sample-template
# to run the function template with different scaleOptions
apiVersion: core.openfunction.io/v1beta1
kind: Function
metadata:
name: function-sample-run
spec:
functionRef:
name: function-sample-template
scaleOptions:
minReplicas: 0
maxReplicas: 5
# directly run a Function (no need to create Template)
apiVersion: core.openfunction.io/v1beta1
kind: Function
metadata:
name: function-sample-run
spec:
inputs:
parameters:
- name: func_name
value: "myfunction-new"
- name: func_msg
value: "Hello!"
functionSpec:
version: "v1.0.0"
image: "openfunctiondev/v1beta1-sample:latest"
imageCredentials:
name: push-secret
port: 8080
build:
successfulBuildsHistoryLimit: 2
failedBuildsHistoryLimit: 3
timeout: 10m
builder: openfunction/builder-go:latest
env:
FUNC_NAME: "{{inputs.parameters.func_name}}"
FUNC_RETURN: "{{inputs.parameters.func_msg}}"
srcRepo:
url: "https://github.com/OpenFunction/samples.git"
sourceSubPath: "functions/Knative/hello-world-go"
serving:
template:
containers:
- name: function
imagePullPolicy: Always
runtime: "knative"
# directly run a Function (no need to create Template and define the parameters)
apiVersion: core.openfunction.io/v1beta1
kind: Function
metadata:
name: function-sample-run
spec:
inputs:
parameters: []
functionSpec:
version: "v1.0.0"
image: "openfunctiondev/v1beta1-sample:latest"
imageCredentials:
name: push-secret
port: 8080
build:
successfulBuildsHistoryLimit: 2
failedBuildsHistoryLimit: 3
timeout: 10m
builder: openfunction/builder-go:latest
env:
FUNC_NAME: "myfunction-new"
FUNC_RETURN: "Hello!"
srcRepo:
url: "https://github.com/OpenFunction/samples.git"
sourceSubPath: "functions/Knative/hello-world-go"
serving:
template:
containers:
- name: function
imagePullPolicy: Always
runtime: "knative"
Open points: 1. What fields can be parameterized? (ENV only? ) 2. How to handle the async case like the binding/input/output? 4. Is it overlap with the serverless workflow? Can it be reused in serverless workflow?
Feel free to give your options, thanks.
I like this design.
In FunctionTemplate we don't have to follow the same spec as in the Function so that we can refine more abstract properties, which will be better for users to understand.
Thanks @tpiperatgod. Can you provide some examples of the expected spec? maybe for both sync and async use cases.
BTW currently openfunction seems like dont have job concept. For example a job to process a file. In this case, having a file path as a parameter will be convenient to reuse the function template.
And giving the example from the serverlessWorkflow here, the concept of the template with parameters can be used when supporting serverlessworflow in the future.
What I mentioned is just an idea ...🥲 But I will try to design it based on your suggestion and maybe give a preliminary example in the next few days.
Duplicated with https://github.com/OpenFunction/OpenFunction/issues/224 ?
Duplicated with #224 ?
#224 is about reusing the components from keda and dapr. Here is to reuse the functionTemplate which include the keda/dapr components and other parameters.
Here is a preliminary proposal: https://hackmd.io/@xcEqMy2IQx-DQu_nUuPNXw/rJiu6IpQ5
Hi @tpiperatgod , thanks for the proposal! I have looked through it. Some of the concepts inside are good like parameterize the value and template of the binding, trigger etc, and you also defined what components can be updated. But I think we can have some discussion on it to align with the usecase. Below is my feedback.
The FunctionTemplate in this issue is a Template of a use case, for example, a job to scrape data from website or a ml model serving. For a data scraping job template, user can reuse it and overwrite the parameters it exposed, for example the url of the target website, max-retry, output location, and max replicas etc. For a ml model serving template, user can reuse it and overwrite the model and threshold parameters.
The Template concept in the proposal is a collections of the sub-components' templates. (multiple pubsub and bindings under the functionIO, multiple scaleOptions under the scaleOptions field, same for trigger, podSpec and build). It is more like a component template collection. From the use case perspective, user can not directly reuse it. From the user perspective it is not so clear how to manage it. (for example I can have one single default Template with all the options, or I can have multiple template as well, but not clear on how to group the options).
I will suggest to have a dedicated CRD for binding, trigger, pubsub so that user can easily reuse and manage them rather than define it in a collection Template. (for scaleOptions, build, podspec, I feel like they are very specified to a function template/use case, and probably not necessary to reuse them. They can be reused on the FunctionTemplate level)
Benefits: user can use: kubectl get ofnbinding -n default to list out all the binding template, and Ref them in the Function to reuse it.
In summary, a solution combine both your proposal and my init proposal maybe better
1. User can define binding, tirgger, pubsub CRD for reuse and ref in different function, or function template. User can easily manage them by kubectl get.
2. User can define a Function Template, where he define a template fit his use case and expose parameters that can be overwritten.
3. User can define a Function, which can use functionRef to the Function Template defined in 2, and overwrite parameters if needed. He can also use functionSpec to directly define a function like current ofn. If both are defined, fields in functionSpec can overwrite the fields in Template ref in functionRef.
below is a sample:
apiVersion: openfunction.io/v1beta1
kind: Binding
metadata:
name: my-cron-binding
spec:
params:
- name: cron_schedule_period
value: '@every 5s'
type: bindings.cron
version: v1
metadata:
- name: schedule
value: '{{params.cron_schedule_period}}'
---
apiVersion: openfunction.io/v1beta1
kind: PubSub
metadata:
name: my-kafka-pubsub
spec:
params:
- name: kafka_topic
value: 'sample-topic'
- name: kafka_server
value: 'kafka-server-kafka-brokers.default.svc.cluster.local:9092'
type: pubsub.kafka
version: v1
metadata:
- name: brokers
value: '{{params.kafka_server}}'
- name: authRequired
value: 'false'
- name: allowedTopics
value: '{{params.kafka_topic}}'
---
apiVersion: openfunction.io/v1beta1
kind: Trigger
metadata:
name: my-kafka-trigger
spec:
params:
- name: kafka_topic
value: 'sample-topic'
- name: kafka_server
value: 'kafka-server-kafka-brokers.default.svc.cluster.local:9092'
- name: kafka_consumer_group
value: 'my-group'
type: kafka
metadata:
topic: '{{params.kafka_topic}}'
bootstrapServers: '{{params.kafka_server}}'
consumerGroup: '{{params.kafka_consumer_group}}'
lagThreshold: '20'
---
apiVersion: core.openfunction.io/v1beta1
kind: Function
metadata:
name: sample-b
spec:
#functionRef:
# name: my-function-template
functionSpec:
input:
params:
- name: min_replicas
value: 5
version: "v1.0.0"
image: openfunctiondev/v1beta1-autoscaling-subscriber:latest
imageCredentials:
name: push-secret
build: # not necessary to have template object as it is very specified to an function/application
builder: 'openfunction/builder-go:v2'
env:
FUNC_NAME_PREFIX: '{{params.func_name_prefix}}'
srcRepo:
url: 'https://github.com/xxx/abc.git'
sourceSubPath: golang/
serving:
runtime: "async"
scaleOptions: # not necessary to have template as it is very specified to function as well
maxReplicas: '{{params.max_replicas}}'
minReplicas: '{{params.min_replicas}}'
keda:
pollingInterval: 30
cooldownPeriod: 60
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
template:
initContainers:
- name: init-myservice
image: 'busybox:1.28'
command:
- sh
- '-c'
- >-
until nslookup myservice.$(cat
/var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local;
do echo waiting for myservice; sleep 2; done
# below triggers, bindings, pubsub can support ref or direct definition
triggers:
- triggerRef: "my-kafka-trigger"
name: kafka-trigger
# or
- name: xxxx # maybe can add a name field?
spec:
type: kafka
metadata:
topic: '{{params.kafka_topic}}'
bootstrapServers: '{{params.kafka_server}}'
consumerGroup: '{{params.kafka_consumer_group}}'
lagThreshold: '20'
inputs:
- name: producer
component: kafka-server
topic: "pubsub"
bindings:
- name: cron
bindingRef: my-cron-binding
pubsub:
- pubsubRef: my-kafka-pubsub
name: kafka-server