この記事は OpenSaaS Studio Advent Calendar 2019 の 22 日目の記事です。
初めまして。サイバーエージェント OpenSaaS Studio の@satoshiyamamoto です。コンテンツモデレーションシステム Orion でデータストリーム処理の開発や自社サービスへの導入などを担当しています。Orion のデータストリームは、 Apache Kafka と Spring Cloud Stream で構成されているのですが、Kubernetes への移行がキッカケで Cloud Native なアーキテクチャに興味を持つようになりました。そこで Google Cloud の Cloud Run や Redhat の OpenShift Serverless で採用されている Knative について、バージョン v0.11 を対象に調べてみました。
Knative とは
Knative は、Google、Pivotal、およびその他の業界リーダーのエンジニアによって開始された新しいオープンソースプロジェクトです。モダンなサーバーレスワークロードをデプロイ、管理するための Kubernetes ベースのプラットフォームで、Serving、Eventing のコンポーネントを含みます。コンテナベースの管理を簡素化することで、ビジネスロジックなどの開発者が重要なことに集中できるようにします。
Knative プロジェクトの概要に触れたところで、Serving、Eventing の各コンポーンエントについて、公式ドキュメントを眺めながら Getting Started をそれぞれ試してみたいと思います。
Serving
Knative Serving は、Kubernetes と Istio 上に構築され、コンテナのアプリケーションと関数のデプロイをサポートします。Serving は簡単に開始でき、高度なシナリオをサポートするように拡張できます。
Knative Serving プロジェクトは、次のことを可能にします。
- サーバーレスコンテナのすばやい展開
- オートスケーリング
- Istio コンポーネントのルーティングとプログラム可能なネットワーキング
- デプロイされたコードと構成のバージョン管理
AWS の API Gateway + Lambda と同じことが Kubernetes 上で実現できるといった印象でしょうか。
Serving のリソース
Knative Serving は、オブジェクトのセットを Kubernetes カスタムリソース定義(CRD)として定義します。これらのオブジェクトを使用して、クラスター上のサーバーレスワークロードの動作を定義および制御します。
- Service:
- Route + Configuration = Service
- Route:
- ネットワークのエンドポイントを 1 つ以上の Revision にマップ
- Configuration:
- デプロイメントの望ましい状態を維持
- Revision:
- コードの変更と Coniguration のスナップショット
- Configuration の変更で Revision が作成される
Getting Started
Installing Knativeに従い Kubernetes のクラスタに Knative をインストールします。Configuring DNSの項を参考に xip.io の DNS で istio-ingressgateway の External IP を設定すると動作が確認しやすいと思います。
では、Serving のマニフェストファイルを作成しサンプルアプリケーションをデプロイしてみます。
# service.yaml
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: helloworld-go
namespace: default
spec:
template:
spec:
containers:
- image: gcr.io/knative-samples/helloworld-go
env:
- name: TARGET
value: OpenSaaS Studio!
---
kubectl apply --filename service.yaml
サンプルアプリケーションが正常にデプロイされたかどうか確認します。
kubectl get ksvc helloworld-go
NAME URL LATESTCREATED LATESTREADY READY REASON
helloworld-go http://helloworld-go.default.1.2.3.4.xip.io helloworld-go-96dtk helloworld-go-96dtk True
Route で作成されたエンドポイントに HTTP リクエストを送ります
curl http://helloworld-go.default.1.2.3.4.xip.io
Hello OpenSaaS Studio!
Kubernetes の標準的なリソースを使用してアプリケーションをデプロイする場合、Deployment、Service、Ingress のマニフェストが必要ですが、それ比較すると Knative Service だけと少ない記述量でアプリケーションがデプロイできました。
続いて Eventing コンポーネントを見ていきます。
Eventing
Knative Eventing は、Cloud Native 開発の一般的なニーズに対応するように設計されたシステムであり、Event source と Event consumer の遅延バインディングを有効にする基本要素を提供します。
現時点でも様々な Event source が公式から提供されています。
- KubernetesEventSource
- GitHubSource
- GcpPubSubSource
- AwsSqsSource
- ContainerSource
- CronJobSource
- KafkaSource
- CamelSource
設計の概要
Knative Eventing は、次のゴールに基づいて設計されています。
- Knative Eventing サービスは疎結合
- Event producer と Event consumer は独立している
- 他のサービスは、Knative のイベントシステムに接続できる
- サービス間の相互運用性を確保。Knative Eventing は、CNCF サーバーレス WG によって開発された CloudEvents に準拠
Consumer
複数のタイプのサービスへの配信を可能にするために、Knative Eventing は複数の Kubernetes リソースによって実装できる 2 つの汎用インターフェイスを定義します。
- Addressable なオブジェクトは、status.address.url フィールドで定義されたアドレスに HTTP 経由で配信されたイベントを受信および確認できます。
- Callable なオブジェクトは、HTTP 経由で配信されるイベントを受信し、イベントを変換して、HTTP レスポンスで 0 または 1 つの新しいイベントを返すことができます。これらの返されたイベントは、外部イベントソースからのイベントが処理されるのと同じ方法でさらに処理されます。
Broker と Trigger
v0.5 の時点で、Knative Eventing は Broker および Trigger オブジェクトを定義して、イベントのフィルタリングをより簡単にします。ブローカーは、attribute で選択できるイベントのバケットを提供します。イベントを受信し、1 つ以上の一致する Trigger によって定義された Subscriber (Service) にイベントを転送します。Trigger は、Addressable なオブジェクトに配信されるイベント attribute の filter を記述します。Trigger は、必要な数だけ作成できます。
Event registry
v0.6 の時点で、Knative Eventing は EventType オブジェクトを定義して、Consumer がさまざまな Broker から消費できるイベントのタイプを簡単に発見できるようにします。レジストリは、EventType のコレクションで構成されています。レジストリに格納されている EventType には、他の帯域外の仕組みに頼らずに、Consumer が Trigger を作成するために必要な情報が含まれています。
Event channel と Subscription
Knative Eventing は、Channel と呼ばれるイベント転送および永続化レイヤーも定義します。各 Channel は個別の Kubernetes カスタムリソースです。イベントは、Subscription を使用してサービスに配信されるか、他の Channel に転送されます。これにより、クラスター内のメッセージ配信が要件に基づいて変化するため、一部のイベントはインメモリ実装によって処理され、他のイベントは Apache Kafka または NATS Streaming を使用して永続化されます。
Eventing はリソースの数が多いので長くなりましたが、諦めずに Getting Started に挑みます。
Getting Started
Installing the Knative Eventing componentの通り Eventing コンポーネントをインストールします。
Getting Started では、event-example 名前空間を作成し Knative リソースをグループ化して整理します。
kubectl create namespace event-example
kubectl label namespace event-example knative-eventing-injection=enabled
Broker が実行されているか確認します。
kubectl --namespace event-example get Broker default
NAME READY REASON URL AGE
default True http://default-broker.event-example.svc.cluster.local 17s
2 つの Event consumer、hello-display と goodbye-display を作成します。
まずは hello-display の Deployment と Service から。
kubectl --namespace event-example apply --filename - << END
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-display
spec:
replicas: 1
selector:
matchLabels: &labels
app: hello-display
template:
metadata:
labels: *labels
spec:
containers:
- name: event-display
# Source code: https://github.com/knative/eventing-contrib/blob/release-0.6/cmd/event_display/main.go
image: gcr.io/knative-releases/github.com/knative/eventing-sources/cmd/event_display@sha256:37ace92b63fc516ad4c8331b6b3b2d84e4ab2d8ba898e387c0b6f68f0e3081c4
---
# Service pointing at the previous Deployment. This will be the target for event
# consumption.
kind: Service
apiVersion: v1
metadata:
name: hello-display
spec:
selector:
app: hello-display
ports:
- protocol: TCP
port: 80
targetPort: 8080
END
続いて goodbye-display。
kubectl --namespace event-example apply --filename - << END
apiVersion: apps/v1
kind: Deployment
metadata:
name: goodbye-display
spec:
replicas: 1
selector:
matchLabels: &labels
app: goodbye-display
template:
metadata:
labels: *labels
spec:
containers:
- name: event-display
# Source code: https://github.com/knative/eventing-contrib/blob/release-0.6/cmd/event_display/main.go
image: gcr.io/knative-releases/github.com/knative/eventing-sources/cmd/event_display@sha256:37ace92b63fc516ad4c8331b6b3b2d84e4ab2d8ba898e387c0b6f68f0e3081c4
---
# Service pointing at the previous Deployment. This will be the target for event
# consumption.
kind: Service
apiVersion: v1
metadata:
name: goodbye-display
spec:
selector:
app: goodbye-display
ports:
- protocol: TCP
port: 80
targetPort: 8080
END
デプロイされたか確認します。
kubectl --namespace event-example get deployments hello-display goodbye-display
NAME READY UP-TO-DATE AVAILABLE AGE
hello-display 1/1 1 1 14s
NAME READY UP-TO-DATE AVAILABLE AGE
goodbye-display 1/1 1 1 7s
イベントを適切な Consumer に転送するため Trigger を作成します。Trigger は、CloudEvents の Context Attributes に基づいてフィルターを指定できます。
type: greeting
のイベントを hello-display に送信する Trigger を作成します。
kubectl --namespace event-example apply --filename - << END
apiVersion: eventing.knative.dev/v1alpha1
kind: Trigger
metadata:
name: hello-display
spec:
filter:
attributes:
type: greeting
subscriber:
ref:
apiVersion: v1
kind: Service
name: hello-display
END
続いて source: sendoff
のイベントを goodbye-display に送信する Trigger を作成します。
kubectl --namespace event-example apply --filename - << END
apiVersion: eventing.knative.dev/v1alpha1
kind: Trigger
metadata:
name: goodbye-display
spec:
filter:
attributes:
source: sendoff
subscriber:
ref:
apiVersion: v1
kind: Service
name: goodbye-display
END
Trigger が作成されたか確認します
kubectl --namespace event-example get triggers
NAME READY REASON BROKER SUBSCRIBER_URI AGE
goodbye-display True default http://goodbye-display.event-example.svc.cluster.local/ 4s
hello-display True default http://hello-display.event-example.svc.cluster.local/ 3m16s
最後に Event producer の Pod を作成します。curl を使用して個々のイベントを手動で送信し、これらのイベントが正しい Consumer によってどのように受信されるかを見てみます。
kubectl --namespace event-example apply --filename - << END
apiVersion: v1
kind: Pod
metadata:
labels:
run: curl
name: curl
spec:
containers:
# This could be any image that we can SSH into and has curl.
- image: radial/busyboxplus:curl
imagePullPolicy: IfNotPresent
name: curl
resources: {}
stdin: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
tty: true
END
Broker に HTTP リクエストでイベントを作成します。
kubectl --namespace event-example attach curl -it
Defaulting container name to curl.
Use 'kubectl describe pod/ -n event-example' to see all of the containers in this pod.
If you don't see a command prompt, try pressing enter.
[ root@curl:/ ]$
さまざまな type のイベントを作成するために 3 つのリクエストを行います。
type: greeting
のイベントを作成する最初のリクエスト
curl -v "http://default-broker.event-example.svc.cluster.local" \
-X POST \
-H "Ce-Id: say-hello" \
-H "Ce-Specversion: 0.3" \
-H "Ce-Type: greeting" \
-H "Ce-Source: not-sendoff" \
-H "Content-Type: application/json" \
-d '{"msg":"Hello Knative!"}'
Broker がイベントを受信すると、hello-display がアクティブ化され、Event consumer に送信されます。イベントが受信された場合、Event producer は 202 Accepted を受け取ります。
source: sendoff
のイベントを作成する 2 番目のリクエスト
curl -v "http://default-broker.event-example.svc.cluster.local" \
-X POST \
-H "Ce-Id: say-goodbye" \
-H "Ce-Specversion: 0.3" \
-H "Ce-Type: not-greeting" \
-H "Ce-Source: sendoff" \
-H "Content-Type: application/json" \
-d '{"msg":"Goodbye Knative!"}'
good-by-display がアクティブになりました。
type: greeting
と source: sendoff
を持つイベントを作成
curl -v "http://default-broker.event-example.svc.cluster.local" \
-X POST \
-H "Ce-Id: say-hello-goodbye" \
-H "Ce-Specversion: 0.3" \
-H "Ce-Type: greeting" \
-H "Ce-Source: sendoff" \
-H "Content-Type: application/json" \
-d '{"msg":"Hello Knative! Goodbye Knative!"}'
3 つのイベントが正しく Subscriber に受信されたか確認します。
Pod のラベルを指定して hello-display
Consumer のログを見ます
kubectl --namespace event-example logs -l app=hello-display --tail=100
☁️ cloudevents.Event
Validation: valid
Context Attributes,
specversion: 0.3
type: greeting
source: sendoff
id: say-hello-goodbye
time: 2019-12-22T16:29:33.624112047Z
datacontenttype: application/json
Extensions,
knativearrivaltime: 2019-12-22T16:29:33Z
knativehistory: default-kne-trigger-kn-channel.event-example.svc.cluster.local
traceparent: 00-aa8665dc43c912ba58c8d710575a595a-8d6f71f444b08f8f-00
Data,
{
"msg": "Hello Knative! Goodbye Knative!"
}
☁️ cloudevents.Event
Validation: valid
Context Attributes,
specversion: 0.3
type: greeting
source: sendoff
id: say-hello-goodbye
time: 2019-12-22T16:29:33.624112047Z
datacontenttype: application/json
Extensions,
knativearrivaltime: 2019-12-22T16:29:33Z
knativehistory: default-kne-trigger-kn-channel.event-example.svc.cluster.local
traceparent: 00-aa8665dc43c912ba58c8d710575a595a-8d6f71f444b08f8f-00
Data,
{
"msg": "Hello Knative! Goodbye Knative!"
}
type: greeting
のイベントを受け取ったことが確認できました。同様に goodbye-display
のログも確認します。
kubectl --namespace event-example logs -l app=hello-display --tail=100
☁️ cloudevents.Event
Validation: valid
Context Attributes,
specversion: 0.3
type: not-greeting
source: sendoff
id: say-goodbye
time: 2019-12-22T16:40:36.143572583Z
datacontenttype: application/json
Extensions,
knativearrivaltime: 2019-12-22T16:40:36Z
knativehistory: default-kne-trigger-kn-channel.event-example.svc.cluster.local
traceparent: 00-12d3b7c70adcc48bc5961cb8bae7576d-20c7194c25944c0b-00
Data,
{
"msg": "Goodbye Knative!"
}
☁️ cloudevents.Event
Validation: valid
Context Attributes,
specversion: 0.3
type: greeting
source: sendoff
id: say-hello-goodbye
time: 2019-12-22T16:40:42.28921708Z
datacontenttype: application/json
Extensions,
knativearrivaltime: 2019-12-22T16:40:42Z
knativehistory: default-kne-trigger-kn-channel.event-example.svc.cluster.local
traceparent: 00-f0c07fbbd7f1f32810c2bdfec1d12ca2-b31ec2a305780987-00
Data,
{
"msg": "Hello Knative! Goodbye Knative!"
}
まとめ
Knative は、業界全体が参画するオープンソースプロジェクトでベンダーニュートラルなサーバーレス環境を Kubernetes 上に構築します。
Serving コンポーネントは、簡素なマニフェストでアプリケーションを簡単にデプロイできトラフィックベースでオートスケールできます。
Eventing コンポーネントは、Event source、Event consumer を組み合わせることで Kubernetes 上にイベントドリブンなシステムを構築できます。Consumer は、様々なプログラミング言語で Addressable または Collable なオブジェクトを実装することができます。 さらに Trigger のフィルタにより逐次発生するイベントを適切な Consumer に伝搬させることが出来ます。
最後に、本当は CronJobSource、KafkaSource に挑戦したかったのですが、Istio の知識が乏しく cluster-local-gateway の問題を解決できず、サンプルアプリケーションを動かすことができませんでした。少し残念な結果となってしまったので、これらについては冬休みの宿題にしたいと思います。
また、Knative に興味を持った方にオススメの書籍とスライドを紹介させて頂きます。
- Knative の歩き方 Kubernetes から Serverless を訪ねて 第 2 版 #技術書典
- Knative Serving のイマ @nak3 - OpenShift Meetup Tokyo #7