Neo4jをAPOC付きで配備するdocker-compose

## はじめに
Neo4jを利用する人が増えてきたとはいえ、まだまだ認知されていない状況。WindowsではNeo4j Desktopがあるので利用には不便しないのだけど、簡単なアプリケーションを作成して、それをチームに配布するとなると、イメージを作るには容量が大きくなるので、Dockerにすると楽。特に今のようなリモートワークをやっていたりするとなおさら。そこで、docker-composeでアプリとセットで配布しようと思ったところ、neo4jのdocker情報があまりなかったので、~~調べてみた~~いろいろやって動いたものを備忘として載せる。

### フォルダ構成
https://github.com/MichitoIchimaru/neo4j_docker-compose

```
Folder
│  docker-compose.yml
├─ docker
│   └─neo4j
│      Dockerfile
│      background.sh
│      init.sh
│      init.cypher
└─volumes
   └─neo4j
      └─plugins
         apoc.jar
```

### docker-compose.yml
```docker-compose.yml
version: "3"

networks:
  app_net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.30.0.0/24

services:
  neo4j:
    container_name: neo4j
    build: ./docker/neo4j
    volumes:
      - ./volumes/neo4j/data:/data
      - ./volumes/neo4j/plugins:/plugins
      - ./volumes/neo4j/logs:/logs
      - ./volumes/neo4j/import:/import
      - ./volumes/neo4j/init:/init
    ports:
      - "7474:7474"
      - "7687:7687"
    environment:
#      - NEO4JLABS_PLUGINS=["apoc"]
      - NEO4J_apoc_export_file_enabled=true
      - NEO4J_apoc_import_file_enabled=true
      - NEO4J_apoc_uuid_enabled=true
      - NEO4J_dbms_security_procedures_unrestricted=apoc.*
      - NEO4J_dbms_security_procedures_whitelist=apoc.*
      - NEO4J_dbms_memory_heap_initial__size=512m
      - NEO4J_dbms_memory_heap_max__size=2G
      - NEO4J_dbms_default__listen__address=0.0.0.0
      - NEO4J_dbms_connector_bolt_listen__address=:7687
      - NEO4J_dbms_connector_http_listen__address=:7474
      - NEO4J_dbms_connector_bolt_advertised__address=:7687
      - NEO4J_dbms_connector_http_advertised__address=:7474
      - NEO4J_dbms_allow__upgrade=true
      - NEO4J_dbms_default__database=neo4j
      - NEO4J_AUTH=neo4j/p@ssw0rd
      - EXTENSION_SCRIPT=/tmp/background.sh
    restart: unless-stopped
    networks:
      app_net:
        ipv4_address: 172.30.0.3
```

##### 解説

```
#      - NEO4JLABS_PLUGINS=["apoc"]
```
この部分のコメントを外すと、apoc.jarをインターネットから自動でダウンロードしてくるようになる。今回はvolumes/neo4j/pluginsフォルダに最初からapoc.jarを格納しているので、ダウンロードは必要ないため、コメントアウトしている。
apoc.jarのダウンロードはかなり時間がかかるので、あらかじめダウンロードしておいたものを使う方が起動時間を短縮できる。ただし、この機能を有効化するメリットが1つある。今回はNeo4jのDockerイメージにneo4j:4.1.3（2020/10/15時点の最新は4.1.3）を利用しているが、neo4jのバージョンに対応したapoc.jarのバージョンが存在するため、組み合わせによっては動かない場合がある。このコメントを外すと、利用しているNeo4jのイメージに対応したapoc.jarを自動で判別し、インストールしてくれる。

```
      - NEO4J_AUTH=neo4j/p@ssw0rd
```
neo4jのパスワードを設定

```
      - EXTENSION_SCRIPT=/tmp/background.sh
```
このEXTENSION_SCRIPTがよく分からなかった。Neo4jのマニュアルでは何をするための用途なのか書いておらず、他者の記事を見ると`Neo4j起動後に実行されるスクリプトである`と書いてある。実際の挙動を見てみると、Neo4j起動後というよりは起動前に実行されているっぽい。なおかつこのスクリプトが正常に終了しないとNeo4jが起動されない様子。そのため、今回はbackground.shの中でinit.shを（&を付けて）実行するようにした。そうするとNeo4jも無事に起動され、バックグラウンドでinit.shがNeo4jの起動を待ち受けて初期データをセットアップしてくれる。ちなみに、EXTENSION_SCRIPTでinit.shを&付きで実行してもうまくいかなかった。

### Dockerfile
```
FROM neo4j:4.1.3

RUN apt-get update
RUN apt-get install -y curl
COPY init.cypher /tmp/init.cypher
COPY background.sh /tmp/background.sh
COPY init.sh /tmp/init.sh
RUN chmod -v +x /tmp/background.sh
RUN chmod -v +x /tmp/init.sh
```
neo4jのイメージを配備すると、apt-getのパッケージリストが空なので、一旦apt-get updateを実行しないと、パッケージがインストールできない。ここでは、Neo4jの起動をチェック（Cypherを実行可能かを確認）するためにcurlをインストールするようにしている。
Proxy環境下の場合はここで`ENV http_proxy http://hogehoge`などを追加すると動くはず。

### background.sh
```background.sh
#!/bin/bash

/tmp/init.sh &
```
上でも解説しているが、EXTENSION_SCRIPTに指定して、init.shをバックグラウンド実行したいだけのシェル。こちらがinit.shで後述のinit.shがbackground.shの方が正しいんじゃないかという気が...。

### init.sh
```init.sh
#!/bin/bash

if [ -f /init/done ]; then
    echo "The initialization process is already completed." >> /init/setup.log
    exit 1
fi

COUNT=1

while [ $COUNT -lt 20 ]; do

  status_code=$(curl --write-out %{http_code} --silent --output /dev/null localhost:7474)
  if [[ "$status_code" = 200 ]] ; then
    touch /init/done
    cypher-shell -u neo4j -p p@ssw0rd -f /tmp/init.cypher
    echo "The initialization process is complete." >> /init/setup.log
    exit 1
  else
    echo "The neo4j service has not started yet. [$COUNT]" >> /init/setup.log
  fi

  COUNT=$(expr $COUNT + 1)
  sleep 10s

done

exit 0
```
このシェルは、起動したNeo4jに初期データを投入したいためのスクリプト。ただし、docker-composeを実行するとどうしてもこのスクリプトが毎回実行してしまうため、初回実行した際に/init/doneというファイルを作成し、そのファイルが存在すると何もせずに終了するようにしている。

##### 解説

```
if [ -f /init/done ]; then
    echo "The initialization process is already completed." >> /init/setup.log
    exit 1
fi
```
この部分で/init/doneの存在チェックをして、あれば終了、なければ後続処理を実施。

```
while [ $COUNT -lt 20 ]; do
```
Neo4jが起動してくるまで20回（厳密にこのコードだと19回）リトライする。

```
  status_code=$(curl --write-out %{http_code} --silent --output /dev/null localhost:7474)
```
curlでhttp://localhost:7474 にアクセスし、HTTPレスポンスコードを取得。

```
  if [[ "$status_code" = 200 ]] ; then
    touch /init/done
    cypher-shell -u neo4j -p p@ssw0rd -f /tmp/init.cypher
    echo "The initialization process is complete." >> /init/setup.log
    exit 1
  else
    echo "The neo4j service has not started yet. [$COUNT]" >> /init/setup.log
  fi
```
curlの結果が200の場合、/init/doneを作成し、/tmp/init.cypherをcypher-shellコマンドで実行する。200でない場合、何もせずリトライする。

```
  COUNT=$(expr $COUNT + 1)
  sleep 10s
```
カウントアップと10秒スリープ。ちなみに、Neo4jイメージではカウントアップに`(( COUNT++ ))`は利用できなかった。

#### init.cypher
```
MATCH (n) return n;
```
初期セットアップCypher。ここではサンプルとして全ノード取得を書いているが、実際はCREATE文などを書く。

このinit.shがうまく実行されると、/init（volumes/neo4j/init）配下のsetup.logに下記のようなログが出力され、同様にdoneファイルも生成されるはず。

```setup.log
The neo4j service has not started yet. [1]
The neo4j service has not started yet. [2]
The neo4j service has not started yet. [3]
The initialization process is complete.
```