【筆記】簡單玩 Knative — — 用於 Kubernetes 的 Serverless/FaaS 開源框架

Alan Wang
34 min readOct 1, 2022

Serverless(無伺服器運算)一直是雲端應用很熱門的主題之一,它的主旨在於讓使用者不必理會基礎設施跟雲端環境,就可以部署和執行自己的程式。從 Amazon Lambda 起頭的 FaaS(Function-as-a-Service)是其中的代表,讓你能用事件驅動(event-driven)的形式在有需要時觸發一個可以用多種語言撰寫的函式。也因為如此,現在 Serverless 與 FaaS 已經經常被視為是同義詞。

如果不使用 Amazon Lambda、Microsoft Azure Function 或 Google Cloud Run 這類服務,也有諸如 KnativeOpenFaaS 這類開源套件可選擇。如果不想被廠商綁死,開源會是個好選擇嗎?

在之前一篇文章中,我記錄了一些關於 Kubernetes 內如何部署容器、內部服務和 Ingress 的過程:

Knative 其實就是建構在 Kubernetes 之上,將 Deployment、Service 和 Ingress 資源簡化成 Knative Service:

Knative 的組成

Knative 其實包含兩部分:Serving(部署容器及服務)和 Eventing(事件)。Serving 可以獨立使用,部署好的東西能透過 URL 直接存取,而 Eventing 則是以 CloudEvents 格式來接收多種觸發來源、並藉此執行容器。

理論上,你可以透過在 HTTP 請求或 MQ 訊息加入額外標頭或欄位的方式來使之符合 CloudEvents 規格

其實,Knative 本身並不包括容器 CI/CD,站上的範例都是你要自己用 Dockerfile 產生映像檔、推送到 Docker Hub 然後讓 Knative 去下載。此外雖然看起來好像已經將許多 K8S 的部署過程抽象化,但為了提供各式各樣的自訂設定,仍然多出了大量的新資源型態,而且官方文件也不見得都會給予解釋。

所以想把 Knative 用在正式環境,應該還是有不小的門檻吧!這大概也是為何廠商會主打基於 Knative 的 Google Cloud Run(底層非 Kubernetes)或 Red Hat Open Shift 這類包裝過 Knative 的服務。

此外,event-driven serverless 模式比較適用於使用流量較難以預測的服務,因為重新部署和啟動容器會有冷啟動(cold start)的時間延遲。這對 Lambda 這類以使用量計費的服務來說是很合理的 — — 反正等到有流量再根據它調整就好 — — 但若你的服務需要更好的待命反應,serverless 可能就不適合你。

有些平台允許你保留最低限度的運作中容器來避免冷啟動問題,但當然也得多花錢了…

總之,本篇我們會來看如何透過官方的 Quickstart 示範環境來部署容器以及簡易的事件來源。

啟動本機示範環境

使用 Knative 最簡單的方式是搭配 Kind 在本機開一個 K8S Cluster:

  1. 下載 Knative Client 執行檔(重新命名為 kn 或 kn.exe)。
  2. 下載 Knative Kn Plugin Quickstart 執行檔(重新命名為 kn-quickstart 或 kn-quickstart.exe)。
  3. 下載 Kind 執行檔(重新命名為 kind 或 kind.exe(Windows))。

此外你當然需要裝一個容器引擎,比如 Docker。

把三者放在同一個目錄,然後在命令列執行

./kn quickstart kind

Knative 於是會透過 Kind 建立一個叫做 knative 的 cluster:

Running Knative Quickstart using Kind
✅ Checking dependencies...
Kind version is: 0.16.0
☸ Creating Kind cluster...
Creating cluster "knative" ...
• Ensuring node image (kindest/node:v1.24.3) 🖼 ...
✓ Ensuring node image (kindest/node:v1.24.3) 🖼
• Preparing nodes 📦 ...
✓ Preparing nodes 📦
• Writing configuration 📜 ...
✓ Writing configuration 📜
• Starting control-plane 🕹️ ...
✓ Starting control-plane 🕹️
• Installing CNI 🔌 ...
✓ Installing CNI 🔌
• Installing StorageClass 💾 ...
✓ Installing StorageClass 💾
• Waiting ≤ 2m0s for control-plane = Ready ⏳ ...
✓ Waiting ≤ 2m0s for control-plane = Ready ⏳
• Ready after 15s 💚
Set kubectl context to "kind-knative"
You can now use your cluster with:
kubectl cluster-info --context kind-knativeHave a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂🍿 Installing Knative Serving v1.7.1 ...
CRDs installed...
Core installed...
Finished installing Knative Serving
🕸️ Installing Kourier networking layer v1.7.0 ...
Kourier installed...
Ingress patched...
Finished installing Kourier Networking layer
🕸 Configuring Kourier for Kind...
Kourier service installed...
Domain DNS set up...
Finished configuring Kourier
🔥 Installing Knative Eventing v1.7.1 ...
CRDs installed...
Core installed...
In-memory channel installed...
Mt-channel broker installed...
Example broker installed...
Finished installing Knative Eventing
🚀 Knative install took: 3m12s
🎉 Now have some fun with Serverless and Event Driven Apps!

它也會自動安裝 DNS 服務、Kourier(Ingress 服務)跟一個 In-memory Broker,但如官方文件所說,不適合用於正式環境。

其實官網也有搭配 minikube 的範例,但吃的記憶體會更多,而且要多下一個指令開 tunnel…總之看個人喜好吧。在 Windows 上要調高 Docker 的記憶體限制,而你的 Docker 是用 WSL2 的話,得在使用者家目錄底下建一個 .wslconfig 來設定,然後重開機即可。

Serving

部署容器

我們沿用我先前文章的 echo 伺服器容器(會將傳進去的東西傳回來),用 YAML 檔建立 Knative Service 定義如下:

echo-app.yaml

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: echo
namespace: default
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
spec:
containers:
- image: ealen/echo-server:latest
ports:
- containerPort: 3000
env:
- name: TARGET
value: "echo"

除了映像檔以外,這裡也有標記 autoscaling 的類別,不過在我們的試驗中不會有什麼差別。此外下面將 port 設為 3000,看別處的說法是這會自動設定容器的 port 並對應到 K8S 服務。至於 env 則是自訂的環境變數,主要似乎是讓我們在試驗中辨識容器身分。

使用 kubectl 來套用資源:

kubectl apply -f echo-app.yaml

然後檢查 Service:

> ./kn service listNAME   URL                                      LATEST       AGE     
echo http://echo.default.127.0.0.1.sslip.io echo-00001 3m23s
CONDITIONS READY REASON
3 OK / 3 True

可以看到 echo 已經有 URL 了,你可以在瀏覽器或 HTTP client 直接存取容器!

你可以如下檢視 echo 的詳細內容:

kubectl describe ksvc echo

Autoscaling

我們可來觀察 Knative 的自動規模調度運作。首先來監看 echo 容器的狀態:

kubectl get pod -l serving.knative.dev/service=echo -w

然後在瀏覽器打開前面的連結(如 http://echo.default.127.0.0.1.sslip.io),然後隨即關掉。你會看到 Knative 在有新請求時啟動了兩個容器,並在一分多鐘後自行終止容器:

NAME             READY   STATUS              RESTARTS   AGE
echo-00001-... 0/2 Pending 0 0s
echo-00001-... 0/2 Pending 0 0s
echo-00001-... 0/2 ContainerCreating 0 0s
echo-00001-... 1/2 Running 0 1s
echo-00001-... 2/2 Running 0 1s
echo-00001-... 2/2 Terminating 0 64s
echo-00001-... 1/2 Terminating 0 90s
echo-00001-... 0/2 Terminating 0 95s
echo-00001-... 0/2 Terminating 0 95s
echo-00001-... 0/2 Terminating 0 95s

Route

我們可以進一步來建立 Route 資源修改這個路徑的部分名稱跟行為:

echo-route.yaml

apiVersion: serving.knative.dev/v1
kind: Route
metadata:
name: echo-route
namespace: default
spec:
traffic:
- configurationName: echo
latestRevision: true
percent: 100

用 kubectl 來套用資源:

kubectl apply -f echo-route.yaml

然後查看 Knative 的 Routes:

> ./kn route listNAME         URL                                            READY
echo http://echo.default.127.0.0.1.sslip.io True
echo-route http://echo-route.default.127.0.0.1.sslip.io True

你可以發現,其實最一開始的路徑就是一個 Route,而這回我們等於是新增了第二個,小改變了開頭名稱。

Route 還有很多用途,比如將流量導向特定的 Revision 版本容器,甚至在多個 Revision 之間分配流量等。在 Knative 中你也可以部署一種叫做 Configuration 的資源,跟 Service 定義長得很像(事實上看來我們前面的 Service 就會產生名為 echo 的 Configuration)。

官方的說法是 Revision 是 Configuration 的 snapshot — — 但實際上 Revision 就是不同版本的容器。反正本篇我們只會用一個版本,暫時不用擔心這麼多。

Eventing

Knative 的事件是由 Source 發出,並由 Broker 收集,再由 Trigger 監聽並轉給接收者(sink):

Broker

我們的示範環境裡已經包含一個 broker:

> ./kn broker listNAME             URL          AGE   CONDITIONS   READY   REASON
example-broker http://... 74m 6 OK / 6 True

Source

為了在這個本機示範環境發 CloudEvents 事件,官方請我們安裝另一個 Service:

example-broker.yaml

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: cloudevents-player
namespace: default
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/min-scale: "1"
spec:
containers:
- image: ruromero/cloudevents-player:latest
env:
- name: BROKER_URL
value: http://broker-ingress.knative-eventing.svc.cluster.local/default/example-broker

注意到這服務的 autoscaling 被設為最低有 1 個容器。Broker URL 環境變數是針對這個服務,讓它知道 broker 在哪(以便將事件用 HTTP POST 發過去)。這樣它發出的事件就會被內建的 example-broker 收到了。

同樣用 kubectl 部署之:

kubectl apply -f example-broker.yaml

現在 K8S 會多一個 cloudevents-player Service:

> ./kn service listNAME                 URL
cloudevents-player http://cloudevents-player.default.127.0.0.1.sslip.io
echo http://echo.default.127.0.0.1.sslip.io
LATEST AGE CONDITIONS READY REASON
cloudevents-player-00001 2m6s 3 OK / 3 True
echo-00001 63m 3 OK / 3 True

如果打開 http://cloudevents-player.default.127.0.0.1.sslip.io 會看到如下畫面:

這個畫面可以讓我們發送 CloudEvents 格式事件。但是現在發不會造成任何影響,因為我們得設定一個 Trigger 來監聽它。

Trigger

echo-trigge.yaml

apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: echo-trigger
namespace: default
annotations:
knative-eventing-injection: enabled
spec:
broker: example-broker
filter:
attributes:
type: echo-event
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: echo

這個 Trigger 會監聽 example-broker,並在事件的 type 屬性為 echo-event 時將事件轉給註冊者,也就是我們的 echo Service。

部署 Trigger:

kubectl apply -f echo-trigger.yaml

事件的流動方式會是

cloudevents-player (service, source)

example-broker (broker)

echo-trigger (trigger)

echo (service, sink)

試驗事件驅動

現在回到 cloudevents-player 服務的事件發送畫面,點 ID 旁邊的鈕產生隨機 ID,將 type 填為 echo-event,隨便打些訊息,然後發送出去:

你可能得重整才會看到發出的訊息:

點事件旁的信箱圖示來檢視 CloudEvents 內容(JSON 資料):

這時感覺好像啥也沒發生,因為 echo 被執行了之後並不會將結果傳回來(我們畢竟不是透過 HTTP 請求來執行它)。確認的辦法之一是讀 Kubernetes 的 log:

kubectl -n default logs -l serving.knative.dev/service=echo -c user-container --tail=-1

這可能會印出多筆資料,但其中可以找到內容如下:

小心等太久容器關掉以後 log 會消失!

{
"name":"echo-server",
"hostname":"echo-00001-deployment-78fb74f994-5hgql",
"pid":1,
"level":30,
"host":{
"hostname":"echo.default.svc.cluster.local",
"ip":"::ffff:127.0.0.1",
"ips":[

]
},
"http":{
"method":"POST",
"baseUrl":"",
"originalUrl":"/",
"protocol":"http"
},
"request":{
"params":{

},
"query":{

},
"cookies":{

},
"body":{
"message":"Hello! Your Knative event has arrived!"
},
"headers":{
"host":"echo.default.svc.cluster.local",
"user-agent":"Go-http-client/1.1",
"content-length":"52",
"accept-encoding":"gzip",
"ce-id":"2bf25ff7-75b3-4cce-aa40-7365c4036480",
"ce-knativearrivaltime":"2022-10-01T09:12:47.4369544Z",
"ce-source":"example-broker",
"ce-specversion":"1.0",
"ce-type":"echo-event",
"content-type":"application/json",

"forwarded":"for=10.244.0.12;proto=http",
"k-proxy-request":"activator",
"prefer":"reply",
"traceparent":"00-58d0ea0c7b51fe61144ca5d1f6aaaa9c-aed89300c9a29457-00",
"x-forwarded-for":"10.244.0.12, 10.244.0.5",
"x-forwarded-proto":"http",
"x-request-id":"d6593ffc-5e0a-4369-9ddf-5e6a7a639281"
}
},
"environment":{
"PATH":"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME":"echo-00001-deployment-78fb74f994-5hgql",
"NODE_VERSION":"16.16.0",
"YARN_VERSION":"1.22.19",
"K_REVISION":"echo-00001",
"K_CONFIGURATION":"echo",
"K_SERVICE":"echo",
"TARGET":"echo",
"PORT":"3000",
"KUBERNETES_PORT_443_TCP":"tcp://10.96.0.1:443",
"KUBERNETES_PORT_443_TCP_PROTO":"tcp",
"KUBERNETES_PORT_443_TCP_PORT":"443",
"KUBERNETES_PORT_443_TCP_ADDR":"10.96.0.1",
"KUBERNETES_SERVICE_HOST":"10.96.0.1",
"KUBERNETES_SERVICE_PORT":"443",
"KUBERNETES_SERVICE_PORT_HTTPS":"443",
"KUBERNETES_PORT":"tcp://10.96.0.1:443",
"HOME":"/root"
},
"msg":"Sat, 01 Oct 2022 09:16:16 GMT | [POST] - http://echo.default.svc.cluster.local/",
"time":"2022-10-01T09:16:16.019Z",
"v":0
}

“ce” 即 CloudEvents 縮寫。這表示 echo 容器確實收到我們發出的事件並執行了。若我們用前面的方式監看容器狀態,也會看到它在你送出事件時被啟動。

當然容器要回應 CloudEvents 事件也是可以的,但你得在自己的應用程式使用對應特定語言的 SDK 套件就是,然後要設額外的 Trigger 去轉發這些事件…

有興趣的話可參考為數還不少的官方範例(ServingEventing)。Serving 的第二個範例 Cloud Events 和 Eventing 的第一個範例 Hello World 都有展示如何從你的應用程式發送或回應事件。當然,範例裡號稱應用程式可以「回覆」事件,但會回覆到哪去筆者還要找機會測試…

這也表示你的應用程式可以當 Source,只要將事件發給 broker 即可。官方列出的兩個正式 broker 是 Apache Kafka 和 RabbitMQ。

新增一個 Webhook Source

Knative 透過 CloudEvents 可以接收非常多樣的事件來源。下面我們來安裝一個 Webhook(由 TriggerMesh 提供),讓我們可以透過 HTTP 端點觸發容器執行。

首先安裝 CRD (custom resource definitions) 和 controller:

kubectl apply -f https://github.com/triggermesh/triggermesh/releases/latest/download/triggermesh-crds.yamlkubectl apply -f https://github.com/triggermesh/triggermesh/releases/latest/download/triggermesh.yaml

接著新增 Webhook 的資源定義:

echo-webhook.yaml

apiVersion: sources.triggermesh.io/v1alpha1
kind: WebhookSource
metadata:
name: echo-api
namespace: default
spec:
eventType: echo-event
eventSource: echo-api
eventExtensionAttributes:
from:
- path
- queries
sink:
ref:
apiVersion: eventing.knative.dev/v1
kind: Broker
name: example-broker

這個 Source 也將 example-broker 設為它的接收器,使之能收到我們的事件。現在來部署它:

kubectl apply -f echo-webhook.yaml

這時如果去查 Routes(或者直接查 WebhookSource 資源),會發現多了一個路徑:

http://webhooksource-echo-api.default.127.0.0.1.sslip.io/

只要在瀏覽器或 HTTP client 輸入該網址,就同樣可以觸發 echo 容器執行:

..."ce-id":"666b631e-f154-4ea8-8f70-f1c9b121b30c",
"ce-knativearrivaltime":"2022-10-01T10:11:15.2950608Z",
"ce-source":"echo-api",
"ce-specversion":"1.0",
"ce-time":"2022-10-01T10:11:15.2946875Z",
"ce-type":"echo-event",
...

Webhook 是透過網址來夾帶參數(QueryString),上面定義檔中的 eventExtensionAttributes 欄位就是將 HTTP request 的網址跟 QueryString 加入到 CloudEvents 內,但這裡就不研究了。

Webhook 的範例裡也有 basicAuthUsername 和 basicAuthPassword 欄位,加了就會有驗證功能 — — 使用者要發送正確的帳密才能使用這個 Webhook。

刪除 Knative Cluster

實驗結束,把東西清乾淨吧:

./kind delete clusters knative

後記

其實如果不管 Eventing 的話,開源版 Knative 本身就只是稍微減化容器跟相關服務的部署,並加上 autoscaling 而已。你的程式會是一個 API 式的程序,在需要時才被呼叫。而為了能在正式環境使用,當中有太多東西(不只是權限、安全性等議題)需要微調,要裝 networking、DNS 和 broker,背後的 CI/CD 你也得自己搞,所以對開發人員來說,便利性上可能也沒有那麼有感吧。

本文有些內容我參考了 Knative in Action 這本書,但老實說作者超不愛用 YAML,實在是很沒幫助(苦笑)下面是另一些參考資料,算是稍微補充了官方文件吧。

補記:讓應用程式容器回應事件

一不做二不休,後來還是研究了一下該怎麼讓應用程式產生回應。大體上是沿用官方的 Go 語言範例,但補充了一些缺少的細節和太老舊的語法。

應用程式和映像檔

kn-app.go

package mainimport (
"context"
"fmt"
"log"
cloudevents "github.com/cloudevents/sdk-go/v2"
"github.com/kelseyhightower/envconfig"
)
type Request struct {
Message string `json:"message"`
}
type Response struct {
Message string `json:"message,omitempty"`
}
type Receiver struct {
client cloudevents.Client
Target string `envconfig:"K_SINK"`
}
func main() {
client, err := cloudevents.NewClientHTTP()
if err != nil {
log.Fatal(err.Error())
}
r := Receiver{client: client}
if err := envconfig.Process("", &r); err != nil {
log.Fatal(err.Error())
}
var receiver any
if r.Target == "" {
receiver = r.ReceiveAndReply
} else {
receiver = r.ReceiveAndSend
}
if err := client.StartReceiver(context.Background(), receiver); err != nil {
log.Fatal(err)
}
}
func newEvent() cloudevents.Event {
r := cloudevents.NewEvent(cloudevents.VersionV1)
r.SetID("42")
r.SetType("app-event")
r.SetSource("myapp")
return r
}
func handle(req Request) Response {
return Response{Message: fmt.Sprintf("Message received: %s", req.Message)}
}
func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Event) cloudevents.Result {
req := Request{}
if err := event.DataAs(&req); err != nil {
return cloudevents.NewHTTPResult(400, "failed to convert data: %s", err)
}
log.Printf("Got an event from: %q", req.Message)
resp := handle(req)
log.Printf("Sending event: %q", resp.Message)
r := newEvent()
if err := r.SetData("application/json", resp); err != nil {
return cloudevents.NewHTTPResult(500, "failed to set response data: %s", err)
}
ctx = cloudevents.ContextWithTarget(ctx, recv.Target)
return recv.client.Send(ctx, r)
}
func (recv *Receiver) ReceiveAndReply(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) {
req := Request{}
if err := event.DataAs(&req); err != nil {
return nil, cloudevents.NewHTTPResult(400, "failed to convert data: %s", err)
}
log.Printf("Got an event from: %q", req.Message)
resp := handle(req)
log.Printf("Replying with event: %q", resp.Message)
r := newEvent()
if err := r.SetData("application/json", resp); err != nil {
return nil, cloudevents.NewHTTPResult(500, "failed to set response data: %s", err)
}
return &r, nil
}

這支程式使用 CloudEvents 的 SDK 套件來發送事件,我就不解釋 Go 語言跟本機套件安裝細節了(非必要),但裡面有兩種形式:

  1. 如果容器環境變數中的 K_SINK 有值,就把它(一個路徑)當成事件接收器。
  2. 如果沒有,直接回覆事件。

等等會來解釋 K_SINK 的作用方式。至於事件中夾帶的 JSON 資料,我們都假設只有一個 message 欄位。

以下先來看如何產生和上傳映像檔到 Docker Hub:

Dockerfile

FROM golang:alpine as builder
WORKDIR /app
COPY . ./
RUN go mod download
RUN CGO_ENABLED=0 go build -mod=readonly -v -o server
FROM alpineRUN apk add --no-cache ca-certificates
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ./server

這裡使用 multi-stage builds,第一階段用 golang:alpine 容器編譯,再把執行檔拷貝到 alpine 容器,得到最終結果。

文件上忘記提的另一點是 SDK 的監聽埠是 8080…

上傳過程:

  1. 在 Docker Hub 註冊帳號後從命令列登入
  2. 編譯映像檔
  3. 上傳映像檔
docker login
docker build . -t <Docker Hub 帳號名稱>/kn-app -f Dockerfile
docker push <Docker Hub 帳號名稱>/kn-app

部署到 Knative

這回我們同樣使用 Quickstart 示範,但改成部署以下四種資源:

  1. kn-app
  2. cloudevents-player
  3. 兩個 trigger

kn-app.yaml

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: my-app
namespace: default
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
spec:
containers:
- image: <Docker Hub 帳號名稱>/kn-app:latest
ports:
- containerPort: 8080
env:
- name: K_SINK
value: "http://broker-ingress.knative-eventing.svc.cluster.local/default/example-broker"
---
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: cloudevents-player
namespace: default
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/min-scale: "1"
spec:
containers:
- image: ruromero/cloudevents-player:latest
env:
- name: BROKER_URL
value: http://broker-ingress.knative-eventing.svc.cluster.local/default/example-broker
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: my-app-trigger
namespace: default
annotations:
knative-eventing-injection: enabled
spec:
broker: example-broker
filter:
attributes:
type: my-event
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: my-app
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: my-reply-trigger
annotations:
knative-eventing-injection: enabled
spec:
broker: example-broker
filter:
attributes:
type: app-event
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: cloudevents-player

將 my-app 定義中 container 下的映象檔路徑(<Docker Hub 帳號名稱>/kn-app:latest)換成你的映像檔位址。

注意到 kn-app 有設環境變數 K_SINK,其值為 example-broker 的事件接收路徑。kn-app 內的程式會看到 K_SINK(也就是 Receiver 結構的 Target 欄位)不是空值,因此透過 ReceiveAndSend 方法來發事件。

如果 K_SINK 沒有設呢?這適用於直接用容器網址存取的狀況,你只要對它發符合 CloudEvents 格式的 HTTP 請求,就會直接得到包在 HTTP 回應內的 CloudEvents 事件。

但既然現在 kn-app 會將事件發給 example-broker,我們就需要另一個 trigger 將事件轉遞給最初的傳送者。這個新 trigger(my-reply-trigger)會過濾類型為 app-event 的事件,接收者則設為 cloudevents-player。

因此事件傳遞過程如下:

實測

部署以上資源:

kubectl apply -f kn-app.yaml

稍等片刻讓 Knative 下載你的 kn-app,然後打開 cloudevents-player 服務的畫面,發一個 type 為 “my-event” 的事件:

這回你會看到 cloudevents-player 收到了來自 kn-app 的事件(有打勾符號那筆)!點開事件內容,可以發現跟 kn-app 程式內的預期效果相同:

我們也能觀察應用程式自身的 logs:

>kubectl -n default logs -l serving.knative.dev/service=my-app -c user-container --tail=-12022/10/04 03:12:43 Got an event from: "Life, the universe and everything"
2022/10/04 03:12:43 Sending event: "Message received: Life, the universe and everything"

可惜的是前面用過的 Webhook 似乎沒辦法透過 broker 收取回應事件,不然就可以像微服務一樣呼叫應用程式了。

--

--

Alan Wang

Technical writer, former translator and IT editor.