Edited at

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


概要


  • CloudNative Days2019でお話した「Knativeで実現するKubernetes上のサーバーレスアーキテクチャ」の文字起こしです

  • スライド単体だと情報量が足りなかったりリンクをたどりづらかったりするため、より多くの人にKnativeを知ってもらうために記事にしようと考えました


自己紹介

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

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

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

メルペイのマイクロサービスの構築と運用

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

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

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


Knativeの概要


Knativeとは

GitHubのKnativeのリポジトリではつぎのように説明されています。

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

ドキュメントにはより詳細な情報が書かれています。

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

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

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


  • コンテナのデプロイ

  • ソースコードからURLでアクセスできるアプリケーションへ

  • ブルー/グリーンデプロイを伴うルーティングとトラフィック管理

  • オートスケーリングと需要に基づくワークロードのサイズ設定

  • 実行中のサービスをイベントエコシステムに結び付ける


Knativeの構成要素

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

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


Servingの役割

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


Servingの構成要素

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を作成・設定できます。

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

KubernetesのServiceとDeploymentにより管理され、knative-serving Namespaceにデプロイされます。実装もServingのリポジトリ内にあります。

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

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


Istioは必須?

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

Gatewayの代替コンポーネントとしてsolo.ioのGlooがありますが、ドキュメント上ではEventingの機能がIstioに依存しているためEventingをサポートしていないとしています。

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

AmbassadorはKnativeのインストールドキュメントにもすでに反映されていました。

Ambassadorのドキュメントにはつぎのように書いてあるものの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

EventingのIstio依存解消の最後のPRは2019/5/2時点でマージされv0.6では反映されていそうでした。

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


Buildの役割と構成要素

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

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

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

BuildTemplateはkaniko、buildpacks、jibなど作成済みのものがGitHub上で公開されています。


BuildとTekton

現在BuidはTektonへの移行議論が進んでいます。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.

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

Migrating from Knative Build

KnativeのBuild APIを利用していたCLIの中にはすでにTektonに移行したものもあります。


Eventingの役割

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

CNCFのCloudEventsに準拠しています。

資料上はCNCF Serverless WGが決めたことになってますが別ですね…


Eventingの構成要素

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

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

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

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


Event Source

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

作成のためのチュートリアルもあります。


KubernetesとKnative


Kubernetesとは?

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

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


Kubernetesのコンセプト

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

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


Kubernetesのアーキテクチャ

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

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


カスタムリソース

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

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

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


カスタムコントローラー

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

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

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


Kuberentes nativeなKnative

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

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

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


Kubernetesとサーバーレス


サーバーレスとは

martinFowler.comではつぎのように定義されています。

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

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

Serverless is a Doctrine, not a Technologyと定義する人もいます。(全訳

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

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

CNCF Serverless WGCNCF Serverless Whitepaper v1.0にはつぎのように書かれています。


  • サーバー管理を必要としないアプリケーションを構築および実行するという概念

  • 必要とされている正確な需要に応じて実行、拡張、請求を行うデプロイモデル
    サーバーレスコンピューティングの利用者は、サーバーのプロビジョニング、メンテナンス、アップデート、拡張、キャパシティプランニングに時間とリソースを費やす必要がなくなる

  • 開発者がよりアプリケーションのビジネスロジック開発に集中できるようになる

  • 運用者がよりビジネスクリティカルなタスクに集中できる

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


Kubernetesとサーバーレス

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

Knativeは


  • ステートレス

  • プロセススケールアウトモデル

  • アプリケーションレベルのリクエストトラフィック駆動

の3点をサーバーレスワークロードと捉えています。

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

その上で

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

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

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

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

のがKnativeです。


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

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

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

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


Knativeのユースケース

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

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

toshi0607/CNDT2019-Knative


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

Deploying a container

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

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

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

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

Migrating from Kubernetes Deployment to Knative Serving

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

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


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

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

Installing logging, metrics and traces


スケール

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

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

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はこちらです。あくまでもexampleです。

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

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直下に書いて有効化します。


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

Routing and managing traffic with blue/green deployment

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


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

gcp-pubsub-source sample

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

そうするとServiceがイベントはハンドリングする際も各言語のCloudEventsのSDKを利用することができます。

knative/eventing-contribではcloudevents/sdk-goを利用したデフォルトクライアントも実装されています。


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

KnativeのサンプルをTektonでデプロイするサンプル

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

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

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

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


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

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

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

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


AWS Lambdaのfunction(Go)

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

 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 関数ハンドラーより

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

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

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


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

  • rpcサーバーの起動

rpcサーバー周りの実装はaws/aws-sdk-goとして再利用できます。

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


AWS LambdaとRuntime Interface

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

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

この一連のイベントハンドリングフローを逆転させたのがre:Invent2018で発表されたCustom AWS Lambda Runtimesです。

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

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

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

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


knative-lambda-runtime

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

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

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

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


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です。

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-runtimeinvocation/nextにリクエストしてイベントを取得する

  • ハンドラーにリクエストしてaws-custom-runtimeinvocation/[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を利用しています。

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

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

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


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を意識せずにデプロイできます。

# 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を部分的に利用してFaaSプラットフォームを構築するというユースケースです。


OpenFaaSのfunction(Go)

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

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より

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

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


OpenFaaSのアーキテクチャ

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

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

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

メトリクスはPrometheusが回収し、AlertManagerからfaas-provider(openfaas/faas-netes)経由でK8sのAPIを操作します。


watchdogによるfunction制御

今回利用するのは各function(K8sのService + Deployment)の中で利用されているwatchdog部分だけです。

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

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

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

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

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


watchdogとfunction同梱のDockerfile

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

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

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

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"]

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

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に準拠していると各イベント用のライブラリを各言語で準備する手間も省ける


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

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

Cloud Runを利用すると、gcloudコマンドでyamlやkubectlを意識することなくコンテナをデプロイ可能です。

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

(運用が何もかも無くなるということはありません)

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

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

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

Cloud Run on GKEに覗くKnative


まとめ


  • KnativeはK8s nativeな方法でK8sプラットフォームを拡張・抽象化する

  • K8s上でサーバーレスを実現する手段の1つがKnativeであり、FaaSに限らず課題に応じて開発者の生産性向上、運用者の負荷低減のための様々な使い方ができる

  • FaaSを自分で構築することもできるし、Knativeを利用したプロダクトを利用することもできる


参考資料


Knativeの参考資料

技術書典7のカエルと空というサークルで


  • Knativeの既刊のアップデート版

  • Knativeの実装とK8sのコントローラーを学ぶ新刊

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

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

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


おまけ

ふりかえり

CloudNative Days Tokyo 2019で『Knativeで実現するKubernetes上のサーバーレスアーキテクチャ』を話してきました #CNDT2019