FJCT Tech blog

富士通クラウドテクノロジーズ公式エンジニアブログです

富士通クラウドテクノロジーズ

FJCT/Tech blog

KnativeのコンポーネントとCRD

この記事は富士通クラウドテクノロジーズ 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 PipelineKnative Caching などのコンポーネントも開発されているようです。

Build、Serving、Eventingを組み合わせることでFaaSやサーバーレスなシステムを構築することができます。もしくは、システムによってはイメージビルドの仕組みだけをKnative Buildで実現する(他のコンポーネントは特に使わない)というシーンもありえるかもしれません。

以下で各コンポーネントについてみていきたいと思います。

Knative Build

公式ドキュメントによると、Knative Buildが果たす具体的な機能としては、下記が挙げられています。

Knative Buildでは、下記のCRDが定義されています。Knative Buildをインストールした環境で、 kubectl api-resourceskubectl 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.devsources.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

今回のイベント生成元となる KubernetesEventSourcetestevents という名前で作成します。 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さんは弊社の社内環境改善に非常に尽力されている方で、私もその恩恵を大きく受けています。楽しみですね!