# template proposal ## Motivation When we create a function, we often need to define "input", "output", "scale options", "trigger", "pod template", etc., which are often the same or very similar in the same cluster. In order to reduce these repetitive operations, we need to introduce a template mechanism that templates common configurations and facilitates the simplification of function creation steps. ## Goals - Provides a template mechanism that serves as a reference source for the same configurations. The scope of this template mechanism is as follows: - Input configuration - Output configuration - Scale options configuration - Trigger configuration - Pod template configuration - Image repo secret configuration ## Proposal To accomplish the above task, we need to add a new CRD to host the template: ```yaml apiVersion: openfunction.io/v1beta1 kind: Template metadata: name: default spec: params: functionTemplate: functionIO: pubsub: bindings: scaleOptions: trigger: podSpec: build: ``` The specification and meaning of the attributes are explained as follows. ### Params `spec.params: []map[string]string` Definition of variables that can be used in templates. ```yaml spec: params: - name: func_name value: "myfunction" - name: func_msg value: "Hello world!" - name: cron_schedule_period value: "@every 5s" ``` ### FunctionTemplate `spec.functionTemplate: *FunctionTemplate` Definition of function templates. #### FunctionIO `spec.functionTemplate.functionIO: *FunctionIO` Definition of the "input" and "output" templates of the function. `spec.functionTemplate.functionIO.pubsub & bindings: map[string]*componentsv1alpha1.ComponentSpec` Definition of "bindings" and "pubsub" component templates for Dapr. ```yaml spec: functionTemplate: functionIO: pubsub: kafka-server: type: pubsub.kafka version: v1 metadata: - name: brokers value: "kafka-server-kafka-brokers:9092" - name: authRequired value: "false" - name: allowedTopics value: "pubsub" nats-streaming-server: type: pubsub.natsstreaming version: v1 metadata: - name: natsURL value: "nats://localhost:4222" - name: natsStreamingClusterID value: "clusterId" bindings: cron: type: bindings.cron version: v1 metadata: - name: schedule value: "@every 2s" ``` #### ScaleOptions `spec.functionTemplate.scaleOptions: map[string]*ScaleOptions` Definition of the function "Scale options" template. ```yaml spec: functionTemplate: scaleOptions: myKeda: maxReplicas: 10 minReplicas: 1 keda: pollingInterval: 30 cooldownPeriod: 60 advanced: horizontalPodAutoscalerConfig: behavior: scaleDown: stabilizationWindowSeconds: 60 policies: - type: Percent value: 50 periodSeconds: 15 scaleUp: stabilizationWindowSeconds: 0 ``` #### Trigger `spec.functionTemplate.trigger: map[string]*Triggers` Definition of the function "Trigger" template. ```yaml spec: functionTemplate: trigger: kafka-trigger: type: kafka metadata: topic: "logs" bootstrapServers: "kafka-server-kafka-brokers.default.svc.cluster.local:9092" consumerGroup: "logs-handler" lagThreshold: "20" ``` #### PodSpec `spec.functionTemplate.podSpec: map[string]*v1.PodSpec` Definition of the function "PodSpec" template. ```yaml spec: functionTemplate: podSpec: has-init-container: 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 with-node-affinity: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - snail ``` #### Build `spec.functionTemplate.Build: map[string]*BuildImpl` Definition of the function "Build" template. ```yaml spec: functionTemplate: build: build-go: builder: openfunction/builder-go:v2 srcRepo: url: "https://github.com/xxx/abc.git" sourceSubPath: "golang/" build-nodejs: builder: openfunction/builder-node:v2 srcRepo: url: "https://github.com/xxx/abc.git" sourceSubPath: "nodejs/" ``` ### Rendering of template The values of the parameters in `spec.functionTemplate` can be rendered by the values in `spec.params`. In the following example, we set two variables in `spec.params`: "func_name_prefix" and "cron_schedule_period". Then use them in `spec.functionTemplate.functionIO` and `spec.functionTemplate.build` respectively. The syntax for rendering is as follows: "{{" + "params" + "." + Parameter Name + "}}" The *rendering* works by finding the corresponding parameter name under `spec.params` and replacing `"{{params.<variable name>}}"` with its `value` . ```yaml spec: params: - name: func_name_prefix value: of - name: cron_schedule_period value: '@every 5s' functionTemplate: functionIO: bindings: cron: type: bindings.cron version: v1 metadata: - name: schedule value: '{{params.cron_schedule_period}}' build: env: FUNC_NAME_PREFIX: '{{params.func_name_prefix}}' ``` ### Examples Assume we have defined a function template: ```yaml apiVersion: openfunction.io/v1beta1 kind: Template metadata: name: default spec: params: - name: func_name_prefix value: of - name: cron_schedule_period value: '@every 5s' - name: kafka_server value: 'kafka-server-kafka-brokers.default.svc.cluster.local:9092' - name: kafka_topics value: sample-topic - name: max_replicas value: '10' - name: min_replicas value: '1' functionTemplate: functionIO: pubsub: kafka-server: type: pubsub.kafka version: v1 metadata: - name: brokers value: '{{params.kafka_server}}' - name: authRequired value: 'false' - name: allowedTopics value: '{{params.kafka_topic}}' bindings: cron: type: bindings.cron version: v1 metadata: - name: schedule value: '{{params.cron_schedule_period}}' scaleOptions: myKeda: 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 trigger: kafka-trigger: type: kafka metadata: topic: '{{params.kafka_topic}}' bootstrapServers: '{{params.kafka_server}}' lagThreshold: '20' podSpec: has-init-container: 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 with-node-affinity: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - snail build: build-go: builder: 'openfunction/builder-go:v2' env: FUNC_NAME_PREFIX: '{{params.func_name_prefix}}' srcRepo: url: 'https://github.com/xxx/abc.git' sourceSubPath: golang/ build-nodejs: builder: 'openfunction/builder-node:v2' env: FUNC_NAME_PREFIX: '{{params.func_name_prefix}}' srcRepo: url: 'https://github.com/xxx/abc.git' sourceSubPath: nodejs/ ``` Based on this function template, we can write Function as follows. > `templateRef.name` from "template name" + "." + "\<the name of the corresponding part of the template\>" > > i.e. `spec.build.templateRef.name` in Function ("default"(template name) + "." + "build-go"), means that it will look for the contents of `spec.functionTemplate.build.build-go` in the Template. ```yaml apiVersion: core.openfunction.io/v1beta1 kind: Function metadata: name: sample-a spec: version: "v1.0.0" image: openfunctiondev/v1beta1-autoscaling-subscriber:latest imageCredentials: name: push-secret build: templateRef: name: default.build-go serving: runtime: "async" scaleOptions: templateRef: name: default.myKeda triggers: templateRef: - name: default.kafka-trigger template: templateRef: - name: default.has-init-container inputs: - name: producer component: kafka-server topic: "pubsub" pubsub: templateRef: - name: default.kafka-server ``` We will also support overriding parameters in Function: > Priority of parameters: function level > template level > > This means that for the function in the following case, the minimum number of replicas will be set to 5, while other functions without this setting will still maintain the minimum number of copies of 1 as defined in the template. ```yaml apiVersion: core.openfunction.io/v1beta1 kind: Function metadata: name: sample-b spec: input: params: - name: min_replicas value: 5 version: "v1.0.0" image: openfunctiondev/v1beta1-autoscaling-subscriber:latest imageCredentials: name: push-secret build: templateRef: name: default.build-go serving: runtime: "async" scaleOptions: templateRef: name: default.myKeda triggers: templateRef: - name: default.kafka-trigger template: templateRef: - name: default.has-init-container inputs: - name: producer component: kafka-server topic: "pubsub" pubsub: templateRef: - name: default.kafka-server ``` Certainly we still support the way of running functions directly without using templates.