Help us understand the problem. What is going on with this article?

Docker Compose で作った Redmine を Ubuntu 上でいい感じに動かす手抜きレシピ

はじめに

Docker Compose を使った自分好みの Redmine 実行環境 というものを作っています。構築やバージョンアップが何かと大変な Redmine を、プラグインやテーマがてんこもりの欲張りセットにしつつも、なるべく手をかけずにメンテナスできるようにするにはどうすればいいのかを悩みながら、ようやくそれなりの形になってきたかなといったところです。

以下は、試行錯誤の結果をまとめた記事リンクです。

この状態でも動かせるのですが、せっかくですのでもう少しサーバーっぽく仕立てていきたいと思います。相変わらずあまり手をかけたくないので、ちょっと手抜きなレシピですがお付き合いください。(お気に召さないところがあればアレンジいただいても問題ありません)

おことわり

本記事の内容で実際に運用した際、何らかの問題やトラブルが起きても自己責任でお願いします。安全に運用できるようにするために可能な限り注意を払って執筆しておりますが、至らぬところもあるかと思います。

内容の不備や、よりよい実現方法があればコメントいただけると幸いです。

今回のゴール

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 のインストール

それぞれ、以下のリンク先を参照してください。

※ 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 のトップページが表示されることを確認してください。

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 の編集

ディレクトリ構成にあわせてパスを編集します。コンテナ名もあわせて変更しておきます。

以下、元のファイルからの変更箇所がわかるように差分形式で表しています。

/opt/redmine/yuni/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 というファイルを作って、以下のように設定します。

/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_sizeproxy_buffers はバックエンドからのレスポンスデータをバッファリングするための設定です。パフォーマンスに問題があれば調整してください。

設定が完了すれば、 Nginx をリロードしてください。設定が反映されます。

$ sudo systemctl reload nginx.service

設定が反映されないときは、Nginx を再起動してください。

$ sudo systemctl restart nginx.service

ブラウザからの動作確認

ここまでの設定が正しくできていれば、ブラウザから http://yuni.[your-domain] にアクセスすると Redmine のトップページが表示されます。

Redmine のトップページ

なお、 http://[ホストの IP アドレス] にアクセスすると、Nginx のトップページが表示されます。

Let's Encrypt で HTTPS 化

まずは現時点のセキュリティレベルを検査

HTTPS 化する前後でセキュリティレベルの違いを確認できるように、今の時点で先ほどアクセスできるようになった Redmine を Redmine Security Scanner で検査してみます。

検査結果-1

結果は「C」です。「暗号化されていない通信で Redmine にアクセスできてしまうよ」と教えてくれています。これを改善します。

Certbot のインストールと設定

Let's Encrypt の SSL 証明書を取得するために Certbot を使います。
Certbot のインストールと設定は、以下のリンク先を参照してください。

コマンドラインで対話形式にいくつかの質問に答えるだけで簡単に設定できます。途中、「HTTP からのアクセスを HTTPS にリダイレクトするか」と問われたら「リダイレクトする」を選んでください。

設定の完了後に Nginx の設定ファイルを開くと、以下のように変更されていることがわかります。

/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;
    }
+
+    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 で検査します。

検査結果-2

結果は「A」です。うーん惜しい、あと一歩。どうやらセキュリティ関連のヘッダが足りていないようです。

検査結果詳細

検査結果を追っていくと「HTTP Strict Transport Security ヘッダがセットされていないよ」と教えてくれています。

どうやら Certbot は、設定時に Strict Transport Security ヘッダをセットしてくれないようですので、手動で Nginx の設定に追記します。

せっかくなので、ついでに HTTP/2 の設定も追記しておきます。以下は変更箇所を抜粋したものです。

/etc/nginx/conf.d/yuni-redmine.conf
-    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+」になります。やったぜ。

検査結果-3

IP アドレスでのアクセスを弾く

Let's Encrypt が発行する SSL 証明書は、Certbot で設定した時に選択したドメインに対してのみ有効です。IP アドレスでアクセスすると不正な SSL 証明書とみなされてしまいます。そもそも IP アドレスだと Redmine にアクセスできないので弾いちゃいましょう。

Nginx のデフォルト設定( /etc/nginx/conf.d/default.conf )を以下の内容に差し替えます。

/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_certificatessl_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 のログローテーション

/etc/logrotate.d/yuni-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 のログローテーション

/etc/logrotate.d/yuni-redmine-chupa-text
/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 日前までに迫ったチケットのリマインダーを通知する例です。

/opt/redmine/scripts/yuni/send_reminders.sh
#!/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 を使います。

こちらもシェルスクリプトにしておきます。

/opt/redmine/scripts/yuni/backup.sh
#!/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 本体の設定にはあまり触れず、足回りのことばかりダラダラと書いてきましたが…

さて、どうだろうか。

1 つ目の 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 番ポートにバインドします。

/opt/redmine/chloe/docker-compose.yml
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 というファイルを作って、以下のように設定します。

/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_nameproxy_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 の設定を追加します。

/etc/nginx/conf.d/chloe-redmine.conf
-    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 Scannerhttps://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 はダンプを格納した任意のディレクトリを表しますので、適宜読み替えてください。

後は、ログインして設定を編集しましょう。
少なくとも 設定 > 全般 の「ホスト名とパス」は変更が必要です。他の箇所は必要に応じて自由に変えましょう。

2 つ目の Redmine

ぷー…まあ、いいけど…てゆか、いいじゃん。

ログローテーションの設定、リマインダー通知の設定、バックアップ

ログローテーションの設定、リマインダー通知の設定、バックアップも同じようにします。
スクリプトや cron ジョブは 1 つ目の Redmine との違いを差分で示します。

Redmine のログローテーション

/etc/logrotate.d/chloe-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 のログローテーション

/etc/logrotate.d/chloe-redmine-chupa-text
-/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
}

リマインダー通知のシェルスクリプト

/opt/redmine/scripts/chloe/send_reminders.sh
#!/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

バックアップスクリプト

/opt/redmine/scripts/chloe/backup.sh
#!/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 と同じく以下の手順に沿って進めれば簡単に作れます。

  1. Redmine の構築
  2. Nginx の設定
  3. Let's Encrypt で HTTPS 化
  4. Redmine の設定
  5. ログローテーションの設定、リマインダー通知、バックアップ

たとえば、3 つ目の Redmine の名前を chieru と名づけて、バインドするポートを 3002 番にして作ることができます。

3 つ目の Redmine

ここまでお読みいただいた皆さんでしたら、詳しく説明しなくてもちぇるっと作れますよね。

以下、 docker-compose.yml と HTTPS 化する前の Nginx 設定ファイルだけ示しておきます。

/opt/redmine/chieru/docker-compose.yml
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:
/etc/nginx/conf.d/chieru-redmine.conf
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 RedmineALMinium を使って構築したり、 ソフトウェアエンジニアリング ― 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 界隈がもっともっと盛り上がっていけばいいなと願っています。

juno-nishizaki
※ 個人の感想です。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした