Tanzu Application Platform Tanzu アプリケーションのモダナイゼーション

Tanzu Application Platform でサーバレスとコンテナを和解?Vol2: Cloud Native Runtimes

※この内容は、Tanzu Application Platform でサーバレスとコンテナを和解?の前作からの続きになっています。

VMware Tanzu Application Platform (以降 TAP )はサーバレス相当のワークロードも管理することが得意です。このブログではサーバレスを語る上で、重要な要素である「イベント」、それを Kubernetes 上で実現することが可能な Cloud Native Runtimes を紹介します。

サーバレスで避けて通れない「イベント」とは?

このブログでは前回に続き、「VS」つまり対立関係になりがちなサーバレスとコンテナ、それを TAP を使い和解するアプローチを紹介していきます。さて、サーバレスを使うことへの期待として、大きく 3 つの点があるとしていました。今回は、この3 つ目の期待すること「サービス間の連係に利用」について取り上げます。

「サーバレス」という言葉をきいて期待されることは大別すると以下の 3 つの理由があると思います。

  1. コストの最適化
  2. 軽量な開発スタイル
  3. サービス間の連係に利用

前回は Hello World を応答する Web アプリケーションを例に解説しました。ところが当たり前ですが、みなさんの Hello World のような Web アプリケーションばかりではないと思います。様々なユースケースを想定した時に、取り上げなかったもの、それがクラウド上の様々な「イベント」をトリガーとしたサービス連携です。

クラウドを利用する醍醐味のひとつがさまざまなマネージドサービスを使いこなして連携していくことです。この連携の過程でサーバレスのアプリケーションが活用されます。 Amazon Web Service のサーバレスの開発パターンをまとめたサイトを一望してみてください。ここでポイントとなってくるのが、サーバレスが Amazon 上のサービスを連携、特にデータの加工のために利用されていることです。

たとえば、Amazon S3 に車の画像がはいったら、Amazon Lambda 経由で画像加工や、画像認識のサービスと連携させて、ナンバープレートを抽出するなど、、、いままでの TAP で紹介してきた Web アプリケーションとは全く異なるユースケースです。

サーバレス vs コンテナの議論において、このユースケースに限って言えばサーバレスの独壇場、さらにいえばコンテナにとっては不可侵の領域でした。各クラウドプロバイダはこのユースケースをより簡易にできるよう、認証情報の隠蔽やバックエンドを透過的に連携できる仕組みを提供します。結果として最小限のコードで目的が達成できてしまいます。これをコンテナで再現しようと思っても、抽象度が低いため、作り込みやコーディングが多くなってしまいます。

TAP は、このギャップに着目しました。そしてイベントトリガーかつサービス連携をするためのアプリケーションの開発、それをコンテナで実現する Cloud Native Runtimes を同梱しています。

サービス連携というユースケースにおいてコンテナをどのように活用できるか?さらになぜわざわさコンテナでやるのか?今回は Cloud Native Runtimes を紹介した上でさらにサーバレスとコンテナを「和解」させていただく手段をさらに議論します。

Cloud Native Runtimes とは?

TAP には、Cloud Native Runtimes とよばれるコンポーネントが同梱されています。専用のマニュアルページも設けられている製品です。

Cloud Native Runtimes も多数の OSS をパッケージ化した製品です。主には以下が商用サポートがついた状態で組み込まれています。

まず、注目すべきが Knative です。 Kubernetes をサーバレス(厳密には Function as a Service )に近づける取り組みや OSS がこの Knative です。Knative は様々な実験的なプロジェクトを経て、 IBM / Red Hat や Google 、そして VMware が中心となってひとつの形のなった OSS です。TAP では、 Knative の二つのコンポーネント Serving / Eventing を同梱しています。 実は他のブログでも紹介してきた Source2URL の機能では Knative Serving が使われています。今回はさらに Knative Eventing 、特定のイベントをうけとり、指定されたコンテナを起動することができる機能に着目します。

次が、 TriggerMesh です。ソフトウェアと同名の企業が存在し、この TriggerMesh は、iPaaS の分野で、そのソリューションを OSS 化した稀有な存在です(その葛藤も公開されているのでぜひ一読ください。)。 iPaaS の” i “は、Integration 、つまり様々なクラウド上のサービスの連携に使うものです。TriggerMesh が得意なこと、それはクラウド上のサービスをイベントとして、検知することができるようにします。

最後に、RabbitMQ です。サービス間連携の上で、連携されるサービスを確実に情報を転送する、もしくは用途ごとに振り分け先を切り替えていく必要があります。それを実現するために、Broker の存在が必要です。Cloud Native Runtime では、VMware が商用サポートしている Tanzu RabbitMQ のライセンスが Broker 目的の利用に限り同梱されています。

これらを組み合わせることで実現できることでクラウドプロバイダに依存しないイベント連携の仕組みを実現します。イメージを沸かせるための一例が以下ですが、S3 + TriggerMesh に入った画像から、Knative 上のアプリケーションで車のカーナンバーを識別し加工、最終的に Google Spreadsheet に保管しています。

https://github.com/mylesagray/anpr-knative

次の章では、テクニカルにこれらがどう連携するかを説明します。

Amazon S3 と Cloud Native Runtimes を連携してみる(シンプル)

Cloud Native Runtimes に同梱されている TriggerMesh の一番の特徴がクラウド上の様々なイベントを検知して、それをコンテナに通知することができることです。執筆時点では、TAP 1.4 でサポートされるイベントの一覧は以下です。( AWS がメインですが、なんと vSphere も含まれています)

  • AWSCloudWatchLogsSource
  • AWSCloudWatchSource
  • AWSCodeCommitSource
  • AWSCognitoIdentitySource
  • AWSCognitoUserPoolSource
  • AWSDynamoDBSource
  • AWSIoTSource
  • AWSKinesisSource
  • AWSPerformanceInsightsSource
  • AWSS3Source
  • AWSSNSSource
  • AWSSQSSource
  • VSphereSource

今後のリリースでは、 Azure や Google 、その他多様なサービスをサポートの予定です。今回は、これを使い Amazon S3 のイベントを TAP でデプロイするコンテナにつなげてみます。

まずはシンプルな例を紹介します。以下がアーキテクチャー図です。なおこのシンプルなユースケースで登場するのは TriggerMesh のみです。

まず、TAP 側にイベントを受け取った時のアプリケーションを定義します。今回はサンプルアプリケーションを使います。ソースコードは以下の通りでありますが、TriggerMesh から送付されるメッセージを正しく処理するために前回紹介した Functions Buildpack が利用されています。

from typing import Any

def main(data: Any, attributes: dict):
    print("Attributes" + str(attributes))
    print("Handled cloudevent!")
    return None, "Handled cloudevent!"

まずはこれを TAP にデプロイします。

tanzu apps workload create -f workload.yaml \
  --source-image  \
  --local-path .

さて、ではこれを S3 のイベントと連動していきます。これはどちらかというと運用よりのメンバーの作業ですが、以下の Kubernetes の YAML を適用します。
(* 事前に Amazon Web Service 側の IAM の権限づけが完了している必要がありますが、ここでは割愛します。)

apiVersion: sources.triggermesh.io/v1alpha1
kind: AWSS3Source
metadata:
  name: demo-bucket
spec:
  arn: arn:aws:s3:::tapdemobucket

  eventTypes:
  - s3:ObjectCreated:*
  - s3:ObjectRemoved:*

  auth:
    iamRole: $ROLE_ARN
  sink:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: py-func-event

ポイントは2つであり

  • .spec.arn に監視対象の Amazon S3 のバケットを指定します。
  • .spec.sink にイベントが発生した時の送付先に先ほどの手順でデプロイしたアプリケーションに繋げます。

この YAML を環境に適用するだけです。全てのセットアップが完了したら、今回作成した S3 のバケット(今回は “tapdemobucket” )にファイルをアップロードしたいと思います。

簡単なファイルをアップロードします。

アップロード後、アプリのログをみてみます。すると、メッセージが確認できました。

以下の部分をみると、アップロードしたファイル名などがでているので、この情報をもとにコンテナのワークロードに繋げられます。

'source': 'arn:aws:s3:::tapdemobucket', 'type': 'com.amazon.s3.objectcreated', 'datacontenttype': 'application/json', 'subject': 'tap.png'

今回はあくまで、イベントをログに表示しているだけで、あまり価値はないですが、もちろん様々なロジックを Python に追加できます。簡単な例ですが、Amazon S3のイベントとコンテナをつなげることができました。

Amazon S3 と Cloud Native Runtimes を連携してみる(アドバンスド)

さきほどのシンプルの例は単一のイベントを単一のコンテナに繋げる程度であれば十分ですが、もうすこし複雑なユースケースを考えます。

  • イベントを処理するアプリケーションが停止中に S3 のイベントを受け取ってしまった
  • S3 のファイル作成とファイル削除で違う処理に分けたい
  • イベント完了後に数珠繋ぎで他のイベントにつなげたい

このように、もうすこし複雑なことをしようと思うと、TriggerMesh だけでは不足です。そこで登場するのが Knative と RabbitMQ です。図が以下のようにアップデートされます。

 

先ほどの処理をすこし複雑にして S3 のオブジェクト生成時と削除時で違うコンテナを起動する設定を行います。まず、もう一つ同じアプリケーションをデプロイします。名前をかえて判別できるようにします。

tanzu apps workload create -f workload.yaml \
  --source-image  \
  --local-path . \
  py-func-del

現在二つのコンテナが起動している状況です。

さて、アプリが準備できたら、再び運用担当の作業です。まず、最初に RabbitMQ の Cluster の定義から Broker までをいっきに以下のYamlで定義します。
(この手順を実施する前に事前に Tanzu RabbitMQ のインストールを完了してください。)

apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq-broker
spec:
  imagePullSecrets:
    - name: tap-registry
  rabbitmq:
    additionalPlugins:
      - rabbitmq_management
---
apiVersion: eventing.knative.dev/v1alpha1
kind: RabbitmqBrokerConfig
metadata:
   name: default-config
spec:
  rabbitmqClusterReference:
     name: rabbitmq-broker
     namespace: ${namespace}
  queueType: quorum
---
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
  name: default
  annotations:
    eventing.knative.dev/broker.class: RabbitMQBroker
spec:
  config:
    apiVersion: eventing.knative.dev/v1alpha1
    kind: RabbitmqBrokerConfig
    name: default-config

次に、Broker から Trigger を定義します。まず、S3 のオブジェクトが生成された場合の Trigger を作成します。

apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: object-created-trigger
spec:
  broker: default
  filter:
    attributes:
      type: com.amazon.s3.objectcreated
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: py-func-event

ポイントは以下です。

  • .spec.filter に “com.amazon.s3.objectcreated” を記載して S3 のオブジェクトが生成された場合のみ実行するように記載
  • .spec.subscriber にアプリケーション(py-func-event)を定義

次に、S3 のオブジェクトが削除された場合の Trigger を作成します。

apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: object-removed-trigger
spec:
  broker: default
  filter:
    attributes:
      type: com.amazon.s3.objectremoved
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: py-func-del

ポイントは以下です。

  • .spec.filters に “com.amazon.s3.objectremoved” を記載して S3 のオブジェクトが削除された場合のみ実行するように記載
  • .spec.subscriber にアプリケーション(py-func-del)を定義

双方が定義できたことを確認します。

最後に TriggerMesh の定義を更新します。

apiVersion: sources.triggermesh.io/v1alpha1
kind: AWSS3Source
metadata:
  name: demo-bucket
spec:
  arn: arn:aws:s3:::tapdemobucket

  eventTypes:
  - s3:ObjectCreated:*
  - s3:ObjectRemoved:*

  auth:
    iamRole: $ROLE_ARN
  sink:
    ref:
      apiVersion: eventing.knative.dev/v1
      kind: Broker
      name: default

ポイントとして、 .spec.sink の定義を直接アプリケーションではなく、Broker に向けます。
この状態で、Amazon  S3 のオブジェクトを生成後、削除します。すると、ファイル生成時および、削除時に違うコンテナに起動し、振り分けられている結果になります。

生成時は以下のコンテナ (py-func-event)

削除時は以下のコンテナ (py-func-del)

なお、この例ですら、非常にシンプルなものです。DeadletterQueue や、別イベントの起動など、より複雑なことものできます。いずれせによ、クラウド上のイベントを連携させることに成功しました。

サービス連携をコンテナでやる意義

さて、前章まで連携の仕方を紹介しました。しかし、おそらく「できるのは、わかったけどなぜ、これをコンテナ / Kubernetes 上でやるの?」という疑問がでてきたと思います。 TriggerMesh のページにもこの点の回答があるので是非ご参照ください。この回答を踏まえた上で、コンテナでこのやり方を進める意義について論じます。

端的にいうと「ベンダーロックイン」に関わる部分です。冒頭部、「このユースケース(サービス連携)に限って言えばサーバレスの独壇場」と触れました。しかし、逆をいえば、クラウドプロバイダと強烈な密結合を産むことになります。結果として、クラウドプロバイダのメリットもデメリットも全てを受け入れざるをえません。

今回のようなサービス連携をコンテナでやることで、前回のブログで紹介した以下の点も享受できます。

  1. プロバイダ側が設けた制約を意識しなくてよい
  2. Kubernetes 上に配置されるので、コストコントロールがしやすい = 不必要に複雑な構成にしなくてよい
  3. 気軽にサーバレス機能のオンオフ(前例でいうBP_FUNCTION 変数)ができるので従来型開発と共存しやすい

さらに、今回のブログでは イベント元として S3 でお見せしましたが、これに対する依存度も解消されるので、別プロバイダのオブジェクトストレージにも移行もしやすくなります。より柔軟に「イベント」を取り扱うことができます。

なお、コンテナも完璧ではないです。今回紹介したサービス連携ですが、はっきり言ってしまえば、Amazon Lambda の構成に比べれば難易度が高いです。CLI 中心のため、GUI や開発支援の機能差もまだ開きが大きです。なので、軽量な処理に関しては、Amazon Lambda のほうが分があります。とはいえ、繰り返しですが、サーバレスが独壇場であったユースケースにおいて、コンテナも選択肢に加えられることは大きな前進です。

今回の一連のブログで、目指したテーマが「和解」です。「サーバレス vs コンテナ」ですが、本来はユースケースで柔軟にサーバレスとコンテナを選択するべきです。ところが、比較するにも、開発スタイルやユースケースがあまりにかけ離れている両者を同じように投資・人材育成する決断をすることは難しかったと思います。TAP は、様々な機能を盛り込み両者の差をなくしていくことに注力しています。結果として、より統制をとりつつ、同じ人材でよりコンテナとサーバレスをスムーズに行き来ができる、そんなプラットフォームを目指しています。

TAP でのサーバレス支援開発の機能は、今後のリリースでも盛り込まれる予定です。さらなるアップデートがあったときに、Vol3. さらにはその先をとりあげます。

まとめ

今回は連載記事の2回目、TAP のサーバレス開発を支援する機能、その中の Cloud Native Runtimes を紹介させていただきました。VMware ブログでは、Tanzu Application Platform の最新機能を引き続きご紹介していきます。