Knative マニュアルでインストールして試してみた（自己研鑽メモ）

Knativeは、GCP や IBMクラウド でマネージドサービスとして提供される。しかし、ここで疑問が起きる。「Kubernetesはマネージドサービスで利用することができ、Knativeは、Kubernetes上のアプリケーションであるため、なぜマネージドサービスが必要なのか？」といった疑問である。その疑問についての答えを探すために、マネージド環境ではなく、オンプレミスのKubernetesで、Knative環境を構築して、運用上の課題について探ってみた。


## Knative の概要

Knative(ケイネイティブ）は、Kubernetesで動作するIstioの上に構築された、一般にサーバーレスと呼ばれるファンクションをコールするタイプのサービスである。そして、イベントのパブ・サブのサービスにも対応する。

このKnativeは以下の特徴を有する。

* スケーラブルで、安全なステートレス・サービスを数秒で立ち上げる。
* 疎結合により、必要な部分を使用できる。
* プラグ可能なコンポーネントを使用すると、独自のロギングとモニタリング、ネットワーク、およびサービスメッシュを使用できる。
* Knativeはポータブルで、どこのKubernetes上でも実行でき、ベンダーのロックインを心配は無い。
* 開発者がよく利用する環境であり、GitOps、DockerOps、ManualOpsなどの一般的なパターンを利用できる。
* Knativeは、Django、Ruby on Rails、Springなどの一般的なツールとフレームワークで使用できる。

Knativeには、サービング(Serving)とイベンティング (Eventing)の２つがあり、以下にその特徴を説明する。

* サービング (Serving): Knativeによって、Kubernetes上でサーバーレス・コンテナを実行できる。これは、リクエストが無い場合、ポッド数としてゼロまで縮小、また、イベントがあれば自動スケーリング、およびリビジョンを追跡する。デベロッパーは、実行環境を気にすることなく、コアロジックに集中できる。
* イベンティング (Eventing): イベントのサブスクリプション（購読）、イベントのパブリッシュ（配信）、そして、イベントの管理を実行する。

参考URL:
* Knativeプロジェクトホームページ、https://knative.dev/
* ドキュメント、https://knative.dev/docs/



## Knativeサービング (Serving)

Knative Servingは、KubernetesとIstio上に構築され、サーバーレスのアプリケーションとファンクションのデプロイとサービング(提供)をおこなう。サービングは簡単に開始でき、高度なシナリオをサポートするように拡張できる。以下を可能にする基本機能を提供する。

* Knativeコンテナの素早い展開
* ポッド数の増加とゼロまでの減少の自動スケーリング
* Istioコンポーネントのルーティングとネットワークプログラミング
* デプロイされたコードと構成のポイントインタイムのスナップショット


### サービング リソースの概要

Knativeサービングは、Kubernetesカスタムリソース定義（CRD）として、オブジェクトのセットを定義する。これらのオブジェクトを使用して、クラスター上のサーバーレス・ワークロードの振舞いの定義および制御する。

* サービスのリソース`service.serving.knative.dev`は、ワークロードのライフサイクルを自動的に管理する。他のオブジェクトの作成を制御して、アプリに、ルート、構成、そして、サービスのアップデートごとに新しいリビジョンがあることを、確かにする。サービスを定義して、常に最新のリビジョンまたは固定されたリビジョンにトラフィックをルーティングできる。
* ルートのリソース`route.serving.knative.dev`は、ネットワークエンドポイントを1つ以上のリビジョンにマップする。断片的なトラフィックや名前付きルートなど、いくつかの方法でトラフィックを管理できる。
* 構成のリソース`configuration.serving.knative.dev`は、デプロイメントについて求められた状態を維持する。明確にコードと構成を分離し、Twelve-Factor Appメソドロジーに準ずる。構成を変更すると、新しいリビジョンが作成される。
* レビジョンのリソース`revision.serving.knative.dev`リソースは、ワークロードに加えられた各変更のコードと構成の特定時点のスナップショットである。リビジョンは不変オブジェクトであり、有用な限り保持される。Knative Serving Revisionsは、着信トラフィックに応じて自動的にスケールアップおよびスケールダウンする。

参考URL:
https://knative.dev/docs/serving/



## Knativeイベンティング(Eventing)

Knativeイベンティングは、クラウドネイティブ・デプロイメントの共通的なニーズに対応するために設計されたシステムである。そして、イベント源とイベント処理を遅延結合(late-binding)を有効にする構成可能な基本機能を提供する。Knativeイベンティングは、次の目標に基づいて設計されている。

* Knativeイベンティングサービスは疎結合である。これらのサービスは、さまざまなプラットフォーム（Kubernetes、VM、SaaS、またはFaaSなど）上で、および、それらを横断して独立して開発およびデプロイできる。
* イベントプロデューサーとイベントコンシューマーは独立である。プロデューサー（またはソース）は、リッスンしているアクティブなイベントコンシューマーが存在する前にイベントを生成できる。イベントコンシューマーは、プロデューサーより前に、イベントまたはイベントのクラスを受け入れることを表明できる。
* 他のサービスは、イベント・システムに接続でき、これらのサービスは、次の機能を実行できる。
    * イベントプロデューサーまたはイベントコンシューマーを変更なしに、新しいアプリケーションを作成する。
    * プロデューサーからイベントの選択と、イベントの特定サブセットを対象にする。
* サービス間の相互運用性を確保する。Knativeイベンティングは、これはCNCFサーバレスWGによって作られたCloudEventsの仕様に準拠する。

参考URL:
* https://knative.dev/docs/eventing/
* https://cloud.ibm.com/docs/containers?topic=containers-serverless-apps-knative
* https://blog.openshift.com/knative-serving-your-serverless-services/
* https://redhat-developer-demos.github.io/knative-tutorial/knative-tutorial/0.7.x/index.html



## Knative検証環境のセットアップ

Knatviveをインストールするには、前もって、HelmとIstio をインストールしなければならない。それぞれ、執筆時点での最新バージョンを利用することにした。Knativeは、Istioのサービスメッシュ上に構築されている。そのため、Kubernetesのワーカーノードのメモリを多く必要とする傾向があるため、メモリ容量に注意を払わなければならない。


## Kubernetesクラスタの環境セットアップ

Kubernetesのバージョンは、1.16を利用する。そして、さすがにメモリ１GBではメモリ不足で障害を起こすので、CPUコアとメモリを増やして、計算資源を補強しておく。

~~~
tkr@luigi:~$ git clone -b 1.16 https://github.com/takara9/vagrant-kubernetes knative
tkr@luigi:~$ cd knative
tkr@luigi:~$ vi Vagrantfile  <-- メモリとコアの数を追加　下記参照
tkr@luigi:~$ vagrant up
~~~

修正箇所は、ワーカーノード 1と2の以下の部分で、CPU 4コア、RAM 8GB を割り当てる。 当然パソコンに相応の搭載量が必要になる。

~~~
    machine.vm.provider "virtualbox" do |vbox|
      vbox.gui = false
      vbox.cpus = 4
      vbox.memory = 8192
    end
~~~

マスターノードのメモリも2GBに増強しておく。

~~~
    machine.vm.provider "virtualbox" do |vbox|
      vbox.gui = false
      vbox.cpus = 2
      vbox.memory = 2048
    end
~~~


## Helm v3 のインストール

Kubernetesクラスタが起動したら、Helmをインストールする。これには、最近 GAになったばかりの Helm v3.0を使用する。インストールは、次の手順を順番に実施する。パソコン側にHelmのクライアントのインストールを避けたい場合は、`vagrant ssh master`で、マスターノードで作業すると良い。ここではマスターノードにHelmをインストールする。

~~~
tkr@luigi:~/knative$ vagrant ssh master
<中略>

vagrant@master:~$ kubectl get nodes
NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   3m56s   v1.16.3
node1    Ready    <none>   3m20s   v1.16.3
node2    Ready    <none>   3m32s   v1.16.3
~~~

Kubernetesクラスタの起動が確認できたら、Helmをインストールする。Helmのバージョンは、まめに上がっていくので、https://github.com/helm/helm/releases/　をチェックしておき、出来るだけ最新を利用する。

~~~
vagrant@master:~$ curl https://get.helm.sh/helm-v3.0.2-linux-amd64.tar.gz  | tar zx
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 11.5M  100 11.5M    0     0  5326k      0  0:00:02  0:00:02 --:--:-- 5328k
vagrant@master:~$ sudo mv linux-amd64/helm /usr/local/bin
vagrant@master:~$ helm version
version.BuildInfo{Version:"v3.0.2", GitCommit:"19e47ee3283ae98139d98460de796c1be1e3975f", GitTreeState:"clean", GoVersion:"go1.13.5"}
vagrant@master:~$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/
"stable" has been added to your repositories
~~~

上記により、Helmのリポジトリを検索できるようになる。以下のコマンドでリストすることができる。

~~~
vagrant@master:~$ helm search repo stable
~~~

参考URL:
* Helmのインストール、https://helm.sh/docs/intro/quickstart/


## Istio の カスタム・インストール

次に、Istioをインストールするが、サービスメッシュの全ての機能を利用するわけではないので、インストールするコンポーネントを限定してKnativeに必要なものだけをインストールする。


~~~
vagrant@master:~$ export ISTIO_VERSION=1.4.2
vagrant@master:~$ curl -L https://git.io/getLatestIstio | sh -
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3015  100  3015    0     0   1758      0  0:00:01  0:00:01 --:--:-- 1111k

<中略>

Begin the Istio pre-installation verification check by running:
	 istioctl verify-install 

Need more information? Visit https://istio.io/docs/setup/kubernetes/install/ 

vagrant@master:~$ cd istio-${ISTIO_VERSION}
vagrant@master:~/istio-1.4.2$ 
~~~

下記のコマンドで、CRDの必要なYAMLだけを適用する。

~~~
vagrant@master:~/istio-1.4.2$ for i in install/kubernetes/helm/istio-init/files/crd*yaml; do kubectl apply -f $i; done
customresourcedefinition.apiextensions.k8s.io/attributemanifests.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/clusterrbacconfigs.rbac.istio.io created
<以下省略>
~~~

Istio では、envoy proxy などをオートインインジェクションするが、その機能を停止させる。

~~~
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
 name: istio-system
 labels:
   istio-injection: disabled
EOF
~~~

以下は実行例で、上記をコピペして適用する。

~~~
vagrant@master:~/istio-1.4.2$ cat <<EOF | kubectl apply -f -
> apiVersion: v1
> kind: Namespace
> metadata:
>  name: istio-system
>  labels:
>    istio-injection: disabled
> EOF
namespace/istio-system created
~~~

Helm のテンプレート機能を利用して、カスタマイズした Istioインストール用マニフェストを生成する。

~~~
helm template --namespace=istio-system \
  --set prometheus.enabled=false \
  --set mixer.enabled=false \
  --set mixer.policy.enabled=false \
  --set mixer.telemetry.enabled=false \
  `# Pilot doesn't need a sidecar.` \
  --set pilot.sidecar=false \
  --set pilot.resources.requests.memory=128Mi \
  `# Disable galley (and things requiring galley).` \
  --set galley.enabled=false \
  --set global.useMCP=false \
  `# Disable security / policy.` \
  --set security.enabled=false \
  --set global.disablePolicyChecks=true \
  `# Disable sidecar injection.` \
  --set sidecarInjectorWebhook.enabled=false \
  --set global.proxy.autoInject=disabled \
  --set global.omitSidecarInjectorConfigMap=true \
  --set gateways.istio-ingressgateway.autoscaleMin=1 \
  --set gateways.istio-ingressgateway.autoscaleMax=2 \
  `# Set pilot trace sampling to 100%` \
  --set pilot.traceSampling=100 \
  install/kubernetes/helm/istio > ./istio-lean.yaml
~~~

生成されたマニフェストをクラスタへ適用する。

~~~
kubectl apply -f istio-lean.yaml
~~~

約40秒ほどすると、Istio の ポッドが稼働状態になる。

~~~
vagrant@master:~/istio-1.4.2$ kubectl get pods --namespace istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
istio-ingressgateway-575fc997f6-rvjg8   1/1     Running   0          42s
istio-pilot-67fc88b5f6-tfh5j            1/1     Running   0          42s
~~~

次に Istio のローカル・ゲートウェイのマニフェストを生成して適用する。

~~~
helm template --namespace=istio-system \
  --set gateways.custom-gateway.autoscaleMin=1 \
  --set gateways.custom-gateway.autoscaleMax=2 \
  --set gateways.custom-gateway.cpu.targetAverageUtilization=60 \
  --set gateways.custom-gateway.labels.app='cluster-local-gateway' \
  --set gateways.custom-gateway.labels.istio='cluster-local-gateway' \
  --set gateways.custom-gateway.type='ClusterIP' \
  --set gateways.istio-ingressgateway.enabled=false \
  --set gateways.istio-egressgateway.enabled=false \
  --set gateways.istio-ilbgateway.enabled=false \
  install/kubernetes/helm/istio \
  -f install/kubernetes/helm/istio/example-values/values-istio-gateways.yaml \
  | sed -e "s/custom-gateway/cluster-local-gateway/g" -e "s/customgateway/clusterlocalgateway/g" \
  > ./istio-local-gateway.yaml
~~~

同様に生成されたマニフェストを適用する。

~~~
kubectl apply -f istio-local-gateway.yaml
~~~

ポッドを表示して、起動状態を確認する。cluster-local-gatewayのポッドが表示されるまでに少し時間がかかるので、少し待って実行すると良い。

~~~
vagrant@master:~/istio-1.4.2$ kubectl get pods --namespace istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
cluster-local-gateway-5d5bc574f-5wl8b   1/1     Running   0          16s
istio-ingressgateway-575fc997f6-rvjg8   1/1     Running   0          81s
istio-pilot-67fc88b5f6-tfh5j            1/1     Running   0          81s
~~~

以上で、Knativeのベースとなる Knative のインストールが完了した。

この時点で、ワーカーノードのメモリは、1GB 近く利用していることがわかる。

~~~
vagrant@master:~/istio-1.4.2$ kubectl top node 
NAME     CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
master   103m         5%     1257Mi          66%       
node1    64m          1%     919Mi           11%       
node2    72m          1%     965Mi           12%   
~~~


参考URL:
* Isitoのインストール、https://knative.dev/docs/install/installing-istio/



## Knative インストール

Knativeのマニフェストは、GitHubリポジトリを直接指定してインストールを実行する。本来、数秒で完了するが、このコマンドの完了が遅い場合、マスターノードのメモリが足りない恐れがあるので、`vagrant destroy -f`でクラスタを一旦消して、メモリを増強して再実行する。

~~~
kubectl apply --selector knative.dev/crd-install=true \
--filename https://github.com/knative/serving/releases/download/v0.11.0/serving.yaml \
--filename https://github.com/knative/eventing/releases/download/v0.11.0/release.yaml \
--filename https://github.com/knative/serving/releases/download/v0.11.0/monitoring.yaml
~~~

もう一度、--selector　を外して再実行する。これで競合を回避して、残りの依存部分をインストールできる。

~~~
kubectl apply --filename https://github.com/knative/serving/releases/download/v0.11.0/serving.yaml \
--filename https://github.com/knative/eventing/releases/download/v0.11.0/release.yaml \
--filename https://github.com/knative/serving/releases/download/v0.11.0/monitoring.yaml
~~~


###　起動状態確認

３つの名前空間に展開されるので、それぞれの名前空間に対して、ポッドをリストして、起動状態を確認する。

~~~
vagrant@master:~/istio-1.4.2$ kubectl get pods --namespace knative-serving
NAME                                READY   STATUS    RESTARTS   AGE
activator-579b5c7bb5-vq5f2          1/1     Running   0          4m1s
autoscaler-79bb5fd64c-mlm2g         1/1     Running   0          4m
autoscaler-hpa-cfc55cc88-z6f7l      1/1     Running   0          4m
controller-68f4c85d59-dlxvj         1/1     Running   0          4m
networking-istio-85dbb4bf6b-gsq57   1/1     Running   0          4m
webhook-66b7549ff6-2gxwf            1/1     Running   0          4m

vagrant@master:~/istio-1.4.2$ kubectl get pods --namespace knative-eventing
NAME                                   READY   STATUS    RESTARTS   AGE
eventing-controller-666b79d867-fjj2g   1/1     Running   0          3m59s
eventing-webhook-5867c98d9b-xvbmt      1/1     Running   0          3m59s
imc-controller-7c4f9945d7-bvfr4        1/1     Running   0          3m59s
imc-dispatcher-7b55b86649-h5ctx        1/1     Running   0          3m59s
sources-controller-694f8df9c4-7ftwv    1/1     Running   0          3m59s

vagrant@master:~/istio-1.4.2$ kubectl get pods --namespace knative-monitoring
NAME                                  READY   STATUS    RESTARTS   AGE
elasticsearch-logging-0               1/1     Running   0          3m59s
elasticsearch-logging-1               1/1     Running   0          71s
grafana-7cb6586756-tgzf8              1/1     Running   0          3m59s
kibana-logging-7cb6b64bff-2qg97       1/1     Running   0          3m59s
kube-state-metrics-5df8bcfdd5-w2c28   1/1     Running   0          3m59s
node-exporter-8gzfs                   2/2     Running   0          3m59s
node-exporter-ffw6x                   2/2     Running   0          3m59s
node-exporter-jppdd                   2/2     Running   0          3m59s
prometheus-system-0                   1/1     Running   0          3m59s
prometheus-system-1                   1/1     Running   0          3m59s
~~~

セットアップが完了した時点で、各ノードのメモリの使用状況は、以下のようになった。前述のポッドのリストから、Knativeのセットアップ用マニフェストを実行すると、Elastic Search, Granfana, Kibana Prometheus なども起動されるので、メモリを大量に消費することがわかる。

~~~
vagrant@master:~/istio-1.4.2$ kubectl top node
NAME     CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
master   153m         7%     1397Mi          73%       
node1    1107m        27%    2943Mi          37%       
node2    139m         3%     4035Mi          51%   
~~~

ノードのメモリが不足すると、動作障害を起こすので、ノードのメモリが不足しないように、十分な余裕を持たせる事が望ましい。


## アプリケーションのテスト

## サービング機能を利用するアプリケーション

~~~
apiVersion: serving.knative.dev/v1 # Current version of Knative
kind: Service
metadata:
  name: helloworld-go  # The name of the app
  namespace: default   # The namespace the app will use
spec:
  template:
    spec:
      containers:
        - image: gcr.io/knative-samples/helloworld-go # The URL to the image of the app
          env:
            - name: TARGET # The environment variable printed out by the sample app
              value: "Go Sample v1"
~~~

オブジェクトの生成

~~~
vagrant@master:~/istio-1.4.2$ kubectl apply --filename service.yaml
service.serving.knative.dev/helloworld-go created
~~~

実行状態確認

~~~
vagrant@master:~/istio-1.4.2$ kubectl get ksvc helloworld-go
NAME            URL                                        LATESTCREATED         LATESTREADY   READY     REASON
helloworld-go   http://helloworld-go.default.example.com   helloworld-go-4qrsx                 Unknown   RevisionMissing
~~~

起動中は、READYが Unknown

~~~
vagrant@master:~/istio-1.4.2$ kubectl get po,deploy
NAME                                                  READY   STATUS              RESTARTS   AGE
pod/helloworld-go-4qrsx-deployment-64449fffcb-smpdf   0/2     ContainerCreating   0          34s

NAME                                                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/helloworld-go-4qrsx-deployment   0/1     1            0           34s
~~~

READYが、Unknownのまま True にならなければ問題が発生している。メモリが足りているか、node1 また node2 に入って、dmesgを実行して、メモリ不足でプロセスが強制停止されていないか確認する。

Knativeサービング オブジェクトの起動完了状態では以下のように表示される。

~~~
vagrant@master:~/istio-1.4.2$ kubectl get ksvc helloworld-go
NAME            URL                                        LATESTCREATED         LATESTREADY           READY   REASON
helloworld-go   http://helloworld-go.default.example.com   helloworld-go-4qrsx   helloworld-go-4qrsx   True    
~~~

ここで、ポッドの起動を確認しておく。ゼロまでスケールするので、起動からしばらくすると、ポッドが存在しない状態になる。

~~~
vagrant@master:~/istio-1.4.2$ kubectl get po
No resources found.
~~~

マネージドサービスでは、KnativeとDNSが連携して、Knativeで公開する場合、アプリケーション名+ドメイン名として、`http://helloworld-go.default.example.com` DNS名やロードバランサーを自動設定するが、このケースでは、設定されていないので、URLにノードのIPアドレスとポート番号をセットしてアクセスする。

Istio Ingress Gateway が存在するノードのIPアドレス、ゲートウェイのポート番号を取得する。

~~~
vagrant@master:~/istio-1.4.2$ HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')
vagrant@master:~/istio-1.4.2$ PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
~~~

Istioの仮想ホストの機能を利用するので、ホスト名を付与して、ノードポートで公開するポートへアクセスする。結果として`Hello Go Sample v1!`が帰ってきたので、正常とみなせる。

~~~
vagrant@master:~/istio-1.4.2$ curl -H "Host: helloworld-go.default.example.com" -X POST http://$HOST:$PORT
Hello Go Sample v1!
~~~

ポッドが起動していることを確認する。ポッドのREADYの項が、2/2になっていることに注目。

~~~
vagrant@master:~/istio-1.4.2$ kubectl get po
NAME                                              READY   STATUS    RESTARTS   AGE
helloworld-go-4qrsx-deployment-64449fffcb-wkj6z   2/2     Running   0          23s
~~~

このポッドの中のコンテナは、kubectl describe po で確認すると、一つはユーザーのコンテナ　user-container　であり、二つめは、queue-proxy　である。

このKubernetesクラスタには、ロードバランサーと連動していないので、外部からアクセスするには、ノードポートを利用する。
また、この環境では、外部DNSサーバーと連携していないので、curlコマンドの -H　オプションにDNS名をセットしておく。

~~~
maho:k8s-knative maho$ export KUBECONFIG=`pwd`/kubeconfig/config
maho:k8s-knative maho$ kubectl get node
NAME     STATUS   ROLES    AGE   VERSION
master   Ready    master   14m   v1.14.3
node1    Ready    <none>   14m   v1.14.3
node2    Ready    <none>   14m   v1.14.3

maho:k8s-knative maho$ HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')
maho:k8s-knative maho$ PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')

maho:k8s-knative maho$ curl -H "Host: helloworld-go.default.example.com" -X POST http://$HOST:$PORT
Hello Go Sample v1!
~~~



## イベントを利用するアプリケーション

Knative Eventing のインストールされていることを確認する。次のように5つのポッドが稼働していることを確かめる。
もしも、これらのポッドが動作していない場合は、Knativeのインストールから見直して、再度インストールを実行する。

~~~
$ kubectl get pods -n knative-eventing
NAME                                   READY   STATUS    RESTARTS   AGE
eventing-controller-666b79d867-fjj2g   1/1     Running   0          21h
eventing-webhook-5867c98d9b-xvbmt      1/1     Running   0          21h
imc-controller-7c4f9945d7-bvfr4        1/1     Running   0          21h
imc-dispatcher-7b55b86649-h5ctx        1/1     Running   0          21h
sources-controller-694f8df9c4-7ftwv    1/1     Running   0          21h
~~~

### Knative Eventing リソースのセットアップ

Eventingの検証に利用するための名前空間 `event-example` を作成する。

~~~
$ kubectl create namespace event-example
namespace/event-example created
~~~

上記で作成した名前空間に、ラベルを付与して、イベント管理の対象に設定する。

~~~
$ kubectl label namespace event-example knative-eventing-injection=enabled
namespace/event-example labeled
~~~

Brokerは、イベントのプロヂューサーによって作成された全てのイベントが、正しいイベント・コンシューマに届く事を確かにする。
対象の名前空間にイベント管理対象とするラベルが設定されることで、ブローカーが生成される。このブローカーが正しく動作していることが重要だ。この検証ではデフォルトのブローカーを使用する。

ラベル設定直後は、次のように READY の項が `False` になっている。

~~~
$ kubectl --namespace event-example get Broker default
NAME      READY   REASON                  URL                                                     AGE
default   False   DeploymentUnavailable   http://default-broker.event-example.svc.cluster.local   9s
~~~

少し時間が経過すると、READY が `True` に変わり、イベントの伝達準備が整ったことが表示される。

~~~
$ kubectl --namespace event-example get Broker default
NAME      READY   REASON   URL                                                     AGE
default   True             http://default-broker.event-example.svc.cluster.local   23s
~~~

## イベント・コンシューマー (イベントの消費者) の作成

イベントを受け取るためのサービスとデプロイメントを作成する。デプロイメントから起動するポッドでは、イベントを受けて、メッセージを表示する。

~~~
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
~~~

上記を実行することで、以下の二つのオブジェクトが生成される。

~~~
deployment.apps/hello-display created
service/hello-display created
~~~

コンテナのソースコートは、前述と同じである。

~~~
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
~~~

マニフェストを適用してオブジェクトが生成する。

~~~
deployment.apps/goodbye-display created
service/goodbye-display created
~~~

上記二つのデプロイメントが起動していることを確認する。

~~~
maho:k8s-knative maho$ kubectl --namespace event-example get deployments hello-display goodbye-display
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
hello-display     1/1     1            1           104s
goodbye-display   1/1     1            1           29s
~~~

## トリガーの作成

トリガーは、イベントを受信するサービスを定義する。ブローカは、トリガーを使用して適切な受信者へイベントを転送する。
個々のトリガーは、Cloud Event コンテキスト属性に基づいて、関連するイベントを選択するフィルターを設定できる。

次は、hello-displayに転送するトリガー

~~~
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
~~~

次は、サービス goodbye-display へ転送するトリガー

~~~
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
~~~

トリガーが動作していることを確認する。 READYが`True`になっている。

~~~
maho:k8s-knative maho$ 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/   49s
hello-display     True             default   http://hello-display.event-example.svc.cluster.local/     90s
~~~

## イベントプロヂューサーの作成

イベント・プロヂューサーとして、curlコマンドが利用できるポッドを起動する。

~~~
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
~~~

そのポッドからcurlコマンドで、デフォルト・ブローカーへメッセージを送る。これにより、イベント・コンシューマーへイベントが伝達される。
次は、ポッドに対話型でcurlコマンド実行した様子である。 各種ヘッダーと共に、メッセージ `Hello Knative!` が送信する。

~~~
maho:k8s-knative maho$ kubectl --namespace event-example attach curl -it
Defaulting container name to curl.
Use 'kubectl describe pod/curl -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:/ ]$ 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!"}'
> POST / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: default-broker.event-example.svc.cluster.local
> Accept: */*
> Ce-Id: say-hello
> Ce-Specversion: 0.3
> Ce-Type: greeting
> Ce-Source: not-sendoff
> Content-Type: application/json
> Content-Length: 24
> 
< HTTP/1.1 202 Accepted
< Content-Length: 0
< Date: Sun, 15 Dec 2019 11:32:07 GMT
< 
~~~

さらに、もういとつ、`Goodbye Knative!` を送信する。

~~~
[ root@curl:/ ]$ 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!"}'
> POST / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: default-broker.event-example.svc.cluster.local
> Accept: */*
> Ce-Id: say-goodbye
> Ce-Specversion: 0.3
> Ce-Type: not-greeting
> Ce-Source: sendoff
> Content-Type: application/json
> Content-Length: 26
> 
< HTTP/1.1 202 Accepted
< Content-Length: 0
< Date: Sun, 15 Dec 2019 11:32:22 GMT
< 
~~~

もう一つ、メッセージを送る。

~~~
[ root@curl:/ ]$ 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!"}'
> POST / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: default-broker.event-example.svc.cluster.local
> Accept: */*
> Ce-Id: say-hello-goodbye
> Ce-Specversion: 0.3
> Ce-Type: greeting
> Ce-Source: sendoff
> Content-Type: application/json
> Content-Length: 41
> 
< HTTP/1.1 202 Accepted
< Content-Length: 0
< Date: Sun, 15 Dec 2019 11:32:53 GMT
< 
[ root@curl:/ ]$ exit
Session ended, resume using 'kubectl attach curl -c curl -i -t' command when the pod is running
~~~

## イベントの受信の確認

ラベル app=hello-display で選択してログを表示する。トリガーで設定されているように、`type: greeting`のメッセージだけが表示される。


~~~
maho:k8s-knative maho$ kubectl --namespace event-example logs -l app=hello-display --tail=100
☁️  cloudevents.Event
Validation: valid
Context Attributes,
  specversion: 0.3
  type: greeting
  source: not-sendoff
  id: say-hello
  time: 2019-12-15T11:32:07.027965287Z
  datacontenttype: application/json
Extensions,
  knativearrivaltime: 2019-12-15T11:32:07Z
  knativehistory: default-kne-trigger-kn-channel.event-example.svc.cluster.local
  traceparent: 00-61ee0ac97b456b370c0a05048ec8d05d-c162b1a4c3e458f3-00
Data,
  {
    "msg": "Hello Knative!"
  }
☁️  cloudevents.Event
Validation: valid
Context Attributes,
  specversion: 0.3
  type: greeting
  source: sendoff
  id: say-hello-goodbye
  time: 2019-12-15T11:32:53.413169436Z
  datacontenttype: application/json
Extensions,
  knativearrivaltime: 2019-12-15T11:32:53Z
  knativehistory: default-kne-trigger-kn-channel.event-example.svc.cluster.local
  traceparent: 00-4c619d3d1369d8760441470a75c138ed-abf973121b2fd74e-00
Data,
  {
    "msg": "Hello Knative! Goodbye Knative!"
  }
maho:k8s-knative maho$ 
~~~

`app=goodbye-display` でフィルターしてログを表示する。こちらは、トリガーに設定した`source: sendoff`の属性だけが表示される。

~~~
maho:k8s-knative maho$ kubectl --namespace event-example logs -l app=goodbye-display --tail=100
☁️  cloudevents.Event
Validation: valid
Context Attributes,
  specversion: 0.3
  type: not-greeting
  source: sendoff
  id: say-goodbye
  time: 2019-12-15T11:32:22.89815569Z
  datacontenttype: application/json
Extensions,
  knativearrivaltime: 2019-12-15T11:32:22Z
  knativehistory: default-kne-trigger-kn-channel.event-example.svc.cluster.local
  traceparent: 00-15338f6cb75cc105d4f7ff2fb3d9c25d-36ae925bef0918a1-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-15T11:32:53.413169436Z
  datacontenttype: application/json
Extensions,
  knativearrivaltime: 2019-12-15T11:32:53Z
  knativehistory: default-kne-trigger-kn-channel.event-example.svc.cluster.local
  traceparent: 00-4c619d3d1369d8760441470a75c138ed-abf973121b2fd74e-00
Data,
  {
    "msg": "Hello Knative! Goodbye Knative!"
  }
maho:k8s-knative maho$ 
~~~

Knativeイベンティング(Eventing)の動作について、箇条書きにすると、以下のようになる。

* 対象とする名前空間にラベルを設定することで、ブローカーが立ち上がる
* イベント・コンシュマーのコンテナでは、クラウド・イベントのライブラリを利用して、イベントの受信待機にする
* トリガーを作成する。トリガーにはサービス名とフィルターを設定する
* イベント・プロヂューサーから、ブローカーのアドレスへ、ヘッダー、データを POSTで送り込む
* プローカーが、トリガーの条件設定に応じて、コンシュマーへイベントを送信する


# まとめ

Knativeは、Istioを利用しており、インストールして環境を整備するだけで、結構なメモリ量を必要とする。ノードのメモリ量が不足した場合、動作障害に陥るので、ノードのメモリ監視が必須となる。そして、DNS名によるアクセスする必要があり、外部DNSサーバーと連動とロードバランサーとの連携がなくてはならない。

Knativeは、サーバーレスの手軽な実行環境をイメージしやすいが、Kubernetesクラスタに、自前でインストールして動作させるのは、いくつもの考慮点が必要となり、デベロッパーが開発に集中して利用するだけの環境ではない事がわかる。そのため、オンプレミスやクラウドで、独自にイントールして運用する場合は、共通インフラサービスとしてサポートを含めてSREチームが運用する必要があると考えられる。手軽に利用したいのであれば、パブリック・クラウドサービスのマネージド Knative を利用するのが得策と考えられる。