この記事は富士通クラウドテクノロジーズ Advent Calendar 2018の22日目です。 昨日は @ntoofu さんの「続・IaaS基盤をテストする環境を作る話」でした。私の場合はいい感じに使いやすいクラウド上で自動テストする環境を作るケースが多く、クラウドの機能等を使って楽ができるのですが、そのクラウド基盤向けの自動テストとなると独自にいろいろ作りこんだり、試行錯誤する場面がより多そうですね…!
はじめに
ニフクラのエンジニアリングパーツの開発・運用を担当している id:heriet です。 この記事では、Knativeを構成するコンポーネント群と、それらを構成するCRDについて紹介したいと思います。
ニフクラではエンジニアリングパーツという形でニフクラ上のシステムに組み込みやすいパーツを提供しています。たとえば、ニフクラ RDBはマネージドなDBサービスで、 ニフクラ スクリプト はサーバーレスを実現するサービスです。エンジニアリングパーツのようなサービスを実現するためには数多くの技術を取り入れる必要があるのですが、Knativeはその中で注目している技術のうちの一つです。
Knativeとは
Knativeとは、2018年7月にGoogleから発表された、Kubernetesネイティブなコンポーネント群です。 OSSとして精力的に開発が行われており、次世代の基盤として期待が集まっています。
Knative自体は複数のコンポーネントから構成されています。これらのコンポーネントは、クラウド上のシステムでよく必要となる機能が実装されたプリミティブなパーツと捉えることができます。Knativeのコンポーネントを利用することで、数多くのベンダーがそれぞれ独自に似たようなものを実装する必要性が薄まります。各コンポーネントは疎結合で、連携して動作させることもできますし、あるシステムにとって必要な一部のコンポーネントだけを利用することもできます。なお、KnativeはKubernetesとIstio上に構築されることを前提としています。
KnativeはよくFaaS(Function as a Service)の基盤というような扱われ方をすることがあります。それが誤りというわけではないのですが、Knativeが実現する世界のうち、FaaSは一部分です。もちろん、Knativeを利用することでFaaSを実現することはできます。が、より広い概念であるサーバーレスなアプリケーションであったり、一般的なKubernetes + Istio上で構築可能な、非サーバーレスのシステムのうえであっても、Knativeのコンポーネントを活用することもできるでしょう。
以下でより詳細に解説していきますが。各コンポーネントは KubernetesのCRD(Custom Resource Definition)で構成されています。コンポーネントとCRDを把握すると、Knativeの内部理解が高まることでしょう。もちろん、CRDだけではないのですが、今回はCRDという視点を中心に説明したいとおもいます。
ちなみに今回検証した環境はニフクラ上で構築したKubernes(v1.11.3)環境で、Knative v0.2.2 をインストールした状態で検証しています。
Knativeのコンポーネント
現在、Knativeでは下記のコンポーネントが存在しています。
コンポーネント | 役割 |
---|---|
Knative Build | ソースからビルドやテストを実行し、レジストリへのプッシュやクラスタにデプロイする仕組み |
Knative Serving | 自動スケール可能なコンテナアプリケーション実行基盤 |
Knative Eventing | CloudEvent仕様に準拠したイベント処理を実現する仕組み |
また、上記以外にも Knative Pipeline や Knative Caching などのコンポーネントも開発されているようです。
Build、Serving、Eventingを組み合わせることでFaaSやサーバーレスなシステムを構築することができます。もしくは、システムによってはイメージビルドの仕組みだけをKnative Buildで実現する(他のコンポーネントは特に使わない)というシーンもありえるかもしれません。
以下で各コンポーネントについてみていきたいと思います。
Knative Build
公式ドキュメントによると、Knative Buildが果たす具体的な機能としては、下記が挙げられています。
- リポジトリからソースコードを取得する
- 複数のジョブを逐次処理する。たとえば
- 依存関係のインストール
- 単体テストと統合テストを実行
- コンテナイメージをビルドする
- コンテナイメージをレジストリにプッシュまたはクラスタにデプロイ
Knative Buildでは、下記のCRDが定義されています。Knative Buildをインストールした環境で、 kubectl api-resources
や kubectl get crd
などで確認することができます。APIGroupは build.knative.dev
となっています。
# kubectl api-resources --api-group=build.knative.dev NAME SHORTNAMES APIGROUP NAMESPACED KIND builds build.knative.dev true Build buildtemplates build.knative.dev true BuildTemplate clusterbuildtemplates build.knative.dev false ClusterBuildTemplate
それぞれのCRDは下記のような役割になっています。
CRD | 役割 |
---|---|
Build | ビルド処理内容の実体。stepsという単位でビルドの処理を記述する。BuildTemplate/ClusterBuildTemplateでstepsなどをテンプレート化することが可能 |
BuildTemplate | 同じようなBuildをテンプレートとして記述できる。namespace単位 |
ClusterBuildTemplate | BuildTemplateのCluster版。そのため、BuildTemplateと異なりnamespaceを跨いで定義可能 |
基本的に、Buildリソースを作成すると、定義されたstepに従って処理される形になります。
また、CRDではありませんが、Knative Buildを扱う上で Builderという概念が出てきます。Builderは、Build内のstepで指定されている個々のコンテナイメージのことを指します。
Knative Buildを使ってみる
公式のシンプルな例 があるので、試してみます。
Knative Buildをインストールした環境で、下記のBuildリソースを作成してみます。stepsに定義されている内容が実行されるリソースになります。
apiVersion: build.knative.dev/v1alpha1 kind: Build metadata: name: hello-build spec: steps: - name: hello image: busybox args: ["echo", "hello", "build"]
$ kubectl apply -f hello-build.yaml
build.build.knative.dev/hello-build created
確認してみると、ビルドは成功しているようです。
$ kubectl get builds NAME SUCCEEDED REASON STARTTIME COMPLETIONTIME hello-build True 46s 32s
実際にはビルド用のPodが動作しており、Pod名を下記で確認することができます。
$ kubectl get build hello-build --output jsonpath={.status.cluster.podName} hello-build-<ID>
Podを確認してみると、実行は完了しており、その中にStepに応じたコンテナである build-step-hello
と、 build-step-credential-initializer
という名前の初期化用途と思われるコンテナが動いていたことがわかります。
$ kubectl get pod hello-build-<ID> NAME READY STATUS RESTARTS AGE hello-build-<ID> 0/1 Completed 0 6m $ kubectl logs hello-build-<ID> --container build-step-hello hello build $ kubectl logs hello-build-<ID> --container build-step-credential-initializer {"level":"info","ts":1545455594.6082551,"logger":"fallback-logger","caller":"creds-init/main.go:40","msg":"Credentials initialized."}
上記は非常にシンプルな例ですが、実際にはこのBuildのstepとしてビルドの処理やイメージリポジトリへのpush処理を書いていく形になるようです。また、同じようなstepsを記述して複数のBuildで使いまわしたい場合は、BuildTemplate/ClusterBuildTemplateでテンプレート化も可能になっています。
Knative Serving
Knative Serving は FaaSやサーバーレスなシステムを実現します。リクエスト駆動でサーバーレスなコンテナの実行を行うことができます。
公式ドキュメントによると、Knative Servingが果たす具体的な機能としては、下記が挙げられています。
- サーバーレスなコンテナの高速デプロイ
- 自動スケールアップ及び、ゼロスケール
- Istioによるルーティングとネットワーク操作
- デプロイされたソースコードと設定のポイントインタイムスナップショット
ゼロスケールというのが大きな特徴で、アプリケーションに全くリクエストが来ない状態が続くと、アプリケーションのPod自体が1個も立ち上がらない状態になります。0個の状態で、新たにアプリケーションへのリクエストが発生すると、改めてPodが立ち上がります。そのため、たまにしかリクエストが来ないアプリケーションかつ初回の応答速度が求められる場合は、向いていないかもしれません。そうではなく、常時リクエストがきていたり、バッチ処理のように起動時間が誤差のようなアプリケーションであれば、問題にならないと思われます。
Knative Serving のCRDは下記のとおりです。APIGroupは serving.knative.dev
となっています。
$ kubectl api-resources --api-group=serving.knative.dev NAME SHORTNAMES APIGROUP NAMESPACED KIND configurations config,cfg serving.knative.dev true Configuration revisions rev serving.knative.dev true Revision routes rt serving.knative.dev true Route services kservice,ksvc serving.knative.dev true Service
それぞれのCRDは下記のような役割になっています。
CRD | 役割 |
---|---|
Configuration | デプロイメントの状態を保持するリソース。Twelve-Factor App に従ってソースコードと設定を分離してくれる。設定の一部は Knative Build が使われる(ので、Knative Serving を使うには Knative Build が必要)。 Configurationを変更するごとにRevisionが生成される |
Revision | Configurationのイミュータブルなスナップショット。指定したリビジョンのアプリケーションにRouteを使ってルーティングしたり、ロールバックすることができる |
Route | エンドポイントを司り、単一または複数のRevisionへトラフィックをルーティングする |
Service | Kubernetes APIのServiceと同名なので若干ややこしい。Knative Servingが提供する各CRDを管理し、アプリケーションを提供するもの |
Knative Servingを使ってみる
既に試して解説されている例がいくつかありますので、そちらを参照するのがわかりやすいです。
Knative Eventing
Knative Eventingは CloudEvents 仕様に準拠した、イベント処理を実現するものです。イベントコンシューマーとイベントソースという概念にわかれており、汎用的なイベント連携インターフェースを備えているようです。
公式ドキュメントによると、Knative Eventingは下記のようにデザインされているようです。(ぱっとよく理解できなかったので、下記は私なりの解釈を交えていますが、間違っていたらすみません)
- イベントソースは、有効なイベントコンシューマーがなくともイベントを生成することができる
- イベントコンシューマーは、イベントソースがなくともイベントを購読できる
- イベントコンシューマーだけではなくService(KnativeのService)にイベントを流すことができる
- CloudEvents 仕様に準拠している
Knative Eventing のCRDは下記のとおりです。APIGroupは eventing.knative.dev
と sources.eventing.knative.dev
の2つあります。前者がイベントコンシューマー、後者がイベントソースに相当するようです。
$ kubectl api-resources --api-group=eventing.knative.dev NAME SHORTNAMES APIGROUP NAMESPACED KIND channels chan eventing.knative.dev true Channel clusterchannelprovisioners ccp eventing.knative.dev false ClusterChannelProvisioner subscriptions sub eventing.knative.dev true Subscription $ kubectl api-resources --api-group=sources.eventing.knative.dev NAME SHORTNAMES APIGROUP NAMESPACED KIND containersources sources.eventing.knative.dev true ContainerSource githubsources sources.eventing.knative.dev true GitHubSource kuberneteseventsources sources.eventing.knative.dev true KubernetesEventSource
それぞれのCRDは下記のような用途になっています。公式のアーキテクチャ図 も合わせて見るとよいでしょう。
CRD | 役割 |
---|---|
Channel | イベントを受信し、Subscription経由で別の1つ以上の宛先(ChannelまたはKnative Service)にイベントを配信するリソース。ある Channelと宛先の関係は 1:Nになる |
ClusterChannelProvisioner | チャネルの実装。インメモリやKafka、NATS、GCP PubSubなど |
Subscription | Channelから流れてくるイベントを次の宛先(ChannelまたはKnative Service)に流す仕組み。流す際に変換をかけることもできる。あるChannelから別のあて先へのSubscriptionは1:1になる |
ContainerSource | イベントを生成できるコンテナを立ち上げ、それをもとにイベントを発生させるイベントソース。なにかのファイルをポーリングしてイベントを発生させしたり、一定間隔でイベントを生成したりするときに使う |
GitHubSource | GitHubのリポジトリをもとにイベントを発生させるイベントソース |
GcpPubSubSource | GCPのPubSubサービスをもとにイベントを発生させるイベントソース |
KubernetesEventSource | Kubernetes Eventの作成または更新をもとにイベントを発生させるイベントソース |
ClusterChannelProvisioner
はインストール直後は in-memory-channel
しかありませんが、公式のprovisioners をみるとKafka、NATS、GCP PubSubなどの実装が既にあり、参考にして自分で実装することもできそうです。イベント配信の耐障害性などの要件に応じて内部の実装の使い分けができるということのようです。
Knative Eventingを使ってみる
公式のKnative Eventingのサンプルが掲載されているので、試してみます。個人的に一番気になったのは ContainerSource なのですが、サンプルが見当たらなかったので、KubernetesEventSourceのサンプルをやってみます。
Channelリソースをつくるためには provisioner
として ClusterChannelProvisioner
がまず必要になります。確認してみると、Knative Eventingのインストール時に、 in-memory-channel
という名前の ClusterChannelProvisioner
が既に作成されています。
$ kubectl get ccp
NAME AGE
in-memory-channel 1d
この、 in-memory-channel
を使った testchannel
という名前の Channel
をつくります。
apiVersion: eventing.knative.dev/v1alpha1 kind: Channel metadata: name: testchannel spec: provisioner: apiVersion: eventing.knative.dev/v1alpha1 kind: ClusterChannelProvisioner name: in-memory-channel
$ kubectl -n default apply -f https://raw.githubusercontent.com/knative/docs/master/eventing/samples/kubernetes-event-source/channel.yaml channel.eventing.knative.dev/testchannel created
次に、ServiceAccountを用意しておきます。
$ kubectl -n default apply -f https://raw.githubusercontent.com/knative/docs/master/eventing/samples/kubernetes-event-source/serviceaccount.yaml serviceaccount/events-sa created role.rbac.authorization.k8s.io/event-watcher created rolebinding.rbac.authorization.k8s.io/k8s-ra-event-watcher created
今回のイベント生成元となる KubernetesEventSource
を testevents
という名前で作成します。 default
namespaceで発生したイベントを testchannel
Channelに流れるように設定しています。
apiVersion: sources.eventing.knative.dev/v1alpha1 kind: KubernetesEventSource metadata: name: testevents spec: namespace: default serviceAccountName: events-sa sink: apiVersion: eventing.knative.dev/v1alpha1 kind: Channel name: testchannel
作成された Sourceを確認してみると KubernetesEventSource と同時に ContainerSourceも作成されていることがわかります。KubernetesEventSourceは内部的にContainerSourceを使っていることがわかります。
$ kubectl get sources NAME AGE testevents 5m NAME AGE testevents-vs58n 5m $ kubectl get kuberneteseventsources NAME AGE testevents 5m $ kubectl get containersources NAME AGE testevents-vs58n 5m $ kubectl describe containersources | grep "Image:" Image: gcr.io/knative-releases/github.com/knative/eventing-sources/cmd/kuberneteseventsource@sha256:cc6c9dffba7659d04bf5f0958a551f4eeb20d0a6ee27ab3e4102e6f1f31e0f56
今度は、 Subscription
リソースを作成します。先ほど作成した testchannel
から message-dumper
という Service(KnativeのServiceなことに注意)にイベントを流す設定になります。message-dumper
は受け取ったイベントをそのままログに流すServiceとなっています。
apiVersion: eventing.knative.dev/v1alpha1 kind: Subscription metadata: name: testevents-subscription namespace: default spec: channel: apiVersion: eventing.knative.dev/v1alpha1 kind: Channel name: testchannel subscriber: ref: apiVersion: serving.knative.dev/v1alpha1 kind: Service name: message-dumper --- # This is a very simple Knative Service that writes the input request to its log. apiVersion: serving.knative.dev/v1alpha1 kind: Service metadata: name: message-dumper namespace: default spec: runLatest: configuration: revisionTemplate: spec: container: image: gcr.io/knative-releases/github.com/knative/eventing-sources/cmd/message_dumper@sha256:73a95b05b5b937544af7c514c3116479fa5b6acf7771604b313cfc1587bf0940
$ kubectl -n default apply -f https://raw.githubusercontent.com/knative/docs/master/eventing/samples/kubernetes-event-source/subscription.yaml subscription.eventing.knative.dev/testevents-subscription created service.serving.knative.dev/message-dumper created
適当なKubernetes Eventを発生させてみます。
$ kubectl run -i --tty busybox --image=busybox --restart=Never -- sh If you don't see a command prompt, try pressing enter. / # exit $ kubectl get pods NAME READY STATUS RESTARTS AGE busybox 0/1 Completed 0 23s message-dumper-00001-deployment-5875c759ff-fpwcf 3/3 Running 0 9m testevents-vs58n-6lrhw-7ddcdd7b5-spqqs 2/2 Running 0 23m $ kubectl delete pod busybox pod "busybox" deleted
message-dumper
のログを確認してみると、先ほどのbusyboxのイベントが流れていることがわかります。
$ kubectl logs -l serving.knative.dev/service=message-dumper -c user-container (中略) {"metadata":{"name":"busybox.15728c012aaf5de0","namespace":"default","selfLink":"/api/v1/namespaces/default/events/busybox.15728c012aaf5de0","uid":"e34ad946-059e-11e9-8fb9-005056b06b2d","resourceVersion":"4688385","creationTimestamp":"2018-12-22T04:05:58Z"},"involvedObject":{"kind":"Pod","namespace":"default","name":"busybox","uid":"e0009ba3-059e-11e9-8fb9-005056b06b2d","apiVersion":"v1","resourceVersion":"4688369","fieldPath":"spec.containers{busybox}"},"reason":"Started","message":"Started container","source":{"component":"kubelet","host":"hoge"},"firstTimestamp":"2018-12-22T04:05:58Z","lastTimestamp":"2018-12-22T04:05:58Z","count":1,"type":"Normal","eventTime":null,"reportingComponent":"","reportingInstance":""}
ひととおり、イベントの発火と連携の流れができることがわかりました。
まとめ
KnativeのコンポーネントとCRDを見ていくことで、Knativeへの理解度を高めることができたかと思います。今後、アプリケーション基盤の標準としてKubernetes + Istioはより広まっていく可能性は高いです。その上で、開発者はよくある機能の実装はKnativeやそれに類するコンポーネントに任せて、自身が独自に実装したいアプリケーションのコアな部分にだけ集中して取り組めるようになる…のかもしれません。ただ、2018年12月現在でも、Knativeはまだまだ絶賛開発中の様子で、本格的に使っていくにはもう少し時間を要しそうです。
今回は調査しきれなかったのですが、CRDの背後にはそれを管理するコントローラー等の存在もあります。 より深く辿りたい方は、CRDとコントローラーの実装を調べてみてもよいかもしれません。
次の23日目は ysaotome さんが社内インフラ環境について語ってくれるそうです。 ysaotomeさんは弊社の社内環境改善に非常に尽力されている方で、私もその恩恵を大きく受けています。楽しみですね!