Go
A simple web app written in Go that you can use to test knative eventing. It shows how to consume a CloudEvent in Knative eventing, and optionally how to respond back with another CloudEvent in the http response, using the Go SDK for CloudEvents
We will deploy the app as a Kubernetes Deployment along with a Kubernetes Service. However, you can also deploy the app as a Knative Serving Service.
Follow the steps below to create the sample code and then deploy the app to your cluster. You can also download a working copy of the sample, by running the following commands:
git clone -b "v0.21.x" https://github.com/knative/docs knative-docs
cd knative-docs/docs/eventing/samples/helloworld/helloworld-go
Before you begin¶
- A Kubernetes cluster with Knative Eventing installed.
- Docker installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry).
Recreating the sample code¶
- Create a new file named
helloworld.go
and paste the following code. This code creates a basic web server which listens on port 8080:
import (
"context"
"log"
cloudevents "github.com/cloudevents/sdk-go/v2"
"github.com/google/uuid"
)
func receive(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) {
// Here is where your code to process the event will go.
// In this example we will log the event msg
log.Printf("Event received. \n%s\n", event)
data := &HelloWorld{}
if err := event.DataAs(data); err != nil {
log.Printf("Error while extracting cloudevent Data: %s\n", err.Error())
return nil, cloudevents.NewHTTPResult(400, "failed to convert data: %s", err)
}
log.Printf("Hello World Message from received event %q", data.Msg)
// Respond with another event (optional)
// This is optional and is intended to show how to respond back with another event after processing.
// The response will go back into the knative eventing system just like any other event
newEvent := cloudevents.NewEvent()
newEvent.SetID(uuid.New().String())
newEvent.SetSource("knative/eventing/samples/hello-world")
newEvent.SetType("dev.knative.samples.hifromknative")
if err := newEvent.SetData(cloudevents.ApplicationJSON, HiFromKnative{Msg: "Hi from helloworld-go app!"}); err != nil {
return nil, cloudevents.NewHTTPResult(500, "failed to set response data: %s", err)
}
log.Printf("Responding with event\n%s\n", newEvent)
return &newEvent, nil
}
func main() {
log.Print("Hello world sample started.")
c, err := cloudevents.NewDefaultClient()
if err != nil {
log.Fatalf("failed to create client, %v", err)
}
log.Fatal(c.StartReceiver(context.Background(), receive))
}
- Create a new file named
eventschemas.go
and paste the following code. This defines the data schema of the CloudEvents.
package main
// HelloWorld defines the Data of CloudEvent with type=dev.knative.samples.helloworld
type HelloWorld struct {
// Msg holds the message from the event
Msg string `json:"msg,omitempty,string"`
}
// HiFromKnative defines the Data of CloudEvent with type=dev.knative.samples.hifromknative
type HiFromKnative struct {
// Msg holds the message from the event
Msg string `json:"msg,omitempty,string"`
}
- In your project directory, create a file named
Dockerfile
and copy the code block below into it. For detailed instructions on dockerizing a Go app, see Deploying Go servers with Docker.
# Use the official Golang image to create a build artifact.
# This is based on Debian and sets the GOPATH to /go.
# https://hub.docker.com/_/golang
FROM golang:1.14 as builder
# Copy local code to the container image.
WORKDIR /app
# Retrieve application dependencies using go modules.
# Allows container builds to reuse downloaded dependencies.
COPY go.* ./
RUN go mod download
# Copy local code to the container image.
COPY . ./
# Build the binary.
# -mod=readonly ensures immutable go.mod and go.sum in container builds.
RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o helloworld
# Use a Docker multi-stage build to create a lean production image.
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM alpine:3
RUN apk add --no-cache ca-certificates
# Copy the binary to the production image from the builder stage.
COPY --from=builder /app/helloworld /helloworld
# Run the web service on container startup.
CMD ["/helloworld"]
-
Create a new file,
sample-app.yaml
and copy the following service definition into the file. Make sure to replace{username}
with your Docker Hub username.# Namespace for sample application apiVersion: v1 kind: Namespace metadata: name: knative-samples --- # A default broker apiVersion: eventing.knative.dev/v1 kind: Broker metadata: name: default namespace: knative-samples spec: {} --- # Helloworld-go app deploment apiVersion: apps/v1 kind: Deployment metadata: name: helloworld-go namespace: knative-samples spec: replicas: 1 selector: matchLabels: &labels app: helloworld-go template: metadata: labels: *labels spec: containers: - name: helloworld-go image: docker.io/{username}/helloworld-go --- # Service that exposes helloworld-go app. # This will be the subscriber for the Trigger kind: Service apiVersion: v1 metadata: name: helloworld-go namespace: knative-samples spec: selector: app: helloworld-go ports: - protocol: TCP port: 80 targetPort: 8080 --- # Knative Eventing Trigger to trigger the helloworld-go service apiVersion: eventing.knative.dev/v1 kind: Trigger metadata: name: helloworld-go namespace: knative-samples spec: broker: default filter: attributes: type: dev.knative.samples.helloworld source: dev.knative.samples/helloworldsource subscriber: ref: apiVersion: v1 kind: Service name: helloworld-go
-
Use the go tool to create a
go.mod
manifest.
go mod init github.com/knative/docs/docs/serving/samples/hello-world/helloworld-go
Building and deploying the sample¶
Once you have recreated the sample code files (or used the files in the sample folder) you're ready to build and deploy the sample app.
- Use Docker to build the sample code into a container. To build and push with
Docker Hub, run these commands replacing
{username}
with your Docker Hub username:
# Build the container on your local machine
docker build -t {username}/helloworld-go .
# Push the container to docker registry
docker push {username}/helloworld-go
- After the build has completed and the container is pushed to docker hub, you
can deploy the sample application into your cluster. Ensure that the
container image value in
sample-app.yaml
matches the container you built in the previous step. Apply the configuration usingkubectl
:
kubectl apply --filename sample-app.yaml
-
Above command created a namespace
knative-samples
and create a default Broker it. Verify using the following command:kubectl get broker --namespace knative-samples
Note: you can also use injection based on labels with the Eventing sugar controller. For how to install the Eventing sugar controller, see Install optional Eventing extensions.
-
It deployed the helloworld-go app as a K8s Deployment and created a K8s service names helloworld-go. Verify using the following command.
kubectl --namespace knative-samples get deployments helloworld-go kubectl --namespace knative-samples get svc helloworld-go
-
It created a Knative Eventing Trigger to route certain events to the helloworld-go application. Make sure that Ready=true
kubectl --namespace knative-samples get trigger helloworld-go
Send and verify CloudEvents¶
Once you have deployed the application and verified that the namespace, sample application and trigger are ready, let's send a CloudEvent.
Send CloudEvent to the Broker¶
We can send an http request directly to the Broker with correct CloudEvent headers set.
- Deploy a curl pod and SSH into it
kubectl --namespace knative-samples run curl --image=radial/busyboxplus:curl -it
- Get the Broker URL
kubectl --namespace knative-samples get broker default
- Run the following in the SSH terminal. Please replace the URL with the URL of the default broker.
curl -v "http://broker-ingress.knative-eventing.svc.cluster.local/knative-samples/default" \
-X POST \
-H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f79" \
-H "Ce-Specversion: 1.0" \
-H "Ce-Type: dev.knative.samples.helloworld" \
-H "Ce-Source: dev.knative.samples/helloworldsource" \
-H "Content-Type: application/json" \
-d '{"msg":"Hello World from the curl pod."}'
exit
Verify that event is received by helloworld-go app¶
Helloworld-go app logs the context and the msg of the above event, and replies back with another event.
-
Display helloworld-go app logs
kubectl --namespace knative-samples logs -l app=helloworld-go --tail=50
You should see something similar to:
Event received. Validation: valid Context Attributes, specversion: 1.0 type: dev.knative.samples.helloworld source: dev.knative.samples/helloworldsource id: 536808d3-88be-4077-9d7a-a3f162705f79 time: 2019-10-04T22:35:26.05871736Z datacontenttype: application/json Extensions, knativearrivaltime: 2019-10-04T22:35:26Z knativehistory: default-kn2-trigger-kn-channel.knative-samples.svc.cluster.local traceparent: 00-971d4644229653483d38c46e92a959c7-92c66312e4bb39be-00 Data, {"msg":"Hello World from the curl pod."} Hello World Message "Hello World from the curl pod." Responded with event Validation: valid Context Attributes, specversion: 1.0 type: dev.knative.samples.hifromknative source: knative/eventing/samples/hello-world id: 37458d77-01f5-411e-a243-a459bbf79682 datacontenttype: application/json Data, {"msg":"Hi from Knative!"}
Play around with the CloudEvent attributes in the curl command and the trigger specification to understand how Triggers work.
Verify reply from helloworld-go app¶
helloworld-go
app replies back with an event of
type= dev.knative.samples.hifromknative
, and
source=knative/eventing/samples/hello-world
. This event enters the eventing
mesh via the Broker and can be delivered to other services using a Trigger
-
Deploy a pod that receives any CloudEvent and logs the event to its output.
kubectl --namespace knative-samples apply --filename - << END # event-display app deploment apiVersion: apps/v1 kind: Deployment metadata: name: event-display namespace: knative-samples spec: replicas: 1 selector: matchLabels: &labels app: event-display template: metadata: labels: *labels spec: containers: - name: helloworld-go # Source code: https://github.com/knative/eventing-contrib/tree/main/cmd/event_display image: gcr.io/knative-releases/knative.dev/eventing-contrib/cmd/event_display --- # Service that exposes event-display app. # This will be the subscriber for the Trigger kind: Service apiVersion: v1 metadata: name: event-display namespace: knative-samples spec: selector: app: event-display ports: - protocol: TCP port: 80 targetPort: 8080 END
-
Create a trigger to deliver the event to the above service
kubectl --namespace knative-samples apply --filename - << END
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: event-display
namespace: knative-samples
spec:
broker: default
filter:
attributes:
type: dev.knative.samples.hifromknative
source: knative/eventing/samples/hello-world
subscriber:
ref:
apiVersion: v1
kind: Service
name: event-display
END
-
Check the logs of event-display service
You should see something similar to:kubectl --namespace knative-samples logs -l app=event-display --tail=50
cloudevents.Event Validation: valid Context Attributes, specversion: 1.0 type: dev.knative.samples.hifromknative source: knative/eventing/samples/hello-world id: 8a7384b9-8bbe-4634-bf0f-ead07e450b2a time: 2019-10-04T22:53:39.844943931Z datacontenttype: application/json Extensions, knativearrivaltime: 2019-10-04T22:53:39Z knativehistory: default-kn2-ingress-kn-channel.knative-samples.svc.cluster.local traceparent: 00-4b01db030b9ea04bb150b77c8fa86509-2740816590a7604f-00 Data, { "msg": "Hi from helloworld-go app!" }
Note: You could use the above approach to test your applications too.
Removing the sample app deployment¶
To remove the sample app from your cluster, delete the service record:
kubectl delete --filename sample-app.yaml