GrowiをRaspbian Busterのdocker-composeで動かした話

# はじめに

私は現在自宅のRaspberryPi3 modelB+(以下Pi3)にpukiwikiをインストールし，自宅内外からPi3にアクセスして備忘録を作成しています．最近ではGrowiが便利という記事を見かけたのでPi3にGrowiをインストールしようとしましたが，[Pi3にGrowiをインストールする記事](https://qiita.com/hawk777/items/4722a12ff957803ac390)はあったものの，Qiita上にRaspbianでGrowiを動かそうとしたという記事は見当たらなかったので，ラズパイにdockerでGrowi入れたいという人の参考になればよいと思い記事にしました．間違いや改善点などがありましたら指摘をして頂ければ幸いです．

- 2020/07/04更新
  - 本家からForkしました → [temple1026/growi-docker-compose-pi](https://github.com/temple1026/growi-docker-compose-pi)
  - githubのファイルはGrowiの最新版(v4.0.7)にしているのと，docker-compose.ymlのvolumesを変更してありますが，記事の内容と大きな差はありません．

- 2020/07/07更新
   - docker-composeをaptでインストールして公式の通りに↓のコマンドを実行してしまうと，/usr/sbin/のdockerd(aptでインストールされたもの)を呼んでしまい，mongoのイメージをpullするときに no matching manifest for unknown...といったエラーが出るみたいです
   - なので sudo /usr/bin/dockerd & で直接dockerdを指定するか，docker.ioを削除してからバイナリファイルを/usr/binにコピーするのがよさそうです．
   - 私の環境ではdocker.ioをaptで削除したら正常にarm64v8/mongo:3.6のイメージをpullできたので，もし動かない場合はwhichなどでdockerdのパスの確認と，dockerdのバージョンとdockerのバージョンが一致しているか確認すると良いと思います
       (docker-compose 1.21.0, docker 19.03.12での動作を確認しました)
　 - 間違いがあったらご指摘をお願いします

# 環境

私のPi3の環境は以下の通りです．Pi3には2019/09/25にリリースされたRaspbian Busterをインストールしました．

マシン: Raspberry Pi 3 ModelB+
OS: Raspbian Buster 2019-09-29版 ([URL]( https://www.raspberrypi.org/downloads/raspbian/))
カーネル: 4.19.93-v8+
ストレージ: SSD 500GB (USBブート対応済み)

Docker: 19.03.5(aarch64版)
Docker-compose: 1.21.0
Growi: 3.6.3
mongo: 3.6
elasticsearch: 6.6.1
nodejs: 10.18.1
npm: 6.13.4
yarn: 1.21.1

私の環境のフォルダとファイルの関係は以下の通りです．

```
/home/usr/growi/-- docker-compose.yml
                 ├ Dockerfile
                 ├ elasticsearch/Dockerfile
                 └ data/-- es_data/
                         ├ growi_data/
                         ├ mongo_configdb/
                         └ mongo_db/      
```

※ dataフォルダ以下は```docker-compose up``` 前に作成する必要があります．



# つまずいた点
Github上に公開されている[growi-docker-compose](https://github.com/weseek/growi-docker-compose)のリポジトリを使用する場合，MongoDB，Elasticsearchのコンテナを動かしてからGrowiのコンテナを実行するのですが，Raspbian + Pi3の環境ではそれぞれのコンテナすべてに問題がありました．それぞれの問題点は次の通りです．

- ***mongodbがRaspbianで使えない(dockerでpullできない)***
- **Elasticsearchが動かない***
- ***growiのコンテナのビルド中にフリーズする***

以下にそれぞれの原因と対策について説明していきたいと思います．

## mongodbがRaspbianで使えない(dockerでpullできない)
　普通にRaspbian Busterをインストールしてdocker-compose版のgrowiを動かそうとすると，まずmongodbのコンテナがpullできずにno manifestとエラーを吐かれます．これはPi3にインストールされているRaspbianが32bit版なのに対し，mongodbは64bit版のOSしかサポートしていないからで，Dockerhubにはmongodbの公式armhf(32bit)用イメージが存在しません．ではどうすればよいかというと，実はPi3のSoC(CPU)はarm64v8で64bitに対応しているので64bit版のOS(Ubuntuとか)を入れれば解決できます．しかし環境構築が終わってるOSを入れ替えるのは非常にめんどくさかったので，最終的に[ここのページ](https://www.raspberrypi.org/forums/viewtopic.php?t=250730)を参考にカーネルのみ64bitに変更することで対応しました．具体的にはまず，

```
sudo rpi-update
```
でファームウェアを最新版にアップデートします．**ファームウェアのアップデートによって不具合が起こる場合もあるようなので完全に自己責任です．**次に

```
sudo echo "arm_64bit=1" >> /boot/config.txt
```
で/boot/config.txtに設定を追記し，再起動すれば64bit版のカーネルが起動時に読み込まれます．このときSDカードの寿命延長対策や動作高速化のために，***起動はmicroSDでrootフォルダは外付けHDD or SSD***にしている場合は,　mmcblk0p1を適当なフォルダにマウントしてからマウントしたフォルダの/boot/config.txtに設定を追記する必要がありました．(当然と言えば当然なんですが，Pi4で↑のような環境を作ったときにkernel8.imgが読み込まれずに悩んだので共有します…)

カーネルを64bitにすればmongodbをpullできると思いたいところですが，既にdockerをインストールしている場合は32bit版のdockerがインストールされていると思いますので，未だにpullできないと思います(私はできませんでした)． dockerはaptでもインストールできますが，確実に64bit版をインストールするためにバイナリファイルを使ってインストールしました．

コマンドは公式のドキュメント通りです．
[Install Docker Engine - Community from binaries](https://docs.docker.com/install/linux/docker-ce/binaries/)

```
wget https://download.docker.com/linux/static/stable/aarch64/docker-19.03.5.tgz
tar xvzf docker-19.03.5.tgz
cp docker/* /usr/bin/
sudo dockerd &
```
これで，mongodbがpullできるようになりました．(ちなみにdocker-composeはaptでインストールしました)

## Elasticsearchが動かない
ElasticsearchはGrowiで全文検索を行う際に必要になるのですが，growi-docker-compose付属のDockerfileからだとpluginのインストール時に以下のエラーが発生しました．

```
standard_init_linux.go:211: exec user process caused "exec format error"
```

また，pluginのインストールをコメントアウトしてもコンテナの起動時にOCI runtime errorでコンテナを立ち上げることができませんでした．恐らくベースのOSが原因だと思うのですが，解決方法が分からなかったのでalpineベースで新しくDockerfileを作成しビルドしました．Dockerfileは以下の通りです．


```:/home/usr/growi/elasticsearch/Dockerfile
# javaの公式パッケージを使用
FROM arm64v8/openjdk:8-alpine

# Elasticsearchのインストール先とElasticsearchのバージョン指定
ENV ES_HOME=/opt/elasticsearch
ENV VER_ELASTICSEARCH 6.6.1

# Elasticsearchのインストールとjavaのメモリ制限の設定
WORKDIR /opt
RUN apk add --no-cache --virtual .del wget bash \
    && wget --no-check-certificate https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${VER_ELASTICSEARCH}.tar.gz \
    && tar xvzf elasticsearch-${VER_ELASTICSEARCH}.tar.gz \
    && mv elasticsearch-${VER_ELASTICSEARCH} ${ES_HOME} \
    && rm elasticsearch-${VER_ELASTICSEARCH}.tar.gz
RUN echo "network.host: 0.0.0.0" >> ${ES_HOME}/config/elasticsearch.yml \
   && echo "discovery.type: single-node" >> ${ES_HOME}/config/elasticsearch.yml \
   && echo "xpack.ml.enabled: false" >> ${ES_HOME}/config/elasticsearch.yml
RUN sed "s/Xms1g/Xms256m/" ${ES_HOME}/config/jvm.options > ${ES_HOME}/config/jvm.options \
    && sed "s/Xmx1g/Xmx256m/" ${ES_HOME}/config/jvm.options > ${ES_HOME}/config/jvm.options

# 一般ユーザの追加(Elasticsearchがrootで実行できないため)
RUN adduser -D elasticsearch -h ${ES_HOME}
RUN chown -R elasticsearch:elasticsearch ${ES_HOME}/*

USER elasticsearch
ENV PATH="${PATH}:${ES_HOME}/bin/"

# Elasticsearchのプラグインのインストール
WORKDIR ${ES_HOME}
RUN sh bin/elasticsearch-plugin install analysis-kuromoji
RUN sh bin/elasticsearch-plugin install analysis-icu

# 使わないパッケージのアンインストール
USER root
RUN apk del .del

# 一般ユーザに切り替え
USER elasticsearch
WORKDIR ${ES_HOME}

```

apkコマンドで新しくインストールしたのはwgetとbashだけなので，容量が気にならない場合は最後のユーザの切り替えとパッケージのアンインストールは気にならなければ省略して大丈夫ですね．bashはプラグインをインストールする際に必要でした．alpineではbashの代わりにashを使うので，bashがインストールされていない環境ではelasticsearch-pluginを実行したときにelasticsearch-cliが見つからないと怒られました．
ビルドに成功したらbin/elasticsearchを実行後に他の端末から```curl http://コンテナが実行中のIPアドレス:9200```を実行して何かが返ってくれば大丈夫です．(ポート番号はコンテナ起動時に-pオプションで指定してください) 私の場合はコンテナを立ち上げてから1分ぐらい待たないとElasticsearchが起動しなかったので，正常に起動するまでに少し待つ必要がありました．


## ビルド中にフリーズする

無事にmongodbとelasticsearchが一応使えるようになったのですが，growiのビルドを開始するとyarnコマンドで必ずフリーズしてしまい，他のコマンドが一切使えなくなるという現象に遭遇しました．topコマンドでビルドしているときのシステム監視していたところ，RAMの余裕がなくなってました(Availableが残り20MBぐらいになってました)．RAM1GBしかないから不可能じゃないか…と思ったんですが，足りないならばSwapメモリを増やせばよいとのことで，初期状態の100MBから2048MBに増やすことでフリーズはしなくなりました．(手持ちのRaspberryPi4Bでビルドしたところうまく動き，メモリが原因だと気付きました...)
スワップメモリの設定方法は[こちらのページ](https://qiita.com/spiderx_jp/items/819750a1f8da3728fdce)を参考にしました．

最終的にビルドに成功したDockerfileは以下の通りです．elasticsearch同様にalpineベースで作成しました．
Growiのバージョンは3.6.3の最新版です．[growi-docker-compose](https://github.com/weseek/growi-docker-compose)と同様に，ElasticsearchとmongoDBのコンテナの起動待ちのためにdockerizeをインストールしますが，amd64版だとうまく動かず，armhf版でをインストールすることで動作しました．
また，yarnコマンドの実行時にはモジュール群のダウンロードに時間がかかる場合があるらしく，タイムアウトを長めに設定することでエラーを回避することが可能でした．

```:/home/usr/growi/Dockerfile
# nodejsのイメージから作成
FROM arm64v8/node:10.18.1-alpine3.11
WORKDIR /opt

# ビルドに必要なパッケージのインストール
RUN apk --no-cache --virtual .del add curl git python make g++ wget openssl

# growiのダウンロード
RUN git clone https://github.com/weseek/growi growi
WORKDIR /opt/growi
RUN git checkout -b v3.6.3 refs/tags/v3.6.3

# 必要なモジュールの取得
RUN yarn --network-timeout 1000000

# ビルド
RUN npm run build:prod

# dockerizeのインストール
ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-armhf-$DOCKERIZE_VERSION.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-linux-armhf-$DOCKERIZE_VERSION.tar.gz \
    && rm dockerize-linux-armhf-$DOCKERIZE_VERSION.tar.gz

RUN apk del .del
```

# docker-compose.ymlの設定
これでGrowi, Elasticsearch, MongoDBのイメージがビルドできたので，docker-composeで実行できるようにdocker-compose.ymlを追加します．こちらのファイルはこれまで同様に[growi-docker-compose](https://github.com/weseek/growi-docker-compose)を編集しました．

変更した点は以下の通りです．
 - 外部からのアクセス許可 (- 3000:3000)
 - volumesにローカルのフォルダをマウント (usrは自分のユーザ名)
 - mongoのベースイメージをarm64v8用に変更
 - dockerizeのタイムアウトを60秒から120秒に変更 (コンテナの起動に時間がかかるため)

ファイルを保存したら```docker-compose up -d --build```で起動するわけですが，私のPi3では最初から最後までで50分かかりました．

```:/home/usr/growi/docker-compose.yml
version: '3'

services:
  growi:
    build:
      context: .
      dockerfile: ./Dockerfile
    container_name: growi
    ports:
      # - 127.0.0.1:3000:3000    # localhost only by default
      - 3000:3000
    links:
      - mongo:mongo
      - elasticsearch:elasticsearch
    depends_on:
      - mongo
      - elasticsearch
    environment:
      - MONGO_URI=mongodb://mongo:27017/growi
      - ELASTICSEARCH_URI=http://elasticsearch:9200/growi
      - PASSWORD_SEED=changeme
      - FILE_UPLOAD=local     # activate this line if you use local storage of server rather than AWS

    command: "dockerize
               -wait tcp://mongo:27017
               -wait tcp://elasticsearch:9200
               -timeout 120s
               npm run server:prod"
    restart: unless-stopped
    volumes:
      - /home/usr/growi-compose/data/growi_data:/data
    tty: true

  mongo:
    image: arm64v8/mongo:3.6
    container_name: mongo
    restart: unless-stopped
    volumes:
      - /home/usr/growi/data/mongo_configdb:/data/configdb
      - /home/usr/growi/data/mongo_db:/data/db

  elasticsearch:
    build:
      context: ./elasticsearch
      dockerfile: ./Dockerfile
    container_name: es
    command: sh bin/elasticsearch
    environment:
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms256m -Xmx256m"  # increase amount if you have enough memory
    ulimits:
      memlock:
        soft: -1
        hard: -1
    restart: unless-stopped
    ports:
      - 9200:9200
      - 9300:9300
    volumes:
      - /home/usr/growi/data/es_data:/usr/share/elasticsearch/data
      - ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
    tty: true
```

# 感想
セットアップしたGrowiはdocker-composeを使って構築しましたが，一人で使う分にはメモリ不足に陥ることもなくVPN越しにアクセスしてもサクサク動いている(と感じる)ので，備忘録をまとめるツールとしては非常に優秀だと思いました．今回のセットアップを振り返ると，単純なことではあるのですがアーキテクチャやOSの違いでうまく動かなかったり，メモリが足りないという初歩的な?ことでつまずいたり，Dockerを使えば必ずしも楽に環境を構築できるわけではないということを思い知りました…

今後の課題としては，今回のビルド出来上がったイメージはgrowiが1.3GB，elasticsearchが461MBと大きい気がするので，時間があるときにdockerの勉強をしつつよりコンパクトなイメージを構築したいと思います．

# 参考
- [Raspberry Pi3でGrowiを動かす](https://qiita.com/hawk777/items/4722a12ff957803ac390)
- [Download Raspbian](https://www.raspberrypi.org/downloads/raspbian/)
- [Pi4 64-bit raspbian kernel for testing - Focus on Pi4](https://www.raspberrypi.org/forums/viewtopic.php?t=250730)
- [github - weseek/growi-docker-compose](https://github.com/weseek/growi-docker-compose)
- [Install Docker Engine - Community from binaries](https://docs.docker.com/install/linux/docker-ce/binaries/)
- [Intel Movidius NCS[Raspberry-Pi3]（その１：SDKインストール）](https://qiita.com/spiderx_jp/items/819750a1f8da3728fdce)

# 履歴
- 2020/01/16　公開
- 2020/06/19 ElasticsearchのDockerfileの誤字を修正 
- 2020/07/04 GitHubのリンクを追加
- 2020/07/07 mongoのイメージがpullできない現象について追記