[Functions] クラウドイベントからFunctionsを自動的に呼び出し/Invoking Functions Automatically With Cloud Events

原文はこちら

ここDeveloper Blogでわたしはいくつかサーバレスファンクションを作成、そして実行するやり方を説明するポストをいくつか書いてきましたが、これまでのところそれらのポストでは、ファンクションをマニュアルで呼び出す、あるいはOCI Java SDKを使ったプログラムから呼び出すやりかただけにしか言及してきませんでした。今回のポストではファンクション呼び出しのもっとパワフルなやり方、すなわちクラウドテナンシー内での特定のアクションに対応して自動で呼び出しを行うやり方を説明したいと思います。このやり方では最近リリースされたCloud Eventsサービスを活用していきます。
このポストでわたしたちが見ていくことになるのは、あなたのテナンシーのObject Storageバケット内でオブジェクトが更新された際にひとつのOracle Functionをトリガーする、という例です。クラウドイベントを使って、画像イメージからメタデータを取得するファンクションを呼び出します。
このポストの内容に入る前に、以下に関連する以前のポストを紹介します。Oracle Functionsを初めて知ったという方は、これらのポストを読むことで入門的な知識、また、ファンクションを作成してマニュアルで起動する際に生じる疑問への答えが見つかるでしょう。

サーバレスアプリケーションの作成

まずはファンクションとして使うための新しいサーバレスアプリケーションを作成するとことから始めます。以前のわたしのポストでは、Fn CLIを使用してアプリケーションを作成していました。なので今回はクラウドダッシュボードを使ってみましょう。サイドバーからDeveloper Services -> Functionsと進んでください。
次に、'Create Application'をクリックして出てくるページの入力欄を埋めましょう。Fn CLIではサブネットのOCIDを直接指定する必要がありましたが、ここでは作成済のVCNがリストされるので適切なサブネットを選べるようになっていますね:
また、スクリーンショットでもう一点注目してほしいのは出力されたログをあとから見られるよう、PapertrailのログURLを指定していることです。こういうこともできるわけですね。
では、ローカルの端末に戻ってアプリケーションを作成しましょう:
fn init --runtime java cloud-events-demo-fn
 pom.xml ファイルにふたつの依存対象を追加します。ひとつは cloudevents-apiで、もうひとつはあとでメタデータを展開するときに使う metadata-extractor です。
<dependency>
    <groupId>io.cloudevents</groupId>
    <artifactId>cloudevents-api</artifactId>
    <version>0.2.1</version>
</dependency>
<dependency>
    <groupId>com.drewnoakes</groupId>
    <artifactId>metadata-extractor</artifactId>
    <version>2.12.0</version>
</dependency>
view rawpom.xml hosted with ❤ by GitHub
次にファンクション用のテストを書きましょう。先にご自身でObject Storageバケットにまずサンプル画像をアップロードしておく必要があります。その後、以下のJSONをテストに使ってください(テスト用の画像名、テナンシー名、バケット名は適宜置き換えてください):
{
"cloudEventsVersion": "0.1",
"eventID": "[UUID]",
"eventType": "com.oraclecloud.objectstorage.createobject",
"source": "objectstorage",
"eventTypeVersion": "1.0",
"eventTime": "2019-07-31T17:41:03Z",
"schemaURL": null,
"contentType": "application/json",
"extensions": {
"compartmentId": "ocid1.compartment.oc1...."
},
"data": {
"compartmentId": "ocid1.compartment.oc1....",
"compartmentName": "[compartment name]",
"resourceName": "[test image name.jpg]",
"resourceId": "",
"availabilityDomain": "all",
"freeFormTags": {
},
"definedTags": {
},
"additionalDetails": {
"eTag": "[UUID]",
"namespace": "[tenancy name]",
"archivalState": "Available",
"bucketName": "[bucket name]",
"bucketId": "ocid1.bucket.oc1.phx..."
}
}
}
view rawcloud-event.json hosted with ❤ by GitHub
テスト画像をアップロードして、JSONを修正したら、以下のようにテストができます:
package com.example.fn;
import com.fnproject.fn.testing.*;
import org.junit.*;
import static org.junit.Assert.*;
public class HelloFunctionTest {
    @Rule
    public final FnTestingRule testing = FnTestingRule.createDefault();
    @Test
    public void shouldReturnGreeting() {
        String event = "[your test image event JSON]";
        testing.givenEvent().withBody(event).enqueue();
        testing.thenRun(HelloFunction.class, "handleRequest");
        FnResult result = testing.getOnlyResult();
        assertTrue(result.isSuccess());
    }
}
テストはこの時点では失敗するはずです。それではテストが通るようにファンクションを実装していきましょう。Oracle Cloud EventsはCNCF Cloud Eventsに準拠しているので、インカミングのパラメータを CloudEvent として安全に型にすることができ、ファンクションがトリガーされるとFDKがパラメータを適切にシリアライズしたうえで扱ってくれます。CloudEventデータが取得できれば、画像(今回はパブリックな画像)を示すURLを構成でき、URLをストリームとして開いてメタデータエクストラクタにわたすことができます。
package com.example.fn;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.cloudevents.CloudEvent;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Map;
public class HelloFunction {
    public Metadata handleRequest(CloudEvent event) throws IOException, ImageProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        Map data = objectMapper.convertValue(event.getData().get(), Map.class);
        Map additionalDetails = objectMapper.convertValue(data.get("additionalDetails"), Map.class);
        String imageUrl = "https://objectstorage.us-phoenix-1.oraclecloud.com/n/" +
                additionalDetails.get("namespace") +
                "/b/" +
                additionalDetails.get("bucketName") +
                "/o/" +
                data.get("resourceName");
        InputStream imageStream = new URL(imageUrl).openStream();
        Metadata metadata = ImageMetadataReader.readMetadata(imageStream);
        System.out.println(objectMapper.writeValueAsString(metadata));
        //todo: do something with the metadata
        return metadata;
    }
}
view rawHelloFunction.java hosted with ❤ by GitHub
この時点でテストが通るようになっており、ファンクションをアプリケーションに以下のコマンドでデプロイできます:
fn deploy --app cloud-events-demo
以下のようにイベントJSONをわたすことでこれをマニュアルで実行することができます:
echo "[event JSON string]" | fn invoke cloud-events-demo cloud-events-demo-fn

ファンクションが想定通り動くことが確認できたでしょう。では、クラウドイベントルールを作成しましょう!

Cloud Eventの作成

新しいクラウドイベントルールを作成するには、サイドバーメニューからApplication Integration -> Events Serviceを選択してください:
'Create Rule'をクリックするとフォームが表示されるので、埋めていきます:
上記のスクリーンショットでの条件に注目してください。わたしは'Service Name'として'Object Storage'を選択して、また、'Event Type'として'Object Storage - Create Object'を選択しています。イベントをAttributeでフィルタすることもできますが、今回はわたしの特定のバケットへのオブジェクトのアップロードを引っ掛けたいだけです。イベントタイプやフィルター方法はいくつかのものから選ぶことができますので、詳細を知りたい方はサービスのドキュメントを参照してください。
残りの部分は以下のようにします:
ここでこのクラウドイベントが発動したときにとりたいアクションを指定しています。この例ではわたしたちは作成したサーバレスファンクションを呼びたいわけですが、Oracle Streamにパブリッシュしたり通知を送信したりもできますよ。'Create Rule'をクリックするとこのルールが即座に利用可能になります。

ルールのテスト

ルールをテストするには、単純にルールで指定したバケットにファイルをアップロードすればいいだけです。そのやり方のひとつにOCI CLIを使う方法があります:
oci os object put --bucket-name object-upload-demo-public --file /Users/trsharp/Pictures/test.jpg
数秒待ってからあなたのPapertrailログをチェックしてみると、System.outと書いたメタデータが出ているでしょう。以下はわたしがアップロードしたテスト画像のものをフォーマットしたものです:

まとめ

このポストでは画像のメタデータを展開するサーバレスファンクションを作成して、指定したObject Storageのバケットに画像がアップロードされると自動的に起動されるようにしました。クラウドイベントルールはOCIテナンシーの中での様々なアクション、例えばDatabaseやObject Storageに関するアクティビティーに結び付けられ、サーバレスファンクションや通知、Oracle Streamへのパブリッシュを自動的にトリガーすることが学べました。

[Kubernetes] OCI Service BrokerとATPデータベースのインテグレーション実践編/Integrating OCI Service Broker with Autonomous Transaction Processing in the Real World

原文はこちら

最近のOracle Cloud Infrastructureはいろいろと進歩してます!Autonomous Databaseにサーバレスファンクション、AIが組み込まれたアプリケーション、などなど他にも盛りだくさんです。そして、こうした大物たちも派手ではあるんですが、実のところOracle Cloudのよいところはそのディティールにあります。そうしたディティールのひとつが、最近リリースされたOCI Service Broker for Kubernetesです。リリース1.2.0では、Oracle Cloud InfrastructureのコンソールあるいはCLIで作成した既存サービスのインスタンスとのサービスバインディングをService Brokerを使って作成できるようにサポートを拡張しました。

Service Brokerは何をしてくれるのか?

Service Brokerについては詳しくないんだよね、という方のために説明しておくと、それがやってくれるのは以下のようなアクションです:
  • あなたのアプリケーションが必要とする、例えばObject StorageバケットやAutonomous Transaction Processing Databaseのような、Oracle Cloud Infrastructureのサービスのインスタンスを作成する
  • Service Brokerによって作成されたそれらのサービスへのバインディングを作成する
ということで、そうしたサービスを使うアプリケーションをデプロイすることができるわけです。
サービスをアンデプロイしたあとには、Service Brokerを使って以下のアクションを行えます:
  • 作成したサービスバインディングの削除
  • 作成したサービスの削除
こうしたことを行うことで、アプリケーションの依存対象をアプリケーションとともにカプセル化することができ、したがってそれら全てのデプロイやアンデプロイをひとつの単位として扱うことができるようになります。さらに、これらを単一のツール、kubectlで行うことができます。コンテナで稼働するアプリケーションは稼働環境として必要なものを揃えられますが、コンテナ外部に存在している依存対象であるクラウドサービスについてはそうではありませんでした。Service Brokerはこれらもリンクできるので、これによりアプリケーションの移植性が高められます。

Service Broker 1.2.0まで

理屈のうえでは、アプリケーションはどれも自身が依存するクラウドサービスの専有のセットを持っている、と言えます。しかし実践では、多くのアプリケーションが他のマイクロサービスとひとつのクラウドサービスインスタンスを共有していたり、あるいはその環境にもともとあったクラウドサービスインスタンスを使いまわしたりしています。例えば、多くのマイクロサービスアーキテクチャではパブリッシュ/サブスクライブメッセージングを非同期実行のために使っています。あるマイクロサービスが与えられたストリーム、あるいはメッセージングエンドポイントに対してリッスンしている場合、メッセージをパブリッシュするアプリケーションはどこにパブリッシュするべきかを知らなければなりません。もうひとつ例を挙げると、リレーショナル・データベースです。多くの場合、Dev-Testのようなシナリオでさえ、既に存在しているデータベースに事前にテストデータが積まれており、アプリケーションをテストするための準備が完了しています。こうしたケースではマイクロサービスは専有のデータベースを必要とせず、むしろ既存のデータベースに接続することが必要になります。
この点がService Brokerのデザインとマイクロサービスのユースケースとに少々ミスマッチが起こっていたところでした。つまり、サービスのライフサイクルはService Brokerが管理することが想定されており、そしてService Brokerは自身で管理しているサービスのみにサービスバインディングを作成できる、というわけです。あなたのアプリケーションが外部のプロセスによって作成された既存のサービスインスタンスに接続することを必要としている場合、Service Brokerはそのためのサービスバインディングを作成できませんでした。

Service Broker 1.2.0で解決!

リリースされたこのバージョンでは、DevOpsチームはKubernetes環境にアプリケーションをデプロイする際に、既存のOracle Cloud Infrastructureサービスインスタンス(Autonomous Transaction Processing、Autonomous Data Warehouse、Object StorageおよびStreaming)を活用することができます。
Service Brokerを既存のサービスインスタンスと使うワークフローは、通常のService Brokerパターンと同様です:
  1. サービスインスタンスの作成: そうですね、このケースでは実のところサービスインスタンスを作成するのではなく、既存のサービスインスタンスをService Brokerに対して登録しなければなりません。Service Brokerがサービスバインディングを作成できるのは、Service Brokerが作成したか、あるいは認識しているサービスに対してのみです。というわけでサービスバインディングを作成できるようにするためには、既存のサービスインスタンスをService Brokerに教えてやらなければなりません。なお、このように登録した既存サービスインスタンスについては、Service Brokerはライフサイクルを管理しないという点にはご留意ください。Service Brokerを使って既存サービスインスタンスのパラメーターを更新することはできません。
  2. サービスバインディングの作成: このステップはService Brokerが作成したクラウドサービスインスタンスへの場合と同じです。
  3. アプリケーションのデプロイ: いつもと同じ。
  4. アプリケーションのアンデプロイ: いつもと同じ。
  5. サービスバインディングの削除: このステップはService Brokerが作成したクラウドサービスインスタンスへの場合と同じです。
  6. サービスインスタンスの削除: 登録した既存サービスインスタンスの場合にはこのステップはちょっと異なってきます。Service Brokerが削除するのは自身で作成したサービスだけです。登録したサービスについては、Service Brokerが削除するのはその登録だけです。
Autonomous Transaction Processingのワークロードタイプの既存Autonomous Databaseインスタンスを使って詳細を見ていきましょう。ここでの例では、わたしはOracle Cloud Infrastructureコンソールから作成したAutonomous Transaction Processingインスタンスを所持しています。
ここでわたしが既存のインスタンスを使うマイクロサービスをデプロイしたいとすると、以下で説明するようなステップを行うことになります。
これらのステップは、あなたがOracle Container Engine for Kubernetesクラスターに既にOCI Service Brokerをインストールしていることを前提としています。
このポストで使用している見本のYAMLファイルはhttps://github.com/oracle/oci-service-broker/tree/master/charts/oci-service-broker/samplesのサンプルを使っています。

前提の準備:データベースパスワードのKubernetesシークレットを作成

パスワード管理のベストプラクティスとして、Kubernetesシークレットを作成してデータベースパスワードとOracle Walletパスワードを格納しmしょう。暗号化を強く推奨します。
シナリオを通して、データベースのADMINパスワードとサービスバインディングでのOracle Wallet作成の際に使うパスワードが必要になります。なので、passwordwalletPasswordの値をbase64エンコードされたかたちでシークレットを作成します。
echo '{ "password" : "yourPassword1@here"}' | base64
echo '{ "walletPassword" : "yourWalletPwd2@here"}' | base64
結果、YAMLファイルは以下のようになります:
以下のコマンドでシークレットを作成してください:
kubectl create -f /Users/path/atp-demo-secret-2.yaml

Step 1: OCI Service Brokerへの既存インスタンスの登録

シークレットを作成したら、既存Autonomous Transaction ProcessingインスタンスをService Brokerに登録しましょう。この登録はService Brokerにどこにインスタンスがあるのかを教えてあげるためのプロビジョニングのいち手順です。以下のパラメーターが必要になります:
  • name: インスタンスの表示名
  • ocid: インスタンスのOCID
  • compartmentId: インスタンスをプロビジョンしたコンパートメントのOCID
  • provisioning: プロビジョニングフラグ、falseをセット
注意:Oracle Cloud Infrastructureの各サービスは登録を作成するために個別のパラメーターを持っています。詳細はドキュメントを参照ください。
インスタンスのYAMLファイルは以下のようになります:
以下のコマンドでインスタンスを登録してください:
kubectl create -f /Users/path/atp-existing-instance.yaml
svcat get instancesを実行すると、他のインスタンスとともにこのインスタンスが表示されます。例:
       NAME          NAMESPACE          CLASS             PLAN     STATUS  +------------------+-----------+----------------------+----------+-------+  
osb-atp-demo-1       default     atp-service            standard   Ready    
osb-demo-pre-exist   default     atp-service            standard   Ready    
testbucket           default     object-store-service   standard   Ready    
testbucket2          default     object-store-service   standard   Ready 
このようにして登録した既存サービスのインスタンスについてはOCI Service Brokerはライフサイクルを管理しないということは覚えておいてください。既存サービスインスタンスのパラメーターをService Brokerを使って更新することはできません。

Step 2: 既存インスタンスへのサービスバインディングの作成

Service Brokerに既存サービスを登録したら、残りのシナリオはService Brokerでインスタンスをプロビジョニングした場合と一緒です。そのインスタンスへのサービスバインディングを作成するには、WalletパスワードをパラメーターとするYAMLファイルが必要です。WalletパスワードはKubernetesシークレットにしておいたので、ここでのYAMLファイルはそのシークレットを参照します。
以下のコマンドでバインディングを作成してください:
kubectl create -f /Users/path/atp-binding-existing.yaml
サービスバインディングが作成されたことを確認するには、svcat get bindingsを実行します。以下が実行結果の例です:
             NAME              NAMESPACE        INSTANCE        STATUS 
+----------------------------+-----------+--------------------+--------+
  atp-demo-binding             default     osb-atp-demo-1       Ready  
  osb-demo-pre-exist-binding   default     osb-demo-pre-exist   Ready  
  test-bucket-binding          default     testbucket           Ready  

Step 3: アプリケーションのデプロイ

OCI Service Brokerソースコードに含まれるサンプルにはデプロイメントYAMLファイルが含まれており、これはinitコンテナを使ってAutonomous Transaction Processingサービスバインディングに含まれる値を取り出すやり方、およびそれらの値を環境変数やボリュームマウントに格納するやり方を説明しています。そのあとには、あなたのアプリケーションはそれらの値を使ってデータベースに接続することができます。

Steps 4–6: アンデプロイ、バインディングの削除、インスタンスの削除

これらのステップの詳細は通常のService Brokerユースケースと同一です。
ただし、Service Brokerによってプロビジョニングされたわけではないサービスインスタンスの削除では、実際にインスタンスが削除されるわけではないということを思い出してくださいね。この際に削除されるのはインスタンスの登録だけです。Service Brokerは自身で作成したサービスインスタンスについてのみ、ライフサイクルを管理できます。

参考情報

OCI Service Broker for Kubernetesの紹介は以下のブログポストを参照ください:
もっと詳しく知りたい、あるいはOCI Service Brokerを試してみたい場合は、OCI Service Broker page on GitHubを見に行ってみてください。
Oracle Cloud Infrastructureサービスの登録やバインディングについて詳細を知りたいなら、ドキュメントを参照ください。