どうも、Elasticのコンサルタントの杉森といいます。Qiitaでははじめての記事です。お手柔らかにお願いします。
この記事ではエンジニアならば誰しもがやってみようかと思っているけど面倒で後回しになっているであろう作業、つまりElasticsearchをソースからビルドしてIntelliJでデバッグする、というところを紹介したいと思います。難しいことは何もないですが、初回ビルドにはそこそこ時間かかりますので、そこはご承知ください。
特にIntelliJについてはあんまり使い慣れていない私のような人を想定した記事です。ベテランの人は暖かく見守って下さい。
前提
この記事の前提は以下とします。
- Macのローカルでビルド(IntelMac, macOS Montereyで確認しています)
- JavaはSDKManでインストールしたJDK17をつかう
- IntelliJ IDEA 2022.1.2 (Community Edition)
- mainブランチのHEADからビルド(執筆時点ではElasticsearchバージョン8.4)
[2024-02-27 追記]
以下の環境でも確認しています。
- Apple M3 Pro, macOS 14.3.1(23D60)
- openjdk 17.0.9 2023-10-17 LTS
- IntelliJ IDEA 2023.3.2 (Community Edition)
- 8.14.0-SNAPSHOT (b4b32aa53a53975d1540dfc37c985729d622c6b6)
JDKインストール
今回はJDKのインストールにはSDKManを利用します。SDKManの公式サイトのインストラクションに従うと:
$ curl -s "https://get.sdkman.io" | bash
でインストールできます。
Elasticsearch 8系ではJDK 17以降が必要です。SDKManでインストールできるJavaを探します。
$ sdk list java
個人的におすすめのディストリビューションというのはないので、今回は単に私が使ったことがあるものということで17.0.3-zuluをインストールします。
$ sdk install java 17.0.3-zulu
きちんとインストールできたか確認します。
$ which java
/Users/daixque/.sdkman/candidates/java/current/bin/java
$ java -version
openjdk version "17.0.3" 2022-04-19 LTS
OpenJDK Runtime Environment Zulu17.34+19-CA (build 17.0.3+7-LTS)
OpenJDK 64-Bit Server VM Zulu17.34+19-CA (build 17.0.3+7-LTS, mixed mode, sharing)
大丈夫そうですね。
Elasticsearchのビルド
そうしたらElasticsearchのコードをダウンロードしてきてビルドします。ビルド方法などはREADME.asciidocやCONTRIBUTING.mdに書いてあります。
まずはコードをGithubからcloneします。大きいコードベースなのでそれなりに時間かかります(環境にもよるでしょうが10分くらい?)。
$ git clone git@github.com:elastic/elasticsearch.git
Elasticでは少し前にmasterからmainに開発のメインブランチを移行しました。cloneできたらmainブランチをチェックアウトしましょう。
$ cd elasticsearch
$ git checkout -b main origin/main
ちなみに本記事執筆時点でのmainブランチの先頭コミットは d7b6a32d666f6232d8c4a0ce4e089b270e84e0c1 でした。
ではビルドします。お分かりのとおり初回は依存関係のライブラリをかき集めるためかなり時間がかかります(30分くらい?)ので、気長に行きましょう。
(ただ、後の説明するようにIntelliJを使ってもビルド可能です。ライブラリのダウンロード時間などを短くしたい場合、IntelliJにインポートしてデバッグ実行までスキップしても大丈夫です。)
README.asciidocにある通り、以下のコマンドを実行します。
$ ./gradlew localDistro
ビルドできたら以下のコマンドで起動します。
$ ./gradlew run
別のターミナルから以下のコマンドで接続を確認しましょう。gradleで起動するとBASIC認証がかかっていますが、ユーザー名elastic
、パスワードpassword
でアクセスできます。
$ curl elastic:password@localhost:9200
うまく行っていればレスポンスが以下のように出力されるはずです。
{
"name" : "runTask-0",
"cluster_name" : "runTask",
"cluster_uuid" : "ZaPn71FLQbyu3HpbCgW3YQ",
"version" : {
"number" : "8.4.0-SNAPSHOT",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "d7b6a32d666f6232d8c4a0ce4e089b270e84e0c1",
"build_date" : "2022-07-08T14:25:14.712327Z",
"build_snapshot" : true,
"lucene_version" : "9.3.0",
"minimum_wire_compatibility_version" : "7.17.0",
"minimum_index_compatibility_version" : "7.0.0"
},
"tagline" : "You Know, for Search"
}
無事にElasticsearchがビルドできました。特に難しいところはないですね。
IntelliJにインポートしてデバッグ実行
それでは今ビルドしたElasticsearchのコードベースをIntelliJにインポートしますが、詳しい手順はElasticが公式のブログ記事を公開しているのでそちらも参照してください。
IntelliJのOpenでelasticsearchのフォルダを選択して開いてください。
すると自動的に依存関係のインポート、コンパイルの処理が始まります。またバイナリのビルドについてもコマンドラインから直接gradlewでのビルドはせず、IntelliJ側のGradleでlocalDistroタスクを実行することももちろん可能です。
インポートが終わったら、Run/Debug Configurationで、デバッグ実行の設定をします。
基本的にプロジェクトをインポートしたタイミングでIntelliJが適切に設定してくれているはずです。私の環境では以下のような状態になっていて、このままでOKでした。ポイントはDebugger Modeが「Listen to remote JVN」、Auto restartにチェックが入っている状態になっていることです。
そうしたら、まずはIntelliJ側でデバッグ実行します。
この時点ではIntelliJ側では特に何も起こりません。
次にコマンドラインからElasticsearchをデバッグモードで起動します。
$ ./gradlew run --debug-jvm
IntelliJ側のデバッグが実行されていないと、この起動に失敗しますのでIntelliJ側をデバッグ開始してから再度gradlewを実行してください。うまく行けば以下のようなメッセージがIntelliJのデバッグコンソールに出力されます。
Connected to the target VM, address: 'localhost:5007', transport: 'socket'
それではブレークポイントを打って実際に処理の流れを追ってみましょう。
今回はorg.elasticsearch.rest.action.RestMainAction#convertMainResponse
メソッドにブレークポイントを打ってみます。
ここで再度以下のAPIを叩いてみましょう。
curl elastic:password@localhost:9200
おお、実際に処理が止まって内部の変数の状態などが参照できますね。ここまでできれば、多くのケースで十分内部の挙動を追えるようになったのではないでしょうか。
ビルド済みのモジュールをIntelliJにアタッチ
これでElasticsearchの挙動をソースレベルで確認したい場合、大体のことはOKだと思います。ただ、設定ファイル周りについて調べているとき、デバッグ実行で読み込んでいるelasticsearch.ymlをどうやって変更するか現時点で理解していなかったので、今回はビルド済みのモジュールをデバッグ実行で起動してIntelliJにアタッチしてみましょう。
ここからは通常のElasticsearchのバイナリ版と同じ操作になりますので、公式のインストール手順なども参考に進めてください。
ビルドしたモジュールは以下に作成されます。
build/distribution/local/elasticsearch-8.4.0-SNAPSHOT
フォルダの内容を確認します。
$ ls
LICENSE.txt NOTICE.txt README.asciidoc bin config jdk.app lib logs modules plugins
設定ファイルはconfig/elasticsearch.ymlですね。必要に応じて修正して下さい。今回はローカルデバッグを目的としているので、セキュリティには気を使わず簡単に動作確認できるように、HTTPSの暗号化なしで接続できるようにします。
xpack.security.http.ssl:
enabled: false
さて、IntelliJからアタッチするためにJVMの起動オプションを追加する必要があります。Run/Debug Configurationのダイアログに表示されているサンプルを参照して、config/jvm.options
ファイルに以下のような行を追記します。アドレスは各環境によって違いますので注意してください。
-agentlib:jdwp=transport=dt_socket,server=n,address=192.168.1.8:5007,suspend=y
そうしたら以下のコマンドでElasticsearchを起動しましょう。
$ bin/elasticsearch
うまく起動できると、以下のような情報がログに出力されます。このビルドを使うにあたっては必要な情報なのでメモしておいて下さい。(なくしてもbin以下のツールを使って復旧はできます。)
✅ Elasticsearch security features have been automatically configured!
✅ Authentication is enabled and cluster connections are encrypted.
ℹ️ Password for the elastic user (reset with `bin/elasticsearch-reset-password -u elastic`):
YX91nt7z*pp4LhTJy36R
ℹ️ HTTP CA certificate SHA-256 fingerprint:
10c57bd1987ab2f9144358e34456007b6fd08f3cd9614cd05b9d06df901c785a
ℹ️ Configure Kibana to use this cluster:
• Run Kibana and click the configuration link in the terminal when Kibana starts.
• Copy the following enrollment token and paste it into Kibana in your browser (valid for the next 30 minutes):
eyJ2ZXIiOiI4LjQuMCIsImFkciI6WyIxOTIuMTY4LjE0Ny4zNzo5MjAwIl0sImZnciI6IjEwYzU3YmQxOTg3YWIyZjkxNDQzNThlMzQ0NTYwMDdiNmZkMDhmM2NkOTYxNGNkMDViOWQwNmRmOTAxYzc4NWEiLCJrZXkiOiJIWi1POVlFQk9Ga18tUl8zdkwzczpPQXZOSkltUlRZR2RWeGFjZlJoN3RRIn0=
ℹ️ Configure other nodes to join this cluster:
• On this node:
⁃ Create an enrollment token with `bin/elasticsearch-create-enrollment-token -s node`.
⁃ Uncomment the transport.host setting at the end of config/elasticsearch.yml.
⁃ Restart Elasticsearch.
• On other nodes:
⁃ Start Elasticsearch with `bin/elasticsearch --enrollment-token <token>`, using the enrollment token that you generated.
もちろんこの情報は本来セキュアなものです。今回は一時的なビルド用なのでオープンにしていますが、本番環境の情報は厳密に管理して下さいね。ではこのユーザーでAPIにアクセスしてみます。
$ curl -u "elastic:YX91nt7z*pp4LhTJy36R" localhost:9200
うまく動きますね。ブレークポイントが打ってあればそこできちんと処理をトレースできるはずです。curl: (52) Empty reply from server
のようなエラーが出力される場合はHTTPSを無効化しているか確認してください。
お疲れ様でした! これでいつでもElasticsearchをデバッグ実行できるので、バグを踏んでも直してPRが投げれますね! ちなみに本当にPR投げる際はCONTRIBUTING.mdを参照して下さい。
Trialライセンスの設定
デバッグ実行すると、ElasticsearchはデフォルトでBasicライセンスを適用して起動します。上位のライセンスで利用する機能を開発したい場合は、トライアルライセンスで起動させることができます。以下のように -Dtests.es.xpack.license.self_generated.type=trial
オプションを指定してください。
$ ./gradlew run --debug-jvm -Dtests.es.xpack.license.self_generated.type=trial
_licenseエンドポイントで正しくライセンスが適用されたか確認しましょう。
$ curl -s elastic:password@localhost:9200/_license
正しく設定できていれば以下のようにレスポンスのtypeがtrialになります。
{
"license" : {
"status" : "active",
"uid" : "722cb3e9-8f9d-4898-a1c1-5b59e12aae46",
"type" : "trial",
"issue_date" : "2024-02-27T09:22:47.833Z",
"issue_date_in_millis" : 1709025767833,
"expiry_date" : "2024-03-28T09:22:47.833Z",
"expiry_date_in_millis" : 1711617767833,
"max_nodes" : 1000,
"max_resource_units" : null,
"issued_to" : "runTask",
"issuer" : "elasticsearch",
"start_date_in_millis" : -1
}
}
すでに立ち上げてしまっているサーバーについては、Start trial APIで後から適用することもできます。
$ curl -u "elastic:password" -X POST "localhost:9200/_license/start_trial?acknowledge=true"
TIPS
その他どちらかと言うと個人的なメモですが、GradleでのUnitテスト実行など開発チームから教わったコマンドなどを残しておきます。参考まで。
# Precommit and spotless apply ensures the code is correctly formatted
$ ./gradlew :x-pack:plugin:core:spotlessApply :x-pack:plugin:core:precommit :x-pack:plugin:ml:spotlessApply :x-pack:plugin:ml:precommit
# Run the XPack core unit tests. Covers ml configuration classes
$ ./gradlew :x-pack:plugin:core:test
# And the ml unit tests
$ ./gradlew :x-pack:plugin:ml:test
# Checkstyle test
$ ./gradlew :x-pack:plugin:core:checkstyleTest
Gitでコードベースをクリーンするコマンド
$ git clean -dXf
(参考) Trouble shooting
一度ビルド中に以下のようなエラーが出るようになってしまう現象がありました。
Execution failed for task ':build-tools-internal:jar'.
> Entry org/elasticsearch/gradle/internal/AntFixtureStop$_setFixture_closure1.class is a duplicate but no duplicate handling strategy has been set. Please refer to https://docs.gradle.org/7.0.2/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:duplicatesStrategy for details.
...
こうなった時はbuild-tools-internal/build
ディレクトリを削除するとまたビルドできるようになるようです。