# 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.