ソースコード検索ツール「Livegrep」を構築する

    DigitalOcean TIDE NYC 2018 のセッションをなんとなく眺めていたら、「Livegrep」というソースコード検索ツールが紹介されました。 面白そうだったので、手元にも構築してみました。

    Livegrepのスクリーンショット

    Livegrep とは

    Livegrep とはソースコード検索ツールです。 Livegrep の使用感は公式ページにアクセスすればわかります。 以下のリンクから、Linux のカーネルを対象に検索できます。

    Livegrep はリアルタイムのソースコード検索ツールです。 GitHub 上でも全文検索できますが、Livegrep はファイルシステムに対して検索します。 そのため GitHub 以外のソースコードや、バージョン管理されてないコードも検索の対象にできます。 また GitHub の全文検索と違い、ユーザー所望のいくつかのレポジトリを対象にもできます。

    Livegrep の構築

    ソースコードは GitHub レポジトリ から取得可能ですが、Docker Hub にも公開されてるのでそちらを利用します。

    Livegrep は codesearch と呼ばれる検索エンジンと、livegrep と呼ばれる Web インターフェイスから構成されます。 2 つのコンポーネントは gRPC で通信します。 codesearch はソースコードからインデックスをオンメモリに作成して gRPC を提供します。 またはあらかじめ作成したインデックスファイルをロードすることもできます。 今回は後者の方法で Livegrep を構築します。

    以下のような構成で Livegrep を構築します。

    Livegrepのアーキテクチャ

    codesearch は 2 回利用します。 1 度目がソースコードからインデックスファイルを作るためです。 2 度目は生成したインデックスをロードして gRPC を提供します。 インデックスは LivegrepIndex という docker volume に保存します。

    Volume の作成

    まずは docker volume を作成します。

    $ docker volume create --name LivegrepRepos
    $ docker volume create --name LivegrepIndex

    ソースコードの取得

    LivegrepRepos ボリュームにソースコードを保存します。 LivegrepRepos/mnt/livegrep-repos にマウントして、そこにレポジトリを git clone します。 alpine/git コンテナを使って git clone します。 今回は自分の好きなKafkasaramaのコードを取得します。

    $ docker run --rm --read-only \
        -v LivegrepRepos:/mnt/livegrep-repos:rw \
        alpine/git clone https://github.com/apache/kafka /mnt/livegrep-repos/github.com/apache/kafka
    Cloning into '/mnt/livegrep-repos/github.com/apache/kafka'...
    $ docker run --rm --read-only \
        -v LivegrepRepos:/mnt/livegrep-repos:rw \
        alpine/git clone https://github.com/Shopify/sarama /mnt/livegrep-repos/github.com/Shopify/sarama
    Cloning into '/mnt/livegrep-repos/github.com/Shopify/sarama'...

    インデックスの作成

    取得したソースコードからインデックスファイルを出力します。 まずはどのソースコードを読むかを json ファイルで記述します。 このファイルを manifest.json という名前で適当な場所に保存します。

    {
      "name": "Kafka",
      "fs_paths": [
        {
          "name": "Shopify/sarama",
          "path": "/mnt/livegrep-repos/github.com/Shopify/sarama",
          "metadata": {
            "url-pattern": "https://github.com/{name}/blob/HEAD/{path}#L{lno}"
          }
        },
        {
          "name": "apache/kafka",
          "path": "/mnt/livegrep-repos/github.com/apache/kafka",
          "metadata": {
            "url-pattern": "https://github.com/{name}/blob/HEAD/{path}#L{lno}"
          }
        }
      ]
    }

    fs_paths はファイルシステムからソースコードを読み込みます。 url-pattern でクリックしたときのリンク先を指定できて、 {name} などのプレースホルダーで URL を組み立てることができます。 fs_paths 以外にも repositories フィールドも利用できるようです。 詳しくは設定例 を参照してください(ただしドキュメントの情報量は極めて少ないです)。

    つぎに取得したソースコードからインデックスを作成します。 LivegrepRepos/mnt/livegrep-repos に、LivegrepIndex/mnt/livegrep-index にマウントします。 そして先程の json を /mnt/manifest.json にマウントします。 インデックスの出力先は /mnt/livegrep-index/kafka.idx です。

    $ docker run --rm --read-only \
        -v LivegrepRepos:/mnt/livegrep-repos:ro \
        -v LivegrepIndex:/mnt/livegrep-index:rw \
        -v /path/to/manifest.json:/mnt/manifest.json:ro \
        livegrep/base:ac5078ceb8-0 /livegrep/bin/codesearch \
          -index_only -dump_index /mnt/livegrep-index/kafka.idx /mnt/manifest.json
    Walking path_spec name=Shopify/sarama, path=/mnt/livegrep-repos/github.com/Shopify/sarama
      walking full tree
    done
    Walking path_spec name=apache/kafka, path=/mnt/livegrep-repos/github.com/apache/kafka
      walking full tree
    done
    Finalizing...
    ......
    == end metrics ==

    サーバーを起動する。

    ここまでで準備が整いました。 あとはサーバーを起動するだけです。

    まずは作成したインデックスをロードして、gRPC を提供します。 先程のインデックスをコンテナにマウントして、 codesearch プロセスを起動します。 これでコンテナの 0.0.0.0:9999 に gRPC で公開されました。

    $ docker run --read-only --name=codesearch --hostname=codesearch \
        -v LivegrepIndex:/mnt/livegrep-index:ro \
        livegrep/base:ac5078ceb8-0 /livegrep/bin/codesearch -load_index /mnt/livegrep-index/kafka.idx -grpc 0.0.0.0:9999

    つづいて Web インターフェイスの起動です。 こちらは livegrep という実行ファイルです。 まずは設定ファイルを記述します。 "backends" はバックエンドのcodesearch サーバーです。 codesearch コンテナが名前解決ができるように、livegrep コンテナ起動時に --link オプションを付与します。

    {
      "backends": [
        {
          "id": "kafka",
          "addr": "codesearch:9999"
        }
      ],
      "listen": "0.0.0.0:8910"
    }

    設定がかけたら livegrep.json という名前で保存します。

    最後に livegrep 本体を起動します。 記述した設定ファイルは /etc/livegrep/livegrep.json にマウントします。

    $ docker run --read-only --name=livegrep --hostname=livegrep --link codesearch -p 8910:8910\
        -v /path/to/livegrep.json:/etc/livegrep/livegrep.json:ro \
        livegrep/base:ac5078ceb8-0 /livegrep/bin/livegrep -docroot /livegrep/web/ /etc/livegrep/livegrep.json

    これでブラウザから http://localhost:8910 を開くと、Kafka と sarama のコードを検索できるようになりました。

    まとめ

    今回は一度きりのインデックス更新でしたが、ポーリングなどでリモートレポジトリに追従すると、より新しいコードを検索できるようになります。 もし読者がレポジトリの管理者なら、CI などで更新することも可能でしょう。

    Livegrep は良いツールだとは思うのですが。日本語の記事も英語の記事も全然見つかりませんでした。 なのでこの記事が読者の役に立てば幸いです。

    今回は全手動で docker コマンドを叩いて Livegrep を構築しましたが、公式サイトの livegrep.com は Kubernets でデプロイされてるようです。


    Profile picture

    Shin'ya Ueoka

    B2B向けSaaSを提供する会社の、元Webエンジニア。今はエンジニアリング組織のマネジメントをしている。