OpenGrok+Dockerで、ダウンタイムがほぼ無いソース検索を作る

## まえおき

OpenGrok+Dockerで、使い捨てソース検索エンジンをつくる
https://qiita.com/YusukeIwaki/items/3f855202e9c00247d5c7

OpenGrokの公式Dockerイメージでソース検索サーバーを立てる
https://qiita.com/YusukeIwaki/items/ccf7dab6b1a284bbff26

と、Docker+OpenGrokでのソース検索サーバを建てるネタは放出してきたんだけども、
実のところ今までのソース検索は「３０分に１度、インデックス再構築のために、４分くらいダウンタイムが発生する」という、大きな欠点があったことは伝えていなかった。

自分用のソース検索だったらそれでよかったんだけど、周りに使ってもらい始めると、３０分に１回「なんかおかしいんですけど」って言われるリスクと戦わないといけないので、いい加減対処しようってなった。
ということで、今回はその欠点を解決した版を紹介しようと思う。

## なぜダウンタイムが発生するのか

雑に絵で説明しよう。理由は２つある。

いままでの構成はこう。ソースコードはホスト側にあって、それをbindfsでDocker側にマウントする＆Docker側でインデックスが保持される。

![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/6849bced-9b79-9768-9e35-e11315f0fa35.png)

これだと、ソースコードを最新化するタイミングでまず**インデックスと実ソースが微妙に不整合な状態**になる。「検索結果には出てくるのに、実際に見に行くと最新ソースでは消されていたのでなくなっていた」みたいなことが起きる。

![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/826e54b1-5892-fac4-52f8-fc7459212551.png)

そんで、次に最大の問題であるダウンタイムが発生する。**まだ出来上がってないインデックスを外からHTTPでアクセスしに来ちゃう**からだ。
![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/1cb6a12d-8dbd-c36f-66bb-22a9454ec6b2.png)


## Dockerらしく、裏でインデックス作って、できあがったら差し替えればいいじゃない？

ソースコードも全部コンテナに押し込めてしまえば、
![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/7b05d765-454e-1724-6400-0f473a6b2256.png)

こんな感じで、裏でdocker buildしておいて、インデックス生成までdocker buildでやってしまって
![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/b809a266-ead8-731e-22ed-37732bb65bfc.png)

イメージのビルドが終わったら、あとは好きなタイミングで、古いコンテナをstopして新しいコンテナをrunすれば、せいぜい２〜３秒程度のダウンタイムで済むんじゃないか？という発想。
![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/e1380eb1-6887-db2c-3b4e-7c3ae9878db1.png)

## 公式Dockerイメージでは無理である

Oracle公式のopengrokのイメージは、docker runしたタイミングで初めてインデックス生成が行われる。
先に書いたように、HTTPを受け付けるにもかかわらずインデックスが出来上がってない状態、みたいなのが少なからずできてしまうので、これをそのまま使うことはできない。

仕方がないので、自前で用意することにした。
https://github.com/YusukeIwaki/docker-opengrok-alpine

https://github.com/oracle/opengrok/wiki/How-to-setup-OpenGrok の手順を参考にしつつ、
公式OpenGrokイメージの https://github.com/oracle/opengrok/blob/master/Dockerfile や https://github.com/oracle/opengrok/tree/master/docker を斜め読みして作成した。
Alpine Linuxの `apk add --no-cache ctags` だとuniversal-ctagsじゃなくて古いほうのctagsが入るので、universal-ctagsを自前でビルドしないといけないのが微妙に厄介。
他は割とすんなり理解できるだろう。

## 裏でイメージをビルドする君をつくる

イメージのビルドスクリプトは、

* 最新のdevelopブランチでも変更がないなら、全部キャッシュを効かせたい
* 最新のdevelopブランチが変更されているなら、git cloneの直前まではキャッシュを効かせ、それ以降はビルドしたい

のようにする。

Dockerfileでgit clone を使う際にキャッシュをADDで回避
https://qiita.com/sawanoboly/items/ac559c43b9662304931a

で書かれているように、ADDでorigin/developのコミットハッシュを書いたファイルをコンテナに送り込むといい。
GitHub APIを使うでもいいし、

```
git ls-remote git@bitbucket.org:YusukeIwaki/some_repository.git refs/heads/develop | cut -f1 > origin_develop.sha1.txt
```

みたいな感じで愚直にファイルを作るでもいいと思う。

ビルド自体は、シンプルに `docker build -t some-source-search .` するだけ。

## コンテナを差し替える君をつくる

雑だが、

```
docker ps | grep "0.0.0.0:8080" | cut -f1 -d' ' | xargs --no-run-if-empty docker stop | xargs --no-run-if-empty docker rm
docker run -d -p 8080:8080 some-source-search
```

こんな感じでいける。

## Rundeckでジョブ管理すると楽

* docker buildが終わり次第、コンテナの差し替えを行う
* 日中のインデックス更新は３０分に１回おこなう
* ログは３日間分くらいは残しておきたい

などなど、cronで管理するのはいやなので、Rundeckにまかせてしまう。
https://www.rundeck.com/open-source

セットアップ方法は、ぐぐればでてくるので省略。


## まとめ

切り替えは雑ですが、イミュータブルデプロイみたいなかんじの構成にすることで、かなり快適にソース検索ができるようになります。

公式OpenGrokイメージが似たようなDockerfileを提供してくれたら、もっとみんな幸せになるんじゃないかなぁ...。