Fn Project でサーバーレスをJavaで試してみる

f:id:ts0818:20200928182945j:plain

遠隔操作(えんかくそうさ、略称リモコン英語Remote control (RC))とは、電気信号などを利用して機器・装置などの操作を、その機器・装置から離れた場所から操作すること。工場のプラントの操作や鉄道進路制御などで行われている。

なお、最も遠距離から行われた遠隔操作は、惑星探査機である。電話による遠隔操作は「テレコントロール」と呼ばれる。

遠隔操作 - Wikipedia

⇧ 遠隔操作と言えば、

 映画、「インターステラー」のある1シーンが印象的でしたが、

⇧ 良い映画でした。海外の映画に出てくるハッカーって皆なんか楽しそうにしてるイメージがありますかね(超偏見だけど)。 


はい、脱線しました。 

全開、「Fn Project」でサーバーレスが実現できるということで、「Fn」をインストールしました。

ts0818.hatenablog.com

 

ということで、今回は、Java でサーバーレスを試していこうと。

そんでは、レッツトライ~。 

 

サーバーレスをJavaで試してみる

では、早速。 

fnproject.io

The fn init command creates an simple function with a bit of boilerplate to get you started. The --runtime option is used to indicate that the function we’re going to develop will be written in Java 9, the default version as of this writing. A number of other runtimes are also supported.

https://fnproject.io/tutorials/JavaFDKIntroduction/

⇧ ってな感じで、「Fn」をインストールしていれば、「init」でボイラーテンプレートで一式、環境を作ってくれるらしい。

 

Javaのバージョンについては、

github.com

⇧ 2020年9月28(月)現在、Java 11 か、Java 8 の2択となる模様。

 

とりあえず、Docker deamonがインストールされてる仮想マシンを起動。

f:id:ts0818:20200928100558p:plain

仮想マシンにログインします。

f:id:ts0818:20200928101801p:plain

で、「fn init」でランタイムをJavaに指定すれば、Mavenプロジェクトができるらしんだが、どのディレクトリに作成すれば良いかの指定は特にないので、どこでも良いのかも知らんけど、

qiita.com

/home/docker 以下や、/usr/local/bin の下などが消えたが、作成したコンテナが残っていたので、何が消えるのか気になった。

docker-machineでvirtualboxに仮想マシンを作って再起動したらデータが消えたなとおもったら - Qiita

⇧ とあるように、Docker ToolBoxで作成した仮想マシンは、停止とかすると、ディレクトリによっては初期化されちゃう、つまり、配置したファイルとかは全て消える仕様らしい...

ファミコンRPGでよくセーブデータが消えることあったな...

まさに、そんな感じですべて無かったことにされてしまうのである!

いやいや、駄目でしょ...

「df -h」でファイルシステムを確認できるらしいんですが、「tmpfs」だとアウトらしいですと。

f:id:ts0818:20200928103751p:plain

 

「tmpfs」ってどんなファイルシステムなのか?

linuc.org

メモリ上に作成するファイルシステムですから、「アクセス速度が非常に速い」というメリットを享受することができます。ストレージにかける負担を減らすこともできますから、一時ファイルなどを置くのに適しています。
一方で、コンピュータの電源を落とすとファイルが消えてしまいますので、その点は十分な注意が必要です。

https://linuc.org/study/knowledge/441/

⇧ な~る、メモリ上にできてるから、仮想マシンを停止したら消えるわな...

なので、とりあえず、「/」とか「/dev/shm」にファイル群を配置したら泡沫の夢の如く消えちまいますと。

Windows でいうところの「C:\Users\[ユーザー名]」にあたるであろう「/home/docker」も「/」配下のディレクトリであるので、当然のことながら消えてしまう定めであると。

って言うか、

f:id:ts0818:20200928105649p:plain

通常のLinuxのフォルダ構成で見られるようなディレクトリにも配置できないやん、そりゃそうだ、ルートディレクトリである「/」がメモリ上のファイルシステムって扱いなんだから。

 

じゃあ、どうすんのよ?

non-programmer-lab.com

docker volumeを作成しておけばVirtualBoxの再起動時にデータが消えることがなく永続化ができます!

他にもコンテナを永続化用のストレージとして使う方法もあるそうなので、気になる方はそちらを試してみても良いかもしれんません。

Docker Toolbox上のデータ永続化|ノンプログラマー研究所

⇧ 上記サイト様の説明にあるように、Dockerでデータを永続化させる方法には、いくらか種類がありますと。

qiita.com

Dockerのマウント3種類(Volumesbind mountstmpfs mounts)について、
公式リファレンスを読んで、以下の項目をまとめたものになります。

Dockerのマウント3種類についてわかったことをまとめる - Qiita

⇧ 上記サイト様によりますと、Dockerの公式リファレンスによると、3つ方法があるらしい。

公式の説明によると、

docs.docker.com

  • Volumes are stored in a part of the host filesystem which is managed by Docker (/var/lib/docker/volumes/ on Linux). Non-Docker processes should not modify this part of the filesystem. Volumes are the best way to persist data in Docker.

  • Bind mounts may be stored anywhere on the host system. They may even be important system files or directories. Non-Docker processes on the Docker host or a Docker container can modify them at any time.

  • tmpfs mounts are stored in the host system’s memory only, and are never written to the host system’s filesystem.

https://docs.docker.com/storage/

⇧ マウント(共有する)方法3つの関係性は、上記のようになるらしい。

単独のマシンだけでなくて、

docs.docker.com

⇧ 複数のマシンでマウント(共有する)もできるみたい。

 

で、Docker ToolBoxのややこしいところは、

⇧ 上記のような構造になってるところですかね。

つまり、LinuxやDocker DeskTopが利用できるMacOSWindowsの環境と、Docker ToolBoxでは「DOCKER_HOST」のポジションが異なるのですと。

なので、Dockerの「volumes」の説明のように「Host」って言われた場合に「DOCKER_HOST」のことを言ってるんだろうな、と読み替える必要がありますと、だってDocker ToolBoxを利用してるWindows環境の「Host」には、Dockerのコンテナは配置できないですし。

まぁ、開発現場でWindows 10 Home環境なんて存在しないはずなんで、無駄なことに時間を使ってるわけなんですが...

 

脱線したんですが、Docker環境の仮想マシンでデータを永続する方法には3パターンあるらしいんだけど、いずれにしろ、永続化用のDockerコンテナを作成する必要がありますと。

Volumes are the preferred mechanism for persisting data generated by and used by Docker containers. While bind mounts are dependent on the directory structure of the host machine, volumes are completely managed by Docker. Volumes have several advantages over bind mounts:

  • Volumes are easier to back up or migrate than bind mounts.
  • You can manage volumes using Docker CLI commands or the Docker API.
  • Volumes work on both Linux and Windows containers.
  • Volumes can be more safely shared among multiple containers.
  • Volume drivers let you store volumes on remote hosts or cloud providers, to encrypt the contents of volumes, or to add other functionality.
  • New volumes can have their content pre-populated by a container.

https://docs.docker.com/storage/volumes/

⇧ 「Volumes work on both Linux and Windows containers.」ってあるんで、Docker ToolBoxを使ってるWindows環境でもイケるでしょう。

 

今回、「Fn」でサーバーレスをJavaで実装する場合、Mavenプロジェクトになるらしいので、Docker環境のデータを永続化する方法としては「bind mounts」が良いらしい。

In general, you should use volumes where possible. Bind mounts are appropriate for the following types of use case:

  • Sharing configuration files from the host machine to containers. This is how Docker provides DNS resolution to containers by default, by mounting /etc/resolv.conf from the host machine into each container.

  • Sharing source code or build artifacts between a development environment on the Docker host and a container. For instance, you may mount a Maven target/ directory into a container, and each time you build the Maven project on the Docker host, the container gets access to the rebuilt artifacts.

    If you use Docker for development this way, your production Dockerfile would copy the production-ready artifacts directly into the image, rather than relying on a bind mount.

  • When the file or directory structure of the Docker host is guaranteed to be consistent with the bind mounts the containers require.

https://docs.docker.com/storage/#good-use-cases-for-bind-mounts

⇧ ビルドツールがGradleの場合も同様になるんではないかと。

 

というわけで、マウントを実施しますか。 

オプションで振る舞いが異なってくるそうです。 

Because the -v and --volume flags have been a part of Docker for a long time, their behavior cannot be changed. This means that there is one behavior that is different between -v and --mount.

If you use -v or --volume to bind-mount a file or directory that does not yet exist on the Docker host, -v creates the endpoint for you. It is always created as a directory.

If you use --mount to bind-mount a file or directory that does not yet exist on the Docker host, Docker does not automatically create it for you, but generates an error.

https://docs.docker.com/storage/bind-mounts/

⇧ マウントするフォルダを作成して無い場合は、「v」オプションを使えば良いらしい。

docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  nginx:latest    

f:id:ts0818:20200928141350p:plain

⇧ 「DOCKER_HOST」上に、「$(pwd)/target」というディレクトリとデータの永続化を実現するDockerコンテナ が作成されたらしい。

なので、この 「$(pwd)/target」で、「fn init」すればOKかと。やってみた。

fn init --runtime java11 first-java-func

f:id:ts0818:20200928143427p:plain

仮想マシン停止してたから、前回の「fn」のインストールが無かったことになってる...

インストールで。

curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh

f:id:ts0818:20200928143842p:plain

再度、「fn init」してみるも、アクセス権で失敗... 

f:id:ts0818:20200928144158p:plain

 

dev.classmethod.jp

マウント先のパスを見ると分かりますが、root配下のファイルを書き換える形になります。そのため、root権限での作業が必要になります。これを指定しない場合は Permission Denied によりContainerの起動に失敗し、fn-serverのプロセスが立ち上がりません(設定ファイルの配置の都合上必須)

https://dev.classmethod.jp/articles/fnproject-with-dockerhub/

⇧ 「docker run」する際のオプションが必要だった模様。(今回は関係ありませんでした、後述。)

作成してしまったDockerコンテナを停止して削除。

f:id:ts0818:20200928150204p:plain

権限のオプション付きで実施。 

docker run --privileged \
  -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  nginx:latest

f:id:ts0818:20200928150705p:plain

駄目だったんだけど...

単純に、スーパユーザー(rootユーザー)じゃないと、「fn init」が実行できないだけだった...Oh, my gosh

さらに、Docker ToolBox で作成される仮想マシンのデフォルトのユーザーのパスワードが変わったのか、「su」での切り替えでのパスワード入力に「tcuser」が使えなくなってたのにもハマった...

f:id:ts0818:20200928151916p:plain

で、実行結果ですが、srcフォルダが作成されてないんだが...

f:id:ts0818:20200928153141p:plain

Visual Studio Codeでリモート接続して確認してみたところ、srcフォルダ作成されてました。ls コマンドだと表示されないらしい...

f:id:ts0818:20200928163138p:plain

Javaのコード的には、文字列を返すだけの内容っぽい。

f:id:ts0818:20200928163506p:plain

 

脱線しましたが、上記で作成されたJavaのFunctionクラスをデプロイするために、アプリケーションを作成するらしいんだけど、その前に、「Fn」のサーバーを起動しておく必要があるらしい。

スタート。

f:id:ts0818:20200928170257p:plain

⇧ はい、エラー。

メモリ足りないって言われてもな...「Fn」使う際のマシンのシステム要件なんて記載なかったけどな、ドキュメントに書いといて欲しいな。

確かに、メモリがむっちゃ少ないけどさ、どれだけ必要なのか先に言ってもらわんと分からんがな。

f:id:ts0818:20200928170900p:plain

 

qiita.com

www.hpe.com

実際、VMに必要以上のRAMを割り当てるのではなく、ユーザーやアプリケーション環境がどれだけのメモリを必要としているのかを把握しようとすることが重要です。たとえば、Windows 7Microsoft Office、そしていくつかのビジネスアプリケーションだけを使用する従業員で構成される小規模なチームをサポートするためにVMをプロビジョニングしている場合、ユーザーが多くのタスクを同時に実行したり、大容量のファイルを使用したりしない限り、2~4GBのメモリで十分です。

https://www.hpe.com/jp/ja/japan/insights/articles/10-virtualization-mistakes-everyone-makes-1808.html

⇧ 上記サイト様を参考にメモリを増やしてみます。

f:id:ts0818:20200928171538p:plain

VirtualBox マネージャーから、該当の仮想マシンを選択した状態で「設定(S)」を選択。

f:id:ts0818:20200928171738p:plain

「1024 MB」ってなってるので、

f:id:ts0818:20200928171846p:plain

3倍の「3072 MB」ぐらいにしてみます。

f:id:ts0818:20200928172122p:plain

そしたら、再起動。

f:id:ts0818:20200928172616p:plain

Dockerコンテナを起動させれば、続きからイケるみたいね、データの永続化できてそうねって思ったら、

f:id:ts0818:20200928172855p:plain

消えてました...

f:id:ts0818:20200928173308p:plain

 

どうやら、仮想マシンを落とすと、Dockerコンテナも停止してしまうので、

docs.docker.jp

コンテナのファイル変更や設定を、新しいイメージに収容(commit;コミット)するために便利です。これにより、インタラクティブなシェル上でコンテナをデバッグ用に動かしたり、作業中のデータセットを他のサーバに持っていくため出力したりできます。通常は、イメージを管理するためには、文書化されメンテナンスのしやすい Dockerfile を使うのが望ましい方法です。 詳細はイメージ名とタグについてをご覧ください 。

https://docs.docker.jp/engine/reference/commandline/commit.html

⇧ Dockerイメージとして保存しておく必要があるみたいです。

 

「Fn」をインストールするところからやり直して、「Fn」のサーバーが起動したので、

f:id:ts0818:20200928174909p:plain

コマンドプロンプトを新たにもう1つ起ち上げて、アプリケーションを作成。おそらくAPI gateway 的なやつですかね?

fn create app java-app

f:id:ts0818:20200928175035p:plain

作成したアプリケーションをデプロイ。

fn --verbose deploy --app java-app --local

f:id:ts0818:20200928175132p:plain

f:id:ts0818:20200928175427p:plain

そしたら、ファンクション用のクラスのプロジェクトを実行。 

fn invoke java-app [ファンクション用のクラスのプロジェクト名]

f:id:ts0818:20200928175537p:plain

引数を与えて実行すると、引数が表示されますと。

fn invoke java-app [ファンクション用のクラスのプロジェクト名]

f:id:ts0818:20200928180252p:plain

 

と言う感じで、「fn invoke」がトリガーになって、ファンクション用のクラスのJavaが実行できたので、サーバーレスっぽいことができたようです。

ファンクション用のクラスが1つしかないけど、curlコマンドを使えば、どのファンクションを使うか指定できるみたい。

fnproject.io

The other way to invoke your function is via HTTP. With the changes to the code, we can pass JSON and return JSON from the the function. The Fn server exposes our deployed function at system produced endpoint. Next, we need to look up the invoke endpoint for our function.

https://docs.docker.jp/engine/reference/commandline/commit.html

Getting a Function’s Invoke Endpoint

In addition to using the Fn invoke command, we can call a function by using a URL. To do this, we must get the function’s invoke endpoint. Use the command fn inspect function <appname> <function-name>. To list the javafn function’s invoke endpoint we can type:

https://docs.docker.jp/engine/reference/commandline/commit.html

⇧ エンドポイントを切り替えれば、複数のファンクションを使い分けできますと。

 id を確認します。

fn inspect function java-app [ファンクション用のクラスのプロジェクト名] 

f:id:ts0818:20200928181546p:plain

実際に実行してみる。 

curl -X "POST" -H "Content-Type: application/json" http://localhost:8080/invoke/[ファンクション用のクラスのプロジェクト名のid] 

f:id:ts0818:20200928181815p:plain

⇧ 実行されました。

では、「Ctrl」+「C」押下で「Fn」のサーバーを停止。

f:id:ts0818:20200928184030p:plain

で、Dockerコンテナの内容をDockerイメージとして保存しておきます。

docker commit [オプション] コンテナ [リポジトリ[:タグ]]    

f:id:ts0818:20200928184547p:plain

⇧ 次回は、保存したDockerイメージを元にDockerコンテナを作成すれば、Dockerコンテナの状態が保存されたところから続けられるはず...

一応、Dockerコンテナと仮想マシンも停止で。

f:id:ts0818:20200928185118p:plain

 

ちょっと気になるのは、Mavenプロジェクトに複数クラスを配置した場合はどうなるのか、ってことですかね。

毎回モヤモヤがありますね...

今回はこのへんで。