Docker内からPrivateサブネットに配置したNeptuneに接続する


最近、東京リージョンにきたNeptuneを開発効率をあげるため、Docker内から接続してみようと思います。


環境
---

* docker
	* docker-compose
* python3.6
	* [gremlin-python](https://github.com/apache/tinkerpop/tree/master/gremlin-python)


大まかな流れ
---

* docker imageの用意
* ポートフォワーディングの設定
	* publicなEC2の用意（省略）
* docker-compose.ymlの設定
	* extra_hostsの追加
	* ホストの`.ssh/config`にポートフォワーディングの設定
	* 起動時にホストの`.ssh/`をイメージ内の適当なところにマウント

ssh/configにポートフォワーディングを書く
---

```
Host neptune
    HostName 踏み台EC2のPublicIP
    User ec2-user
    IdentityFile ~/.ssh/EC2の鍵ファイル.pem
    GatewayPorts   yes
    LocalForward 8182 接続したいNeptuneクラスターエンドポイント:8182
```


Dockerfileの用意
---

とりあえずpython3.6の公式イメージを使います。

```
FROM python:3.6

RUN apt-get update -y && apt-get upgrade -y
RUN apt-get install autossh -y

RUN mkdir /root/.ssh
```

docker-compose.ymlの用意
---

足りないところは補完してください。
私がやっていることは

1. ホストの`.ssh/config`をコンテナの`/root/.sshdummy`にマウントする
2. コンテナ内で、EC2の鍵ファイルを、/root/.sshにコピーする
3. 鍵ファイルのパーミッションを設定する
4. ポートフォワーディングする
5. /bin/bashで起動

また、extra_hostsでローカルホストにNeptuneクラスターのエンドポイントを紐づけている理由は、今回pythonで使う`gremlin-python`が`https://localhost`という接続だとエラーが起こるためです。


```
version: '3'
services:
  gremlin:
    command: >
      /bin/bash -c "cp /root/.sshdummy/config /root/.ssh/ && 
      cp /root/.sshdummy/EC2の鍵ファイル.pem /root/.ssh/ && 
      chmod 600 /root/.ssh/EC2の鍵ファイル.pem &&
      autossh neptune -f -N &&
      /bin/bash"
    volumes:
      - ~/.ssh:/root/.sshdummy
      - 
    extra_hosts:
      - "Neptuneクラスターエンドポイント:127.0.0.1"
```


とりあえず、接続チェック
---

```
docker-compose run gremlin
```

で、中にはいって以下を叩いてみます。


```
curl https://Neptuneクラスターのエンドポイント:8182

> {"requestId":"28b45dc4-842c-c129-2d4d-******","detailedMessage":"no gremlin script supplied","code":"MissingParameterException"}
```

帰ってきました。接続オッケーです。


gremlin-pythonを使ってみる
---

パッケージのインストールをします。

```
pip install gremlinpython
```

私は、現在駅すぱあとという経路検索サービスを提供する会社に現在勤めているので、駅データと路線データをNeptuneにロードして簡単な経路検索をしてみようと思います。

せっかくグラフDBを使うので、N頂点〜N頂点の経路検索を一度にやってみます。


```
from gremlin_python import statics
from gremlin_python.structure.graph import Graph
from gremlin_python.process.graph_traversal import __
from gremlin_python.process.strategies import *
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
from gremlin_python.structure.graph import VertexProperty
import time

graph = Graph()
g = graph.traversal().withRemote(DriverRemoteConnection('wss://Neptuneクラスターのエンドポイント:8182/gremlin','g'))

# 出発地点
origin = g.V().has('station', '県コードキー', '県コード').has('駅種別キー', 駅種別).sample(10).toList()

# 到着地点
destination = g.V().has('station', '県コードキー', '県コード').has('駅種別キー', 駅種別).sample(10).toList()

# 頂点IDの配列化
origin_ids = [x.id for x in origin]
destination_ids = [x.id for x in destination]

start_time = time.time()
result = g.V().hasId(*origin_ids).repeat(__.outE().hasLabel('特定の辺のラベルのみ').inV().simplePath()).until(__.hasId(*destination_ids)).path().limit(100).map(__.unfold().hasLabel('station').values('name').fold())

for path in result.toList():
    print('->'.join(path))

print(time.time() - start_time)
```

結果

```
大崎広小路->五反田->戸越->中延
大崎広小路->戸越銀座->荏原中延->旗の台->荏原町->中延
青物横丁->新馬場->北品川->品川->大崎->五反田->大崎広小路
大崎広小路->五反田->大崎->大井町->下神明->戸越公園->中延

〜〜〜〜　省略　〜〜〜〜〜

代田橋->笹塚->新宿->四ツ谷->御茶ノ水->秋葉原->浅草橋->両国->錦糸町->押上
大崎広小路->五反田->大崎->品川->新橋->東銀座->銀座->東京->上野->日暮里->新三河島
青物横丁->新馬場->北品川->品川->新橋->東銀座->銀座->東京->上野->日暮里->新三河島
代田橋->笹塚->新宿->池袋->板橋->十条(東京都)->赤羽->尾久->上野->日暮里->新三河島0.43123531341552734（←処理時間
```

できました。

気をつけたいこと
---

* Neptuneで使えるGremlinは完全互換があるとは言えません。
	* 公式ドキュメントの[実装の相違点](https://docs.aws.amazon.com/ja_jp/neptune/latest/userguide/access-graph-gremlin-differences.html)をよく読みましょう

* Gremlinムズカシー(愚痴) :tired_face: