The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

OpenShift上のアプリケーションのデプロイにQuarkusがどのように使用されたか。

この記事では、Kubernetes上でのアプリケーションのデプロイに関して、私が仕事上で直面している特定の課題について、Quarkusを使ってどのように目標を達成するソリューションを提供できたかについてフィードバックします。

チャレンジ

数年前から、私はKubernetesのプロジェクトに参加しています。

  • 新しい技術(アプリケーション・フレームワークやミドルウェア製品)をより簡単に、より早く導入する。

  • アンブレラチャートパターンのように、サービスの上に論理的な展開の抽象化を行うことで、相互に関連する分散型サービスを展開する際の管理負担を軽減する。

  • アプリケーションチームがコンポーネントをデプロイするために必要な作業を限定し、Kubernetesのデプロイメントオブジェクトを超標準化する。

  • ハイブリッドクラウドのデプロイモデルへの移行

これらの目標に加えて、アプリケーションがテナントごとにデプロイされるという事実に関連した制約がありました(つまり、規制に関連した理由もありますが、マルチテナントではありません)。アプリケーションによっては、数十のテナントが存在する可能性があり、その場合、テナントごとにアプリケーションを1回デプロイすることになります(つまり、あるアプリケーションに10のテナントがあった場合、本番では10のプロセスが実行されることになります)。しかし、物理的にプロセスを分離する必要がある場合は、異なるビジネスルールに基づいて、一部のテナント、IT環境、地理的ゾーンの間で、設定の一部を共有することができます。高度に単純化、因数分解された構成部分は、開発チームにとっては真の意味での単純さの源となり、デプロイメントプロセスにとっては真の意味での複雑さの源となります。

プロジェクト終了時には、さまざまなクラスターで10,000個のポッドが含まれているという予測もありました。

最初の試み

その複雑さを考慮して、私たちは設定とデプロイのための独自のツールを作成しました。最初のバージョンはAnsibleをベースにしたもので、Towerのワークフローに対応していました。さまざまな理由から、1年前にJavaで書き直すことにしました。主な理由は:

  • パフォーマンスの向上

  • アルゴリズムの複雑さに対応できる高レベルの言語の使用

  • ユニットテストや統合テストの充実したサポート

  • 高品質な開発ツール(IDEなど)による生産性の向上

しかし、一つ気になったのは、メモリ消費量、CPU使用率、起動時間でした。というのも、あるアプリケーションでは、各テナントがそれぞれのデプロイメントプロセスで独立してデプロイされるからです。というのも、アプリケーションフレームワークは生産性や標準化の面では優れていますが、抽象化の面ではリソースコストがかかるからです。そのため、プログラムはプレーンなJavaで書かれており、サイズもユースケースも十分に小さかったので、クリーンなコードのためにいくつかのパターン(例:コンストラクタベースのインジェクションを手作業で行う)に頼ることを条件に、それで済ませることができました。このプログラムは ocpdeploy といいます。

Tower からの移行に伴い、Tekton パイプラインのタスクとして _ocpdeploy_を実行することで、Kubernetes インフラストラクチャを活用することにしました。これにより、再現性が得られ、異なるアプリケーションや、同じアプリケーションの異なるテナントを異なる _ocpdeploy_バージョンでデプロイすることができました。java (他のハイレベル言語でも可) を使用することで、設定処理のための複雑なアルゴリズムを実装することができる一方で、高いレベルの生産性と保守可能なコードを提供し、広範なリグレッション・テスト・スイートのおかげで _ocpdeploy_のリリースの品質レベルが向上しました。

全てが順調だったのは、複数のテナントを同時にデプロイしたり、異なるアプリケーションをデプロイするようになるまでです。 _ocpdeploy_コンテナのサイズは 200 millicores 、RAMは 280 Mb でした。いくつかのアプリケーションでは、約30のテナントがあり、それらはすべて並行してデプロイされます。これは30ポッドを意味し、 6 cores8 Gb のRAMを占めていました。しかし、デプロイは多くの場合、営業時間後の午後7時から8時の間に行われることが多いのです。また、起動時にクラスタに負担をかけるSpringBootやWildFlyのアプリケーションに加えて、複数のデプロイを並行して実行する場合、 _ocpdeploy_自体が受ける影響を心配し始めました。

Quarkus Universeへの参加

1年前、私たちは _Ocpdeploy_でQuarkusのPOCを開始することにしました。その目的は、QuarkusをGraalVMの実行可能ファイルとしてデプロイすることでした。遭遇した主な課題は、使用していたいくつかのライブラリでGraalVMがサポートされていないことに関連していました。FreeMarker、 SMBJ(Server Message Block SMB2およびSMB3プロトコルを実装したJavaクライアントライブラリ)、Fabric8 OpenShiftクライアント(OpenShift固有のCRDをインスタンス化する際)、そして独自の集中型ログシステムにログを送信する自作のJavaエージェントです。

幸いなことに、コマンドモードアプリケーションのサポート、restクライアント、Vaultエクステンション、Fabric8 Kubernetes Clientのサポートなど、他の部分はすでにQuarkusに存在しており、コアに追加された新しいOpenShift Clientエクステンションのベースとなりました。Kubernetes Clientエクステンションだけでも、プロジェクトにとって大きな後押しとなりました。というのも、スタート時にはArgoが利用できなかったので、 apply_と _prune を自分たちで実装しなければならなかったからです。

FreeMarkerについては、 ppalagacarlosthe19916の作業に大きく影響されたコードで、Quarkiverseに新しいエクステンションを導入しました。SMBJについては、社内のQuarkiverseでエクステンションを作成しました。また、ロギングクライアントについては、 quarkus-logging-gelf のエクステンションからヒントを得て、追加の社内エクステンションを作成しました。

ライブラリをネイティブで動作させるには、Quarkusプロジェクトから提供される非常に優れたドキュメントと、フレームワークでのサポート( _プロセッサ_や _レコーダー_など)が役立ちました。 Quarkusのエクステンションでネイティブをサポートする

ちょっとした問題が発生したとき(SMBJは思ったよりも厄介だった)、 GraalVMのトレースエージェントから助けを得ました。

時々使用していたエクステンションのリストは以下の通りです。 cdi, config-yaml, freemarker, hibernate-validator, kubernetes-client, openshift-client, rest-client, restclient-jackson, vault (加えて、SMBJと私たちの内部集中型ログシステムのための内部エクステンション)。

最終的には、必要なライブラリがすべてネイティブモードで動作するようになったので、アプリケーションを _本物の_Quarkusアプリケーションのように移行する作業に切り替えることができました。

  • 必要に応じて qualifiers や producers を用いたインジェクションを使用。

  • (当時における)新しい QuarkusTestのプロファイルを含む、さまざまなモッキングアプローチを使用するためのテストの書き換え。

これにより、Kubernetesのyaml生成、 snapshot、様々な状況での _replay_を通じた広範なテストが可能となり、すべての設定を _MP config_に準拠したものに変換することができました。

ベンチマーク

そして、いくつかのベンチマークを実行し、旧バージョンと比較してリソース消費量を評価しました。起動時間についてはあまり気にしていませんでした。非常に優れていることはわかっていましたから。起動時間が数秒以内に収まっていれば(プレーンなJavaバージョンで経験していたことですが)、問題ありませんでした。しかし、メモリとCPUの消費は別の話でした。この試みは、実際に何らかの利益が得られることを期待して行われました。

プレーンなJavaバージョンでは、 180 Mb までコンテナを絞り込むことができました(それ以下では、 _OOM_になるか、 _GC_がパフォーマンスを殺してしまいます)。また、ネイティブ版では、 50 Mb まで下げることができました。

そして、さまざまな構成( plain Java 180 MbQuarkus native 180 MbQuarkus jvm 180 MbQuarkus native 50 Mb50 millicores request=limit から始めて、 100150 、 …​)で数回の実行を行いました。

50 millicores では、 Quarkus native 180 Mb が 20 秒、 Quarkus native 50 Mb が 60 秒、その他の 2 つが 300 秒で実行されます。つまり、 180 Mbocpdeploy に許可すれば、300秒から20秒になるということです。 _15_倍の改善です。

250 millicores では、 Quarkus native 180 Mb が5秒以下、 Quarkus native 50 Mb が10秒、その他の2つが40秒前後でした。

また、デプロイメントを60秒以下にするために、どれだけのCPUが必要かを調べました。

Quarkus native 50 Mb では 50 millicoresplain Java 180 MbQuarkus jvm 180 Mb では 240 millicores でした( Quarkus native 180 Mb は、最初のテストで説明したように「最悪」の結果が 20 秒だったため、対象外となりました)。つまり、時間に制約がある場合は、 240 millicores から 50 millicores へ、また、 180 Mb から 50 Mb へは、Quarkus native へ移行することで対応できるということです。

Quarkus native 180 Mb50 Mb の比較も興味深いものでした。というのも、メモリと CPU のノブを上げ下げすることで、ユースケースの実行時間を調整できることがわかったからです。実行時間とリソース消費の間のスイートスポットをどこに設定するかは、私たち次第です。

最後に興味深い観察結果として、 plain Java 180 Mb の結果が Quarkus jvm 180 Mb とほぼ同じだったことが挙げられます。これは、保守性と生産性を提供するアプリケーションフレームワークのコストが、私たちのケースでは0だったということです。ケーキを食べているようなものですね。私たちの場合、メモリとCPUを大幅に節約できるのであれば、実行速度が遅くても構わないと考えていましたが、それが実現できました。

Program execution in seconds for different limits (in millicores)

plain Java (180Mb) Quarkus Native (180Mb) Quarkus JVM (180Mb) Quarkus Native (50Mb)

50m

283

18

306

61

100m

95

9

120

32

250m

38

4

43

11

500m

17

3

21

8

1000m

11

3

11

5

1500m

9

3

8

5

2000m

7

3

7

5

No Limit

8

3

6

5

results

いくつかの問題点

GraalVMでアドホック・ライブラリを動作させる上での課題以外にも、予期せぬ動作やちょっとした問題点がいくつかありました。

  • 不明なアプリケーション設定プロパティに対する警告の欠落 #14889

  • src/main/resources/application.yaml で設定した場合、pojoの設定上のデフォルトは同じ動作をするべき。 #13423

  • quarkus-config-yaml の依存関係なしで application.yaml が提供された場合、警告または失敗する #13227

  • _MP Rest Client_2.0 #10520へのアップグレード。待望のものであり、"リダイレクトへの追従 "が可能になります。

  • アノテーションでテストごとにモックを定義する方法があればと思いましたが、これは このスレッドで回答されています。

  • 証明書をビルド時ではなく実行時に提供できない(GraalVMの制限) #3091

  • このスレッド で議論されているように、テストにおける src/main/resources/application.properties の可視性; 以来、"Use AbstractLocationConfigSourceLoader to load application.properties and application.yaml" #15282 など、多くの作業が行われてきました、そのため、再チェックが必要です

  • このスレッドで議論されているように、オプションの設定プロパティが空の値で上書きされます。

  • Quarkus YAML設定キーは暗黙のうちにエスケープされる #11744

  • アーカイブに含まれるapplication.properties

  • quarkus-config-yaml の依存関係なしで application.yaml が提供された場合、警告または失敗する #13227

これはかなりの量に見えますが、実際にはこれらの箇条書きはどれも大きな問題ではなく、回避できないものでもありませんでした。いくつかの問題は設定に関するものでしたが、これはプログラムがTektonタスクとして実行されていたためで、Tektonではオプションのパラメータを定義する際の柔軟性が限られています。ここに挙げたのは、回避しなければならなかった点のみです。多くの問題や質問は、Google Groupでの回答や実際の修正により、進行中に解決されました。

ネイティブビルドのためのCI

もう一つの課題は、ネイティブビルドの時間と、その間のメモリとCPUの使用量に関するものでした。そこで私たちがとった方法は、マスターでのみネイティブ実行可能ファイルを生成し、フィーチャーブランチではjvmモードでテストを実行するというものでした。必要であれば、特定のフィーチャーブランチでネイティブテストを行うという選択肢もあります。しかし、新しいライブラリを統合するのでなければ、jvmテストを通過させることで、ネイティブでも同様の動作をするという十分な自信を持つことができました。

もう一つの工夫は、新しいビルドが開始されたときに、 _マスター_の現在のビルドをキャンセルすることでした。理論的には、別のコミットが行われたためにビルドがキャンセルされるのはフラストレーションがたまるかもしれません。実際には、PRが _master_にマージされる割合は低いままだったので、この問題はありませんでした。

しかし、社内で開発するQuarkusアプリケーションの数を増やしたいと考えた場合、ビルドインフラのサイズには疑問が残ります。

オリジナルのソリューションと比較した場合の唯一の欠点は、リグレッションテストスイートの実行が遅くなったことです。 _MPconfig_を使用していたので、異なるコンフィグをテストするたびに、新しいQuarkusコンテキストを起動する必要がありました。幸いなことに、新しいコンテキストの起動はQuarkusでは非常に高速ですが、それでも元々のプレーンなJavaソリューションに比べればかなり遅いです。

すべてはコミュニティのために

Vault、FreeMarker、OpenShift Clientのエクステンションの作業の他に、Quarkus内外でいくつかのPRに貢献し始めました。これは、以下のような改善を得るためのプロセスをスピードアップすることを期待しています。

  • CRDオブジェクトに appendResourceVersionInObject を追加 #2365(merged)

  • cert-manager エクステンションサポートの追加 #2930(merged)

  • ci で cekit を使って distroless イメージをビルド #118(merged)

  • ネイティブイメージの実行時にルート証明書を設定できるようにしました #3091 (_teshull_はオラクルの Christian Wimmer から GraalVM 21.3 リリースのための作業を任されました)

特筆すべきは、Quarkusのコミュニティが成功の大きな要因になっていることです:

  • googleグループやzulipなど、stackoverflowなどで質問に答えること。

  • 問題をタイムリーに調査し、状況に応じて、修正、正しいアプローチの適用に関するガイダンス、または回避策を提供すること。

  • 設定などの改善に前向きであること。

  • PRを迅速にレビューし、貢献を促進すること。

  • 高い頻度でリリースすること。

引継ぎ

移行に要した期間はわずか数ヶ月で、昨年末にはQuarkusを搭載した _ocpdeploy_で最初のOpenShiftアプリケーションのデプロイを開始しました。それ以来、何百ものデプロイメントを実行するために使用されました。限られた労力で新しいリリースのペースを追うことができました。バグはほとんど発生せず、通常は次のリリースに間に合うように修正されました。Quarkusが提供するアプリケーションフレームワークにより、コードの構造化が進み、保守性が向上し、モック機能やテストプロファイルを使ったテストが容易になりました。 _ocpdeploy_の開発は、Quarkusの開発者ではなく、Javaの専門家でもないチームメンバーによって行われたことも興味深い点です。これは、フレームワークが、全体的な構造(例:コンポーネント、テスト、構成、ci)が整ってしまえば、それを忘れることができるほど軽量であることを示しています。

私たちのプログラムは、それが動作しなければビジネスが止まらないという意味で、ビジネスクリティカルではありませんでした。しかし、OpenShiftのマイクロサービスをすべてデプロイするには、このプログラムしかありません。 _ocpdeploy_は明らかに超高度なものではなく、Quarkusでできることの表面をなぞっただけでしたが、それでも私たちのために動いてくれたので、さまざまなユースケースに取り組めることがわかりました。

結論として、Quarkusの強力なセールスポイントは以下の通りだと思います。

  • 積極的にフィードバックに耳を傾け、貢献を歓迎する生き生きとしたコミュニティ

  • ハイスピードで開発されたプロジェクト

  • 汎用性のある適切なポジショニング(クラウドネイティブ、デベロッパー満足度、…​)。

  • Quarkus の価値観に反するような仕様であっても、現実的に少しでも曲げようとする姿勢

  • コンパクトなコアとエクステンションを持ち、迅速な拡張を可能にすることで、イノベーションを育むアーキテクチャ

  • デフォルトではネイティブをサポートしていますが、jvmモードでもいくつかの改良が加えられていること

  • コアのエクステンション、ユニバースエクステンション(例:camel)、Quarkiverseによる急成長のエコシステム

  • 開発モード(ローカルおよびリモート)

  • ビルド時の初期化などのパターンを実現するためのエクステンションを開発するためのフレームワーク

10年前、15年前のアプリストアと同じように、私が考える今後の主な課題は、エクステンションのエコシステムが、品質を犠牲にすることなく量的に成長するようにすることです。あるいは、少なくともエクステンションを評価する方法を提供して、ビジネスクリティカルなアプリケーションを構築する人々が、テクノロジーへの投資が適切であることを保証できるようにすることです。言い換えれば、"広く広く "に加えて "深く "ということです。私が考えるもう一つの課題は、企業の準備状況のギャップを徐々に埋めていくことです。例えば、今日、部分的に開発されているソリューションを完成させ、最も一般的なユースケースに対して明確で成熟したソリューションを提供できるようにすることです。

それが、Quarkusがエンタープライズに勝つ方法です。そして、これが動き出しているのがわかります。

このプロジェクトにはとても期待しています。早くユースケースを広げたいですね。