libertyEventDrivenSurvey is an example event-driven survey application demonstrating Liberty InstantOn, CloudEvents, KNative, and MicroProfile Reactive Messaging 3.
The way it works is that users scan a QR code presented by the person running the survey and users type in a location (e.g. New York). This is submitted to a microservice that can scale from zero using KNative Serving and quickly using Liberty InstantOn. This web service publishes the location to a Kafka topic. Another microservice that can scale from zero using KNative Eventing and the KNative Kafka Broker and quickly using Liberty InstantOn then receives this event and geocodes the location to a latitude and longitude through a Geoapify or Google Maps API. Finally, this pin works its way back through another Kafka topic and WebSockets back to the map that the person running the survey is showing.
- Create project
amq-streams-kafka - Install Kafka; for example, the Red Hat
Streams for Apache Kafkaoperator- OperatorHub } AMQ Streams } A specific namespace =
amq-streams-kafka - Kafka } Create Instance } my-cluster } Use all default options } Create
- Wait for
Condition: Ready- If you get a warning about "inter.broker.protocol.version", apply the known workaround
- Kafka Topic } Create KafkaTopic }
locationtopic} Create - Kafka Topic } Create KafkaTopic }
geocodetopic} Create
- OperatorHub } AMQ Streams } A specific namespace =
- Install KNative; for example, the Red Hat
OpenShift Serverlessoperator- Install the
kncommand line utility- Alternatively, install the latest version of kn and the kafka plugin
- macOS:
brew install knative/client/kn knative-sandbox/kn-plugins/source-kafka
- macOS:
- Alternatively, install the latest version of kn and the kafka plugin
- Install KNative Serving
- Operators } Installed Operators
- Project =
knative-serving - Red Hat OpenShift Serverless } Knative Serving
- Create KnativeServing
- Click
Create - Wait for the
ReadyCondition inStatus
- Install KNative Eventing
- Operators } Installed Operators
- Project =
knative-eventing - Red Hat OpenShift Serverless } Knative Eventing
- Create KnativeEventing
- Click
Create - Wait for the
ReadyCondition inStatus
- Install the
KNativeKafkabroker- Knative Kafka } Create KnativeKafka
- channel } enabled; bootstrapServers } my-cluster-kafka-bootstrap.amq-streams-kafka.svc:9092
- source } enabled
- broker } enabled; defaultConfig } bootstrapServers } my-cluster-kafka-bootstrap.amq-streams-kafka.svc:9092
- sink } enabled
- Click
Create - Wait for the
ReadyCondition inStatus
- Install the
- Change directory to this cloned repository
- Create and switch to some test project:
oc new-project libertysurvey - Choose your geocoding provider:
- Get a Geoapify key (default)
- Free commercial usage:
When you stay within the Free pricing plan quota, you can use the Maps API for free, even for a commercial app.
- Attribution policy
Geoapify attribution is mandatory when using Free subscription plan.
- Free commercial usage:
- Or get a Google Maps API key (simple usage should fit within the free tier)
- In general, it's recommended to use a restricted API key in case it is stolen. If you would like to do this, note that the same API key is used both by the JavaScript frontend in the browser and one of the services running in Kubernetes, so both would need to be allowed (e.g. by IP, etc.).
- After creating the API key, go to Enabled APIs & services, click
ENABLE APIS AND SERVICES, and make sure thatMaps JavaScript APIandPlaces APIare enabled.
- Get a Geoapify key (default)
- Create a service account for InstantOn:
oc create serviceaccount instanton-sa - Create an InstantOn SecurityContextConstraints:
oc apply -f lib/instantonscc.yaml - Associate the InstantOn SecurityContextConstraints with the service account:
oc adm policy add-scc-to-user cap-cr-scc -z instanton-sa - To use InstantOn, we need to modify KNative Serving configuration which must be done through the operator:
- Operators } Installed Operators
- Project =
knative-serving - Red Hat OpenShift Serverless } Knative Serving
- Click
knative-serving - Click
YAML - Under
spec, add:config: features: kubernetes.containerspec-addcapabilities: enabled kubernetes.podspec-securitycontext: enabled - Click
Save
If you want to enable Kafka Security (e.g. SASL), then you will need to follow the relevant steps in Kafka connector security configuration and change the relevant YAML configurations below. This may involve, for example, adding a keystore to some of the containers (either during the build phase or by mounting a directory).
- Copy
lib/example_surveyinputservice.yaml.templateintolib/example_surveyinputservice.yaml, and then:- If needed, replace
mp.messaging.connector.liberty-kafka.bootstrap.serverswith the AMQ Streams Kafka Cluster bootstrap address - If using a local build, replace
imagewithimage-registry.openshift-image-registry.svc:5000/libertysurvey/surveyinputservice - Run:
oc apply -f lib/example_surveyinputservice.yaml
- If needed, replace
- Query until
READYisTrue:kn service list surveyinputservice - Open your browser to the URL from the
kn service listoutput above and click onLocation Survey. - Double check logs look good:
oc exec -it $(oc get pod -o name | grep surveyinputservice) -c surveyinputservice -- cat /logs/messages.log - Note that
scale-down-delaydoes not apply to the initial pod creation so the pod will be terminated about 30 seconds after it's initially created. Once a real user request is made to this application, thenscale-down-delaywill apply. Therefore, if you want to tail the logs of the pod, first wait for the initial pod to terminate and then make a request to the application and then you can tail the pod logs.
- Copy
lib/example_surveyadminservice.yaml.templateintolib/example_surveyadminservice.yaml, and then:- If using Google as the map provider, add a
GOOGLE_API_KEYenventry with your Google Maps API key - Replace
INSERT_URLwith the URL from theserviceInputServiceabove appended withlocation.html - If needed, replace
SURVEY_LATITUDEandSURVEY_LONGITUDE(defaults to Las Vegas, NV, USA) - If needed, replace
mp.messaging.connector.liberty-kafka.bootstrap.serverswith the AMQ Streams Kafka Cluster bootstrap address - If using a local build, replace
imagewithimage-registry.openshift-image-registry.svc:5000/libertysurvey/surveyadminservice - Run:
oc apply -f lib/example_surveyadminservice.yaml
- If using Google as the map provider, add a
- Query until
READYisTrue:kn service list surveyadminservice - Open your browser to the URL from the
kn service listoutput above and click onStart New Geolocation Survey. - Double check logs look good:
oc exec -it $(oc get pod -o name | grep surveyadminservice) -c surveyadminservice -- cat /logs/messages.log - Create a KNative Eventing KafkaSource for
surveyAdminService(if needed, replacebootstrapServerswith the AMQ Streams Kafka Cluster bootstrap address):oc apply -f lib/example_surveyadminkafkasource.yaml - Query until
OKis++for all lines:kn source kafka describe geocodetopicsource - Note that
scale-down-delaydoes not apply to the initial pod creation so the pod will be terminated about 30 seconds after it's initially created. Once a real user request is made to this application, thenscale-down-delaywill apply. Therefore, if you want to tail the logs of the pod, first wait for the initial pod to terminate and then make a request to the application and then you can tail the pod logs.
- Copy
lib/example_surveygeocoderservice.yaml.templateintolib/example_surveygeocoderservice.yaml, and then:- If using Geoapify as the geocoding provider (default), add a
GEOAPIFY_API_KEYenventry with your Geoapify API key - If using Google as the geocoding provider, add a
GOOGLE_API_KEYenventry with your Google Maps API key - If needed, replace
mp.messaging.connector.liberty-kafka.bootstrap.serverswith the AMQ Streams Kafka Cluster bootstrap address - If using a local build, replace
imagewithimage-registry.openshift-image-registry.svc:5000/libertysurvey/surveygeocoderservice - Run:
oc apply -f lib/example_surveygeocoderservice.yaml
- If using Geoapify as the geocoding provider (default), add a
- Query until
READYisTrue:kn service list surveygeocoderservice - Double check logs look good:
oc exec -it $(oc get pod -o name | grep surveygeocoderservice) -c surveygeocoderservice -- cat /logs/messages.log - Create a KNative Eventing KafkaSource for
surveyGeocoderService(if needed, replacebootstrapServerswith the AMQ Streams Kafka Cluster bootstrap address):oc apply -f lib/example_surveygeocoderkafkasource.yaml - Query until
OKis++for all lines:kn source kafka describe locationtopicsource - Note that
scale-down-delaydoes not apply to the initial pod creation so the pod will be terminated about 30 seconds after it's initially created. Once a real user request is made to this application, thenscale-down-delaywill apply. Therefore, if you want to tail the logs of the pod, first wait for the initial pod to terminate and then make a request to the application and then you can tail the pod logs.
- Open the
surveyadminservicegeolocation.jsppage in one browser tab. - Open the
surveyinputservicelocation.htmlpage in another browser tab, enter a location, and click Submit. - Switch back to the
surveyadminserviceand wait for the pin to be added.
- If using
podman machine:- Set your connection to the
rootconnection:podman system connection default podman-machine-default-root - If the machine has SELinux
virt_sandbox_use_netlinkdisabled (i.e. the following returnsoff):Then, enable it:podman machine ssh "getsebool virt_sandbox_use_netlink"Note that this must be done after every time the podman machine restarts.podman machine ssh "sudo setsebool virt_sandbox_use_netlink 1"
- Set your connection to the
- Build:
mvn clean deploy - Ensure the internal OpenShift registry is available:
oc patch configs.imageregistry.operator.openshift.io/cluster --patch "{\"spec\":{\"defaultRoute\":true}}" --type=merge - Push
surveyInputServiceto the registry:REGISTRY=$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}') echo "Registry host: ${REGISTRY}" printf "Does it look good (yes=ENTER, no=Ctrl^C)? " read trash podman login --tls-verify=false -u $(oc whoami | sed 's/://g') -p $(oc whoami -t) ${REGISTRY} podman tag localhost/surveyinputservice $REGISTRY/libertysurvey/surveyinputservice podman push --tls-verify=false $REGISTRY/libertysurvey/surveyinputservice - Push
surveyAdminServiceto the registry:REGISTRY=$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}') echo "Registry host: ${REGISTRY}" printf "Does it look good (yes=ENTER, no=Ctrl^C)? " read trash podman login --tls-verify=false -u $(oc whoami | sed 's/://g') -p $(oc whoami -t) ${REGISTRY} podman tag localhost/surveyadminservice $REGISTRY/libertysurvey/surveyadminservice podman push --tls-verify=false $REGISTRY/libertysurvey/surveyadminservice - Push
surveyGeocoderServiceto the registry:REGISTRY=$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}') echo "Registry host: ${REGISTRY}" printf "Does it look good (yes=ENTER, no=Ctrl^C)? " read trash podman login --tls-verify=false -u $(oc whoami | sed 's/://g') -p $(oc whoami -t) ${REGISTRY} podman tag localhost/surveygeocoderservice $REGISTRY/libertysurvey/surveygeocoderservice podman push --tls-verify=false $REGISTRY/libertysurvey/surveygeocoderservice
Run kn service update with the service name and the same image name from the YAML; for examples:
kn service update surveyinputservice --image=image-registry.openshift-image-registry.svc:5000/libertysurvey/surveyinputservice
kn service update surveygeocoderservice --image=image-registry.openshift-image-registry.svc:5000/libertysurvey/surveygeocoderservice
kn service update surveyadminservice --image=image-registry.openshift-image-registry.svc:5000/libertysurvey/surveyadminservice
- Submit a location input:
- Using the command line:
- Execute:
curl -k --data "textInput1=New York, NY" "$(kn service list surveyinputservice -o jsonpath="{.items[0].status.url}{'\n'}")/LocationSurvey" - Check for a successful output:
Your submission has been received
- Execute:
- Using the browser:
- Find and open the URL:
kn service list surveyinputservice -o jsonpath="{.items[0].status.url}{'/location.html\n'}" - Click
Location Surveyand submit the form
- Find and open the URL:
- Using the command line:
- Double check logs look good:
oc exec -it $(oc get pod -o name | grep surveygeocoderservice) -c surveygeocoderservice -- tail -f /logs/messages.log
- Tail
surveyinputservicelogs:oc exec -it $(oc get pod -o name | grep surveyinputservice) -c surveyinputservice -- tail -f /logs/messages.log - Tail
surveyadminservicelogs:oc exec -it $(oc get pod -o name | grep surveyadminservice) -c surveyadminservice -- tail -f /logs/messages.log - Tail
surveygeocoderservicelogs:oc exec -it $(oc get pod -o name | grep surveygeocoderservice) -c surveygeocoderservice -- tail -f /logs/messages.log
lib/cleanup_all.sh
- Delete the KafkaSource:
kn source kafka delete geocodetopicsource - Delete the KNative Service:
kn service delete surveyadminservice
- Delete the KafkaSource:
kn source kafka delete locationtopicsource - Delete the KNative Service:
kn service delete surveygeocoderservice
kn service delete surveyinputservice
Only some functions can be tested locally without KNative.
- Run
surveyAdminService:podman run --privileged --rm -e GEOAPIFY_API_KEY=YOUR_KEY -p 8080:8080 -p 8443:8443 -it localhost/surveyadminservice:latest - Open browser to http://localhost:8080/geolocation.jsp
- Post a
CloudEvent:curl -X POST http://localhost:8080/api/cloudevents/geocodeComplete \ -H "Ce-Source: https://example.com/" \ -H "Ce-Id: $(uuidgen)" \ -H "Ce-Specversion: 1.0" \ -H "Ce-Type: CloudEvent1" \ -H "Content-Type: text/plain" \ -d "40.7127753 -74.0059728 New York, NY" - Switch back to the browser and you should see the point.
- Create Kafka container network if it doesn't exist:
podman network create kafka - Start Kafka if it's not started:
podman run --rm -p 9092:9092 -e "ALLOW_PLAINTEXT_LISTENER=yes" -e "KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka-0:9092" -e "KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093" -e "KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT" -e "KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka-0:9093" -e "KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER" -e "KAFKA_CFG_PROCESS_ROLES=controller,broker" -e "KAFKA_CFG_NODE_ID=0" --name kafka-0 --network kafka docker.io/bitnami/kafka - Run
surveyInputService:podman run --privileged --rm --network kafka --rm -p 8080:8080 -p 8443:8443 -it localhost/surveyinputservice:latest - Wait for the message:
[...] CWWKZ0001I: Application surveyInputService started [...] - Access http://localhost:8080/location.html or https://localhost:8443/location.html
- Create Kafka container network if it doesn't exist:
podman network create kafka - Start Kafka if it's not started:
podman run --rm -p 9092:9092 -e "ALLOW_PLAINTEXT_LISTENER=yes" -e "KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka-0:9092" -e "KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093" -e "KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT" -e "KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka-0:9093" -e "KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER" -e "KAFKA_CFG_PROCESS_ROLES=controller,broker" -e "KAFKA_CFG_NODE_ID=0" --name kafka-0 --network kafka docker.io/bitnami/kafka - Run
surveyGeocoderService:podman run --privileged --rm -p 8080:8080 -p 8443:8443 -e "GEOAPIFY_API_KEY=INSERT_API_KEY" -it localhost/surveygeocoderservice:latest - Post a
CloudEvent:curl -X POST http://localhost:8080/api/cloudevents/locationInput \ -H "Ce-Source: https://example.com/" \ -H "Ce-Id: $(uuidgen)" \ -H "Ce-Specversion: 1.0" \ -H "Ce-Type: CloudEvent1" \ -H "Content-Type: text/plain" \ -d "New York, NY"
- Change directory to the application
GEOAPIFY_API_KEY=INSERT_API_KEY mvn clean liberty:dev- Open http://localhost:8080/
- Set a variable to this version in your shell; for example:
VERSION="240012" - List local images:
podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/surveygeocoderservice latest 4fb2fc7d5a7c 19 seconds ago 1.52 GB localhost/surveyinputservice latest 92ee9560daa7 3 minutes ago 1.49 GB localhost/surveyadminservice latest 449a71e98b45 5 minutes ago 1.52 GB - Tag the images for Quay:
podman tag localhost/surveygeocoderservice quay.io/ibm/libertyeventdrivensurvey:surveygeocoderservice${VERSION} podman tag localhost/surveyinputservice quay.io/ibm/libertyeventdrivensurvey:surveyinputservice${VERSION} podman tag localhost/surveyadminservice quay.io/ibm/libertyeventdrivensurvey:surveyadminservice${VERSION} - Log into Quay:
podman login quay.io - Push with the version in step 1:
podman push quay.io/ibm/libertyeventdrivensurvey:surveygeocoderservice${VERSION} podman push quay.io/ibm/libertyeventdrivensurvey:surveyinputservice${VERSION} podman push quay.io/ibm/libertyeventdrivensurvey:surveyadminservice${VERSION}
- https://developer.ibm.com/articles/develop-reactive-microservices-with-microprofile/
- https://openliberty.io/guides/microprofile-reactive-messaging.html
- https://smallrye.io/smallrye-reactive-messaging/latest/concepts/concepts/
- https://openliberty.io/blog/2022/10/17/microprofile-serverless-ibm-code-engine.html
- OpenLiberty/open-liberty#19889
- OpenLiberty/open-liberty#21659