Micronaut and GraalVM: An integration story

原文はこちら。
The original article was written by Iván López Martín (Senior Software Engineer. Micronaut & Grails team at Object Computing, Inc. (OCI)).
https://micronaut.io/blog/2020-10-12-micronat-graalvm-integration.html

MicronautとGraalVMの関係ははるか昔にさかのぼります。2018年10月に1.0をリリースして以来、MicronautはGraalVMを標準でサポートしていました。これを行った最初の大規模なJavaフレームワークの1つでした。

GraalVM
https://www.graalvm.org/

MicronautとGraalVMの統合がGraalVM 1.0-RC6で例外的に動作した初期の頃を今でも覚えていますが、その後、1.0-RC7で何かが変更され、うまくいかなくなりました。しばらくして1.0-RC8がリリースされ、すべてが再び動作するようになりました。

GraalVMチームが新しいバージョンをリリースする前に、統合のテストができることを確認する必要がありました。早期にテストを行うことで、発見した問題やリグレッションは、次のリリースまでに報告・修正することができるからです。

2019年1月(Micronaut 1.0をリリースしてから3ヶ月弱)、その当時、MicronautのテストにTravis CIを使用していました。

Micronaut 1.0 GA released
https://micronaut.io/blog/2018-10-23-micronaut-10-ga-released.html

Travisには、GraalVMと制約がたくさんあり、GraalVMとは相性が悪かったのです。具体的には、メモリ制限が非常に低いこと、複雑なCIワークフロー作成時の禁止オプション、後のCIパイプラインジョブで使用するためのアーティファクトの作成をサポートしていない、などです。そのため、別の方法を必要としていました。

INTRODUCING GITLAB CI

Gitlab CIはGitlabの一部です(ただし、CIを使うのにGitLabをきちんと使う必要はありません)。GitLab CIは前職でよく使っていたので、私にとっては簡単な選択でしたし、これを使って仕事をするのが本当に好きなのです。

GitLab CI/CD
https://docs.gitlab.com/ee/ci/
DevOps Platform Delivered as a Single Application – GitLab
https://about.gitlab.com/

以下のような目的でCIパイプラインを作成することを考えていました。

  • コミット毎ではなく、定期的に実行する
  • MicronautまたはGraalVMのいずれかに変更があった場合にのみ実行
  • masterブランチからGraalVMをコンパイルし、次のGraalVMリリースの前にできるだけ早く問題やリグレッションを検出できるようにする
  • 異なるMicronautアプリケーションを簡単にテストできるようにする
  • 異なるCIステージやジョブの間に依存関係があるような、CIステージやジョブをサポートする

新しいリポジトリでいくつかの異なるアプローチを実験した後、このCIパイプラインを使ってテストが成功するビルドを得ることができました。

micronaut-projects > micronaut-graal-tests
https://gitlab.com/micronaut-projects/micronaut-graal-tests
micronaut-projects > micronaut-graal-tests – pipelines
https://gitlab.com/micronaut-projects/micronaut-graal-tests/-/pipelines/43802345

First CI pipeline

今でも使っている4つのステージがあります。

ステージ
Log-commitsMicronautとGraalVMの両方のビルドのトリガーとなったコミットを保存する。ビルドが失敗した場合、コミットをチェックしてその理由を確認できる。
Build-graalGraalVM リポジトリをクローンし、master ブランチからビルドする。このステージで生成されたGraalVM JDKディストリビューションは、次のステージで使用するためのアーティファクトとして保存される。
Micronaut様々なMicronautテストアプリケーションをクローンし、前のステージで作成したGraalVMディストリビューションを使用してネイティブイメージをビルドする。テストに使用できるよう、ネイティブイメージは再びアーティファクトとして保存される。
Test前のステージで生成されたネイティブイメージを起動し、すべてが期待通りに動作することを確認するためにいくつかのテストを実行する。これらのテストは異なるエンドポイントへの curl リクエストであり、レスポンスをチェックする。

Micronaut と GraalVM の新しいコミットを追跡し、スケジュールされたジョブを実行するために、別のコンパニオンプロジェクトを用意しています。この中で、両プロジェクトの最新のコミットを保存し、メインリポジトリでCIをトリガーするかどうかを決定します。

MicronautとGraalVMでの新しいコミットを追跡し、スケジュールされたジョブを実行するために、両方のプロジェクトの最新のコミットを保存し、メインリポジトリでCIを起動するかどうかを決定する別のコンパニオンプロジェクトを持っています。

micronaut-projects > micronaut-graal-tests-scheduler
https://gitlab.com/micronaut-projects/micronaut-graal-tests-scheduler

TEST APPLICATIONS

当初のパイプラインから、たくさん変更しました。最初に開発した2つのアプリケーションは、39の異なるジョブを持つ26のテストアプリケーションになりました。これは、一部は同じテストを実施しますが、そのテストでは異なるオプションを使っているからです。例えば、Micronaut JDBCテストアプリケーションでは、サポートされているすべてのデータベース(H2、Postgres、Oracle、MariaDB、SQL Server、MySQL)のブランチがあります。

micronaut-graal-tests/micronaut-data-jdbc-graal
https://github.com/micronaut-graal-tests/micronaut-data-jdbc-graal

また、現在はJDK 8とJDK 11のGraalVMをテストしており、2つのMicronautバージョン(現在は2.0.xと2.1.x)に対してもテストを行っているため、ジョブの数は4倍になっています。今日のCIパイプラインはこんな感じです。

CI pipeline today

非常に長く、しかも美しいリストですよね。

リストには、RabbitMQ、gRPC、ConsulとEurekaを使うService Discovery、Security、Micronaut Data、Flyway、Elasticsearch、Redisなどのテストアプリケーションが含まれています。リストには新しいアプリケーションを追加して増やしており、リリースごとにどんどんサポートしていくことを約束しています。

OWN RUNNERS ON AWS

数ヶ月前、GitlabのCI共有ランナーに限界が来ました。ネイティブイメージをビルドする際にメモリ不足の例外が発生し、いくつかのジョブが失敗していました。基本的には、ネイティブイメージをビルドするのに十分なメモリがなく、プロセスが失敗していました。

Gitlab CIには独自のランナーを使用できるので、追加のメモリを必要とするジョブのためにAWS上に自動スケールするランナーインフラを設定しました。

GitLab runner
https://docs.gitlab.com/runner/
Autoscaling GitLab Runner on AWS EC2
https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/

適切なインスタンスタイプを選択するために、オートスケール時に起動するインスタンス数を制限せずにテストをしてみました。

InstanceSpecsTime to run pipelineCPU usage
c5.xlarge4 CPUs – 8 GB RAM17 mins60-70%
c5.2xlarge8 CPUs – 16 GB RAM13 mins30-40%
c5.24xlarge96 CPUs – 192 GB RAM43 mins10%

c5.24xlarge のような巨大なインスタンスを使うとその他の場合に比べてCIパイプラインの実行が長時間かかるのか疑問に思われるかもしれません。その答えは、AWSサポートに対してCPUの増強をリクエストしなければ、最大512 CPUまでしか割り当てることができないためです。そのため、最大のインスタンスを選択しても5個しか並列実行できませんでした。

この数字を考えると、c5.xlargeを選択したのは正解だと感じました。c5.xlargeとの時間差が少なく、前者はCPU使用量とコストを後者の半分に最大化することができたからです。

EC2 instances auto-scaling

結局、ネイティブイメージのビルド時間の関係で、他のいくつかのジョブをAWSランナーで動かすことにしました。AWSランナーは共有ランナーに比べて非常に速いので、共有ランナーが1つのネイティブイメージをビルドする時間で3つ近くのネイティブイメージをビルドすることができます。現在の共有ランナーと専用ランナーのミックスでは、ビルドにかかる時間は35~40分程度です。

下表は、ランナーによるパフォーマンスの違いです。

AppShared runnersOwn runners
GraalVM23 min7 min 29 sec
MS SQL14 min 18 sec5 min 33 sec
gRPC (server + client)35 min7 min 3 sec

Gitlabを使えばとても簡単に独自のランナーで実行するジョブを設定できます。awsmemoryspeedのどれかでタグを付ければいいのです。特定のジョブが自分のランナー上で実行されている理由を文書化するため、aws + memoryaws + speed のようなタグの組み合わせを使っています。

Runners tags

TEST DEV-RELEASE VERSIONS

しばらく前に、GraalVMチームは、コミュニティがテストできるように、数日ごとに開発版のリリースを公開しはじめました。そのため、自らソースからビルドする必要がなくなりました。

graalvm/graalvm-ce-dev-builds
https://github.com/graalvm/graalvm-ce-dev-builds/releases
build-graalvm.sh
https://gitlab.com/micronaut-projects/micronaut-graal-tests/-/blob/2.1.x_dev-preview/build-graalvm.sh#L9

GraalVMチームは、GraalVMの最終バージョンをリリースする数日前に、Micronautが最新の開発版リリースで動作するかどうかを検証できないかと、私たちに尋ねてきました。私たちは非常に粒度が細かく柔軟なCIパイプラインを持っているので、新しいブランチを追加して、それらの開発リリースのテストを自動化することは簡単でした。今では、2~3日ごとに実行されるスケジューリングされたジョブで、自動的に最新のリリースをダウンロードし、それを使ってすべてのテストを実行します。興味のある方のために、唯一の変更点はGraalVMのビルドスクリプトで、最新の開発版をダウンロードするようになった点で、それ以外は変更していません。

WAS IT WORTH THE EFFORT?

小さなことから始まったことが、この21ヶ月で大きく進化しました。これは、私たちがこれにかけた努力の価値があったことを時間が証明してくれました。例えば、ほんの数日前、gRPCテストアプリケーションが失敗したのですが、私はビルドを壊したコミットを追跡し、GraalVMチームに問題を報告することができました。それから数日後、彼らは問題を修正してくれたおかげで、私たちのビルドは再び成功するようになりました。

micronaut-projects > micronaut-graal-tests > Jobs > #774292619
https://gitlab.com/micronaut-projects/micronaut-graal-tests/-/jobs/774292619
Regression with Micronaut gRPC: “org.graalvm.compiler.debug.GraalError: should not reach here: Intrinsic graph can only have one node with an exception edge” #2896
https://github.com/oracle/graal/issues/2896
micronaut-projects > micronaut-graal-tests > Pipelines > #199942162
https://gitlab.com/micronaut-projects/micronaut-graal-tests/-/pipelines/199942162

issueの報告に関して言うと、MicronautでのGraalVMのサポートを改善するためにGraalVMチームと協力し、GraalVMをより良いものにするために多くのissueを報告してきました。

Tweet Thomas Wuerthinger

私たちObject ComputingのMicronautチームは、Micronautのリリース毎にGraalVMとの統合を改善していきます。また、MicronautアプリケーションとGraalVMの統合を可能な限り簡単に行えるよう、できる限りの努力をしていきます。

GROOVY, GRAILS, AND MICRONAUT TEAM
https://objectcomputing.com/products/2gm-team

Micronaut 2.1では、新しいMicronaut Gradleプラグインにより、サポートが大幅に改善されました。Graeme Rocher氏によるデモをご覧ください。

Micronaut Gradle Plugin
https://github.com/micronaut-projects/micronaut-gradle-plugin

Micronaut 2.2では、こうした新機能も含む、現在のMicronaut Mavenプラグインの改良版をリリースする予定です。

Micronaut Maven Plugin
https://github.com/micronaut-projects/micronaut-maven-plugin

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中