Redmineプラグインのお試し環境を立てる (docker-compose & SQLite3)


## はじめに

Redmineに関する投稿や情報でも、Dockerを使って利用する例が増えて来ましたね。
私も実運用では使っていませんが、特にテスト方面で利用しています。

もちろん、RedmineやRedmineのコミュニティでもDockerのイメージが提供されていますので、Redmine本体の起動であればそうした環境が利用できます。

今回は、自分の作ったプラグインを「簡単に試してもらう」のを目的として、シンプルなDockerの環境を用意してみました。

Redmineのプラグイン開発環境については、すでにQiitaにもたくさん記事があるので、目新しいものではないですし、
MySQLを利用される場合は、以下をご覧いただいたほうが良いかと思います。

- [Redmine x Dockerの記事](https://qiita.com/search?q=redmine+docker)
- [Redmine x Docker x プラグインの記事](https://qiita.com/search?utf8=✓&sort=&q=redmine+docker+プラグイン)

### わたしの場合のお試し環境

基本的には 上記の記事のとおりでプラグインがどんなものか試すことはできます。

ただ、わたしの場合は以下のような希望があったので、自分で用意しています。

- プラグインを簡単に試せる
- MySQLを使わず、早く立ち上げができる（実運用は想定しない構成）
- Mac側のプラグインのソースを修正しながらコンテナ内のRedmineに修正を反映させたい（developmentモードにする）
- プラグインを継続してメンテナンスする場合、Redmine本体のバージョンアップに追随しないといけないので、Dockerで起動するRedmineのバージョンも切り替え可能にしたい
- Redmineのバージョンを切り替えつつ、プラグインも対応するブランチに切り替えて作業したい

一番大きいのが **プラグインを継続してメンテナンスする場合、Redmine本体のバージョンアップに追随しないといけない** という点です。
Redmineのメジャーバージョンアップの時には、多くのプラグインがバージョンアップに対応できず、色々プラグインを入れて運用している場合はバージョンアップがしにくくなります。
このため、公開しているプラグインは、メジャーバージョンアップの動きがでてきたら、リポジトリのtrunk (開発ブランチ）を利用してプラグイン側を利用して動作チェックや改修をしておくようにしています。

基本的に、公式のDockerのイメージはRedmineはMySQL, productionモードでの起動で、Redmine以外に前提になっているものがいくつかあるので、起動中のコンテナや必要なイメージが色々あってちょっとオーバースペックだな、と思っていました。

ということで、簡単に起動、確認、切り替え可能なようにしてみました。


### やりたいこと / やらないこと

あらためて、やりたいこと/やらないことの整理です。

- やりたいこと
 - Mac側にRedmineを入れたりrubyのバージョンを入れず、プラグインのソースだけで、プラグイン混みのRedmineを立ち上げたい
 - できるだけ簡単な構成にしたい
 - 可能ならRedmineのバージョンを簡単に切り替えられるようにしたい

- やらないこと
 - Dokcerそのものについての説明
 - Redmineのプラグインの作り方   

前置きが長くなりましたが、こんな感じです。

## 今回の設定例

ただいまこの構成でためしているリポジトリはこちらです。

- https://github.com/akiko-pusu/redmine_issue_badge
 - メニューバー右上に、担当チケット数をバッジ表示するだけの簡単なプラグイン

あまり複雑な設定が要らないプラグインなので、起動してログインしたらすぐ利用できるタイプのものになります。
このリポジトリのディレクトリの直下に、Dockerfile, docker-compose.yml というファイルがあります。  

DBはSQLite3を利用しているので、docker-composeを利用しなくても稼働できそうですが、ポート指定やプラグインのソースディレクトリをコンテナ側と共有する設定が面倒なので、docker-compose up で完結するように書いています。

利用しているDockerの環境は、Docker for Macになります。

## 基本の動かし方

基本的には、これだけです。

```
$ docker-compose up -d 
```

今回、READMEやリポジトリの調整をするにあたって、キャプチャを撮ってみたので添えてみます。 

![after-docker-compose-d.png](
https://raw.githubusercontent.com/wiki/akiko-pusu/redmine_issue_badge/img/plugin-with-docker.gif)

途中でコンテナの起動状況や、ポートの設定などを確認するために、KitematicというGUIのツールを使って確認しています。

- 参考: [[Docker] Kitematicを使ってみよう！](https://qiita.com/akiko-pusu/items/d07751a6bf0ce6df44ce)

                                
## もうちょっと詳しく                
  
### Dockerfileを眺める

長くなりますので、抜粋です。
ベースのイメージは、現在ruby2.4.2にしています。

- ARG REDMINE_VERSION というところで、デフォルトでRedmine最新バージョンの安定版を指定
- 最低限必要なパッケージを追加
- Redmineのソースを /tmp/ ディレクトリにgit cloneする
- SQLite3用のdatabase.ymlを作成する
- bundle installする
- Redmine本体のマイグレーションをする
- port 3000で起動する

Dockerfile自体は、単体で利用した時にはプラグインなしのRedmineが起動するようになっています。

さて、デフォルトではmasterブランチなのですが、Redmineのgitリポジトリは **masterはtrunk (開発の一番先頭）** にあたりますので、安定版（リリース版）ではありません。
この状態でcloneしてしまうと、大抵のプラグインは動かなくなります。

ということで、git cloneするときに安定版のブランチ指定でcloneしています。
また、少し早くするために、--depth 1（shallow cloneで過去の履歴は持ってこない）設定にしています。

```
FROM ruby:2.4.2
LABEL maintainer="AKIKO TAKANO / (Twitter: @akiko_pusu)" \
  description="Image to run Redmine simply with sqlite to try/review plugin."


### get Redmine source
ARG REDMINE_VERSION="3.4-stable"

### install default sys packeges ###

RUN apt-get update
RUN apt-get install -qq -y \
    git vim                \
    sqlite3

RUN cd /tmp && git clone --depth 1 -b ${REDMINE_VERSION} https://github.com/redmine/redmine redmine
RUN echo "REDMINE_VERSION: ${REDMINE_VERSION}"
WORKDIR /tmp/redmine

RUN echo $'test:\n\
  adapter: sqlite3\n\
  database: /tmp/data/redmine_test.sqlite3\n\
  encoding: utf8mb4\n\
\n\
development:\n\
  adapter: sqlite3\n\
  database: /tmp/data/redmine_development.sqlite3\n\
  encoding: utf8mb4\n'\
>> config/database.yml


以下略 ....

CMD bundle exec rails s -p 3000 -b '0.0.0.0'
EXPOSE 3000
```

ポイントはDocker RUNのコマンドの中でdatabase.ymlを作成している点です。
cloneした段階のRedmineはデータベースの設定を保持していないので、まずはそこを設定してあげないといけません。
ちょっと無理矢理感がありますが、echoを使ってこのタイミングで作成しています。


### docker-composeを眺める

docker-compose.yml 側はこちらです。
ここでは、こんな内容になります。

#### イメージ生成の段階

- docker-compose で起動する場合は、Dockerfileを使ってイメージ作成
- イメージ名は redmine_sqlite3 として作成
- イメージ作成の際、引数を指定してgit cloneするRedmineのバージョンを変更
 - デフォルトでは 3.4-stable

#### サービス起動の段階

- buildの段階で作成したイメージを利用
- コンテナは web という名前で起動する
- イメージは buildの際に指定したredmine_sqlite3を利用
- docker-compose up (コンテナを作成して起動）の場合は、redmine_sqlite3のコンテナを作る
 - コンテナ起動時は、プラグインのソースディレクトリをコンテナ内の /tmp/redmine/plugins/ にマップ 
 - こうすることでコンテナ起動中にMac側でのプラグインのソース変更が反映される
- コンテナ起動時には command: で指定した処理で、Dockerfileの最後のCMDを上書き
 - docker run だとプラグインなしでRedmine起動
- docker-compose upの場合はこの流れ
 - プラグインのボリュームをマウント
 - プラグインに必要なGemがあれば追加でインストール
 - プラグインのマイグレーションをしてからRedmine起動  

```
version: '3'
services:
  # start redmine with plugin
  web:
    build:
      context: .
      args:
        REDMINE_VERSION: $REDMINE_VERSION:-3.4-stable
    image: redmine_sqlite3
    container_name: redmine_sqlite3
    command: >
      bash -c "bundle exec rake redmine:plugins:migrate &&
           bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      RAILS_ENV: development
    volumes:
      - .:/tmp/redmine/plugins/redmine_issue_badge
    ports:
      - "3000:3000"
```

docker-composeの"service" というのは、コンテナの名前ではなく、

- どのイメージを使って
- どのコンテナを用意して
- docker run で渡すような引数をファイルにひとまとめにして
- 必要に応じてDockerfileでのデフォルトのコマンドを上書きして
- ほかに連携するコンテナがあれば起動順番を調整して

といったことを定義したまとまりです。
上記の例では、"web" という、「Redmineをプラグイン付きで起動する」という内容になります。

docker-compose up とすると、"service"として定義されたもの全てが起動します。
今回の例は"service"が1つだけなのですが、明示的に docker-compose up web とすることもできます。

### 起動してみる

初回で docker-compose up -d でも起動は出来ますが、順を追っていくとこのような感じ。

```
$ docker-compose build

# 一回ビルドすると、 redmine_sqlite3というイメージができます
# あとは docker-compose up or downで起動 / 停止

$ docker-compose up -d

```

buildでイメージを作成する途中では、DockerfileのRUN ... に書かれている処理ごとにコンテナが立ち上がります。
バックグラウンドで進行しているのでわかりにくいかもしれませんが、Kitematicでbuild中の様子を見てみると、RUNの単位でコンテナが立ち上がって処理がおわったら停止しているのが確認できます。

（中間のコンテナは最終的には消去されます）

![intermediate-containers.png](https://qiita-image-store.s3.amazonaws.com/0/27976/fac443b8-4b2f-a99f-888e-844e1ebabc04.png "intermediate-containers.png")

イメージが作成され、コンテナを起動する際、Dockerfileの最後に書かれているCMDを上書きします。
この時、プラグインのソースをコンテナ側にマウントし、プラグイン用のマイグレーションを行い、その上で rails s を実行してRedmineを起動しています。

Kitematicとターミナルの様子をみると、このような感じになります。


![after-docker-compose-d.png](https://qiita-image-store.s3.amazonaws.com/0/27976/76965c21-6f42-e978-6aa3-c565f2ddbde9.png "after-docker-compose-d.png")


#### docker-compose upの動作

docker-composeのやっていることは、ざっくり図にするとこんな感じです。
（だいぶはしょっていますが....）

![docker-compose-working.jpeg](https://qiita-image-store.s3.amazonaws.com/0/27976/1c18715a-b4bf-1f41-8be9-d0e5dd35db09.jpeg "docker-compose-working.jpeg")


自前でDockerfileを使ってイメージをビルドする際に、デフォルトの設定を上書きしたり、自前でビルドせずにDockerHubに登録しているイメージを取ってきたりして、アプリケーションの起動に必要な準備を行い、起動や停止を管理してくれるものです。

今回の場合は利用するイメージは1つですが、コンテナを起動する際に、ボリュームを変えたりデフォルトの処理を上書きしたり、コンテナの名前を固定にしたりといったことで利用しています。


### Redmineのバージョンを切り換える

この場合はイメージを作り直しします。
もちろん、dockerコンテナを起動してから、その中でRedmineのブランチを切り替えるのでも問題ないです。

同じ名前でイメージを作るよりは、別のdocker-composeファイルを用意したほうがいいかもしれませんが、今回はDockerfile, docker-compose.yml 1つずつにとどめています。


```
$ docker-compose down # 先にあげてたら停止

# まずはmaster (trunk) のRedmine入りイメージ再作成
$ REDMINE_VERSION=master docker-compose build --no-cache

# つぎに docker-compose up -d してみる
$ docker-compose up -d

```

Redmineのバージョンをmasterにしても、プラグイン側が対応していないと、docker-compose up は失敗してしまいます...。だめだったらプラグインを粛々と修正することになります...。

#### Redmineを起動しないで作業する

この場合は、Redmineは起動しない形で作成したイメージからコンテナを立ち上げます。
docker-composeの場合は、ここでもserviceを指定することで、単純にコンテナを立ち上げるときに指定のボリュームをマウントしてくれます。

```
$ docker-compose run --rm --entrypoint bash web

Creating network "redmineissuebadge_default" with the default driver

root@31f70633063d:/tmp/redmine# ls
CONTRIBUTING.md  Gemfile  Gemfile.lock	README.rdoc  Rakefile  app  appveyor.yml  bin  config  config.ru  db  doc  extra  files  lib  log  plugins  public  test  tmp

# マウントされているかのチェック
root@31f70633063d:/tmp/redmine# ls plugins/
README	redmine_issue_badge

root@31f70633063d:/tmp/redmine# 

# プラグインがRedmine masterに対応していない状態で migrationするとエラー....
root@31f70633063d:/tmp/redmine# bundle exec rake db:migrate
rake aborted!

NoMethodError: undefined method `alias_method_chain' for MyController:Class
Did you mean?  alias_method
/tmp/redmine/plugins/redmine_issue_badge/lib/issue_badge/my_controller_patch.rb:6:in `block in <module:MyControllerPatch>'
/usr/local/bundle/gems/activesupport-5.1.2/lib/active_support/concern.rb:120:in `class_eval'


# プラグイン側のブランチを切り替え（対応中のRedmine masterを追いかけているブランチ）
root@31f70633063d:/tmp/redmine# cd plugins/redmine_issue_badge/
root@31f70633063d:/tmp/redmine/plugins/redmine_issue_badge# git checkout support-rails5
Switched to branch 'support-rails5'

root@31f70633063d:/tmp/redmine/plugins/redmine_issue_badge# cd ../../

# この状態で作業するとOK

root@31f70633063d:/tmp/redmine# bundle exec rake db:migrate

root@31f70633063d:/tmp/redmine# bundle exec rake redmine:plugins:migrate
Migrating redmine_issue_badge (Redmine Issue Badge plugin)...
== 1 CreateIssueBadgeUserSettings: migrating ==================================
-- adapter_name()
   -> 0.0000s


```

申し訳ないと思いつつ、Redmineのメジャーバージョンアップのときは、プラグイン側は前後の互換性を保たずに別バージョンとして対応しています。


## MySQLを使う場合はどうするの？

テストに関しては、直接ではないにしても、3年くらい前からDockerを利用しています。

どういうシーンかというと、プラグインのテストがちゃんと通るかを、SaaSのCI (werckerやCircleCI, Jenkinsなど）で試す場合です。
おかげさまで、テストがある分インストールでエラーになってしまう、というissueは少なくなりました。
ですが、SQLite3では大丈夫でも、MySQLだと思ったようにプラグイン側で想定しているマイグレーションが走らないことがあります。

その辺りも確認しないといけないのですが、こちらは手間なので、手元ではSQLite3で、CIの時だけMySQLにするようにしてみました。


![sample-screen-shot.png](https://qiita-image-store.s3.amazonaws.com/0/27976/a5a3bc09-9aa7-4321-f5bb-ce13cf5df89f.png "sample-screen-shot.png")

CircleCIに関する設定は、リポジトリの .circleci/config.yml に配置しています。

こちらもRedmineのmaster (trunk) とstableブランチではソースコードを切り替えたり処理を変えたりしないといけないため、Workflowでうまく切り替えて走らせることができないか色々試行錯誤しているところです。

- [CircleCI向けの設定はこちら](https://github.com/akiko-pusu/redmine_issue_badge/blob/master/.circleci/config.yml)

## まとめ

もうちょっと簡単に紹介をしたいな...と思っていましたが、長々としてしまいました。

いちばんアピールしたいのは、『MySQLを使わなくとも、SQLite3 + Docker で簡単にプラグインの確認や開発ができますよ』というあたりですが、うまく伝わったでしょうか...。


RedmineはRails4系になってから、Contributeするみなさん、ユーザのみなさんも増えてきており、活動が活発になっているな...という気がしています。Redmineのもともとのユーザ（どちらかというとインフラや運用管理担当）ではなく、Railsエンジニアやフロントエンド寄りの方が増えた、というほうが合っているかもしれません。

わたしもなんとか何回かのメジャーバージョンアップを乗り越えておりますが、よくよく振り返ると、お世話になっているツールも変わってきています。
小さいプロジェクトではありますが、OSSを通して色々試せるのはありがたいことだと思っております。

『ここはこうしたほうがもっといいよ！』『ここはまちがっているよ！』というあたりもご指摘いただけると幸いです。