From f378919b64029cbfc74a941bf7eb431c48b79a8e Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Wed, 8 Jul 2020 23:53:30 +0530 Subject: [PATCH] Add NATS connector blog Signed-off-by: Vivek Singh --- ...driven-functions-with-openfaas-and-nats.md | 152 ++++++++++++++++++ .../nats-publish-subscribe.png | Bin 0 -> 16442 bytes 2 files changed, 152 insertions(+) create mode 100644 _posts/2020-08-31-event-driven-functions-with-openfaas-and-nats.md create mode 100644 images/2020-openfaas-nats/nats-publish-subscribe.png diff --git a/_posts/2020-08-31-event-driven-functions-with-openfaas-and-nats.md b/_posts/2020-08-31-event-driven-functions-with-openfaas-and-nats.md new file mode 100644 index 00000000..ee8cc657 --- /dev/null +++ b/_posts/2020-08-31-event-driven-functions-with-openfaas-and-nats.md @@ -0,0 +1,152 @@ +--- +title: "Event-driven functions with OpenFaaS and NATS" +description: Vivek outlines how you can make use of NATS to trigger your functions in OpenFaaS through the new connector-sdk component. +date: 2020-08-31 +image: /images/kafka-connector/aluminium-building.jpg +categories: + - NATS + - tutorial + - examples +author_staff_member: vivek +dark_background: true +--- + +In this blog post, I will show how you can invoke OpenFaaS function in response to messages sent on NATS topics in publish-subscribe model. +OpenFaaS functions are accessible over HTTP endpoints via gateway service but OpenFaaS provides several other way to invoke OpenFaaS functions with help of the [connector-sdk](https://github.com/openfaas-incubator/connector-sdk).The connector sdk provides a reusable and tested interface and implementation that allows developers to quickly create a new event source. The code that is unique to OpenFaaS is standardized so that the develop can focus on the details of the receiving message from the event source. + +* [kafka-connector](https://github.com/openfaas-incubator/kafka-connector) connects OpenFaaS functions to Kafka topics. +* [nats-connector](https://github.com/openfaas-incubator/nats-connector) an OpenFaaS event-connector to trigger functions from NATS.  +* [mqtt-connector](https://github.com/openfaas-incubator/mqtt-connector) MQTT connector for OpenFaaS. +* [cron-connector](https://github.com/openfaas-incubator/cron-connector) triggers OpenFaaS functions based on cron events.  +* [VMware vCenter connector](https://github.com/openfaas-incubator/openfaas-vcenter-connector) an OpenFaaS event-connector built to consume events from vCenter and to trigger functions. + +There are several other connectors which allows you to trigger OpenFaaS functions based on events, are written and managed as a third party project. Please refer to this [link](https://docs.openfaas.com/reference/triggers/) for full list. + +## NATS + +[NATS](https://nats.io) is a simple, secure and high performance open source messaging system for cloud native applications, IoT messaging, and microservices architectures. In this blog post, I will be using NATS publish-subscribe concept where publishers publishes a message on a topic/subject and subscribers consumes that message by subscribing to that subject (sometimes called topics in other event systems). + +![NATS Publish Subscribe](/images/2020-openfaas-nats/nats-publish-subscribe.png "https://docs.nats.io/nats-concepts/pubsub") + +## Prerequisites + +Before we start we need a couple of tools to help us quickly set up our environment: + +* [docker](https://docs.docker.com/get-docker/) - required to use KinD +* [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) - a tool for running local Kubernetes clusters using Docker +* [arkade](https://github.com/alexellis/arkade) - official installer for OpenFaaS, that supports many other applications for Kubernetes + +> Please make sure you have these tools installed before you proceed next. + +## Install Components + +#### Create cluster + +First thing we need is running Kubernetes cluster: + +```bash +kind create cluster +``` + +Wait for the installation to finish run this command to verify that the cluster is ready: + +```bash +kubectl -n kube-system rollout status deployment/coredns +``` + +#### Install OpenFaaS +Install OpenFaaS using `arkade` + +``` +arkade install openfaas +``` + +#### Install NATS connector using arkade +`nats-connector` is connector which invokes OpenFaaS function when a message is sent to a NATS topic. + +``` +arkade install nats-connector +``` + +> NATS comes with OpenFaaS installation, so we are not setting up NATS here + +#### Install kubectl and faas-cli + +install `kubectl` and `faas-cli` using `arkade` if they are not already installed. + +``` +arkade get kubectl +``` + +``` +arkade get faas-cli +``` + +#### Login to OpenFaaS gateway using CLI + +Port forward the gateway service to access it. + +``` +kubectl port-forward -n openfaas svc/gateway 8080:8080 & +``` + +Get the password + +``` +PASSWORD=$(kubectl get secret -n openfaas basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode; echo) +``` + +Login to the gateway + +``` +echo -n $PASSWORD | faas-cli login --username admin --password-stdin +``` + + +## Deploy functions +`nats-connector` repo comes with test functions to verify installation. It has two OpenFaas functions. + +`receive-message` function subscribes to NATS topic nats-test. On every new message on nats-test topic, receive-message will be invoked. + +`publish-message` function publishes message to nats-test topic. + +Deploy functions + +``` +faas-cli deploy -f https://raw.githubusercontent.com/openfaas-incubator/nats-connector/master/contrib/test-functions/stack.yml --read-template=false +``` + +> Note: You can also build and deploy this function using the stack.yml and code present in nats-connector repository. + +## Verify the installation +Invoke `publish-message` function to publish a test message + +``` +echo "test message" | faas-cli invoke publish-message +``` + +When `publish-message` was invoked, it would have pushed `test-message` to the `nats-test` topic on NATS, which would have invoked `receive-message`. We can verify that by checking logs of `receive-message` function. + +``` +faas-cli logs receive-message + +2020-05-29T15:41:17Z 2020/05/29 15:41:17 Started logging stderr from function. +2020-05-29T15:41:17Z 2020/05/29 15:41:17 Started logging stdout from function. +2020-05-29T15:41:17Z Forking - ./handler [] +2020-05-29T15:41:17Z 2020/05/29 15:41:17 OperationalMode: http +2020-05-29T15:41:17Z 2020/05/29 15:41:17 Timeouts: read: 10s, write: 10s hard: 10s. +2020-05-29T15:41:17Z 2020/05/29 15:41:17 Listening on port: 8080 +2020-05-29T15:41:17Z 2020/05/29 15:41:17 Writing lock-file to: /tmp/.lock +2020-05-29T15:41:17Z 2020/05/29 15:41:17 Metrics listening on port: 8081 +2020-05-29T15:42:24Z 2020/05/29 15:42:24 stderr: 2020/05/29 15:42:24 received "test message" +2020-05-29T15:42:24Z 2020/05/29 15:42:24 POST / - 200 OK - ContentLength: 28 +``` + +## What Next ? +* Do you have a cool "event-driven" use case you want to share? Let us know and become a guest blogger! + +* If you are new to OpenFaaS, I would recommend you trying [OpenFaaS workshop](https://github.com/openfaas/workshop) . + +* If you don't find connector for messaging platform you are using, checkout the [connector-sdk](https://github.com/openfaas-incubator/connector-sdk) which allows you to build event-connectors for OpenFaaS. + +* If you are looking to contribute to open source project, please join OpenFaaS community [slack channel](https://docs.openfaas.com/community/) and start contributing. diff --git a/images/2020-openfaas-nats/nats-publish-subscribe.png b/images/2020-openfaas-nats/nats-publish-subscribe.png new file mode 100644 index 0000000000000000000000000000000000000000..6140b4591632bb9d3126d921bda2898434aaff0f GIT binary patch literal 16442 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 literal 0 HcmV?d00001