gremlin-server コンテナを立ち上げる

# はじめに

gremlin を気軽に使いたいなと思って始めたら、まあまあハマったので、他の方も同じ目に合わないために、記録しておきます。
gremlin console, gremlin python を使うための環境構築を中心に記録していきます。

とにかく、古い海外の情報しか見つけられず、情報整理・実験に時間がかなりかかりました・・


# TL;DR

- サンプルそのままでは動作しない
- gremlin-server.yml をセットアップ
    - host を、0.0.0.0 に
    - serializer を設定
- remote.yml をセットアップ
- バージョンを揃える！
    - バージョンにかなりセンシティブな印象
        - gremlin-server
        - gremlin-console
        - gremlin python
- コード一式は、[こちら](https://github.com/tkosht/gremlin.git)

# 環境

| 対象 | バージョン |
| --- | ------- |
| OS | macOS Big Sur (11.5.2) |
| docker | Docker version 20.10.8, build 3967b7d |
| docker-compose | docker-compose version 1.29.2, build 5becea4c |
| python | Python 3.8.10 |


# セットアップでやったこと

## Dockerfile

### gremlin-server

以下の内容の Dockerfile を作成

```file:docker/gremlin/server/Dockerfile
FROM tinkerpop/gremlin-server:3.4.8

WORKDIR /opt/gremlin-server/

ARG gremlin_version=3.4.8
RUN /bin/bash -c "bin/gremlin-server.sh install org.apache.tinkerpop gremlin-python ${gremlin_version}"
RUN /bin/bash -c "bin/gremlin-server.sh install org.apache.tinkerpop neo4j-gremlin ${gremlin_version}"
```

gremlin-python をインストールしておきます。
あとで、neo4j も試したいので、neo4j-gremlin も入れておきます。


### gremlin-console

結局、Dockerfile 作らなくてもよかったんですが、何かインストールするかもしれないと作成していたので、そのまま Dockerfile　に残しておきます。

```file:docker/gremlin/console/Dockerfile
FROM tinkerpop/gremlin-console:3.4.8
```

ここで、`tinkerpop/gremlin-server` とバージョンがあってないと、console 利用時にうまく通信できずエラーになります。少なくとも、ためした範囲ではそうでした。


## docker-compose.override.yml

docker-compose.yml に追記しても良かったのですが、開発用のコンテナ（app）がなくても動かせるように、override に切り出しました。

こんな感じです。

```file:docker-compose.override.yml
version: '3.7'

services:
  gremlin-console:
    build:
      context: ./
      dockerfile: docker/gremlin/console/Dockerfile
    image: $project.gremlin-console
    container_name: $project.gremlin-console
    volumes:
      - ./data/:/data/
      - ./conf/gremlin/console/remote.yml:/opt/gremlin-console/conf/remote.yml
      - ./conf/gremlin/console/remote-research.yml:/opt/gremlin-console/conf/remote-research.yml

  gremlin-server:
    build:
      context: ./
      dockerfile: docker/gremlin/server/Dockerfile
    image: $project.gremlin-server
    container_name: $project.gremlin-server
    ports:
      - "8182:8182"
    volumes:
      - ./data/:/data/
      - ./conf/gremlin/server/gremlin-server.yml:/opt/gremlin-server/conf/gremlin-server.yml
      - ./conf/gremlin/server/gremlin-server-neo4j.yml:/opt/gremlin-server/conf/gremlin-server-neo4j.yml
      - ./conf/gremlin/server/neo4j.properties:/opt/gremlin-server/conf/neo4j.properties
    entrypoint: >
      /bin/bash bin/gremlin-server.sh conf/gremlin-server.yml
      # /bin/bash bin/gremlin-server.sh conf/gremlin-server-neo4j.yml

```

ここで、設定ファイルを変更するため、ローカルの設定ファイル（conf 配下）をマウントしておき、entrypoint を上書きしています。
[tinkerpop/gremlin-server](https://github.com/apache/tinkerpop/blob/master/gremlin-server/Dockerfile) のエントリーポイントが、設定ファイルの host を `sed -i` で変更したり、拡張子が `yml` じゃなくて、`yaml` なのが好みに合わなかったので、entrypoint を上書きする方法を取りました。

※　Ubuntu　18.04 on WSL2 では、docker-server が、gremlin 用のユーザとグループ（100:101）で起動されるので、/data などにマウントしたり、ホストとファイル共有する際には、注意が必要です（自分は、この記事で紹介しているリポジトリではないですが、起動前に、chown する make タスクを作成しました。）


## 設定ファイルの作成＆変更

gremlin-server のコンテナに入って、`gremlin-server.yaml` をマウントした共有フォルダ（`/data` など）を経由して、`gremlin-server.yml` として保存しておきます。（拡張子を変えておく、`yaml` -> `yml`）


### gremlin-server.yml の変更点

#### host を変更

よくある、バインドする`host` について、localhost を、0.0.0.0 に変更します。

```file:conf/gremlin/server/gremlin-server.yml
host: 0.0.0.0
```

#### serializers に追加

gremlin 3.4.8 では、`GryoMessageSerializerV3d0` (V3) を使うみたいなので、追記しておきます。(設定しないと、console 利用時にエラーになります)

```file:conf/gremlin/server/gremlin-server.yml
serializers:
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { useMapperFromGraph: graph }}            # application/vnd.gremlin-v1.0+gryo
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoLiteMessageSerializerV3d0, config: { useMapperFromGraph: graph }}        # application/vnd.gremlin-v1.0+gryo-lite
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { serializeResultToString: true }}        # application/vnd.gremlin-v1.0+gryo-stringd
```

### remote.yml の作成

gremlin-console 利用時に、gremlin-server へ接続するための設定ファイルを作成しておきます。
ファイル名は、`remote.yml` でなくてもよいです、その際は、gremlin-console のプロンプトで `:remote connect tinkerpop.server conf/remote.yml` を実行する際に、`remote.yml` のところを変更したファイル名に変更してください。

```file:conf/gremlin/console/remote.yml
hosts: [gremlin-server]
port: 8182
```



## gremlin-python をインストール

開発用の Dockerfile でインストールに使う `requirements.txt` を作成しておきます。
`gremlinpython` の バージョンを、gremlin-server, gremlin-console に合わせて、`3.4.8` を指定します。（ここも重要です！）

```file:requirements.txt
# for sqa
pudb
pylint
flake8
black
coverage
pytest
pytest-flake8
pytest-cov
pytest-xdist
pytest-html
docutils==0.15
Sphinx

# for graph db
neo4j
neomodel
gremlinpython==3.4.8
```


以上で、セットアップは完了です。では、使っていきましょう


# 環境構築

## リポジトリをクローン

リポジトリとして、まとめたのでクローンしてご利用ください。

```zsh
git clone https://github.com/tkosht/gremlin
cd gremlin
```

## コンテナのビルド＆起動

```zsh
make
```

## 起動確認

`docker-compose ps` または、`make ps` (docker-compose ps を Makefile でタスク定義したもの)で、確認します。

```zsh
make ps
~/p/gremlin ❯❯❯ make ps
docker-compose ps
         Name                       Command               State                    Ports                  
----------------------------------------------------------------------------------------------------------
gremlin.app              /bin/bash                        Up                                              
gremlin.gremlin-server   /bin/bash bin/gremlin-serv ...   Up      0.0.0.0:8182->8182/tcp,:::8182->8182/tcp
~/p/gremlin ❯❯❯ 
```

上のような感じで表示されれば、OKです。


# 使ってみる

## gremlin-console から使う

gremlin-console の コマンドを並べたものを、`src/gremlin/test.gremlin` として保存しているので、そのコマンドをリダイレクトして実行します。

```zsh
~/p/gremlin ❯❯❯ make gremlin-console < src/gremlin/test.gremlin
docker-compose up -d app gremlin-server
gremlin.gremlin-server is up-to-date
gremlin.app is up-to-date
docker-compose run --rm gremlin-console
Creating gremlin_gremlin-console_run ... done
Sep 05, 2021 12:27:37 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.

         \,,,/
         (o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.utilities
plugin activated: tinkerpop.tinkergraph
gremlin> :remote connect tinkerpop.server conf/remote.yml
==>Configured gremlin-server/172.30.0.2:8182
gremlin> :remote console
==>All scripts will now be sent to Gremlin Server - [gremlin-server/172.30.0.2:8182] - type ':remote console' to return to local mode
gremlin> g.addV('person').property('name','hello')
==>v[0]
gremlin> g.addV('person').property('name','world')
==>v[2]
gremlin> g.V().values('name')
==>hello
==>world
gremlin> g.V().count()
==>2
gremlin> :exit
~/p/gremlin ❯❯❯ 
```

上のような出力結果が得られれば成功です！


## python から使う

以下のように、`make bash` で開発用コンテナをインタラクティブモードで起動し、python モジュール `src.trial` を実行します。

```
~/p/gremlin ❯❯❯ make bash
docker-compose up -d app gremlin-server
gremlin.app is up-to-date
gremlin.gremlin-server is up-to-date
docker-compose exec app bash
dsuser@8307be2e3004:~/workspace$ python -m src.trial
names: ['hello', 'world']
dict: [{'name': ['hello']}, {'name': ['world']}]
count: 2
dsuser@8307be2e3004:~/workspace$ 
```

以上のように、表示されれば、成功です！

# 注意

コンテナを停止・削除（docker-compose down)すると、gremlin 上で作成・追加したグラフが消えます。


## 簡易エクスポート

エクスポートするための簡単な gremlin-console 用のスクリプト（`src/gremlin/export.gremlin`）を作成したので、ご利用ください。
`data/test.json` に保存します。

```zsh
~/p/gremlin ❯❯❯ make gremlin-console < src/gremlin/export.gremlin
docker-compose up -d app gremlin-server
gremlin.app is up-to-date
gremlin.gremlin-server is up-to-date
docker-compose run --rm gremlin-console
Creating gremlin_gremlin-console_run ... done
Sep 05, 2021 12:37:37 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.

         \,,,/
         (o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.utilities
plugin activated: tinkerpop.tinkergraph
gremlin> :remote connect tinkerpop.server conf/remote.yml
==>Configured gremlin-server/172.30.0.2:8182
gremlin> :remote console
==>All scripts will now be sent to Gremlin Server - [gremlin-server/172.30.0.2:8182] - type ':remote console' to return to local mode
gremlin> // g.addV('person').property('name','hello')
==>null
gremlin> // g.addV('person').property('name','world')
==>null
gremlin> g.V().values('name')
==>hello
==>world
gremlin> g.V().count()
==>2
gremlin> g.io("/data/test.json").write()
gremlin> :exit
~/p/gremlin ❯❯❯ cat data/test.json
{"id":{"@type":"g:Int64","@value":0},"label":"person","properties":{"name":[{"id":{"@type":"g:Int64","@value":1},"value":"hello"}]}}
{"id":{"@type":"g:Int64","@value":2},"label":"person","properties":{"name":[{"id":{"@type":"g:Int64","@value":3},"value":"world"}]}}
~/p/gremlin ❯❯❯ 

```


## 簡易インポート

インポートするための簡単な gremlin-console 用のスクリプト（`src/gremlin/import.gremlin`）を作成したので、ご利用ください。
`data/test.json` からロードします。

動作確認のために、gremlin-server を down & up しておきます。

```zsh
~/p/gremlin ❯❯❯ make reup
docker-compose down
Stopping gremlin.gremlin-server ... done
Stopping gremlin.app            ... done
Removing gremlin.gremlin-server ... done
Removing gremlin.app            ... done
Removing network gremlin_default
docker-compose up -d app gremlin-server
Creating network "gremlin_default" with the default driver
Creating gremlin.gremlin-server ... done
Creating gremlin.app            ... done
~/p/gremlin ❯❯❯ make gremlin-console < src/gremlin/ref.gremlin
docker-compose up -d app gremlin-server
gremlin.gremlin-server is up-to-date
gremlin.app is up-to-date
docker-compose run --rm gremlin-console
Creating gremlin_gremlin-console_run ... done
Sep 05, 2021 12:40:24 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.

         \,,,/
         (o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.utilities
plugin activated: tinkerpop.tinkergraph
gremlin> :remote connect tinkerpop.server conf/remote.yml
==>Configured gremlin-server/172.31.0.3:8182
gremlin> :remote console
==>All scripts will now be sent to Gremlin Server - [gremlin-server/172.31.0.3:8182] - type ':remote console' to return to local mode
gremlin> g.V().values('name')
gremlin> g.V().count()
==>0
gremlin> :exit
~/p/gremlin ❯❯❯ 

```

次に、インポートします。

```zsh
~/p/gremlin ❯❯❯ make gremlin-console < src/gremlin/import.gremlin
docker-compose up -d app gremlin-server
gremlin.app is up-to-date
gremlin.gremlin-server is up-to-date
docker-compose run --rm gremlin-console
Creating gremlin_gremlin-console_run ... done
Sep 05, 2021 12:41:36 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.

         \,,,/
         (o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.utilities
plugin activated: tinkerpop.tinkergraph
gremlin> :remote connect tinkerpop.server conf/remote.yml
==>Configured gremlin-server/172.31.0.3:8182
gremlin> :remote console
==>All scripts will now be sent to Gremlin Server - [gremlin-server/172.31.0.3:8182] - type ':remote console' to return to local mode
gremlin> g.io("/data/test.json").read()
gremlin> g.V().values('name')
==>hello
==>world
gremlin> g.V().count()
==>2
gremlin> :exit
~/p/gremlin ❯❯❯ 
```

ロードできているか確認します。

```
~/p/gremlin ❯❯❯ make gremlin-console < src/gremlin/ref.gremlin
docker-compose up -d app gremlin-server
gremlin.gremlin-server is up-to-date
gremlin.app is up-to-date
docker-compose run --rm gremlin-console
Creating gremlin_gremlin-console_run ... done
Sep 05, 2021 12:42:30 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.

         \,,,/
         (o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.utilities
plugin activated: tinkerpop.tinkergraph
gremlin> :remote connect tinkerpop.server conf/remote.yml
==>Configured gremlin-server/172.31.0.3:8182
gremlin> :remote console
==>All scripts will now be sent to Gremlin Server - [gremlin-server/172.31.0.3:8182] - type ':remote console' to return to local mode
gremlin> g.V().values('name')
==>hello
==>world
gremlin> g.V().count()
==>2
gremlin> :exit
WARN  io.netty.channel.nio.NioEventLoop  - Selector.select() returned prematurely 512 times in a row; rebuilding Selector io.netty.channel.nio.SelectedSelectionKeySetSelector@239e1c3a.
~/p/gremlin ❯❯❯ 
```

`hello`, `world` が ロードされていることが確認できました。

おっと、WARN も出てますね。。
`:exit` 後なので、一旦、置いておきます。


# まとめ

上位互換性を結構期待して、環境セットアップをやってたら、本当にハマりました。
gremlin は、バージョンセンシティブだとかなり、体験できました。今後、間違いなく気をつけるでしょう笑。

同じように苦労し始めている方の助けになれば幸いです。

サーバ down&up の度に、export & import するのは微妙なので、
次は、neo4j ファイルとして永続化する方法を整理したいと思います。

Makefile のタスクとして定義すれば、export & import を毎回やってもよいのですが、データ量が増えた時は、export & import コストも侮れないと思いますので。


# 参考URL

めちゃくちゃ調べたんですが、記憶に残っているサイトだけ・・
Stack Overflow のサイトとか・・忘れました（ ;  ; ）


- [Pythonを使ってGremlin ServerとNeo4jを試してみる](https://kumano-te.com/activities/docker-gremlin-server-neo4j-by-python)
- [グラフデータベースのNeo4jをCypherではなくGremlinで操作](https://qiita.com/ike_dai/items/2d859dd54cb152fabfdc)
- [tinkerpop/gremlin-server at master・apache/tinkerpop](https://github.com/apache/tinkerpop/tree/master/gremlin-server)
- [gremlin-orm/gremlin-orm-neo4j:Docker image to run Neo4j with ...](https://github.com/gremlin-orm/gremlin-orm-neo4j)
- [TinkerPop Documentation](https://tinkerpop.apache.org/docs/current/reference/)