はじめに
Docker Compose を使った自分好みの Redmine 実行環境 というものを作っています。構築やバージョンアップが何かと大変な Redmine を、プラグインやテーマがてんこもりの欲張りセットにしつつも、なるべく手をかけずにメンテナスできるようにするにはどうすればいいのかを悩みながら、ようやくそれなりの形になってきたかなといったところです。
以下は、試行錯誤の結果をまとめた記事リンクです。
- Docker Compose を使って自分好みの Redmine 実行環境を作ってみた
- Redmine draw.io plugin がすごい便利そうなので紹介したい
- Docker Compose で作った Redmine だって Full Text Search plugin と ChupaText サーバーを連携させたい
- (番外編) 今度は Redmine を Docker Compose で Raspberry に入れてみた
この状態でも動かせるのですが、せっかくですのでもう少しサーバーっぽく仕立てていきたいと思います。相変わらずあまり手をかけたくないので、ちょっと手抜きなレシピですがお付き合いください。(お気に召さないところがあればアレンジいただいても問題ありません)
おことわり
本記事の内容で実際に運用した際、何らかの問題やトラブルが起きても自己責任でお願いします。安全に運用できるようにするために可能な限り注意を払って執筆しておりますが、至らぬところもあるかと思います。
内容の不備や、よりよい実現方法があればコメントいただけると幸いです。
今回のゴール
Ubuntu 18.04 LTS を使って、複数の Redmine を同時に稼働させる環境を作ります。(Ubuntu 20.04 LTS の方が良いかもしれませんが、リリースされてからまだ日が浅いので今回は見送ります)
詳細は以下のとおりです。
- それぞれの Redmine には HTTPS でアクセスできるようにします。HTTPS 化には Let's Encrypt を使います。
- どの Redmine にアクセスするかはサブドメインで振り分けるようにします。ついでに、ドメイン名ではなく IP アドレスでアクセスしてきた場合は弾くようにします。
- 僕は ConoHa の VPS と ムームードメイン を組み合わせて使っています。自分でサブドメインを自由に割り当てられる環境があれば何でもよいです。
- リバースプロキシサーバーに Nginx を使います。Docker Compose で作った Redmine はバックエンドで動作させます。
- 手抜きして、Nginx はホスト側にインストールします。
- Redmine Security Scanner の検査結果が「A+」になるようにします。
- ログローテーション、Redmine のリマインダー通知、データベースや添付ファイルのバックアップを定期的に行うようにします。
Nginx をホスト側にインストールする理由
ここまで Docker Compose を使ってきたんだから、Nginx も Docker Compose を使えばいいのにと思われる方もいらっしゃると思いますが、これには理由があります。
それは、今回の構成だと、Nginx に限っては Docker を使うよりもホストに直接インストールする方が手っ取り早いと感じたからです。
Certbot で証明書を発行したり定期更新のタスクを仕掛けるのはホスト側のコマンドラインで操作した方が楽だと思ってますし、サブドメイン 1 つに対して 1 回設定するだけでよいので、そこまで手間ではないと思っています。あとは Nginx の conf ファイルをちょこっと触るくらしかないので、Docker を使おうが使わまいがさほど変わらない気がしています。
また、Nginx と Redmine を同じ Docker 内部ネットワークで接続した方が構成としては綺麗なのかもしれませんが、この程度の規模なら Redmine のポートをホスト側にバインドしてもさほど影響ないのではと思っています。
もちろん、Docker 化した方が望ましいと思われる要件があれば検討したいとは思いますが、今回はどうにもそうは思えなかったです。たとえるなら「100 点満点のテストで 100 点を取ろうとして時間切れで結局 20~30 点しか取れないくらいなら、最初から 60~70 点くらいを狙おう」という考えだということです。
と、長々と言い訳がましく書きましたが、結局のところ以下のような構成となります。
ホスト側の Nginx と、Docker Compose の Redmine がちょうどいい感じにソーシャルディスタンスを保っているんじゃないかなと思います。このくらいの方が、後になって方向転換したくなったときに融通が利きやすいだろうという見立てです。
レシピ
前置きが長くなりましたが、ここからが詳細なレシピとなります。
まず、Redmine を 1 つ稼働させるところまでを説明します。その後、2 つ目以降の Redmine を追加していきます。
下準備
ホストマシンの準備
Ubuntu 18.04 LTS のマシンを用意します。VPS でも何でもよいです。設定手順は割愛しますが、以下のリンク先などが参考になります。
以降、下記の条件が満たされている前提で進めます。
- sudo 権限をもつユーザーが作られていること
- 以降、ユーザー名は
[user]
で説明しますので、適宜読み替えてください
- 以降、ユーザー名は
- インターネットから IP アドレス指定でアクセスできること
あとは、SSH 公開鍵認証で接続できるようにしておいた方が便利です。SSH は root
で接続できなくしておいたり、パスワードでの認証を無効にしたりするなど、セキュリティリスクを抑えるように設定した方が望ましいです。
ドメインの準備
自分でサブドメインを自由に割り当てられる DNS サービスを用意します。以降、ドメイン名は [your-domain]
で説明しますので、適宜読み替えてください。
サブドメイン名は何でもよいです。今回は説明の便宜上、名前を決め打ちします。以降の説明では、その名前にあわせてファイル名やディレクトリ名などを決めていきます。みなさんがお手元で試される際は、ご自身の気に入った名前をつけるようにしてください。
今回、1 つ目の Redmine には yuni
と名づけます。yuni.[your-domain]
で前述の Ubuntu マシンの IP アドレスが引けるように DNS のレコードを追加してください。
ホストマシンの設定
ファイアウォールの設定
ファイアウォールは ufw
を使います。今回は以下のポートを開けて、他は塞ぎます。
- HTTP(80 番ポート)
- HTTPS(443 番ポート)
- SSH(22 番ポート) ※ リッスンするポート番号を変更している場合は読み替えてください。
コマンドは以下のとおりです。
$ sudo ufw enable
$ sudo ufw allow 22
$ sudo ufw allow 80
$ sudo ufw allow 443
SSH 接続でファイアウォールの設定をする場合、誤って SSH 接続用ポートを塞いだまま切断してしまうと再接続できなくなってしまうため注意してください。別のセッションで接続確認しながら設定するのがよいです。
設定した内容は ufw status
で確認できます。
$ sudo ufw status
状態: アクティブ
To Action From
-- ------ ----
22 ALLOW Anywhere
80 ALLOW Anywhere
443 ALLOW Anywhere
22 (v6) ALLOW Anywhere (v6)
80 (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)
Docker と Docker Compose のインストール
それぞれ、以下のリンク先を参照してください。
-
Install Docker Engine on Ubuntu | Docker Documentation
- docker グループに
[user]
を追加するところまで進めてください。
- docker グループに
- Install Docker Compose | Docker Documentation
※ Ubuntu 20.04 LTS だと apt install
だけでインストールできるらしいので簡単そうです。
なお、動作確認時のバージョンは以下のとおりです。
$ docker -v
Docker version 19.03.8, build afacb8b7f0
$ docker-compose -v
docker-compose version 1.25.5, build 8a1c60f6
インストール先のパスは以下のとおりです。
$ which docker
/usr/bin/docker
$ which docker-compose
/usr/local/bin/docker-compose
Web サービスの設定
Nginx のインストール
以下のリンク先を参照してください。
インストールが完了したら Nginx を起動します。
$ sudo systemctl start nginx.service
起動が完了したら、ブラウザから http://[ホストの IP アドレス]
と http://yuni.[your-domain]
のそれぞれにアクセスして、どちらも Nginx のトップページが表示されることを確認してください。
Redmine の構築
Redmine を構築するため、 Docker Compose を使った自分好みの Redmine 実行環境 からファイル一式を取得します。
ディレクトリ構成
今回は以下のディレクトリ構成とします。(細部は割愛)
あくまで一例ですので、好みに合わせて変更いただいても問題ありません。
/
+-- opt/
+-- redmine/
+-- yuni/
+-- docker-compose.yml
+-- db/
+-- redmine/
+-- srv/
+-- redmine/
+-- yuni/
+-- files/
+-- var/
+-- log/
+-- redmine/
+-- yuni/
+-- redmine-chupa-text/
+-- yuni/
ファイル一式の配置
/opt
配下にディレクトリを作って、取得したファイル一式をコピーします。ディレクトリ名を yuni
にリネームしておきます。
$ cd /opt
$ sudo mkdir redmine
$ sudo chown [user]:[user] redmine
$ cd /opt/redmine
$ cp /path/to/myfav-redmine .
$ mv myfav-redmine yuni
※ /path/to
はファイル一式を格納した任意のディレクトリを表しますので、適宜読み替えてください。
docker-compose.yml の編集
ディレクトリ構成にあわせてパスを編集します。コンテナ名もあわせて変更しておきます。
以下、元のファイルからの変更箇所がわかるように差分形式で表しています。
version: "3.7"
services:
redmine:
build: ./redmine
- container_name: myfav-redmine
+ container_name: yuni-redmine
restart: always
depends_on:
- db
- chupa-text
ports:
- "3000:3000"
environment:
TZ: Asia/Tokyo
REDMINE_DB_POSTGRES: db
REDMINE_DB_DATABASE: redminedb
REDMINE_DB_USERNAME: redmineuser
REDMINE_DB_PASSWORD: redminepassword
REDMINE_PLUGINS_MIGRATE: "true"
volumes:
- - "/srv/redmine/files:/usr/src/redmine/files:z"
+ - "/srv/redmine/yuni/files:/usr/src/redmine/files:z"
- - "/var/log/redmine:/usr/src/redmine/log:z"
+ - "/var/log/redmine/yuni:/usr/src/redmine/log:z"
db:
build: ./db
- container_name: myfav-redmine-db
+ container_name: yuni-redmine-db
restart: always
environment:
TZ: Asia/Tokyo
POSTGRES_DB: redminedb
POSTGRES_USER: redmineuser
POSTGRES_PASSWORD: redminepassword
volumes:
- "dbdata:/var/lib/postgresql/data"
chupa-text-proxy:
image: groonga/chupa-text:proxy
- container_name: myfav-redmine-chupa-text-proxy
+ container_name: yuni-redmine-chupa-text-proxy
restart: always
environment:
TZ: Asia/Tokyo
volumes:
- - "/var/log/redmine-chupa-text/proxy:/var/log/squid:z"
+ - "/var/log/redmine-chupa-text/yuni/proxy:/var/log/squid:z"
chupa-text:
image: groonga/chupa-text:ubuntu-latest
- container_name: myfav-redmine-chupa-text
+ container_name: yuni-redmine-chupa-text
restart: always
depends_on:
- chupa-text-proxy
environment:
TZ: Asia/Tokyo
http_proxy: http://chupa-text-proxy:3128/
https_proxy: http://chupa-text-proxy:3128/
RAILS_SERVE_STATIC_FILES: "true"
volumes:
- - "/var/log/redmine-chupa-text/rails:/home/chupa-text/chupa-text-http-server/log:z"
+ - "/var/log/redmine-chupa-text/yuni/rails:/home/chupa-text/chupa-text-http-server/log:z"
volumes:
dbdata:
必要に応じて、データベース名/ユーザー名/パスワードも変更してください。
Redmine の起動
必要に応じて、プラグインやテーマの追加、メールの設定を行います。それぞれ下記ファイルを編集します。
- プラグインやテーマの追加:
/opt/redmine/yuni/redmine/Docerfile
- メールの設定:
/opt/redmine/yuni/redmine/config/configuration.yml
準備ができたら Redmine を起動します。
$ cd /opt/redmine/yuni
$ docker-compose up -d --build
docker-compose ps
を実行すると、起動できているか確認できます。
以下は、正常に起動できているときの結果です。
$ docker-compose ps
Name Command State Ports
-----------------------------------------------------------------------------------------------
yuni-redmine /docker-entrypoint.sh pass ... Up 0.0.0.0:3000->3000/tcp
yuni-redmine-chupa-text /bin/sh -c ./start.sh Up
yuni-redmine-chupa-text-proxy /bin/sh -c ./start.sh Up
yuni-redmine-db docker-entrypoint.sh postgres Up 5432/tcp
この時点では、ブラウザから Redmine の表示は確認できません。
curl -v http://localhost:3000
を実行して Redmine のトップページの HTML ソースが取得できれば OK です。
Nginx の設定
Nginx を通じてバックエンドの Redmine にアクセスするための設定を追加します。
/etc/nginx/conf.d
配下に yuni-redmine.conf
というファイルを作って、以下のように設定します。
server {
listen 80;
server_name yuni.[your-domain];
client_max_body_size 32m;
proxy_buffer_size 32k;
proxy_buffers 100 32k;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
location / {
proxy_pass http://localhost:3000;
}
}
Nginx はデフォルトで 1MB までしかリクエストを受け付けません。それ以上のサイズのファイルをアップロードできるようにするため client_max_body_size
の設定が必要です。
proxy_buffer_size
と proxy_buffers
はバックエンドからのレスポンスデータをバッファリングするための設定です。パフォーマンスに問題があれば調整してください。
設定が完了すれば、 Nginx をリロードしてください。設定が反映されます。
$ sudo systemctl reload nginx.service
設定が反映されないときは、Nginx を再起動してください。
$ sudo systemctl restart nginx.service
ブラウザからの動作確認
ここまでの設定が正しくできていれば、ブラウザから http://yuni.[your-domain]
にアクセスすると Redmine のトップページが表示されます。
なお、 http://[ホストの IP アドレス]
にアクセスすると、Nginx のトップページが表示されます。
Let's Encrypt で HTTPS 化
まずは現時点のセキュリティレベルを検査
HTTPS 化する前後でセキュリティレベルの違いを確認できるように、今の時点で先ほどアクセスできるようになった Redmine を Redmine Security Scanner で検査してみます。
結果は「C」です。「暗号化されていない通信で Redmine にアクセスできてしまうよ」と教えてくれています。これを改善します。
Certbot のインストールと設定
Let's Encrypt の SSL 証明書を取得するために Certbot を使います。
Certbot のインストールと設定は、以下のリンク先を参照してください。
コマンドラインで対話形式にいくつかの質問に答えるだけで簡単に設定できます。途中、「HTTP からのアクセスを HTTPS にリダイレクトするか」と問われたら「リダイレクトする」を選んでください。
設定の完了後に Nginx の設定ファイルを開くと、以下のように変更されていることがわかります。
server {
- listen 80;
server_name yuni.[your-domain];
client_max_body_size 32m;
proxy_buffer_size 32k;
proxy_buffers 100 32k;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
location / {
proxy_pass http://localhost:3000;
}
+
+ listen 443 ssl; # managed by Certbot
+ ssl_certificate /etc/letsencrypt/live/yuni.[your-domain]/fullchain.pem; # managed by Certbot
+ ssl_certificate_key /etc/letsencrypt/live/yuni.[your-domain]/privkey.pem; # managed by Certbot
+ include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+}
+
+
+server {
+ if ($host = yuni.[your-domain]) {
+ return 301 https://$host$request_uri;
+ } # managed by Certbot
+
+
+ listen 80;
+ server_name yuni.[your-domain];
+ return 404; # managed by Certbot
+
}
これで https://yuni.[your-domain]
にアクセスできるようになりました。
http://yuni.[your-domain]
にアクセスしても HTTPS にリダイレクトされるようになります。
もう一度セキュリティレベルを検査(と微調整)
HTTPS 化ができたら、もう一度 Redmine Security Scanner で検査します。
結果は「A」です。うーん惜しい、あと一歩。どうやらセキュリティ関連のヘッダが足りていないようです。
検査結果を追っていくと「HTTP Strict Transport Security ヘッダがセットされていないよ」と教えてくれています。
どうやら Certbot は、設定時に Strict Transport Security ヘッダをセットしてくれないようですので、手動で Nginx の設定に追記します。
せっかくなので、ついでに HTTP/2 の設定も追記しておきます。以下は変更箇所を抜粋したものです。
- listen 443 ssl; # managed by Certbot
+ listen 443 ssl http2; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/yuni.[your-domain]/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/yuni.[your-domain]/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+ add_header Strict-Transport-Security "max-age=31536000;";
}
Nginx をリロードまたは再起動させて設定を反映した後に、もう一度検査してみると、結果が「A+」になります。やったぜ。
IP アドレスでのアクセスを弾く
Let's Encrypt が発行する SSL 証明書は、Certbot で設定した時に選択したドメインに対してのみ有効です。IP アドレスでアクセスすると不正な SSL 証明書とみなされてしまいます。そもそも IP アドレスだと Redmine にアクセスできないので弾いちゃいましょう。
Nginx のデフォルト設定( /etc/nginx/conf.d/default.conf
)を以下の内容に差し替えます。
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
ssl_certificate /etc/letsencrypt/live/yuni.[your-domain]/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yuni.[your-domain]/privkey.pem;
return 444;
}
Nginx は return 444;
とすると HTTP レスポンスを終了します。これを、他のどの設定の server_name
にも一致しなかったときにデフォルトで適用されるようにすることで、IP アドレスでのアクセスを弾けるようになります。
ただし、HTTPS でのアクセスも弾こうとして listen 443 ssl
を追加すると、 ssl_certificate
と ssl_certificate_key
も追加しないと怒られてしまいます。ここでは苦し紛れで、先ほど作った Let's Encrypt の設定を流用しています。厳密に言えば正しくないのですが、アクセスできないページに労力をかけるのもどうかと思うので、今回はこれでヨシ!とします。
Redmine の設定
ようやく Redmine の設定に触れていきます。といいつつ、基本的には Redmineガイド を参照してください。
ここでは、本環境で固有の設定だけピックアップして説明します。
Full text search plugin「ChupaTextサーバーのURL」
管理 > プラグイン から Full text search plugin の設定画面を開き、「ChupaTextサーバーのURL」に http://chupa-text:3000/extraction.json
と設定します。ChupaText の README に書いている説明と、ホスト名:ポート番号が違っているので注意してください。
データベースのダンプ
管理画面から一通り設定が完了したら、データベースのダンプを取っておきましょう。何か問題が起きたとき、データのリストアに使えます。
今回はデータベースに PostgreSQL を使っているので pg_dump
を使います。
$ cd /opt/redmine/yuni/
$ docker-compose exec -T db pg_dump -U redmineuser redminedb > yuni-redmine.dump
今回はデフォルトのフォーマットでダンプしているので平文で出力されます。他のフォーマットも選択可能です。詳しくは PostgreSQL 12.0 文書 PostgreSQLクライアントアプリケーション - pg_dump を参照してください。
本記事では、この後 2 つ目の Redmine を作るときにリストアの手順を説明します。
ログローテーションの設定
ログローテーションはホスト側の logrotate.d
で行います。
/etc/logrotate.d
に格納されている他のログローテーション設定に倣って、以下のように設定します。
Redmine のログローテーション
/var/log/redmine/yuni/*.log {
rotate 4
weekly
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml restart redmine
endscript
}
ChupaText のログローテーション
/var/log/redmine-chupa-text/yuni/*/*.log {
rotate 4
weekly
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml restart chupa-text chupa-text-proxy
endscript
}
動作確認
動作確認は logrotate
コマンドで行います。
-dv
オプションで dry-run 、-f
オプションで強制手動実行できます。
以下は dry-run のコマンド例です。
$ sudo logrotate -dv /etc/logrotate.d/yuni-redmine
$ sudo logrotate -dv /etc/logrotate.d/yuni-redmine-chupa-text
リマインダー通知の設定
rake タスクの実行方法
リマインダー通知を含む rake
タスクの実行方法は以下のとおりです。
$ cd /opt/redmine/yuni/
$ docker-compose exec -T redmine rake redmine:send_reminders
任意のディレクトリから docker-compose
コマンドを実行するときは、 -f
オプションで docker-compose.yml
のパスを指定します。
$ docker-compose -f /opt/redmine/yuni/docker-compose.yml exec -T redmine rake redmine:send_reminders
リマインダー通知のシェルスクリプト
リマインダー通知の設定をメンテナンスしやすくするために /opt/redmine/scripts/yuni
配下にシェルスクリプトを作っておきます。
以下は、期日が 3 日前までに迫ったチケットのリマインダーを通知する例です。
#!/bin/bash
/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml \
exec -T redmine rake redmine:send_reminders days=3
シェルスクリプトのファイルには実行権限を付与してください。( chmod +x
)
リマインダーの定期実行
リマインダーの定期実行は、ホスト側の cron
を使います。
以下は、毎平日の朝 6 時にリマインダー通知を実行する例です。ついでにリマインダーの実行結果をログに残すようにしています。
0 6 * * 1-5 /opt/redmine/scripts/yuni/send_reminders.sh >> /var/log/redmine/yuni/send_reminders.log 2>&1
バックアップ
バックアップの取得先
バックアップの取得先は /var/opt/redmine/yuni/backup
にします。念のため root
ユーザーしかアクセスできないようにしておきます。
$ sudo mkdir -p /var/opt/redmine/yuni/backup/{db,files}
$ sudo chmod 700 /var/opt/redmine/yuni/backup
バックアップスクリプト
添付ファイルのバックアップには rsync
、データベースのバックアップには前述の pg_dump
を使います。
こちらもシェルスクリプトにしておきます。
#!/bin/bash
# 添付ファイルのバックアップ
rsync -avh /srv/redmine/yuni/files/ /var/opt/redmine/yuni/backup/files
# データベースのバックアップ
/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml \
exec -T db pg_dump -U redmineuser redminedb \
> /var/opt/redmine/yuni/backup/db/yuni-redmine.dump
今回は自サーバー内でバックアップを取得しているため、誤操作などで誤ってデータを紛失したときにリストアが可能ですが、サーバー自身がダメになったときはリストアできません。
取得したバックアップを別のサーバーなどに転送しておけば、サーバー自身がダメになったときもリストアできます。(転送処理は割愛します)
バックアップの定期実行
バックアップの定期実行も、ホスト側の cron
を使います。
以下は、毎日朝 4 時にバックアップを実行する例です。ついでにログも残すようにしています。
0 4 * * * /opt/redmine/scripts/yuni/backup.sh >> /var/log/redmine/yuni/backup.log 2>&1
ここまでのまとめ
これで無事に 1 つ目の Redmine の設定が終わりました。Redmine 本体の設定にはあまり触れず、足回りのことばかりダラダラと書いてきましたが…
さて、どうだろうか。
端的に換言すれば、いい感じ。
ディレクトリ構成
追加したディレクトリ、ファイルを含めてディレクトリ構成を再度まとめておきます。
/
+-- etc/
+-- logrotate.d/
+-- yuni-redmine
+-- yuni-redmine-chupa-text
+-- nginx/
+-- conf.d/
+-- default.conf
+-- yuni-redmine.conf
+-- opt/
+-- redmine/
+-- scripts/
+-- yuni/
+-- backup.sh
+-- send_reminders.sh
+-- yuni/
+-- docker-compose.yml
+-- db/
+-- redmine/
+-- srv/
+-- redmine/
+-- yuni/
+-- files/
+-- var/
+-- log/
+-- redmine/
+-- yuni/
+-- redmine-chupa-text/
+-- yuni/
+-- opt/
+-- redmine/
+-- yuni/
+-- backup/
+-- db/
+-- yuni-redmine.dump
+-- files/
2 つ目の Redmine を作ってみる
さて、次は 2 つ目の Redmine を作ってみましょう。やはり名前は何でもいいです。気に入った名前をつけましょう。
本記事では chloe
と名づけます。 chloe.[your-domain]
で DNS のレコードを追加してください。
ディレクトリ構成
2 つ目の Redmine ができあがると、以下のようなディレクトリ構成になります。
追加するファイルおよびディレクトリを差分で示します。
/
+-- etc/
+-- logrotate.d/
+ +-- chloe-redmine
+ +-- chloe-redmine-chupa-text
+-- yuni-redmine
+-- yuni-redmine-chupa-text
+-- nginx/
+-- conf.d/
+-- default.conf
+ +-- chloe-redmine.conf
+-- yuni-redmine.conf
+-- opt/
+-- redmine/
+-- scripts/
+ +-- chloe/
+ +-- backup.sh
+ +-- send_reminders.sh
+-- yuni/
+-- backup.sh
+-- send_reminders.sh
+ +-- chloe/
+ +-- docker-compose.yml
+ +-- db/
+ +-- redmine/
+-- yuni/
+-- docker-compose.yml
+-- db/
+-- redmine/
+-- srv/
+-- redmine/
+ +-- chloe/
+ +-- files/
+-- yuni/
+-- files/
+-- var/
+-- log/
+-- redmine/
+ +-- chloe/
+-- yuni/
+-- redmine-chupa-text/
+ +-- chloe/
+-- yuni/
+-- opt/
+-- redmine/
+ +-- chloe/
+ +-- backup/
+ +-- db/
+ +-- chloe-redmine.dump
+ +-- files/
+-- yuni/
+-- backup/
+-- db/
+-- yuni-redmine.dump
+-- files/
Redmine の構築
ファイル一式のコピーと docker-compose.yml の編集
今回は /opt/redmine/yuni
をまるっとコピーして /opt/redmine/chloe
を作ります。
$ cd /opt/redmine
$ cp -a yuni chloe
コピーができたら次は docker-compose.yml
を編集します。
今回は Redmine をホスト側の 3001 番ポートにバインドします。
version: "3.7"
services:
redmine:
build: ./redmine
- container_name: yuni-redmine
+ container_name: chloe-redmine
restart: always
depends_on:
- db
- chupa-text
ports:
- - "3000:3000"
+ - "3001:3000"
environment:
TZ: Asia/Tokyo
REDMINE_DB_POSTGRES: db
REDMINE_DB_DATABASE: redminedb
REDMINE_DB_USERNAME: redmineuser
REDMINE_DB_PASSWORD: redminepassword
REDMINE_PLUGINS_MIGRATE: "true"
volumes:
- - "/srv/redmine/yuni/files:/usr/src/redmine/files:z"
+ - "/srv/redmine/chloe/files:/usr/src/redmine/files:z"
- - "/var/log/redmine/yuni:/usr/src/redmine/log:z"
+ - "/var/log/redmine/chloe:/usr/src/redmine/log:z"
db:
build: ./db
- container_name: yuni-redmine-db
+ container_name: chloe-redmine-db
restart: always
environment:
TZ: Asia/Tokyo
POSTGRES_DB: redminedb
POSTGRES_USER: redmineuser
POSTGRES_PASSWORD: redminepassword
volumes:
- "dbdata:/var/lib/postgresql/data"
chupa-text-proxy:
image: groonga/chupa-text:proxy
- container_name: yuni-redmine-chupa-text-proxy
+ container_name: chloe-redmine-chupa-text-proxy
restart: always
environment:
TZ: Asia/Tokyo
volumes:
- - "/var/log/redmine-chupa-text/yuni/proxy:/var/log/squid:z"
+ - "/var/log/redmine-chupa-text/chloe/proxy:/var/log/squid:z"
chupa-text:
image: groonga/chupa-text:ubuntu-latest
- container_name: yuni-redmine-chupa-text
+ container_name: chloe-redmine-chupa-text
restart: always
depends_on:
- chupa-text-proxy
environment:
TZ: Asia/Tokyo
http_proxy: http://chupa-text-proxy:3128/
https_proxy: http://chupa-text-proxy:3128/
RAILS_SERVE_STATIC_FILES: "true"
volumes:
- - "/var/log/redmine-chupa-text/yuni/rails:/home/chupa-text/chupa-text-http-server/log:z"
+ - "/var/log/redmine-chupa-text/chloe/rails:/home/chupa-text/chupa-text-http-server/log:z"
volumes:
dbdata:
この時、変更箇所に漏れがないように気を付けてください。もし変更漏れがあると 1 つ目に作った Redmine と競合してエラいことになります。
競合を避けるために最初から container_name をつけないという流儀もあるようです。
Redmine の起動
準備ができたら 1 つ目と同じように Redmine を起動します。
$ cd /opt/redmine/chloe
$ docker-compose up -d --build
docker-compose ps
で起動確認しましょう。
$ docker-compose ps
Name Command State Ports
------------------------------------------------------------------------------------------------
chloe-redmine /docker-entrypoint.sh pass ... Up 0.0.0.0:3001->3000/tcp
chloe-redmine-chupa-text /bin/sh -c ./start.sh Up
chloe-redmine-chupa-text-proxy /bin/sh -c ./start.sh Up
chloe-redmine-db docker-entrypoint.sh postgres Up 5432/tcp
curl -v http://localhost:3001
を実行して Redmine のトップページの HTML ソースが取得できれば OK です。
Nginx の設定
/etc/nginx/conf.d
配下に chloe-redmine.conf
というファイルを作って、以下のように設定します。
server {
listen 80;
server_name chloe.[your-domain];
client_max_body_size 32m;
proxy_buffer_size 32k;
proxy_buffers 100 32k;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
location / {
proxy_pass http://localhost:3001;
}
}
server_name
と proxy_pass
のポート番号が違うだけで、残りの箇所は 1 つ目と同じです。
設定が完了すれば、Nginx をリロードまたは再起動して反映してください。
ここまでの設定が正しくできていれば、ブラウザから http://chloe.[your-domain]
にアクセスすると Redmine のトップページが表示されます。
Let's Encrypt で HTTPS 化
certbot instructions - Nginx on Ubuntu 18.04 LTS (bionic) の Certbot インストール後の手順から進めます。
対話形式の質問には「 chloe.[your-domain]
を HTTPS 化する 」⇒「HTTP からのアクセスを HTTPS にリダイレクトする」を選んでください。
HTTPS 化が終われば Strict Transport Security ヘッダと HTTP/2 の設定を追加します。
- listen 443 ssl; # managed by Certbot
+ listen 443 ssl http2; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/chloe.[your-domain]/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/chloe.[your-domain]/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+ add_header Strict-Transport-Security "max-age=31536000;";
}
Nginx をリロードまたは再起動させて設定を反映した後に、 Redmine Security Scanner で https://chloe.[your-domain]
を検査して、結果が「A+」になることを確認しておきましょう。
Redmine の設定
Redmine の設定ですが、今回は 1 つ目の Redmine から取得したデータベースのダンプをリストアしてみましょう。
Redmine を停止してデータベースをいったん空にして、ダンプをリストアして Redmine を起動するという流れになります。
Redmine の停止は以下のコマンドです。
$ cd /opt/redmine/chloe
$ docker-compose stop redmine
次にデータベースを空にする手順ですが、PostgreSQL の場合は「public スキーマを削除してから再作成する」のが手っ取り早いようです。
$ cd /opt/redmine/chloe
$ docker-compose exec -T db psql -U redmineuser redminedb << EOF
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO redmineuser;
GRANT ALL ON SCHEMA public TO public;
COMMENT ON SCHEMA public IS 'standard public schema';
CREATE EXTENSION IF NOT EXISTS pgroonga;
EOF
後はリストアしたい時点のダンプファイルを psql
で流し込んで、 Redmine を再起動するだけです。
$ cd /opt/redmine/chloe
$ docker-compose exec -T db psql -U redmineuser redminedb < /path/to/yuni-redmine.dump
$ docker-compose start redmine
※ /path/to
はダンプを格納した任意のディレクトリを表しますので、適宜読み替えてください。
後は、ログインして設定を編集しましょう。
少なくとも 設定 > 全般 の「ホスト名とパス」は変更が必要です。他の箇所は必要に応じて自由に変えましょう。
ぷー…まあ、いいけど…てゆか、いいじゃん。
ログローテーションの設定、リマインダー通知の設定、バックアップ
ログローテーションの設定、リマインダー通知の設定、バックアップも同じようにします。
スクリプトや cron
ジョブは 1 つ目の Redmine との違いを差分で示します。
Redmine のログローテーション
-/var/log/redmine/yuni/*.log {
+/var/log/redmine/chloe/*.log {
rotate 4
weekly
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
- /usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml restart redmine
+ /usr/local/bin/docker-compose -f /opt/redmine/chloe/docker-compose.yml restart redmine
endscript
}
ChupaText のログローテーション
-/var/log/redmine-chupa-text/yuni/*/*.log {
+/var/log/redmine-chupa-text/chloe/*/*.log {
rotate 4
weekly
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
- /usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml restart chupa-text chupa-text-proxy
+ /usr/local/bin/docker-compose -f /opt/redmine/chloe/docker-compose.yml restart chupa-text chupa-text-proxy
endscript
}
リマインダー通知のシェルスクリプト
#!/bin/bash
-/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml \
+/usr/local/bin/docker-compose -f /opt/redmine/chloe/docker-compose.yml \
exec -T redmine rake redmine:send_reminders days=3
リマインダーの定期実行
-0 6 * * 1-5 /opt/redmine/scripts/yuni/send_reminders.sh >> /var/log/redmine/yuni/send_reminders.log 2>&1
+0 6 * * 1-5 /opt/redmine/scripts/chloe/send_reminders.sh >> /var/log/redmine/chloe/send_reminders.log 2>&1
バックアップの取得先
$ sudo mkdir -p /var/opt/redmine/chloe/backup/{db,files}
$ sudo chmod 700 /var/opt/redmine/chloe/backup
バックアップスクリプト
#!/bin/bash
# 添付ファイルのバックアップ
-rsync -avh /srv/redmine/yuni/files/ /var/opt/redmine/yuni/backup/files
+rsync -avh /srv/redmine/chloe/files/ /var/opt/redmine/chloe/backup/files
# データベースのバックアップ
-/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml \
+/usr/local/bin/docker-compose -f /opt/redmine/chloe/docker-compose.yml \
exec -T db pg_dump -U redmineuser redminedb \
- > /var/opt/redmine/yuni/backup/db/yuni-redmine.dump
+ > /var/opt/redmine/chroe/backup/db/chroe-redmine.dump
3 つ目以降の Redmine も同様に
3 つ目以降の Redmine を作りたいときも、名前とバインドするポートを変えて、あとは 2 つ目の Redmine と同じく以下の手順に沿って進めれば簡単に作れます。
- Redmine の構築
- Nginx の設定
- Let's Encrypt で HTTPS 化
- Redmine の設定
- ログローテーションの設定、リマインダー通知、バックアップ
たとえば、3 つ目の Redmine の名前を chieru
と名づけて、バインドするポートを 3002 番にして作ることができます。
ここまでお読みいただいた皆さんでしたら、詳しく説明しなくてもちぇるっと作れますよね。
以下、 docker-compose.yml
と HTTPS 化する前の Nginx 設定ファイルだけ示しておきます。
version: "3.7"
services:
redmine:
build: ./redmine
- container_name: chloe-redmine
+ container_name: chieru-redmine
restart: always
depends_on:
- db
- chupa-text
ports:
- - "3001:3000"
+ - "3002:3000"
environment:
TZ: Asia/Tokyo
REDMINE_DB_POSTGRES: db
REDMINE_DB_DATABASE: redminedb
REDMINE_DB_USERNAME: redmineuser
REDMINE_DB_PASSWORD: redminepassword
REDMINE_PLUGINS_MIGRATE: "true"
volumes:
- - "/srv/redmine/chloe/files:/usr/src/redmine/files:z"
+ - "/srv/redmine/chieru/files:/usr/src/redmine/files:z"
- - "/var/log/redmine/chloe:/usr/src/redmine/log:z"
+ - "/var/log/redmine/chieru:/usr/src/redmine/log:z"
db:
build: ./db
- container_name: chloe-redmine-db
+ container_name: chieru-redmine-db
restart: always
environment:
TZ: Asia/Tokyo
POSTGRES_DB: redminedb
POSTGRES_USER: redmineuser
POSTGRES_PASSWORD: redminepassword
volumes:
- "dbdata:/var/lib/postgresql/data"
chupa-text-proxy:
image: groonga/chupa-text:proxy
- container_name: chloe-redmine-chupa-text-proxy
+ container_name: chieru-redmine-chupa-text-proxy
restart: always
environment:
TZ: Asia/Tokyo
volumes:
- - "/var/log/redmine-chupa-text/chloe/proxy:/var/log/squid:z"
+ - "/var/log/redmine-chupa-text/chieru/proxy:/var/log/squid:z"
chupa-text:
image: groonga/chupa-text:ubuntu-latest
- container_name: chloe-redmine-chupa-text
+ container_name: chieru-redmine-chupa-text
restart: always
depends_on:
- chupa-text-proxy
environment:
TZ: Asia/Tokyo
http_proxy: http://chupa-text-proxy:3128/
https_proxy: http://chupa-text-proxy:3128/
RAILS_SERVE_STATIC_FILES: "true"
volumes:
- - "/var/log/redmine-chupa-text/chloe/rails:/home/chupa-text/chupa-text-http-server/log:z"
+ - "/var/log/redmine-chupa-text/chieru/rails:/home/chupa-text/chupa-text-http-server/log:z"
volumes:
dbdata:
server {
listen 80;
server_name chieru.[your-domain];
client_max_body_size 32m;
proxy_buffer_size 32k;
proxy_buffers 100 32k;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
location / {
proxy_pass http://localhost:3002;
}
}
おわりに
僕が初めて Redmine に触れてからおそらくは 8 年くらい、自分で構築するようになってから 5 年くらい経ちます。これまでに Bitnami Redmine や ALMinium を使って構築したり、 ソフトウェアエンジニアリング ― RedmineをCentOS 7上で動かすーUnicornとNginx編 を参考にスクラッチで構築したりしてきました。
しかし、どの方法で構築しても、インストールはともかく頻繁なバージョンアップにつらみを感じていました。スクラッチで構築したときには Ruby を rbenv
でインストールしてみたり、Redmine プラグインやテーマを git submodule
で Redmine 本体とリンクさせてみたり色々やってみたのですが、2, 3 くらいのインスタンスならまだしも、数十くらい…下手すりゃ百を超えてるかもしれないオーダーで存在するインスタンスのバージョンアップを面倒見るには限界がある、と。
そこで Docker に着目して、どうやったら最小の手数でインストールからバージョンアップまでできるかを考えてみました。それも素の Redmine ではなく、なるべく便利なプラグインや素敵なテーマを詰め込んだ状態で。この Docker Compose で作った Redmine は docker-compose build --no-cache
を実行するだけで簡単に最新バージョンに更新することができます。そして、どうやったら誰でもこの Redmine を使って簡単にサーバー構築ができることが伝えられるか、ということで本記事を執筆しました。
このレシピはあくまで一例ですが、これでもっともっと多くの方が最新でプラグインやテーマが盛りだくさんな Redmine にすぐ触れられるようになって、Redmine 界隈がもっともっと盛り上がっていけばいいなと願っています。