Functions Red Hat OpenShift Serverless 1.35 Setting up and using OpenShift Serverless Functions Red Hat OpenShift Documentation Team

Commands:
kn func create -l <runtime> -t <template> <path>
kn func create -l typescript -t cloudevents examplefunc
Created typescript function in /home/user/demo/examplefunc
cd examplefunc
kn func run
kn func deploy
Function deployed at: http://func.example.com
kn func invoke
kn func create -r <repository> -l <runtime> -t <template> <path>
kn func create -l typescript -t cloudevents examplefunc
Created typescript function in /home/user/demo/examplefunc
kn func create -r https://github.com/boson-project/templates/ -l node -t hello-world examplefunc
Created node function in /home/user/demo/examplefunc
kn func tkn-tasks | oc apply -f -
oc apply -f https://raw.githubusercontent.com/openshift-knative/kn-plugin-func/serverless-1.34/pkg/pipelines/resources/tekton/pipeline/dev-console/0.1/nodejs-pipeline.yaml
kn func run
kn func run --path=<directory_path>
kn func run --build
kn func run --build=false
kn func help run
kn func deploy [-n <namespace> -p <path> -i <image>]
Function deployed at: http://func.example.com
kn func build
kn func build
Building function image Function image has been built, image: registry.redhat.io/example/example-function:latest
kn func build --registry quay.io/username
Building function image Function image has been built, image: quay.io/username/example-function:latest
kn func build --push
kn func help build
kn func list [-n <namespace> -p <path>]
NAME NAMESPACE RUNTIME URL READY example-function default node http://example-function.default.apps.ci-ln-g9f36hb-d5d6b.origin-ci-int-aws.dev.rhcloud.com True
kn service list -n <namespace>
NAME URL LATEST AGE CONDITIONS READY REASON example-function http://example-function.default.apps.ci-ln-g9f36hb-d5d6b.origin-ci-int-aws.dev.rhcloud.com example-function-gzl4c 16m 3 OK / 3 True
kn func invoke
kn func delete [<function_name> -n <namespace> -p <path>]
kn func create <function_name> -l <runtime>
kn func deploy --remote
🕕 Creating Pipeline resources Please provide credentials for image registry used by Pipeline. ? Server: https://index.docker.io/v1/ ? Username: my-repo ? Password: ******** Function deployed at URL: http://test-function.default.svc.cluster.local
kn func config git set
git add .tekton/pipeline.yaml .tekton/pipeline-run.yaml git commit -m 'Add the Pipelines and PipelineRuns configuration' git push
kn func deploy --remote \ 1 --git-url <repo-url> \ 2 [--git-branch <branch>] \ 3 [--git-dir <function-dir>] 4
kn func deploy --remote --git-url https://example.com/alice/myfunc.git --git-branch my-feature --git-dir functions/example-func/
kn func deploy --remote --pvc-size='2Gi'
kn func subscribe --filter type=com.example.Hello --source my-broker
kn func subscribe --filter type=com.example --filter extension=my-extension-value
kn func deploy
🙌 Function image built: <registry>/hello:latest 🎯 Creating Triggers on the cluster ✅ Function deployed in namespace "default" and exposed at URL: http://hello.default.my-cluster.example.com
fn ├── README.md ├── func.yaml 1 ├── go.mod 2 ├── go.sum ├── handle.go └── handle_test.go
go get gopkg.in/yaml.v2@v2.4.0
func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) { // Read body body, err := ioutil.ReadAll(req.Body) defer req.Body.Close() if err != nil { http.Error(res, err.Error(), 500) return } // Process body and function logic // }
Handle() Handle() error Handle(context.Context) Handle(context.Context) error Handle(cloudevents.Event) Handle(cloudevents.Event) error Handle(context.Context, cloudevents.Event) Handle(context.Context, cloudevents.Event) error Handle(cloudevents.Event) *cloudevents.Event Handle(cloudevents.Event) (*cloudevents.Event, error) Handle(context.Context, cloudevents.Event) *cloudevents.Event Handle(context.Context, cloudevents.Event) (*cloudevents.Event, error)
customerId: 0123456
productId: 6543210
type Purchase struct { CustomerId string `json:"customerId"` ProductId string `json:"productId"` } func Handle(ctx context.Context, event cloudevents.Event) (err error) { purchase := &Purchase{} if err = event.DataAs(purchase); err != nil { fmt.Fprintf(os.Stderr, "failed to parse incoming CloudEvent %s\n", err) return } // }
func Handle(ctx context.Context, event cloudevents.Event) { bytes, err := json.Marshal(event) // }
func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) { // Set response res.Header().Add("Content-Type", "text/plain") res.Header().Add("Content-Length", "3") res.WriteHeader(200) _, err := fmt.Fprintf(res, "OK\n") if err != nil { fmt.Fprintf(os.Stderr, "error or response write: %v", err) } }
func Handle(ctx context.Context, event cloudevents.Event) (resp *cloudevents.Event, err error) { // response := cloudevents.NewEvent() response.SetID("example-uuid-32943bac6fea") response.SetSource("purchase/getter") response.SetType("purchase") // Set the data from Purchase type response.SetData(cloudevents.ApplicationJSON, Purchase{ CustomerId: custId, ProductId: prodId, }) // OR set the data directly from map response.SetData(cloudevents.ApplicationJSON, map[string]string{"customerId": custId, "productId": prodId}) // Validate the response resp = &response if err = resp.Validate(); err != nil { fmt.Printf("invalid event created. %v", err) } return }
go test
. ├── func.yaml 1 ├── mvnw ├── mvnw.cmd ├── pom.xml 2 ├── README.md └── src ├── main │ ├── java │ │ └── functions │ │ ├── Function.java 3 │ │ ├── Input.java │ │ └── Output.java │ └── resources │ └── application.properties └── test └── java └── functions 4 ├── FunctionTest.java └── NativeFunctionIT.java
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.8.0</version> <scope>test</scope> </dependency> </dependencies>
public class Functions { @Funq public void processPurchase(Purchase purchase) { // process the purchase } }
public class Purchase { private long customerId; private long productId; // getters and setters }
import io.quarkus.funqy.Funq; import io.quarkus.funqy.knative.events.CloudEvent; public class Input { private String message; // getters and setters } public class Output { private String message; // getters and setters } public class Functions { @Funq public Output withBeans(Input in) { // function body } @Funq public CloudEvent<Output> withCloudEvent(CloudEvent<Input> in) { // function body } @Funq public void withBinary(byte[] in) { // function body } }
curl "http://localhost:8080/withBeans" -X POST -H "Content-Type: application/json" -d '{"message": "Hello there."}'
curl "http://localhost:8080/withBeans?message=Hello%20there." -X GET
curl "http://localhost:8080/" -X POST -H "Content-Type: application/json" -H "Ce-SpecVersion: 1.0" -H "Ce-Type: withBeans" -H "Ce-Source: cURL" -H "Ce-Id: 42" -d '{"message": "Hello there."}'
curl http://localhost:8080/ -H "Content-Type: application/cloudevents+json" -d '{ "data": {"message":"Hello there."}, "datacontenttype": "application/json", "id": "42", "source": "curl", "type": "withBeans", "specversion": "1.0"}'
curl "http://localhost:8080/" -X POST -H "Content-Type: application/octet-stream" -H "Ce-SpecVersion: 1.0" -H "Ce-Type: withBinary" -H "Ce-Source: cURL" -H "Ce-Id: 42" --data-binary '@img.jpg'
curl http://localhost:8080/ -H "Content-Type: application/cloudevents+json" -d "{ \"data_base64\": \"USD(base64 --wrap=0 img.jpg)\", \"datacontenttype\": \"application/octet-stream\", \"id\": \"42\", \"source\": \"curl\", \"type\": \"withBinary\", \"specversion\": \"1.0\"}"
public class Functions { private boolean _processPurchase(Purchase purchase) { // do stuff } public CloudEvent<Void> processPurchase(CloudEvent<Purchase> purchaseEvent) { System.out.println("subject is: " + purchaseEvent.subject()); if (!_processPurchase(purchaseEvent.data())) { return CloudEventBuilder.create() .type("purchase.error") .build(); } return CloudEventBuilder.create() .type("purchase.success") .build(); } }
public class Functions { @Funq public List<Purchase> getPurchasesByName(String name) { // logic to retrieve purchases } }
public class Functions { public List<Integer> getIds(); public Purchase[] getPurchasesByName(String name); public String getNameById(int id); public Map<String,Integer> getNameIdMapping(); public void processImage(byte[] img); }
./mvnw test
quarkus.smallrye-health.root-path=/health 1 quarkus.smallrye-health.liveness-path=alive 2 quarkus.smallrye-health.readiness-path=ready 3
build: builder: s2i buildEnvs: - name: QUARKUS_SMALLRYE_HEALTH_LIVENESS_PATH value: alive 1 - name: QUARKUS_SMALLRYE_HEALTH_READINESS_PATH value: ready 2
deploy: healthEndpoints: liveness: /health/alive readiness: /health/ready
. ├── func.yaml 1 ├── index.js 2 ├── package.json 3 ├── README.md └── test 4 ├── integration.js └── unit.js
npm install --save opossum
// Expects to receive a CloudEvent with customer data function handle(context, customer) { // process the customer const processed = handle(customer); return context.cloudEventResponse(customer) .source('/handle') .type('fn.process.customer') .response(); }
customerId: 0123456
productId: 6543210
function handle(context, data)
{ "id": "12345", "contact": { "title": "Mr.", "firstname": "John", "lastname": "Smith" } }
function handle(context, customer) { return "Hello " + customer.contact.title + " " + customer.contact.lastname; }
Hello Mr. Smith
function handle(context, data) { if (context.headers['content-type'] === 'application/json') { // handle JSON data } else if (context.headers['content-type'] === 'application/xml') { // handle XML data } else { // handle other data types } }
function handle(context, customer) { // process customer and return a new CloudEvent return new CloudEvent({ source: 'customer.processor', type: 'customer.processed' }) }
function handle(context) { return "This function Works!" }
curl https://myfunction.example.com
This function Works!
function handle(context) { let somenumber = 100 return { body: somenumber } }
curl https://myfunction.example.com
100
function handle(context) { let someboolean = false return { body: someboolean } }
curl https://myfunction.example.com
false
function handle(context) { let someboolean = false return someboolean }
http :8080
HTTP/1.1 204 No Content Connection: keep-alive
function handle(context, customer) { // process customer and return custom headers // the response will be '204 No content' return { headers: { customerid: customer.id } }; }
function handle(context, customer) { // process customer if (customer.restricted) { return { statusCode: 451 } } }
function handle(context, customer) { // process customer if (customer.restricted) { const err = new Error('Unavailable for legal reasons'); err.statusCode = 451; throw err; } }
npm test
export interface Function { init?: () => any; 1 shutdown?: () => any; 2 liveness?: HealthCheck; 3 readiness?: HealthCheck; 4 logLevel?: LogLevel; handle: CloudEventFunction | HTTPFunction; 5 }
const Function = { handle: (context, body) => { // The function logic goes here return 'function called' }, liveness: () => { process.stdout.write('In liveness\n'); return 'ok, alive'; }, 1 readiness: () => { process.stdout.write('In readiness\n'); return 'ok, ready'; } 2 }; Function.liveness.path = '/alive'; 3 Function.readiness.path = '/ready'; 4 module.exports = Function;
run: envs: - name: LIVENESS_URL value: /alive 1 - name: READINESS_URL value: /ready 2
deploy: healthEndpoints: liveness: /alive readiness: /ready
function handle(context) { context.log.info("Processing customer"); }
kn func invoke --target 'http://example.function.com'
level: 30
time: 1604511655265
pid: 3430203
hostname: localhost.localdomain
reqId: 1
msg: Processing customer
function handle(context) { // Log the 'name' query parameter context.log.info(context.query.name); // Query parameters are also attached to the context context.log.info(context.name); }
kn func invoke --target 'http://example.com?name=tiger'
level: 30
time: 1604511655265
pid: 3430203
hostname: localhost.localdomain
reqId: 1
msg: tiger
function handle(context) { // log the incoming request body's 'hello' parameter context.log.info(context.body.hello); }
kn func invoke -d '{"Hello": "world"}'
level: 30
time: 1604511655265
pid: 3430203
hostname: localhost.localdomain
reqId: 1
msg: world
function handle(context) { context.log.info(context.headers["custom-header"]); }
kn func invoke --target 'http://example.function.com'
level: 30
time: 1604511655265
pid: 3430203
hostname: localhost.localdomain
reqId: 1
msg: some-value
. ├── func.yaml 1 ├── package.json 2 ├── package-lock.json ├── README.md ├── src │ └── index.ts 3 ├── test 4 │ ├── integration.ts │ └── unit.ts └── tsconfig.json
npm install --save opossum
function handle(context:Context): string
// Expects to receive a CloudEvent with customer data export function handle(context: Context, cloudevent?: CloudEvent): CloudEvent { // process the customer const customer = cloudevent.data; const processed = processCustomer(customer); return context.cloudEventResponse(customer) .source('/customer/process') .type('customer.processed') .response(); }
// Invokable is the expeted Function signature for user functions export interface Invokable { (context: Context, cloudevent?: CloudEvent): any } // Logger can be used for structural logging to the console export interface Logger { debug: (msg: any) => void, info: (msg: any) => void, warn: (msg: any) => void, error: (msg: any) => void, fatal: (msg: any) => void, trace: (msg: any) => void, } // Context represents the function invocation context, and provides // access to the event itself as well as raw HTTP objects. export interface Context { log: Logger; req: IncomingMessage; query?: Record<string, any>; body?: Record<string, any>|string; method: string; headers: IncomingHttpHeaders; httpVersion: string; httpVersionMajor: number; httpVersionMinor: number; cloudevent: CloudEvent; cloudEventResponse(data: string|object): CloudEventResponse; } // CloudEventResponse is a convenience class used to create // CloudEvents on function returns export interface CloudEventResponse { id(id: string): CloudEventResponse; source(source: string): CloudEventResponse; type(type: string): CloudEventResponse; version(version: string): CloudEventResponse; response(): CloudEvent; }
customerId: 0123456
productId: 6543210
function handle(context: Context, cloudevent?: CloudEvent): CloudEvent
export const handle: Invokable = function ( context: Context, cloudevent?: CloudEvent ): Message { // process customer and return a new CloudEvent const customer = cloudevent.data; return HTTP.binary( new CloudEvent({ source: 'customer.processor', type: 'customer.processed' }) ); };
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, any> { // process customer and return custom headers const customer = cloudevent.data as Record<string, any>; return { headers: { 'customer-id': customer.id } }; }
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, any> { // process customer const customer = cloudevent.data as Record<string, any>; if (customer.restricted) { return { statusCode: 451 } } // business logic, then return { statusCode: 240 } }
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, string> { // process customer const customer = cloudevent.data as Record<string, any>; if (customer.restricted) { const err = new Error('Unavailable for legal reasons'); err.statusCode = 451; throw err; } }
npm install
npm test
export interface Function { init?: () => any; 1 shutdown?: () => any; 2 liveness?: HealthCheck; 3 readiness?: HealthCheck; 4 logLevel?: LogLevel; handle: CloudEventFunction | HTTPFunction; 5 }
const Function = { handle: (context, body) => { // The function logic goes here return 'function called' }, liveness: () => { process.stdout.write('In liveness\n'); return 'ok, alive'; }, 1 readiness: () => { process.stdout.write('In readiness\n'); return 'ok, ready'; } 2 }; Function.liveness.path = '/alive'; 3 Function.readiness.path = '/ready'; 4 module.exports = Function;
run: envs: - name: LIVENESS_URL value: /alive 1 - name: READINESS_URL value: /ready 2
deploy: healthEndpoints: liveness: /alive readiness: /ready
export function handle(context: Context): string { // log the incoming request body's 'hello' parameter if (context.body) { context.log.info((context.body as Record<string, string>).hello); } else { context.log.info('No data received'); } return 'OK'; }
kn func invoke --target 'http://example.function.com'
level: 30
time: 1604511655265
pid: 3430203
hostname: localhost.localdomain
reqId: 1
msg: Processing customer
export function handle(context: Context): string { // log the 'name' query parameter if (context.query) { context.log.info((context.query as Record<string, string>).name); } else { context.log.info('No data received'); } return 'OK'; }
kn func invoke --target 'http://example.function.com' --data '{"name": "tiger"}'
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"tiger"} {"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"tiger"}
export function handle(context: Context): string { // log the incoming request body's 'hello' parameter if (context.body) { context.log.info((context.body as Record<string, string>).hello); } else { context.log.info('No data received'); } return 'OK'; }
kn func invoke --target 'http://example.function.com' --data '{"hello": "world"}'
level: 30
time: 1604511655265
pid: 3430203
hostname: localhost.localdomain
reqId: 1
msg: world
export function handle(context: Context): string { // log the incoming request body's 'hello' parameter if (context.body) { context.log.info((context.headers as Record<string, string>)['custom-header']); } else { context.log.info('No data received'); } return 'OK'; }
curl -H'x-custom-header: some-value'' http://example.function.com
level: 30
time: 1604511655265
pid: 3430203
hostname: localhost.localdomain
reqId: 1
msg: some-value
fn ├── func.py 1 ├── func.yaml 2 ├── requirements.txt 3 └── test_func.py 4
def main(context: Context): """ The context parameter contains the Flask request object and any CloudEvent received with the request. """ print(f"Method: {context.request.method}") print(f"Event data {context.cloud_event.data}") # ... business logic here
def main(context: Context): body = { "message": "Howdy!" } headers = { "content-type": "application/json" } return body, 200, headers
@event("event_source"="/my/function", "event_type"="my.type") def main(context): # business logic here data = do_something() # more data processing return data
pip install -r requirements.txt
python3 test_func.py
kn func config
kn func config ? What do you want to configure? Volumes ? What operation do you want to perform? List Configured Volumes mounts: - Secret "mysecret" mounted at path: "/workspace/secret" - Secret "mysecret2" mounted at path: "/workspace/secret2"
kn func config ├─> Environment variables │ ├─> Add │ │ ├─> ConfigMap: Add all key-value pairs from a config map │ │ ├─> ConfigMap: Add value from a key in a config map │ │ ├─> Secret: Add all key-value pairs from a secret │ │ └─> Secret: Add value from a key in a secret │ ├─> List: List all configured environment variables │ └─> Remove: Remove a configured environment variable └─> Volumes ├─> Add │ ├─> ConfigMap: Mount a config map as a volume │ └─> Secret: Mount a secret as a volume ├─> List: List all configured volumes └─> Remove: Remove a configured volume
kn func deploy -p test
kn func config envs [-p <function-project-path>]
kn func config envs add [-p <function-project-path>]
kn func config envs remove [-p <function-project-path>]
kn func config volumes [-p <function-project-path>]
kn func config volumes add [-p <function-project-path>]
kn func config volumes remove [-p <function-project-path>]
{{ env:ENV_VAR }}
name: test namespace: "" runtime: go envs: - name: MY_API_KEY value: '{{ env:API_KEY }}'
name: test namespace: "" runtime: go annotations: <annotation_name>: "<annotation_value>" 1
name: test namespace: "" runtime: go annotations: author: "alice@example.com"
name: test namespace: "" runtime: go volumes: - secret: mysecret path: /workspace/secret
name: test namespace: "" runtime: go volumes: - configMap: addresses path: /workspace/secret-addresses
name: test namespace: "" runtime: go volumes: - configMap: myconfigmap path: /workspace/configmap
name: test namespace: "" runtime: go volumes: - configMap: addresses path: /workspace/configmap-addresses
name: test namespace: "" runtime: go envs: - name: EXAMPLE value: '{{ secret:mysecret:key }}'
name: test namespace: "" runtime: go envs: - value: '{{ configMap:userdetailssecret:userid }}'
name: test namespace: "" runtime: go envs: - name: EXAMPLE value: '{{ configMap:myconfigmap:key }}'
name: test namespace: "" runtime: go envs: - value: '{{ configMap:userdetailsmap:userid }}'
name: test namespace: "" runtime: go envs: - value: '{{ secret:mysecret }}' 1
name: test namespace: "" runtime: go envs: - value: '{{ configMap:userdetailssecret }}'
name: test namespace: "" runtime: go envs: - value: '{{ configMap:myconfigmap }}' 1
name: test namespace: "" runtime: go envs: - value: '{{ configMap:userdetailsmap }}'
buildEnvs: - name: EXAMPLE1 value: one
buildEnvs: - name: EXAMPLE1 value: '{{ env:LOCAL_ENV_VAR }}'
name: test namespace: "" runtime: go envs: - name: EXAMPLE1 1 value: value - name: EXAMPLE2 2 value: '{{ env:LOCAL_ENV_VALUE }}' - name: EXAMPLE3 3 value: '{{ secret:mysecret:key }}' - name: EXAMPLE4 4 value: '{{ configMap:myconfigmap:key }}' - value: '{{ secret:mysecret2 }}' 5 - value: '{{ configMap:myconfigmap2 }}' 6
name: test namespace: "" runtime: go volumes: - secret: mysecret 1 path: /workspace/secret - configMap: myconfigmap 2 path: /workspace/configmap
name: test namespace: "" runtime: go options: scale: min: 0 max: 10 metric: concurrency target: 75 utilization: 75 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 1000m memory: 256Mi concurrency: 100
labels: - key: role value: backend
labels: - key: author value: '{{ env:USER }}'