Google Cloud FunctionsでnativeビルドしたJavaを動かす
Google Cloud FunctionsはJavaをサポートしない。しかし、GraalVMでビルドしたshared libraryをNodeJS経由で呼び出すことならできるのではないかと試してみた。
やること
- GraalVMで共有ライブラリをビルドする
- 共有ライブラリ内の関数を呼び出す関数をNodeJSで実装する
- JS関数に共有ライブラリを同梱しGoogle Cloud Functionsへデプロイ
GraalVMで共有ライブラリをビルドする
基本的にこちらと同様だが、GoogleCloudFunctionにデプロイするので、Linux向けの共有ライブラリをビルドする必要がある。
2019/04時点のGraalVM(Community Edition 1.0 RC15)のnative-imageは他のOS向けにビルドは出来ないようなので、MacなどではDockerを使うことになる。
具体的には、共有ライブラリを生成するコマンドを以下に置き換える。
docker run -it --rm -v $PWD:/work -w /work oracle/graalvm-ce:1.0.0-rc15 native-image --shared -H:Name=libnativeimpl -cp java
共有ライブラリ内の関数を呼び出す関数をNodeJSで実装する
お手軽にffi経由で呼び出すことにする。
準備
ffiをインストールする。ffiをインストールする過程で、node-gyp
がPython2.7を必要とするので、Python2.7もインストールしておく必要がある。
npm init -y
npm install ffi --save
jsコード
やっていることはこちらのCコードと同様だが、ヘッダファイル相当の情報を ffi.Library
でのロード時に指定する。
型の対応関係は以下の通り。
- C側で構造体へのポインタは
ref.refType(ref.types.void)
- 構造体へのポインタのポインタは
ref.refType(ref.refType(ref.types.void))
- 文字列は
ref.types.CString
また、構造体のポインタを渡すような場合は、先立って ref.alloc
で構造体のメモリを確保しておく必要がある。
その他の実装での注意点は以下の通り。
- あとで同梱してデプロイするので、実行ファイルと共有ライブラリを同じディレクトリに格納し、実行ファイルのあるディレクトリからロードするよう実装する
- GoogleCloudFunctionsのNodeJSのHTTPはexpress互換らしい
const ref = require('ref');
const ffi = require('ffi');
// ライブラリをロード
const libJava = ffi.Library(__dirname + '/libnativeimpl', {
graal_create_isolate: [
ref.types.int, [
ref.refType(ref.types.void),
ref.refType(ref.types.void),
ref.refType(ref.refType(ref.types.void))
]],
graal_tear_down_isolate: [
ref.types.int, [
ref.refType(ref.types.void)]
],
Java_org_pkg_apinative_Native_hello: [
ref.types.CString,
[ref.refType(ref.types.void)]],
})
exports.handler = (req, res) => {
const p_graal_isolatethread_t = ref.alloc(ref.refType(ref.types.void))
const rc = libJava.graal_create_isolate(ref.NULL, ref.NULL, p_graal_isolatethread_t)
if (rc !== 0) {
res.send('error on isolate creation or attach')
return;
}
const hello = libJava.Java_org_pkg_apinative_Native_hello(ref.deref(p_graal_isolatethread_t))
res.send(hello);
libJava.graal_tear_down_isolate(ref.deref(p_graal_isolatethread_t));
};
JS関数に共有ライブラリを同梱しGoogle Cloud Functionsへデプロイ
いよいよデプロイ。
当然GCPへの登録と、gcloud
のインストールを済ませておく。
上記.jsファイルとと libnativeimpl.so
を置いたディレクトリで以下のコマンドを実行。
gcloud functions deploy hello-graal-native-node --runtime nodejs8 --entry-point handler --trigger-http
1分ほど待ったのち、成功すれば以下のような表示がされるはず。
--trigger-httpDeploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
entryPoint: handler
httpsTrigger:
url: https://us-central1-xxxxxxxx.cloudfunctions.net/hello-graal-native-node
labels:
deployment-tool: cli-gcloud
name: projects/xxxxxxxx/locations/us-central1/functions/hello-graal-native-node
runtime: nodejs8
...以下略
curl
コマンドで叩いてみる。
$ curl https://us-central1-xxxxxxxx.cloudfunctions.net/hello-graal-native-node
hello from native lib
めでたい 🎉
なにがうれしい?
- Javaを未サポートのGoogleCloudFunctionでもnative-imageで共有ライブラリ化すればワンチャン
- むしろ素のJavaより起動が速い嬉しさもあるかも
コード一式
https://github.com/vertical-blank/google-cloud-function-graal-native-node