Oracle FunctionsからAutonomous Databaseに問い合わせる (The Quick, Easy & Completely Secure Way) (2022/04/01)

Oracle FunctionsからAutonomous Databaseに問い合わせる (The Quick, Easy & Completely Secure Way) (2022/04/01)

https://blogs.oracle.com/developers/post/querying-autonomous-database-from-an-oracle-function-the-quick-easy-completely-secure-way

投稿者:Todd Sharp


Autonomous Databaseインスタンスへの接続については、過去に何度もブログ記事を書いてきました。ツールやサービス、フレームワークが成熟するにつれて、ベストプラクティスは進化しています。今回は、ツールや言語の現状を踏まえて、このトピックを再検討してみたいと思います。ここで説明する方法は、Autonomous Databaseインスタンスに接続する最も安全な方法であると確信していますし、Autonomous Databaseウォレットをシークレットに保存するなど、あまり理想的でない方法を必要とした以前の方法よりもさらに簡単に行えます。この記事を公開した時点で、以下のブログ記事は廃止されています。



それはさておき、今回のアプローチについてお話します。今回はMicronautというフレームワークを使って、サーバーレスファンクションを作ってみます。Micronautはいくつかの利点を私たちに与えてくれます。1 つは、Micronaut には広範な Oracle Cloud モジュールがあり、OCI シークレットサービスへの統合を提供して、データベースのユーザー資格情報を安全でない方法で保存していないことを保証してくれます。さらに、Autonomous Databaseのウォレットを自動的にダウンロードするMicronautの機能を利用して、ウォレットの取得に関わる余分なステップを回避し、コードの簡潔性と保守性を維持します。最後に、GraalVMを使用してファンクションのネイティブイメージバージョンを作成し、ファンクションのコールドおよびホットスタート時間を劇的に改善し、より良いランタイムパフォーマンスを可能にします。このチュートリアルでは、プロセス全体を通して説明しますが、行き詰まったり、もっと読みたくなったりした場合は、ドキュメントを参照してください。以下は、この投稿で行う手順です。



シークレットの作成


Micronaut Oracle Cloudモジュールは、機密事項を安全かつセキュアに保管するための優れたオプションを提供します。ドキュメントが充実しているので、必要に応じて参照してください。OCI Vaultにシークレットを作成することについてはすでにご存じだと思いますので、この記事を短くまとめるために、そのプロセス全体をカバーすることはしません。今回のサーバーレスファンクションでは、Vault に 4 つのシークレットが必要です。


  1.     ATP_USER: Autonomous DB インスタンスのユーザー
  2.     ATP_PASSWORD:AutonomousDBインスタンスのパスワード
  3.     ATP_OCID:Autonomous DBインスタンスのOCID
  4.     ATP_WALLET_PASSWORD: ウォレット内の鍵を暗号化するパスワード。8文字以上で、少なくとも1つの文字と1つの数字または1つの特殊文字を含まなければなりません。


Micronautはこれらの名前を持つシークレットを探し、以下のアプリケーションで設定変数を作成するため、これらのシークレットには上記のような名前を付けてください。



Vaultにシークレットを作成したら、VaultのOCIDとVaultのコンパートメントのOCIDを収集します。



Micronaut Functionアプリケーションの作成


次に、サーバーレスファンクションを実現するためのJavaアプリケーションを作成しましょう。Micronautに慣れていない方でも、ストレスはありません! Micronautと他の一般的なJavaフレームワークの間に大きな違いはありませんし、このファンクションはあなたにとってフレームワークの良い、穏やかな入門になるでしょう ファンクションの作成にはMicronaut CLIを使用しますので、ローカルにインストールされていることを確認してください。

$ mn create-function-app atp-auto-wallet-fn --features oracle-function

$ cd atp-auto-wallet-fn


CLIをインストールしたくない場合は、Micronaut Launchを使ってアプリケーションを作成する方法もあります。そのルートを選択した場合、以下の入力を入力してアプリを生成し、ダウンロードしてローカルディレクトリに解凍します。



さて、Micronautアプリケーションを生成したら、お気に入りのIDEで開いて、設定を始めましょう。



アプリケーションの設定


アプリケーションを設定するために、ビルドスクリプトを変更する必要があります。build.gradleを開き、以下のエントリを探します。

dockerBuild {
images = ["[REGION].ocir.io/[TENANCY]/[REPO]/$project.name:$project.version"]
}
dockerBuildNative {
images = ["[REGION].ocir.io/[TENANCY]/[REPO]/$project.name-native:$project.version"]

}


これらのブロックのイメージパスは、最終的に生成するDockerイメージが格納されるOCIR(OCI Container Registry)内の場所を定義しています。REGION]、[TENANCY]、[REPO]には適切な値を代入しますが、$トークン表記を使用している値は、後でタスクを呼び出す際に入力されるため、そのままにします。更新すると、私のエントリーは以下のようになりました。


dockerBuild {
images = ["phx.ocir.io/toddrsharp/atp-auto-wallet/$project.name:$project.version"]
}
dockerBuildNative {
images = ["phx.ocir.io/toddrsharp/atp-auto-wallet/$project.name-native:$project.version"]

}


次に、いくつかの依存関係を追加する必要があります。


implementation("io.micronaut.oraclecloud:micronaut-oraclecloud-atp") //1
implementation("io.micronaut.oraclecloud:micronaut-oraclecloud-sdk") //2
implementation("io.micronaut.oraclecloud:micronaut-oraclecloud-vault") //3
implementation("io.micronaut.sql:micronaut-jdbc-ucp") //4

implementation("com.oracle.database.jdbc:ojdbc11-production:21.1.0.0") //5


  1.     Autonomous Databaseモジュール
  2.     OCI SDKモジュール(Vaultが必要とするもの)
  3.     OCI Vaultモジュール(シークレットの取得と解読に使用)
  4.     コネクションプーリング用UCPモジュール
  5.     OJDBCドライバ


OCI Vault統合を使用するには、/src/main/resources/bootstrap.ymlに新しいファイルを作成する必要があります。このファイルには、上記で収集したデータVaultとデータVaultコンパートメントの OCID 値を入力する必要があります。また、このファンクションをローカルでテストできるように、ローカルの OCI 設定へのパス、プロファイル、リージョンを入力しました。


micronaut:
application:
name: atpAutoWalletFn
config-client:
enabled: true
oci:
vault:
config:
enabled: true
vaults:
- ocid: ocid1.vault.oc1.phx...
compartment-ocid: ocid1.compartment.oc1...
path-to-config: ~/.oci/config
profile: DEFAULT

region: US-PHOENIX-1


注:DockerイメージにOCI設定ファイルを含める必要はありません。テナントのリソースプリンシパル認証を設定すると、OCI ファンクションをデプロイする際に Micronaut モジュールが適切にその認証プロトコルを利用するようになります。


次に、/src/main/resources/application.yml にある設定にデータソースを追加する必要があります。以下の設定をコピーして貼り付けることができます。Vaultに上記の一致する名前のシークレットを作成したと仮定します。


micronaut:
application:
name: atpAutoWalletFn
datasources:
default:
dialect: ORACLE
username: ${ATP_USER}
password: ${ATP_PASSWORD}
ocid: ${ATP_OCID}
walletPassword: ${ATP_WALLET_PASSWORD}

connection-factory-class-name: oracle.jdbc.pool.OracleDataSource


MicronautはデータVaultからシークレットを取得し、それをデコードして、実行時に適切な値をデータソース設定に入力します。



データベースを照会するファンクションを修正


次のステップは、データベースへの問い合わせを行うようにファンクション自体を変更することです。mainファンクションクラス(/src/main/java/atp/auto/wallet/fn/Function.javaにあります)を開くと、ファンクションは以下のように表示されるはずです。


@Singleton
public class Function extends OciFunction {
@Inject
TenancyIdProvider tenantIdProvider;
@ReflectiveAccess
public String handleRequest() {
String tenancyId = tenantIdProvider.getTenancyId();
return "Your tenancy is: " + tenancyId;
}

}


注入された TenancyIdProvider と handleRequest() メソッドの本体は、このファンクションでは必要ないので削除します。データベースクエリを実行するために、DataSource Bean を注入し、handleRequest() メソッドでクエリを実行するためにその Bean を使用することができます。ResultSetをListに変換する便利なヘルパーファンクションと、Jacksonでいくつかのシリアライズを行い、ファンクションからMapオブジェクトのJSONシリアライズされたリストを返すことができるようになりました。


@Singleton
public class Function extends OciFunction {
@Inject
DataSource dataSource;
@ReflectiveAccess
public String handleRequest() throws SQLException, JsonProcessingException {
Connection conn = dataSource.getConnection();
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery("select id, first_name, last_name from users");
return new ObjectMapper().writeValueAsString(convertResultSetToList(resultSet));
}
private List<HashMap<String,Object>> convertResultSetToList(ResultSet rs) throws SQLException {
ResultSetMetaData md = rs.getMetaData();
int columns = md.getColumnCount();
List<HashMap<String,Object>> list = new ArrayList<HashMap<String,Object>>();
while (rs.next()) {
HashMap<String,Object> row = new HashMap<String, Object>(columns);
for(int i=1; i<=columns; ++i) {
row.put(md.getColumnName(i),rs.getObject(i));
}
list.add(row);
}
return list;
}

}


ファンクションのテスト


Micronautは私たちのために親切にもテストを作成してくれましたので、必要に応じて拡張することができます。このテストは /src/test/java/atp/auto/wallet/fn/FunctionTest.java で見つけることができます。以下はその様子です。


public class FunctionTest {
@Test
public void testFunction() {
FnTestingRule rule = FnTestingRule.createDefault();
rule.givenEvent().enqueue();
rule.thenRun(Function.class, "handleRequest");
String result = rule.getOnlyResult().getBodyAsString();
assertNotNull(result);
}

}


これは単なるデモなので、呼び出されたファンクションの結果がヌルでないことを確認するテストだけで十分です。あなたのビジネス要件に合わせて、自由にテストを更新してください。次に、このテストを実行します。


$ ./gradlew test


そして完了したら、build/reports/tests/index.htmlにあるレポートを開いてください。成功したテストレポートは、以下のようになります。





Dockerコンテナのビルドとプッシュ


アプリケーションから Docker イメージをビルドして、Container Registry にプッシュする準備ができました。そのために、以下のコマンドを実行します。

$ ./gradlew dockerBuild

$ ./gradlew dockerPush


dockerPushコマンドの出力はこのような感じになっているはずです。

> Task :dockerPush

Pushing image 'phx.ocir.io/toddrsharp/atp-auto-wallet/atp-auto-wallet-fn:0.1'.


上のメッセージは、私たちのイメージが OCI Container Registry に楽しく登録されたことを知らせています。では、OCI で「アプリケーション」を作成する作業に移りましょう。



OCI でのアプリケーションの作成


Oracle Functionsは、クラウド上で関連する「アプリケーション」エンティティを必要とします。Oracle Functionsは、アプリケーションを使用して、グループ化されたファンクションの設定を共有します。OCIコンソールまたはCLIで作成することができます。私はCLIの方が簡単だと思いますが、この場合、アプリケーションに関連付けるサブネットの適切なOCIDを知っておく必要があります。私はよくOCI CLIを使うので、よく使う値を保存するためにいくつかの環境変数を作成し、必要なときに覚えたり調べたりする必要がないようにしています。今回は、OCI_FAAS_SUBNETとOCI_FAAS_COMPARTMENTの環境変数を突っ込みました。


$ oci fn application create \
--display-name=atp-wallet-demo \
--subnet-ids='["'$OCI_FAAS_SUBNET'"]' \

--compartment-id=$OCI_FAAS_COMPARTMENT


このJSON出力のようなレスポンスになるはずです。


{
"data": {
"compartment-id": "ocid1.compartment.oc1...",
"config": {},
"defined-tags": {
"Oracle-Tags": {
"CreatedBy": "[redacted]",
"CreatedOn": "2022-03-29T17:07:43.860Z"
}
},
"display-name": "atp-wallet-demo",
"freeform-tags": {},
"id": "ocid1.fnapp.oc1.phx...",
"image-policy-config": null,
"lifecycle-state": "ACTIVE",
"network-security-group-ids": [],
"subnet-ids": [
"ocid1.subnet.oc1.phx..."
],
"syslog-url": "",
"time-created": "2022-03-29T17:07:44.502000+00:00",
"time-updated": "2022-03-29T17:07:44.502000+00:00",
"trace-config": {
"domain-id": "",
"is-enabled": false
}
},
"etag": "de9758b214f461ac549683b708118fd345378c80f1edd8120288d472817f0934"

}


後でアプリケーションIDを使う必要があるので、ocid1.fnappというフォーマットを使っているレスポンスJSONから「id」をコピーして手元に置いておきましょう。



OCIでファンクションを作成


さて、いよいよ Micronaut Application から生成した Docker イメージを使用するサーバーレスファンクションを OCI で作成します。ここでもCLIを使用しています。前項で取得したばかりのOCIアプリケーションの「id」を差し込んでいます。このファンクションには好きな名前を付けることができます。このファンクションは少ないメモリでも問題なく動作するはずですが、2GBのRAMを付与しておけば間違いないでしょう。最後に、--image 引数が OCI Container Registry にある Docker イメージの場所と一致していることを確認します。


$ oci fn function create \
--application-id=ocid1.fnapp.oc1.phx... \
--display-name=atp-auto-wallet-fn \
--memory-in-mbs=2048 \

--image=phx.ocir.io/toddrsharp/atp-auto-wallet/atp-auto-wallet-fn:0.1


上記のコマンドの結果、別のJSONレスポンスが返されます。そのレスポンスは次のようなものに近いでしょう。


{
"data": {
"application-id": "ocid1.fnapp.oc1.phx...",
"compartment-id": "ocid1.compartment.oc1...",
"config": {},
"defined-tags": {
"Oracle-Tags": {
"CreatedBy": "[redacted]",<br> "CreatedOn": "2022-03-29T17:10:35.939Z"
}
},
"display-name": "atp-auto-wallet-fn",
"freeform-tags": {},
"id": "ocid1.fnfunc.oc1.phx...",
"image": "phx.ocir.io/toddrsharp/atp-auto-wallet/atp-auto-wallet-fn:0.13",
"image-digest": "sha256:e66ab37e888a12e3ec961e4a26a758891afa1b4b6520e0369af44ba933d75290",
"invoke-endpoint": "https://khenedvczma.us-phoenix-1.functions.oci.oraclecloud.com",
"lifecycle-state": "ACTIVE",
"memory-in-mbs": 2048,
"time-created": "2022-03-29T17:10:36.185000+00:00",
"time-updated": "2022-03-29T17:10:36.185000+00:00",
"timeout-in-seconds": 30,
"trace-config": {
"is-enabled": false
}
},
"etag": "234ac973ed5753b335f97f18ff2e81c6a9fb578b5d5220196ab2e52acaba09ab"

}


ファンクション「id」(ocid1.fnfunc...)がすぐに必要になるので、手元に置いておくとよいでしょう。



ファンクションを呼び出し


OCI CLI を使ってファンクションを起動し、すべてが期待通りに動作していることを確認します。


$ oci fn function invoke \
--function-id=ocid1.fnfunc.oc1.phx... \
--body="" \

--file -


以下は、私のファンクションがテスト呼び出しから返したJSONの例です(jqでプリチー化されています)。


[
{
"LAST_NAME": "Sharp",
"FIRST_NAME": "Todd",
"ID": "CE51F8AEFBD6C772E0539914000A4500"
},
{
"LAST_NAME": "Sharp",
"FIRST_NAME": "Rhonda",
"ID": "CE51F8AEFBD7C772E0539914000A4500"
}

]



ファンクションを更新


もちろん、時間の経過とともに機能が変更される可能性もあります。その時は、必要に応じてMicronautアプリケーションを更新してください。更新された変更をデプロイするとき、最初にbuild.gradleのバージョン番号を更新することを確認してください。

version = "0.2"


そして、Dockerイメージをビルドしてプッシュしたら、OCI CLIを使って更新されたイメージを指し示す。

$ oci fn function update \

  --function-id= ocid1.fnfunc.oc1.phx... \<br>  --image=phx.ocir.io/toddrsharp/atp-auto-wallet/atp-auto-wallet-fn:0.2



ボーナス:ネイティブイメージとしてデプロイ


また、ボーナスとしてGraalVMのネイティブイメージとしてファンクションをビルドし、デプロイすることができます。このネイティブイメージは、私たちのファンクションのパフォーマンスを向上させる結果になります。そのためには、build.gradleのgraalvmNativeブロックを少し修正する必要があります。今現在は、以下のような感じになっているはずです。


graalvmNative {
binaries.configureEach {
buildArgs.addAll(
"-H:+StaticExecutableWithDynamicLibC",
"-Dfn.handler=atp.auto.wallet.fn.Function::handleRequest",
"--initialize-at-build-time=atp.auto.wallet.fn"
)
}

}


このセクションにReflectionconfigのJSONファイルの引数を追加する必要があります。上記のブロックを以下のブロックのように修正します。


graalvmNative {
binaries.configureEach {
buildArgs.addAll(
"-H:+StaticExecutableWithDynamicLibC",
"-Dfn.handler=atp.auto.wallet.fn.Function::handleRequest",
"--initialize-at-build-time=atp.auto.wallet.fn",
"-H:ReflectionConfigurationFiles=/home/app/resources/reflectionconfig.json"
)
}

}


次に、/src/main/resources/reflectionconfig.jsonにファイルを作成し、以下のJSONを入力します。このファイルは、この必要なクラスに対して、リフレクティブアクセスを有効にするようGraalVMに指示します。


[{
"name" : "oracle.security.crypto.cert.ext.ExtKeyUsageExtension",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredFields" : true,
"allPublicFields" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true

}]


ヘッズアップ! 近日中に、上記のリフレクションコンフィグを手動で指示する必要がなくなります。詳細については、このイシューに注目してください。


ネイティブイメージをビルドしてプッシュするには、以下のGradleタスクを使用します。

$ ./gradlew dockerBuildNative

$ ./gradlew dockerPushNative



ここで、OCI Function を更新して、ネイティブの Docker イメージを使用するようにします。


$ oci fn function update \
--function-id= ocid1.fnfunc.oc1.phx... \

--image=phx.ocir.io/toddrsharp/atp-auto-wallet/atp-auto-wallet-fn-native:0.3


呼び出してテストします。

$ oci fn function invoke \
--function-id=ocid1.fnfunc.oc1.phx... \
--body="" \

--file -




パフォーマンス


GraalVMのネイティブイメージは、JVM自体で私たちのファンクションを実行するよりも、大幅に改善されたパフォーマンスを提供します。


このデモのために、このファンクションの非ネイティブとネイティブの実装を比較するために、いくつかの初歩的なテストを実行しました。さらなる最適化が可能ですが、このファンクションをネイティブイメージとしてデプロイするだけで、コールドスタート時間が55%も改善されました。


#non-native
0.53s user 0.22s system 4% cpu 18.521 total
#native

0.48s user 0.18s system 8% cpu 8.315 total


ホットスタートで見た場合、パフォーマンスは23%向上しています。


#non-native
0.49s user 0.18s system 25% cpu 2.590 total
#native

0.45s user 0.16s system 30% cpu 2.042 total



まとめ


この投稿では、Autonomous Databaseインスタンスに接続しクエリを実行するOCI FunctionとしてMicronautアプリケーションを作成し、デプロイしました。HTTPSサーバーレスファンクションのためにOCI API Gatewayと統合することも、検討すべき選択肢の一つでしょう。Micronaut Oracle Cloud モジュールのドキュメントで詳細を確認してください。


Image by Bethany Drouin from Pixabay

コメント

このブログの人気の投稿

Oracle Database 19cサポート・タイムラインの重要な更新 (2024/11/20)

Oracle APEXのInteractive Gridで、Oracle Formsと比較して、重複行の検証を制御/通過させる方法 (2022/07/21)

Oracle APEX 24.1の一般提供の発表 (2024/06/17)