「Knativeで実現するKubernetes上のサーバーレスアーキテクチャ」の文字起こし #CNTD2019

# 概要

* CloudNative Days2019でお話した「Knativeで実現するKubernetes上のサーバーレスアーキテクチャ」の文字起こしです
* スライド単体だと情報量が足りなかったりリンクをたどりづらかったりするため、より多くの人にKnativeを知ってもらうために記事にしようと考えました

<img width="515" alt="1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/9a31a0e5-89b5-2ced-57ab-4858c362f3b6.png">

# 自己紹介

<img width="515" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/3830de9d-fcb9-e8e8-cf68-8224517d7796.png">

[@toshi0607](https://twitter.com/toshi0607)というTwitterアカウントで発信しています。このセッションに限らずなにかあれば気軽にメンションしてください。

普段はメルペイというメルカリの決済システムを開発している会社でGo、gRPC、GKEのマイクロサービスのバックエンドを開発しています。

開発体制などはこの資料が詳しいです。

[メルペイのマイクロサービスの構築と運用](https://speakerdeck.com/tjun/cloudnative-days-tokyo2019)

僕はメルペイに入るまでKubernetesを触ったり、個人検証用以外にインフラのコード定義などをしたことがありませんでした。メルペイでは各マイクロサービスを担当するエンジニアがアプリケーションだけでなく、CI/CDのパイプラインの設定やKubernetesのmanifestを書いたり、Datadogで監視設定をしたりしながら運用しています。

僕はその過程でKubernetesをキャッチアップし、もともと興味のあったサーバーレスとの交点としてのKnativeに出会いました。

今回お話するKnativeやAWS Lambdaについての本を書きました。

* [Knativeの歩き方 KubernetesからServerlessを訪ねて](https://toshi0607.booth.pm/items/1309468)
* [Goで学ぶAWS Lambda](https://toshi0607.booth.pm/items/1034858)

# Knativeの概要

## Knativeとは

<img width="516" alt="3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/20583a5d-fb88-936d-8ed1-8e9c2553ed18.png">

[GitHubのKnativeのリポジトリ](https://github.com/knative)ではつぎのように説明されています。

「モダンなサーバーレスワークロードをビルド、デプロイ、管理するためのKubernetesベースのプラットフォーム」

<img width="516" alt="4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/cf0f8984-bad5-38cc-3919-191eafa18cb5.png">

[ドキュメント](https://github.com/knative/docs/tree/master/docs)にはより詳細な情報が書かれています。

Kubernetesのリソースを抽象化する。開発者や運用者から見てよりシンプルにKubernetesを扱うことができます。

独自のPaaS/FaaSを構築するためのパーツを提供。Knative自体がPaaSやFaaSなのではありません。Serving、Build、Eventingというコンポーネントを組み合わせてプラットフォームを構築します。

よくあるが難しい課題を解決。コンテナベースのアプリケーションを開発する上で直面するつぎのような課題を解決します。

* コンテナのデプロイ
* ソースコードからURLでアクセスできるアプリケーションへ
* ブルー/グリーンデプロイを伴うルーティングとトラフィック管理
* オートスケーリングと需要に基づくワークロードのサイズ設定
* 実行中のサービスをイベントエコシステムに結び付ける


## Knativeの構成要素

<img width="516" alt="5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/d11ba2fd-e0ae-79f8-70ce-1b52548b1f54.png">

ベースとなるプラットフォームはKnativeリポジトリの説明にも書かれていたとおりKubernetesです。

その上にGatewayの役割を果たすコンポーネントとしてIstioかGlooを乗せ、Knativeはそれらの上に乘るコンポーネント群です。

## Servingの役割

<img width="518" alt="6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/77a0f8b8-387e-f807-e975-473f144382fa.png">

コンテナの迅速なデプロイ、オートスケールイン・アウト、トラフィック管理、コードと設定のバージョン管理を担当します。

## Servingの構成要素

<img width="516" alt="7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/aade917c-a6c0-9bcb-dba5-307f2215b8fe.png">

Servingは主に4種類のカスタムリソースで構成されます。

Revisionはコード（どのDockerイメージをデプロイするか）と設定（環境変数に何をセットするかなど）の履歴を保持します。

Configurtationは最新のRevision情報を保持します。Configurtation経由でRevisionを管理しますが、Revisionはイミュータブルに管理されるため、Configurtationを変更すると新たなRevisionが作成されます。

RouteはどのRevisionにトラフィックを流すかを設定します。Revision Aに90%、Revision Bに10%のような設定ができます。

ServiceはRouteとConfigurtationを管理します。KubernetesにもServiceリソースがありますがそれとは別でKnative固有のものです。ConfigurtationやRouteを個別に作成・設定することもできますが、Serviceを作成するとConfigurtationと最新のRevisionに100%のトラフィックを流すRouteを作成・設定できます。

<img width="518" alt="7.5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/ceae4810-d383-bd6e-f69b-e24cf2ffb18a.png">

オートスケールイン・アウトはAutoscalerとActivatorというコンテナによって実現されています。

[KubernetesのServiceとDeployment](https://github.com/knative/serving/tree/master/config)により管理され、`knative-serving` Namespaceにデプロイされます。実装も[Servingのリポジトリ](https://github.com/knative/serving/tree/master/pkg)内にあります。

AutoscalerはPodの並列リクエスト数を監視（PodのSidecarとしてqueue-proxyコンテナがメトリクスを送信）し、Pod数を調整します。

Pod数が0のときはRouteはReserve Statsになり、Activatorがリクエストを受けます。リクエストがまたき始めるとRouteをActive Stateに更新し、Revision管理下のPodがリクエストを受けられるようにします。

## Istioは必須？

<img width="517" alt="8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/b6df3f01-f7e2-ee5b-f787-5e320c34e1e8.png">

これまで何度かIstioが登場していますが、現時点ではKnativeの全機能を利用するには導入必須です。

Gatewayの代替コンポーネントとして[solo.ioのGloo](https://gloo.solo.io/)がありますが、[ドキュメント](https://github.com/knative/docs/blob/master/docs/install/README.md#installing-knative-with-gloo)上ではEventingの機能がIstioに依存しているためEventingをサポートしていないとしています。

※登壇後に以下のご指摘をいただきました。

* Gatewayコンポーネントとして他にも[Ambassador](https://www.getambassador.io/user-guide/knative/)がある
* Istioの依存も[もうない](https://github.com/knative/eventing/issues/294)のでは

AmbassadorはKnativeの[インストールドキュメント](https://github.com/knative/docs/blob/master/docs/install/Knative-custom-install.md#installing-istio)にもすでに反映されていました。

[Ambassadorのドキュメント](https://www.getambassador.io/user-guide/knative/)にはつぎのように書いてあるもののEventingについてどう扱うか明示されてはいません。（動くかどうかも試してません）

> We will be focusing on Knative Serving which builds on Kubernetes to support deploying and serving of serverless applications and functions.

一方GlooではEventing対応が済んでいそうでした。

[support knative-eventing]([support knative-eventing](https://github.com/solo-io/gloo/issues/753))

EventingのIstio依存解消の[最後のPR](https://github.com/knative/eventing/pull/1119)は2019/5/2時点でマージされv0.6では反映されていそうでした。


ご指摘ありがとうございます！

## Buildの役割と構成要素

<img width="515" alt="9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/ea46349f-5af0-9720-2823-199170156c07.png">

Buildはソースコードをコンテナイメージに変換します。

ビルドパイプラインは複数のstepで構成され、各stepはコンテナとしてクラスタ上で実行されます。

BuildTemplateを利用するとテンプレート化・再利用できます。

<img width="517" alt="10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/d38ed934-0dcf-a7d6-cf0b-6c7fcb0f9afe.png">

BuildTemplateはkaniko、buildpacks、jibなど作成済みのものが[GitHub](https://github.com/knative/build-templates)上で公開されています。


## BuildとTekton

<img width="517" alt="11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/6981f7ea-f956-6f37-6edb-c1cd3e8f5a84.png">

現在BuidはTektonへの[移行議論が進んで](https://github.com/knative/build/issues/614)います。[tektoncd/pipeline](https://github.com/tektoncd/pipeline)はknative/buildの強化プロジェクトとしてKnativeのリポジトリに存在していました。（knative/build-pipeline）

Knative v0.7ではServingのServiceのオプション機能でBuildに依存したものがありましたが、それも廃止されています。

BuildのREADMEでもつぎのように記載されています。

```
🚨NOTE: There is an open proposal to deprecate this component in favor of Tekton Pipelines. If you are a new user, consider using Tekton Pipelines, or another tool, to build and release. If you use Knative Build today, please give feedback on the deprecation proposal.
```

<img width="515" alt="11.5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/4c6f662e-1f9d-90eb-19cb-695c1fea7199.png">

Tekton側でも移行ドキュメントが用意されています。

[Migrating from Knative Build](https://github.com/tektoncd/pipeline/blob/master/docs/migrating-from-knative-build.md)


<img width="516" alt="12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/5dd85419-527f-46a4-90bc-fefe81530fb7.png">

KnativeのBuild APIを利用していたCLIの中には[すでにTektonに移行](https://github.com/triggermesh/tm/releases/tag/v0.0.12)したものもあります。

## Eventingの役割

<img width="518" alt="13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/06d8d175-9b08-2c0d-83a1-6a55395a05a3.png">

イベントドリブンなアーキテクチャを実現します。

CNCFの[CloudEvents](https://github.com/cloudevents/spec/blob/master/spec.md)に準拠しています。

資料上は[CNCF Serverless WG](https://github.com/cncf/wg-serverless)が決めたことになってますが別ですね…

## Eventingの構成要素

<img width="518" alt="14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/2df760d3-c146-a6ca-99fb-afbd17279b2d.png">

Sourceは文字通りイベントの発行元です。Sourceというコンポーネントがあるわけではなく、AWS SQSやCloud Pub/Subごとにカスタムリソースが存在します。

Brokerはイベントソースが発行したイベントを受け取り、Serviceに送ります。そのときフィルタリングする役割を果たすのがTriggerです。

<img width="517" alt="15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/4c4d8262-790b-e9e7-1665-1eb343dc0b4a.png">

つぎの図がREADMEにまだ残っていますが、ChannelとSubscriptionは内部実装となり直接設定することはv0.4からなくなりました。

特にEventingはこれまでも大きな設計変更があったため、情報を追うときはバージョンを意識するとよさそうです。

## Event Source

<img width="517" alt="16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/98899a42-ccf1-4376-31ba-d8164be5bbb6.png">

スライド左にあるようにGitHub、Apache Kafkaなどすでにたくさんあります。ContainerSourceとして自分で実装することもできます。

[作成のためのチュートリアル](https://github.com/knative/docs/tree/master/docs/eventing/samples/writing-a-source)もあります。

# KubernetesとKnative

## Kubernetesとは？

<img width="516" alt="17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/4113b534-92bf-eeb5-51c7-10c94d9af501.png">

Kubernetesはコンテナアプリケーションのデプロイ、スケール、管理を自動化するためのOSSです。

以後この資料ではKubernetesを原則K8sと表記します。

## Kubernetesのコンセプト

<img width="517" alt="18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/4a893516-55a3-5d1f-5b5f-5aba429b104e.png">

Kubernetesは「あるべきリソースの状態」を宣言的に定義した設定を適用するとリソースを監視し、差分が生じた「あるべきリソースの状態」になるよう調整します。

これにより、故障時の復旧したり、高負荷時にオートスケールしたりして運用負荷を低減できます。

## Kubernetesのアーキテクチャ

<img width="517" alt="19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/6483bcdf-fb43-2ac9-d233-1d91eb6b317b.png">

kubectlでkube-apiserverにリクエストし、リソースをetcdに保存します。それをkube-controller-managerが監視し、各リソースのコントローラーが調整を行います。

Podリソースが登録されるとkube-schedulerがノードへのアサインを行い、該当ノード内のkubeletがコンテナイメージをプル・実行します。

## カスタムリソース

<img width="516" alt="20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/48267ff3-773b-5914-8388-13ba51787ead.png">

Deployment、ReplicaSet、Podのようなリソースの他にもユーザーが独自にリソースを登録することができます。

その方法の1つであるCustomResourceDefinitionではリソースの登録時のバリデーションやその他メタ情報を定義します。

CustomResourceDefinitionを登録するとカスタムリソースをCRUD操作することができるようになります。

## カスタムコントローラー

<img width="518" alt="21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/f90d7bf4-4ff2-2773-d18c-43577acde331.png">

理想状態と現在の状態の差分を調整するコントローラーもまたユーザーが独自に追加できます。

Deployment、ReplicaSet、Podのような既存のリソースやユーザーが追加したカスタムリソースの監視し、調整するように実装できます。

カスタムコントローラーも他のアプリケーションと同じようにDeploymentでK8sクラスタ上にデプロイします。

## Kuberentes nativeなKnative

<img width="517" alt="22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/45c20c92-c364-ec85-1a8d-706b53a24f42.png">

Knativeもカスタムリソースとカスタムコントローラーで構成されています。

Knativeはスライド青枠のようなCRDやカスタムコントローラーのDeploymentなど必要なコンポーネントのyamlをkubectl applyすることでK8sにインストールすることができます。

KnativeはK8s nativeな方法でK8sを拡張していると言えます。

# Kubernetesとサーバーレス

## サーバーレスとは

<img width="517" alt="23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/0d8c0a12-f1f5-30e4-22ae-92a03c17779a.png">

[martinFowler.com](https://martinfowler.com/articles/serverless.html)ではつぎのように定義されています。

サーバーレスアーキテクチャは、サードパーティのBaaSサービスを組み込んだ、またはFaaSプラットフォーム上の管理された一時的なコンテナで実行されるカスタムコードを含むアプリケーション設計。

ベンダー依存と比較的未成熟なサービスサポートと引き換えに運用コスト、複雑性、エンジニアリングリードタイムを低減する。

<img width="518" alt="24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/fd46aee0-4643-3090-b11b-9e6c1d3df857.png">

[Serverless is a Doctrine, not a Technology](https://medium.com/@PaulDJohnston/serverless-is-a-doctrine-not-a-technology-4193ccb66cfc)と定義する人もいます。（[全訳](https://qiita.com/toshi0607/items/42940d25ae77d9023302)）

（FaaSなどの）サービス自体はサーバーレスではない。どのように構築されるかのアプローチがサーバーレスであり、サーバーレスアプリケーションを生み出す。

サーバーレスアプリケーションは、アプリケーションのライフサイクル全体にわたって最大のビジネス価値を提供するものであり、データストレージの費用を除いて、誰も使用していないときに実行する必要はないというものです。

<img width="517" alt="25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/ec6c0fa0-f94a-65f2-d60a-61d315d9287c.png">

[CNCF Serverless WG](https://github.com/cncf/wg-serverless)の[CNCF Serverless Whitepaper v1.0](https://github.com/cncf/wg-serverless/tree/master/whitepapers/serverless-overview)にはつぎのように書かれています。

* サーバー管理を必要としないアプリケーションを構築および実行するという概念
* 必要とされている正確な需要に応じて実行、拡張、請求を行うデプロイモデル
サーバーレスコンピューティングの利用者は、サーバーのプロビジョニング、メンテナンス、アップデート、拡張、キャパシティプランニングに時間とリソースを費やす必要がなくなる
* 開発者がよりアプリケーションのビジネスロジック開発に集中できるようになる
* 運用者がよりビジネスクリティカルなタスクに集中できる

サーバーレス、サーバーレスコンピューティング、サーバーレスアーキテクチャなど議論していることがそもそも異なっているので直接的な比較はできないかもしれませんが、主要な構成要素や目的は読み取れそうです。

## Kubernetesとサーバーレス

<img width="517" alt="26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/723914d1-2786-be25-7675-06c8c8d5ce38.png">

KnativeのServingのリポジトリにはKnativeがサーバーレスをどう捉えているかに関するドキュメントがあります。

Knativeは

* ステートレス
* プロセススケールアウトモデル
* アプリケーションレベルのリクエストトラフィック駆動
 
の3点をサーバーレスワークロードと捉えています。

それらのうち、K8s自体がすでに基本的なパーツを提供しているとしています。

その上で

共通インフラの自動化を一層推進するための抽象度の高いパーツを標準化することで「yamlをkubectlで更新」より使いやすいツールキットを提供できるはず

というのがKnativeがK8s上でサーバーレスを実現するモチベーションです。

<img width="516" alt="27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/88dfcdd7-c779-7c30-392b-7ed18bfc4cb9.png">

より具体的な課題に対するアプローチはKnativeとは？などで述べたとおりですが、それらを通じて

`開発者・運用者がよりビジネス価値に集中できるプラットフォームを構築する`

のがKnativeです。

## Knativeによるプラットフォームの拡張

<img width="515" alt="28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/2d7deb4a-262c-a493-d5ab-0dd4c33a9f1e.png">

すでにKnativeを使って構築されたプロダクトがあります。

これらを利用してもいいですし、Knativeのコンポーネントを活用してプラットフォームを構築していくこともできます。

ここからはどのようにしてKnativeを使えばいいのかを解決したい課題と合わせて見ていきます。

# Knativeのユースケース

Demoは当日コマンドを叩きながらやっていきましたが、この記事ではセットアップ含めより試しやすいハンズオンのリンクを貼ることで代用します。

当日たたいたコマンドもこのリポジトリに残しています。

[toshi0607/CNDT2019-Knative](https://github.com/toshi0607/CNDT2019-Knative)

## ユースケース①　コンテナのデプロイ

[Deploying a container](https://github.com/knative/docs/blob/master/docs/install/getting-started-knative-app.md)

<img width="517" alt="29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/d372e397-14d3-3110-36cf-0d47c718037c.png">


KnativeのServiceを登録するとConfigurationと最新のRevisionにトラフィックを流すRouteが作成されます。

このとき、RevisionはDeploymentとK8sのServiceを管理しています。図のようなイメージです。

K8sの従来のリソースを直接作成するよりも抽象化されています。

<img width="518" alt="30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/b088deb6-959b-1d81-bbe9-61a9d067cd27.png">

より厳密な比較はこの記事がオススメです。

[Migrating from Kubernetes Deployment to Knative Serving](https://medium.com/google-cloud/migrating-from-kubernetes-deployment-to-knative-serving-bdc45ef1bb9e)

Demoではつぎのようなmanifestをapplyしました。

```yaml
apiVersion: serving.knative.dev/v1alpha1 # 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"
```

最終的にはPodを起動させるので、ReplicaSetやDeploymentで定義するするようなPodのテンプレートを記述します。

## ユースケース②　オートスケール

[Scaling automatically and sizing workloads based on demand
](https://github.com/knative/docs/blob/master/docs/serving/configuring-the-autoscaler.md)

<img width="517" alt="31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/5611d1e3-1832-9c2f-13a6-d2306ce43162.png">

### メトリクス、ログ、トレース

メトリクス、ログ、トレースの選択肢とインストール方法はこちらのページにまとまっています。

[Installing logging, metrics and traces](https://github.com/knative/docs/tree/master/docs)

### スケール

スケール方法はKnative Pod Autoscaler（KPA）とK8のHorizontal Pod Autoscaler（HPA）が選択できます。

DemoではKPAを利用したmanifestをapplyしましたが、コメントアウトした部分にHPAを利用したい場合はどう設定するのかを書いています。

```yaml
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
  name: autoscale-go
  namespace: default
spec:
  template:
    metadata:
      annotations:
        # KPA
        # Knative concurrency-based autoscaling (default).
        # autoscaling.knative.dev/class: kpa.autoscaling.knative.dev
        # autoscaling.knative.dev/metric: concurrency
        
        # Target 10 in-flight-requests per pod.
        autoscaling.knative.dev/target: "10"

        # HPA
        # Standard Kubernetes CPU-based autoscaling.
        # autoscaling.knative.dev/class: hpa.autoscaling.knative.dev
        # autoscaling.knative.dev/metric: cpu
    spec:
      containers:
      - image: gcr.io/knative-samples/autoscale-go:0.1
```

KPAの各閾値は`ConfigMap`として変更できます。

インストール時に適用されるyamlは[こちら](https://github.com/knative/serving/blob/master/config/config-autoscaler.yaml)です。あくまでもexampleです。

コメント以外の部分を抜き出すとこうなります。

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: config-autoscaler
  namespace: knative-serving
  labels:
    serving.knative.dev/release: devel
data:
  _example: |
    container-concurrency-target-percentage: "70"
    container-concurrency-target-default: "100"
    target-burst-capacity: "0"
    stable-window: "60s"
    panic-window-percentage: "10.0"
    panic-window: "6s"
    panic-threshold-percentage: "200.0"
    max-scale-up-rate: "1000.0"
    enable-scale-to-zero: "true"
    tick-interval: "2s"
    scale-to-zero-grace-period: "30s"
```

上でServiceのannotationで設定したコンテナあたりの並列リクエスト数をはじめ、Activatorを0 Podでなくても動かしたり、スケールアウトのリミットなどいろいろと設定できます。

利用する設定をdata直下に書いて有効化します。

<img width="518" alt="32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/52574799-1d01-77fc-c302-8b42acb0a756.png">

## ユースケース③　トラフィック分割

[Routing and managing traffic with blue/green deployment](https://github.com/knative/docs/blob/master/docs/serving/samples/blue-green-deployment.md)

あるRevision Aに100%のトラフィックが流れているときに、Revision Bにテスト用のエンドポイントを払い出せるのがいい感じです。（Named Route）

<img width="515" alt="33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/36fb00c2-b132-2f4c-0bed-3c4f252ccbb4.png">

## ユースケース④　イベントエコシステム

[gcp-pubsub-source sample](https://github.com/knative/docs/tree/master/docs/eventing/samples/gcp-pubsub-source)

<img width="516" alt="34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/799115ce-9aee-b621-ca52-0d7240364801.png">

knative/evenringはCloudEventsとの互換性を謳っています。

そうするとServiceがイベントはハンドリングする際も各言語の[CloudEventsのSDK](https://github.com/cloudevents/sdk-go)を利用することができます。

knative/eventing-contribではcloudevents/sdk-goを利用した[デフォルトクライアント](https://github.com/knative/eventing-contrib/blob/master/pkg/kncloudevents/good_client.go)も実装されています。

<img width="514" alt="35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/881651e8-e68e-a1ab-876d-1fefc036d2c0.png">

## ユースケース⑤　ビルドパイプライン

[KnativeのサンプルをTektonでデプロイするサンプル](https://github.com/toshi0607/CNDT2019-Knative/tree/master/usecase5)

<img width="517" alt="36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/2a2a9360-a4a7-d4b7-f962-f2e111bb1985.png">

`Task`は「Dockerイメージをビルドする」「K8s上にデプロイ」するのような単位で、stepで構成されます。

`PipelineResource`のinputにはgitリポジトリ、outputにはコンテナレジストリなどを指定します。

`Pipeline`は`Task`を組み合わせて構成し、ここまでがテンプレートです。

`PipelineRun`に`Pipeline`や構成要素である`Task`に渡す具体的なパラメタを指定し、実行します。

<img width="516" alt="37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/54545f66-d6ab-7cb7-1b4f-e6b291900dc0.png">

## ユースケース⑥　FaaS　イベントpull型

knative/serviceのServiceで指定するのはコンテナで、コンテナで動かすアプリケーションはサーバーであることが多いと思います。

そのため、functionだけを書いてK8s上にデプロイできる状態にしたい場合、受け取ったリクエスト（イベントを含む）をfunctionに渡すための処理を毎回書くか、開発者がfunctionだけを書けばデプロイできるようにするための機構を事前に準備しておく必要があります。

ユースケース⑥では開発者はAWS Lambda互換のfunctionを書けばそれをK8s上にデプロイできるという[triggermesh/knative-lambda-runtimeのKnative](https://github.com/triggermesh/knative-lambda-runtime)活用プロダクトを紹介します。

<img width="516" alt="38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/b1c96262-dec4-cfc0-44b5-183671ec0056.png">

### AWS Lambdaのfunction（Go）

 開発者が書くfunctionを見てみましょう。

 ```go
 package main

import (
	"fmt"
	"context"
	"github.com/aws/aws-lambda-go/lambda"
)

type MyEvent struct {
	Name string `json:"name"`
}

func HandleRequest(ctx context.Context, name MyEvent) (string, error) {
	return fmt.Sprintf("Hello %s!", name.Name ), nil
}

func main() {
	lambda.Start(HandleRequest)
}
 ```

[GoのAWS Lambda 関数ハンドラー](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-programming-model-handler-types.html)より

これは `{"name": "toshi0607"}`のようなイベントが送られてくると`Hello toshi0607`を出力するfunctionです。

`HandleRequest`がイベントのハンドラーです。イベントを引数にそれをハンドリングするメインの処理を書きます。

`func main()`のに書いてある`lambda.Start(HandleRequest)`では主に2つの処理を実行します。

* AWS Lambdaの実行環境で実行されるコンテナ内で立ち上げるrpcサーバーへイベントハンドラーを登録
* rpcサーバーの起動

rpcサーバー周りの実装は[aws/aws-sdk-go](https://github.com/aws/aws-sdk-go)として再利用できます。

そのため、開発者はSDKを利用してfunction（イベントハンドラー）を記述してビルド、アップロードするだけでクラウド上でfunctionを実行できます。


### AWS LambdaとRuntime Interface

<img width="518" alt="39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/abc8fb5a-900e-4122-73c1-7bc3bb6e0b9c.png">

図にするとこうなります。

イベントを受けたAWS Lambdaの実行環境がrpcサーバーを含むコンテナを起動し、イベントをもとにrpcサーバーにリクエストします。

この一連のイベントハンドリングフローを逆転させたのがre:Invent2018で発表された[Custom AWS Lambda Runtimes](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-custom.html)です。

それまではAWS Lambdaの実行環境がイベントをrpcサーバーに渡していましたが、AWS Lambdaの実行環境からイベントを取得するためのAPIが提供され始めたことで、開発者が書いたコードからイベントを取得しに行けるようになりました。

シェルスクリプトやCOBOLなどでfunctionが書ける、というのは単純化するとAWS Lambda Runtime Interfaceを実装するAWS Lambdaの実行環境が提供するAPIにHTTPリクエストできるプログラムであれば動かせるからです。

別途記事を書きましたので詳細はそちらをご参照ください。

* [GoとRustで学ぶCustom AWS Lambda Runtimes 〜入門編〜](https://qiita.com/toshi0607/items/39004c19d7361a837986)
* [GoとRustで学ぶCustom AWS Lambda Runtimes 〜よりLambdaらしいCustom Runtime実装編〜](https://qiita.com/toshi0607/items/32aaeaa49e659698955a)

図にするとつぎのような感じになります。

<img width="518" alt="40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/66419263-ec1d-67f5-0ebf-970d9aeb1de9.png">

### knative-lambda-runtime

knative-lambda-runtimeはここまでに紹介した通常のLambdaのハンドラーとAWS Lambda Runtime Interface（を満たすAPIを独自実装したもの）を組み合わせて実現しています。

Revisionの配下で管理するのは複数のコンテナから構成されるPodです。

「通常のLambdaのハンドラー」を書いてデプロイするだけで実行される状態にするには先ほどの例のようにrpcサーバーを起動し、イベントを渡す部分が必要です。

<img width="517" alt="41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/751934c2-af8f-ee3b-54b1-74b31b57121e.png">

この図をイメージしながら追っていきましょう、

### triggermesh/aws-custom-runtime

リクエストを受けるエンドポイントとなるサーバーです。

主な処理はこんな感じです。

* 受けたリクエスト（イベント）をmessage型のtaskとしてtasks（message型のチャネル）に保持する
* `exec.Command`でつぎのセクションのbootstrap（functionのinvoker）を複数goroutineで起動
* AWS Lambda Runtime Interfaceを満たすAPIを実装し、bootstrapからイベント取得などのリクエストを受ける

少量のコードを読めば一目瞭然です。
https://github.com/triggermesh/aws-custom-runtime/blob/master/main.go#L258

bootstrapからイベント取得のリクエストを受けるとtasksからtaskを取り出して返します。

https://github.com/triggermesh/aws-custom-runtime/blob/master/main.go#L133

イベント取得の場合リクエストパスは

`http://127.0.0.1/2018-06-01/runtime/invocation/next`です。

```go
awsEndpoint = "/2018-06-01/runtime"
apiRouter.HandleFunc(awsEndpoint+"/invocation/next", getTask)
```

getTaskにマッピングされているので、イベントをtasksから取り出して返す実装はここを見ればわかります。

https://github.com/triggermesh/aws-custom-runtime/blob/master//main.go#L133

### bootstrap

bootstrapはイベントを上でのセクションで見たaws-custom-runtimeからAPI経由で取得し、AWS Lambda互換のfunctionにリクエストして結果をaws-custom-runtimeに返します。

主な処理はこんな感じです。

* `exec.Command`でhandler（を含むrpcサーバー）を起動する
* `aws-custom-runtime`の`invocation/next`にリクエストしてイベントを取得する
* ハンドラーにリクエストして`aws-custom-runtime`の`invocation/[requestID]`に結果を返す

https://github.com/triggermesh/knative-lambda-runtime/blob/master/go-1.x/bootstrap.go#L40

AWS Lambdaのfunctionが言語毎にSDKを用意し、それぞれイベントをhandlerに渡す機構を準備しているので、knative-lambda-runtimeのbootstrapも言語毎に実装が用意されています。

## BuildTemplateで生成するDockerfile

これまでknative-lambda-runtimeの構成要素として

* aws-custom-runtime
* bootstrap
* AWS Lambdaのfunction（開発者が書くもの）

を見てきました。これらは1つのDockerイメージに同梱してPodとして自分のクラスターにデプロイする必要があります。

それをサポートするためにBuildTemplateを利用しています。

<img width="517" alt="42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/a0f4b154-8cbc-e4a1-966c-cfbf7f7fd1be.png">

https://github.com/triggermesh/knative-lambda-runtime/blob/master/go-1.x/buildtemplate.yaml

最終的にはつぎのDockerfileからイメージをbuild、pushします。

aws-custom-runtimeがエントリーポイントとなっていて、handlerとbootstrapを特定パスに同梱します。

```dockerfile

FROM golang:alpine
# Skip known public key check to be able to pull from private repositories 
ENV GIT_SSH_COMMAND "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
RUN apk --no-cache add git ca-certificates openssh \
  && go get github.com/triggermesh/aws-custom-runtime \
  && go get github.com/triggermesh/knative-lambda-runtime/go-1.x \
  && go get github.com/golang/dep/...
WORKDIR /go/src/handler
COPY . .
RUN if [ -f "$HOME/.ssh/id_${SSH_KEY}" ]; then \
    eval "\$(ssh-agent -s)"; \
    ssh-add $HOME/.ssh/id_${SSH_KEY}; \
fi \
  && if [ -f "Gopkg.toml" ]; then dep ensure; fi \
  && go get -v \
  && go install

FROM alpine
WORKDIR /opt

ENV LAMBDA_TASK_ROOT "/opt"
ENV _HANDLER "handler"

COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=0 /go/bin/go-1.x /opt/bootstrap
COPY --from=0 /go/bin/ /opt

ENTRYPOINT ["/opt/aws-custom-runtime"]
```

triggermeshはCLIも準備しており、K8sのyamlやkubectlを意識せずにデプロイできます。

```bash
# Install Go buildtemplate
$ tm deploy buildtemplate -f https://raw.githubusercontent.com/triggermesh/knative-lambda-runtime/master/go-1.x/buildtemplate.yaml

# Deploy function
$ tm deploy service go-lambda -f . --build-template knative-go-runtime --wait
```

Knativeを活用してFaaSをクラスタ上にデプロイするアーキテクチャとして面白いです。

## ユースケース⑦　　FaaS　イベントpush型

もう1つFaaSのユースケースを見ます。今回はOSSのFaaSである[OpenFaaS](https://github.com/openfaas)を部分的に利用してFaaSプラットフォームを構築するというユースケースです。

<img width="515" alt="43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/863a05af-62a3-9af4-a183-402a19b44c36.png">

## OpenFaaSのfunction（Go）

開発者が書くfunctionを見てみましょう。

```go
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

func main() {
	input, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		log.Fatalf("Unable to read standard input: %s", err.Error())
	}
	fmt.Println(string(input))
}
```

[faas/sample-functions/BaseFunctions/golang/handler.go](https://github.com/openfaas/faas/blob/master/sample-functions/BaseFunctions/golang/handler.go)より

ポイントはイベント情報など入力値を標準入力で受け取っている点です。

今回もKnativeのServiceで指定するのはコンテナで、コンテナで動かすアプリケーションはサーバーですがどのように実現されているのでしょうか。

<img width="516" alt="44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/cde03be1-03d2-19ce-1bff-cb4573ff678b.png">


## OpenFaaSのアーキテクチャ

OpenFaaSはつぎのようなアーキテクチャで実現されています。

<img width="516" alt="45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/7f7064e9-06ac-b452-cf6f-3501bcf7fa87.png">

OpenFaaS API Gatewayがエンドポイントのコンポーネントです。

特定のfunctionに対するAPI Gateway経由のリクエストが一定数を越えるとfunctionをスケールアウトさせます。

メトリクスはPrometheusが回収し、AlertManagerからfaas-provider（[openfaas/faas-netes](https://github.com/openfaas/faas-netes)）経由でK8sのAPIを操作します。

## watchdogによるfunction制御

今回利用するのは各function（K8sのService + Deployment）の中で利用されている[watchdog](https://docs.openfaas.com/architecture/watchdog/)部分だけです。

watchdogはfunctionの起動、モニタリングを責務とするGoで書かれたサーバーです。

リクエストを標準入力経由でfunctionのプロセスに渡す。

これがあることでFaaSプラットフォーム上でfunctionを実行したい開発者はビジネスロジック（function）の実装に集中できます。

<img width="516" alt="46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/19a8636a-30cd-9718-2109-849e7b13891a.png">

Revisionで管理するコンテナにwatchdogとそこから起動されリクエストを受けるfunctionをそのまま流用します。

スケールの管理はknative/servingで行います。

<img width="515" alt="46.5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/8d0e4650-3193-2b1f-b442-c7f288289365.png">


## watchdogとfunction同梱のDockerfile

最終的にはつぎのDockerfileからイメージをbuild、pushします。

fwatchdogがエントリーポイントとなっていて、functionのバイナリを同梱します。

利用言語毎にベースとなるイメージは異なるものの、標準入力経由でリクエスト（イベント情報）を渡す実装になっているため各言語毎にSDKを準備したりする必要はありません。


```dockerfile
FROM openfaas/classic-watchdog:0.14.4 as watchdog

FROM golang:1.9.7-alpine

MAINTAINER alexellis2@gmail.com
ENTRYPOINT []

WORKDIR /go/src/github.com/openfaas/faas/sample-functions/golang
COPY . /go/src/github.com/openfaas/faas/sample-functions/golang

RUN go install

COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog
RUN chmod +x /usr/bin/fwatchdog

ENV fprocess "/go/bin/golang"
HEALTHCHECK --interval=1s CMD [ -e /tmp/.lock ] || exit 1

RUN addgroup -g 1000 -S app && adduser -u 1000 -S app -G app
USER 1000

CMD [ "fwatchdog"]
```

<img width="516" alt="47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/ddc61e1a-afdb-1dd5-3c4f-6a4c75bbfa2b.png">

BuildTemplateを利用したパイプラインの構築も可能です。

[First look at knative build for OpenFaaS functions](https://blog.alexellis.io/first-look-at-knative-build-for-openfaas-functions/)

## Knativeを使ったFaaSプラットフォーム

KnativeのユースケースとしてFaaSプラットフォームの構築を2つ見てきました。

共通してつぎのようなことが言えそうです。

* Serviceでデプロイするのはコンテナなので、functionの実装に集中するためにリクエストをfunctionに渡すサーバーを準備する必要がある
* サーバーからfunctionを独立させることでサーバーとfunctionを同梱するイメージテンプレートを準備できる
* faas-cliやtmのようにCLIでmanifestとkubectlを意識させないデプロイも実現できる
* イベントがcloudeventsに準拠していると各イベント用のライブラリを各言語で準備する手間も省ける

<img width="516" alt="48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/d15d26d6-0e0b-9eb6-431e-644c6f81c2a3.png">

## ユースケース⑧　コンテナサーバーレス

指定したコンテナをデプロイするプラットフォームを構築することもできます。

<img width="515" alt="49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/073657c3-c3b9-6604-e7bb-4dc6f043d2f0.png">

[Cloud Run](https://cloud.google.com/run/)を利用すると、gcloudコマンドでyamlやkubectlを意識することなくコンテナをデプロイ可能です。

自分たちが運用するK8sクラスタでの利用に加え、マネージドサービスもあるためK8sクラスタ自体を運用することなくコンテナのデプロイ・実行が可能です。
（運用が何もかも無くなるということはありません）

いずれもKnative Servingまたは互換のAPIを利用しており、実行対象はコンテナであるためポータビリティも高いです。

コンテナベースのアプリケーション実行環境の選択肢の1つとしていかがでしょうか。

Cloud Run on GKEでknativeがどのように利用されているかについて別記事を書いたのでご興味のある方はそちらもご参照ください。

[Cloud Run on GKEに覗くKnative](https://qiita.com/toshi0607/items/eeeabe81b1beac343b6b)

<img width="517" alt="50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/49599aba-950f-2d6b-67e1-572a8c4f59bb.png">

# まとめ

<img width="515" alt="51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/d33b9fb0-e92f-3467-4317-e7be80e4151e.png">

* KnativeはK8s nativeな方法でK8sプラットフォームを拡張・抽象化する
* K8s上でサーバーレスを実現する手段の1つがKnativeであり、FaaSに限らず課題に応じて開発者の生産性向上、運用者の負荷低減のための様々な使い方ができる
* FaaSを自分で構築することもできるし、Knativeを利用したプロダクトを利用することもできる

# 参考資料

## Knativeの参考資料

<img width="515" alt="52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/a1ffc0d6-31cd-7b86-f95e-84d9f2e59875.png">

* 公式ドキュメント
  * とても整理されていて図も豊富でわかりやすいです
  * 主なユースケースのサンプル・解説もある
  * https://github.com/knative/docs
* 『Getting Started with Knative』
  * Pivotalの方が執筆したebookが無料です
  * https://content.pivotal.io/ebooks/getting-started-with-knative
  * バージョンは古くなってしまったがそれでもユースケースまで載っていて楽しい
* 『Knativeの歩き方 KubernetesからServerlessを訪ねて』
  * 完全な手前味噌
  * https://booth.pm/ja/items/1309468

<img width="518" alt="53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/f5dcd772-e6a6-efcb-6b4e-a225b5b2fd70.png">

技術書典7の[カエルと空](https://techbookfest.org/event/tbf06/circle/71450004)というサークルで

* Knativeの既刊のアップデート版
* Knativeの実装とK8sのコントローラーを学ぶ新刊

を出展予定ですのでそちらもよろしくお願いします！

<img width="516" alt="54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/4627279e-c800-11c8-efca-e91ef86c0fed.png">

10/21、22のServerlessDays Tokyo 2019ではKnativeのハンズオンをやる予定です。

Tokyo、Fukuokaともにプロポーザルを募集中ですのでふるってご提出ください！

* https://tokyo.serverlessdays.io/
* https://fukuoka.serverlessdays.io/


<img width="518" alt="55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/8c5e7cee-db5e-815c-8f9f-b954c29eb975.png">

# おまけ

ふりかえり
[CloudNative Days Tokyo 2019で『Knativeで実現するKubernetes上のサーバーレスアーキテクチャ』を話してきました #CNDT2019](https://toshi0607.com/event/serverless-architecture-on-the-top-of-k8s-with-knative/)