OpenFunction icon indicating copy to clipboard operation
OpenFunction copied to clipboard

[Feature request] Support Function Template

Open lizzzcai opened this issue 3 years ago • 7 comments

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.

lizzzcai avatar Mar 23 '22 05:03 lizzzcai

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.

tpiperatgod avatar Mar 23 '22 06:03 tpiperatgod

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.

lizzzcai avatar Mar 23 '22 07:03 lizzzcai

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.

tpiperatgod avatar Mar 23 '22 07:03 tpiperatgod

Duplicated with https://github.com/OpenFunction/OpenFunction/issues/224 ?

benjaminhuo avatar Mar 29 '22 05:03 benjaminhuo

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.

lizzzcai avatar Mar 29 '22 05:03 lizzzcai

Here is a preliminary proposal: https://hackmd.io/@xcEqMy2IQx-DQu_nUuPNXw/rJiu6IpQ5

tpiperatgod avatar Apr 08 '22 07:04 tpiperatgod

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

lizzzcai avatar Apr 12 '22 04:04 lizzzcai