プロジェクト

全般

プロフィール

RedmineをCentOS 8上で動かすーUnicornとNginx編

RedmineをCentOS 8上で動かすためのセットアップメモです。
RedmineをCentOS 7上で動かすーUnicornとNginx編のCentOS 8版です。

動作環境

仮想化環境

項目 内容 備考
仮想ホストOS Windows 10 Pro 1909 64bit版
仮想化ソフトウェア Hyper-V Windows 10標準搭載
仮想CPU数 2
仮想メモリ 2048MB
ディスクイメージ VHDX

Redmine動作環境

項目 内容 備考
OS CentOS 8.1 1911
Redmine 4.1.1
Ruby 2.6.3
RDBMS MariaDB 10.3.17
Rackサーバー Unicorn 5.5.4
Webサーバー Nginx x

Redmine構築の準備

開発ツール類のインストール

開発ツール一式をインストールします。

# dnf groupinstall "Development Tools" 
  :

上述開発ツール一式("Development Tools")には含まれていないパッケージを追加インストールします。
ただし、CentOS 7の時に入れていたパッケージのうち以下はCentOS 8では提供されていないので、以下のインストールは保留して次へ進みます。

  • libyaml-devel
  • ImageMagick
  • ImageMagick-devel
# dnf install openssl-devel readline-devel zlib-devel curl-devel

RDBMS MariaDBのインストール

現行のRedmineをMySQL(MariaDB)で運用しているので、ここではMariaDBを入れます。
MariaDBは、モジュラーリポジトリで提供されています。

# dnf module list mariadb
CentOS-8 - AppStream
Name          Stream         Profiles                        Summary
mariadb       10.3 [d]       client, server [d], galera      MariaDB Module

# dnf module install mariadb/server

開発用ライブラリをインストール

# dnf install mariadb-devel

perlの依存関係で問題報告

インストールされたパッケージを確認したところ、問題報告が・・・

# dnf list installed | grep maria
モジュラーの依存に関する問題:

 問題 1: conflicting requests
  - nothing provides module(perl:5.26) needed by module perl-DBD-MySQL:4.046:8010020191114030811:073fa5fe-0.x86_64
 問題 2: conflicting requests
  - nothing provides module(perl:5.26) needed by module perl-DBI:1.641:8010020191113222731:16b3ab4d-0.x86_64
  :

perlコマンドは実行可能です。

#dnf module list perl
CentOS-8 - AppStream
Name   Stream     Profiles             Summary
perl   5.24       common [d], minimal  Practical Extraction and Report Language
perl   5.26 [d]   common [d], minimal  Practical Extraction and Report Language

ヒント: [d]efault, [e]nabled, [x]disabled, [i]nstalled

ぐぐってみたところ、module perlをenableすると解消できるとの記載を見かけたので対応を実施。
https://centosfaq.org/centos/yum-dnf-possible-confusion-centos-8/

# dnf module enable perl:5.26
依存関係が解決しました。
================================================================================
 パッケージ        アーキテクチャー バージョン          リポジトリー      サイズ
================================================================================
モジュールストリームの有効化:
 perl                               5.26

トランザクションの概要
================================================================================

これでよろしいですか? [y/N]: y
完了しました!

MariaDBの設定

  • /etc/my.cnf.d/mariadb-server.cnf の編集
      [mysqld]
    + character-set-server=utf8mb4
    
  • /etc/my.cnf.d/mysql-clients.cnf の編集
      [mysql]
    + default-character-set = utf8mb4
    + show-warnings
    

MariaDBのサービス起動設定

# systemctl enable --now mariadb

MariaDBは、同一マシン上で実行するRedmineプロセス、および同一マシン上にログインしたコンソールから接続するので、ファイアウォール設定は省略します。

MariaDBのrootアカウントにパスワード設定

# mysql -uroot
MariaDB [(none)]> SELECT user,host,password FROM mysql.user;
+------+-----------+----------+
| user | host      | password |
+------+-----------+----------+
| root | localhost |          |
| root | aberlour  |          |
| root | 127.0.0.1 |          |
| root | ::1       |          |
+------+-----------+----------+
4 rows in set (0.000 sec)

MariaDB [(none)]> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('magdala');
Query OK, 0 rows affected (0.00 sec)

MariaDB [none]> SET PASSWORD FOR 'root'@'aberlour' = PASSWORD('magdala');
Query OK, 0 rows affected (0.00 sec)

MariaDB [none]> SET PASSWORD FOR 'root'@'127.0.0.1' = PASSWORD('magdala');
Query OK, 0 rows affected (0.00 sec)

MariaDB [none]> SET PASSWORD FOR 'root'@'::1' = PASSWORD('magdala');
Query OK, 0 rows affected (0.00 sec)

Rubyのインストール

Redmine 4.0.1 でRuby 2.6に対応しています。
https://www.redmine.org/issues/30118

CentOS 8では、ruby 2.5と2.6が提供されています。

#  dnf module list ruby
CentOS-8 - AppStream
Name  Stream   Profiles    Summary
ruby  2.5 [d]  common [d]  An interpreter of object-oriented scripting language
ruby  2.6      common      An interpreter of object-oriented scripting language

ヒント: [d]efault, [e]nabled, [x]disabled, [i]nstalled

デフォルトは2.5ですが、今回は2.6をインストールします。

# dnf module enable ruby:2.6
依存関係が解決しました。
================================================================================
 パッケージ        アーキテクチャー バージョン          リポジトリー      サイズ
================================================================================
モジュールストリームの有効化:
 ruby                               2.6

トランザクションの概要
================================================================================

これでよろしいですか? [y/N]: y
完了しました!
# dnf module list ruby
CentOS-8 - AppStream
Name  Stream   Profiles    Summary
ruby  2.5 [d]  common [d]  An interpreter of object-oriented scripting language
ruby  2.6 [e]  common      An interpreter of object-oriented scripting language

ヒント: [d]efault, [e]nabled, [x]disabled, [i]nstalled
# dnf module install ruby/common
  :
# dnf install ruby-devel rubygem-bundler
 :

Redmineのインストール

redmineアプリケーション実行用アカウントの作成

Readmineアプリケーションをroot権限で実行することは望ましくないので、Redmineを実行する専用のユーザーアカウントを作成します。

# useradd redmine
# passwd redmine
ユーザー redmine のパスワードを変更。
新しいパスワード: ********
新しいパスワードを再入力してください: ********
passwd: すべての認証トークンが正しく更新できました。

redmineのリポジトリのクローンを作成

Redmineを、/var/lib/ディレクトリ下に、リポジトリのクローンとして展開します。
Redmineの公式リポジトリはSubversionですが、githubにミラーが公開されているので、そちらから取得します。
ブランチ名を指定して取得します。Redmine 4.1系は、ブランチ名が4.1-stableとなります。

# cd /var/lib
# git clone -b 4.1-stable https://github.com/redmine/redmine.git redmine-4.1-stable
  :
# chown -R redmine.redmine redmine-4.1-stable

Redmineデータベースの設定

データベース設定ファイル

  • /var/lib/redmine-4.1-stable/config/database.yml
    Redmineにはサンプルとしてdatabase.yml.sampleが含まれているので、このサンプルからMySQL用の設定を抜き出し修正するとよいでしょう。
    production:
      adapter: mysql2
      database: redmine
      host: localhost
      username: redmine
      password: xxxxxxxx
      encoding: utf8mb4
    
  • サンプルにはproduction以外にdevelopment、testの設定がありますが、通常productionのみ使用するので設定はproductionだけ記述します。
  • adapter項はMariaDBはmysql2を指定します。
  • database項はMariaDB上でRedmineのテーブルを収容するデータベース名を指定します。
  • username項はMariaDBのデータベースに接続するMariaDBのユーザー名を指定します。MariaDBのユーザーは後の手順で作成します。
  • password項はusernameで指定したMariaDBのユーザーのパスワードを指定します。
  • encoding項はMariaDBで使用する文字エンコーディングを指定します。通常utf8mb4です。

MariaDBのデータベースユーザーのパスワードを記述しているのでパーミッションを厳しく設定しておきます。

$ chmod 600 /var/lib/redmine-4.1-stable/config/database.yml

MariaDB上にRedmine用のデータベースを作成

database.ymlのdatabase項に指定したMariaDB上でRedmineのテーブルを収容するデータベースをMariaDB上に作成します。

~$ mysql -uroot -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 9
Server version: 10.3.17-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> CREATE DATABASE redmine;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| redmine            |
+--------------------+
4 rows in set (0.00 sec)

MariaDB上にRedmine接続用ユーザーを作成

database.ymlのusername項とpassword項に指定したユーザーをMariaDB上に作成します。

MariaDB [(none)]> GRANT ALL ON redmine.* TO 'redmine'@'localhost' IDENTIFIED BY 'xxxxxxxx' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)
  • RedmineからMariaDBへは同じマシン上でUNIXソケットを使って接続するのでホスト名はlocalhostとしています。
  • WITH GRANT OPTIONを指定するとそのユーザーが持つ権限範囲以内の権限を他のユーザーに付与できます。

Redmineのメール接続設定

設定ファイル(configuration.yml)の作成

Redmineにはサンプルとしてconfiguration.yml.sampleが含まれているので、このサンプルから設定を抜き出し修正するとよいでしょう。
今回はメールサーバーをローカルとして使用します。

/var/lib/redmine-4.1-stable/config/configuration.ymlを作成します。

production:
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      address: "localhost" 
      port: 25

上記例では記載していませんが、メールサーバによってはパスワードを記述するのでパーミッションを厳しく設定しておきます。

$ chmod 600 /var/lib/redmine/config/configuration.yml

Rubyモジュール群のインストール

DNS名前解決結果からIPv4を優先使用する

rubyモジュールのリポジトリである rubygems.org はDNSで名前解決をするとIPv6とIPv4のアドレスを返却しますが、このうちIPv6アドレスでrubygems.orgに接続しようとしても無応答でインストールが失敗します。

回避策として、このマシンの名前解決優先順を RFC 3484の規定順ではなく、IPv4を優先するよう変更します。変更は、getaddrinfo関数の振る舞いを変更する設定ファイル/etc/gai.confに記述します。
  • /etc/gai.conf を新規作成し以下を記述
    precedence ::ffff:0:0/96  100
    

設定ファイルを作成したらマシンを再起動します。

Rubyモジュールのインストール

Redmine本体が依存するRubyモジュール群をインストールします。Rubyモジュールのインストールには、rubyのbundlerを使用します。
Redmineが依存するRubyパッケージは、Redmineのインストールディレクトリ下のvender/bundlerを指定し、そこへインストールします。

ユーザーredmineで以下を実行します。

~$ cd /var/lib/redmine-4.1-stable
redmine$ bundle install --path vendor/bundler --without development test
Fetching gem metadata from https://rubygems.org/.........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...
  :

Redmineの初期化

Redmine本体および依存するRubyモジュールのインストールが終わったら、Redmineの初期化を行います。

セッションデータ暗号化の鍵生成

改ざん防止のため、セッションデータを格納するクッキーを暗号化する鍵をランダムに生成します。

redmineユーザーで以下を実行します。

$ bundle exec rails generate_secret_token

  • 注記)bundlerでRedmine固有ディレクトリ(--path vendor/bundler)にRubyモジュールをインストールしたので、railsコマンドを実行する際はbundle exec rails xxx のようにbundle execをつけて実行する必要があります。

データベースのスキーマを構築

Redmineが使用するデータベースにスキーマを構築します。

redmineユーザーで以下を実行します。

$ bundle exec rails db:migrate RAILS_ENV=production
  :

動作確認

Redmine(Ruby on Rails)デフォルトのWebアプリケーションサーバー WEBrick でRedmineの動作確認をします。

ファイアウォールで一時的にポート3000を許可

以下のコマンドで一時的にファイアウォールでポート3000を許可します。永続設定ではないので再起動すれば3000の許可は消えています。

# sudo firewall-cmd --add-port=3000/tcp

WEBrickサーバーでRedmineを実行

$ bundle exec rails server -e production
=> Booting WEBrick
=> Rails 5.2.4.2 application starting in production on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
[2020-04-19 23:14:33] INFO  WEBrick 1.4.2
[2020-04-19 23:14:33] INFO  ruby 2.6.3 (2019-04-16) [x86_64-linux]
[2020-04-19 23:14:33] INFO  WEBrick::HTTPServer#start: pid=11651 port=3000

ブラウザから、ポート3000へアクセスしRedmineの画面が表示されることを確認します。

Unicornのセットアップ

Rubyアプリケーションを動かすアプリケーションサーバー(Rackサーバー)のUnicornをセットアップします。

Unicornのインストール

UnicornはRubyモジュール(gem)として提供されるので、bundlerでインストールします。
Redmineをインストールしたディレクトリ下(例:/var/lib/redmine-4.1-stable)に、Gemfile.localというファイルを作成し、そこに以下を記述します。

gem "unicorn" 

Redmineをインストールしたディレクトリで bundle update を実行します。

redmine$ bundle update
  :
Installing kgio 2.11.3
  :
Installing raindrops 0.19.1 with native extensions
  :
Installing unicorn 5.5.4 with native extensions
Bundle updated!
Gems in the groups development and test were not installed.

インストール後の単独起動確認

コマンドラインからUnicornを起動してRedmineの動作を確認します。
先にWEBrick確認時に一時的にファイアウォールにあけたポート3000を使用します。

$ bundle exec unicorn_rails -l 3000 -E production
I, [2020-04-19T23:43:12.051845 #12279]  INFO -- : listening on addr=0.0.0.0:3000 fd=7
I, [2020-04-19T23:43:12.052069 #12279]  INFO -- : worker=0 spawning...
I, [2020-04-19T23:43:12.054041 #12280]  INFO -- : worker=0 spawned pid=12280
I, [2020-04-19T23:43:12.054255 #12280]  INFO -- : Refreshing Gem list
I, [2020-04-19T23:43:12.054374 #12279]  INFO -- : master process ready
I, [2020-04-19T23:43:15.967983 #12280]  INFO -- : worker=0 ready

これでWebブラウザからポート3000で接続し動作確認をします。

参考)コマンドラインオプションは次です。

    --listen -l [アドレス:]ポート
        ソケットのエンドポイントを指定 
    --config-file -c ファイル
        設定ファイルを指定 
    -D
        デーモンプロセス起動指定 
    -E <RAILS_ENV>
        production等を指定 

Unicornプロセスに対する制御

Unicornは、マスタープロセス1つにワーカープロセスが複数という構成で実行します。

終了方法

  • フォアグラウンドプロセスとして起動した場合、Ctrl-Cで停止します。
  • デーモンプロセスとして起動した場合、シグナルINT(強制終了)またはQUIT(グレースフル停止)をマスタープロセスに送ります。
    • シグナルINT: ワーカープロセスを即終了させて自らも終了

再起動方法

デーモンプロセスに対する設定ファイル再読み込みトリガーはシグナルHUPをマスタープロセスに送ります。このとき、preload_appがfalseだとアプリケーションプログラムも再読み込みされますが、preload_appがtrueだと再読み込みされません。

シグナルUSR2を使うと、古いマスタープロセスは新しいプロセスを作ってソケットを引継ぎます。古いプロセスはそのまま残っています。pidファイルは新プロセスの値に書き換えられます。古いpidファイルは.oldbinの拡張子が付いたファイルに変更されます。preload_appがtrueのときにアプリケーションプログラムを再読み込みするにはこのシグナルUSR2を使います。

シグナルUSR2を送っただけでは古いマスタープロセスとその配下のワーカープロセスは終了しないので、シグナルUSR2を送ったあとに古いマスタープロセスに対してシグナルQUITを送るとよいのかと思います。

  • シグナルQUIT: ワーカープロセスがリクエストの処理を終えるまで待って終了
  • そのほか
    • シグナルWINCH: ワーカープロセスをグレースフルに停止しますが自らは終了しません
    • シグナルTTIN: ワーカープロセスを1つ増やします
    • シグナルTTOU: ワーカープロセスを1つ減らします

マスタープロセスのプロセスIDを調べる

次のように調べます。

$ pgrep -f 'unicorn_rails master'
3783
$

Unicornの設定

CentOS 8で動かすにあたり、少し時間をかけてUnicornの設定ファイル(unicorn.rb および Systemd の service ファイル)の書き方を追究し、理解を深めます。

バージョン非依存な絶対パスをシンボリックリンクで作成

Nginxの設定ファイルには、Redmineへの絶対パスを記述する箇所があります。Redmineのバージョンにより絶対パスが異なると、バージョンアップの度に設定ファイルを変更する必要が生じます。これを避けるため、バージョンには依存しないパスをシンボリックリンクで作成します。

# cd /var/lib
# ln -s redmine-4.1-stable redmine
# ls -l
  :
lrwxrwxrwx.  1   18  4月 20 11:56 redmine -> redmine-4.1-stable
drwxr-xr-x. 19 4096  4月 19 23:38 redmine-4.1-stable

設定ファイル(unicorn.rb)

Redmineインストールディレクトリ下のconfigディレクトリに、unicorn.rbというファイル名で設定を記述します。
Unicorn起動時にオプションで設定ファイルを指定します。

これらを参考にUnicornの設定ファイルを定義します。

  • /var/lib/redmine/config/unicorn.rb
    # -*- coding: utf-8 -*-
    # Unicorn設定ファイル
    
    # ワーカープロセスの数。1ワーカーで1つのリクエストを処理する。
    # ワーカー数が上限に達すると、先行するリクエストが完了するまで待ちとなる。
    worker_processes 2
    
    # リクエスト待ち受け口、TCPとUNIXドメインとが指定可能。
    listen "/var/run/unicorn/unicorn.sock", :backlog => 32
    listen 8282, :tcp_nopush => true
    
    # タイムアウト秒数
    timeout 30
    
    # 稼働中のプロセスのPIDを書いておくファイル。
    pid "tmp/pids/unicorn.pid" 
    
    # デーモンで起動すると標準出力/標準エラー出力が/dev/nullになるので、
    # それぞれログファイルに出力する。
    stderr_path 'log/unicorn.stderr.log'
    stdout_path 'log/unicorn.stdout.log'
    
    # マスタープロセス起動時にアプリケーションをロードする(true時)。
    # ワーカープロセス側でロードをしないのでメモリ消費、応答性良好になる。
    # ただし、ソケットはfork後に開きなおす必要あり。
    # HUPシグナルでアプリケーションはロードされない。
    preload_app true
    
    # unicornと同一ホスト上のクライアントとのコネクション限定で、維持されているかを
    # アプリケーションを呼ぶ前にチェックする。
    check_client_connection false
    
    before_fork do |server, worker|
      # Railsでpreload_appをtrueにしているときは強く推奨
      defined?(ActiveRecord::Base) and
        ActiveRecord::Base.connection.disconnect!
      # new master phase out the old master
      old_pid = "#{server.config[:pid]}.oldbin" 
      if old_pid != server.pid
        begin
          sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
          Process.kill(sig, File.read(old_pid).to_i)
        rescue Errno::ENOENT, Errno::ESRCH
        end
      end
    end
    
    after_fork do |server, worker|
      # Railsでpreload_appをtrueにしているときは必須
      defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
    
    end
    

この設定ファイルは、Unicornを実行するときにカレントディレクトリが/var/lib/redmine/にあることを前提としています。

worker_processes

応答性を良好に保つには、コア数以上のワーカーを指定します。ただし、仮想化環境ではCPU使用率とメモリ使用量を見ながらワーカー数を許容範囲まで増やします。

listen

リクエストを受け付けるプロトコルとポート、オプション設定を指定します。

UNIXドメインソケットの場合の例

listen "/var/run/unicorn/unicorn.sock", :backlog => 32

TCPソケットの場合の例
listen 8080, :tcp_nopush => true

  • 複数のプロトコルを列挙することで複数のプロトコル・ポートを扱うことができます。
  • UNIXドメインソケットはUnicornプロセスとNginxプロセスの間で読み書きする特殊ファイルです。Systemd 環境および SELinux 有効下では、/tmp や Unicornディレクトリ配下に置くことが難しいので、/var/run以下に置きます。/var/run 直下にファイルを作成するにはroot権限が必要なため、Unicorn起動時にroot権限で/var/run/unicornディレクトリを作成し、その後に Unicorn が/var/run/unicorn/unicorn.sockを設ける手順を踏みます。
  • backlogは、workerが作業中でもコネクションのリクエストを受理して待機しておくことができる個数で、デフォルトは1024です。例えば1件平均30msで捌く処理に対して1000個バックログによる待ちがあると、新たにリクエストした処理は結果が返るまで30秒間待たされることになります。それだったらコネクションを受け付けずにエラーにした方がよいということがあります。参考までに自宅で運用しているRedmineのログからCompleted行にある処理時間の平均は約100msでした。
  • tcp_nopushは、TCP_CORK(Linux)を制御します。デフォルトはfalseです。trueにすると、TCPフレームの断片が小出しに送られることを抑止するので、リモートにあるNginxのタスクを早めに起こさずにすませます。
timeout

workerがこの秒数以上処理に費やすとプロセスを落とします。デフォルトは60秒です。大抵の処理はずっと短いので60秒は大きすぎる値ですが、中に時間のかかる処理があれば処理時間に応じて値を増やします。

Redmineでは、大きなサイズの添付ファイルのアップロード・ダウンロードが該当します。Redmineの添付ファイルサイズ上限のファイルを実際にアップロード・ダウンロードさせてかかる時間を計測し、それを許容できる処理時間を設定します。

ここで設定するunicornのタイムアウト値とNginxのタイムアウト値が不整合だとかなり怪しい挙動となりますので、両者のタイムアウト値を整合させるようにしてください(unicornのタイムアウト値+αをnginxのタイムアウト値にする、αは1ないし2秒程度、等)。

pid

unicornを起動したときにそのプロセスIDを記録しておくファイルを指定します。

stderr_path、stdout_path

Redmineインストールディレクトリ下のlogディレクトリの中にログファイルを生成します。

preload_app

trueに設定すると、マスタープロセス起動時にアプリケーションをロードし、ワーカープロセスをフォークするとアプリケーションが実行可能となります。複数ワーカープロセスでコードを共有するため、メモリ使用効率もよくなります。デメリットは、ワーカープロセスを再起動してもアプリケーションはロードされない点です。

before_fork

ワーカープロセスをforkする前にマスタープロセスによって呼ばれます。
USR2シグナルで新旧マスタープロセスが共存する場合は、旧マスタープロセスにQUITシグナルを送って終了させます。

after_fork

ワーカープロセスがforkされた後に呼び出されます。

Unicornのサービス化(起動終了設定)

  • /usr/lib/systemd/system/redmine-unicorn.service
    [Unit]
    Description=Redmine Unicorn Server
    After=mariadb.service
    
    [Service]
    User=redmine
    Group=redmine
    WorkingDirectory=/var/lib/redmine-4.1-stable
    Environment=RAILS_ENV=production
    SyslogIdentifier=redmine-unicorn
    PIDFile=/var/lib/redmine-4.1-stable/tmp/pids/unicorn.pid
    PermissionsStartOnly=true
    ExecStartPre=/usr/bin/install -m 755 -o redmine -g redmine -d /var/run/unicorn
    ExecStart=/usr/bin/bundle exec "unicorn_rails -c config/unicorn.rb -E production" 
    ExecStop=/usr/bin/kill -QUIT $MAINPID
    ExecReload=/usr/bin/kill -USR2 $MAINPID
    
    [Install]
    WantedBy=multi-user.target
    
サービス起動順序の制御

Redmine(Unicorn)は、起動時にデータベースに接続できないとエラー終了してしまいます。
そこで、[Unit]セクションにAfterで、mariadb.serviceが実行されてからredmine-unicorn.serviceが起動されるよう順序を指定します。

起動プロセスの実行ユーザー・グループ

SystemdでExecStartにより実行したプロセスの実行ユーザーをroot以外にする場合、[Service]セクションのUser、Groupでユーザー・グループを設定します。

WorkingDirectoryとPIDFileは実パス(シンボリックリンクではなく)を指定

SELinuxの設定上、WorkingDirectoryとPIDFileに設定するパスはシンボリックリンクファイルを含まない実パスで設定します。

起動前に /var/run/unicorn ディレクトリ作成

Unicorn起動時にUNIXドメインソケットファイルを/var/run/unicornディレクトリ下に作成します。/var/runディレクトリ直下にファイルを作成するにはroot権限が必要なため、ExecStartPreで/var/run/unicornディレクトリを作成します。ここではディレクトリ作成にinstallコマンドを使っていますが、これはディレクトリ作成とパーミッションの設定をまとめて実施できるためです。

PermissionsStartOnly=true を指定しておくと、UserおよびGourpを指定していてもExecStartPreはroot権限で実行されます。

Unicornサービスの動作確認

Systemd からサービスとして起動し動作することの確認を行います。

一時的にファイアウォールの許可

一時的にファイアウォールでポート8282を許可します。

# firewall-cmd --add-port=8282/tcp
success

Unicornサービスの開始

以下のようにsystemctlでstartし、statusを確認し実行状態になっていれば正常に起動しています。

# systemctl start redmine-unicorn
# systemctl status redmine-unicorn
● redmine-unicorn.service - Redmine Unicorn Server
   Loaded: loaded (/usr/lib/systemd/system/redmine-unicorn.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2020-04-21 22:10:00 JST; 2min 28s ago
  Process: 13131 ExecStartPre=/usr/bin/install -m 755 -o redmine -g redmine -d /var/run/unicorn (code=exited, status=0/SUCCESS)
 Main PID: 13133 (ruby)
    Tasks: 7 (limit: 11103)
   Memory: 194.7M
   CGroup: /system.slice/redmine-unicorn.service
           tq13133 unicorn_rails master -c config/unicorn.rb -E production
           tq13157 unicorn_rails worker[0] -c config/unicorn.rb -E production
           mq13159 unicorn_rails worker[1] -c config/unicorn.rb -E production

 4月 21 22:10:00 pan systemd[1]: Starting Redmine Unicorn Server...
 4月 21 22:10:00 pan systemd[1]: Started Redmine Unicorn Server.

Nginxのセットアップ

Nginxのインストール

$ dnf module list nginx
CentOS-8 - AppStream
Name            Stream             Profiles             Summary
nginx           1.14 [d]           common [d]           nginx webserver
nginx           1.16               common               nginx webserver

ヒント: [d]efault, [e]nabled, [x]disabled, [i]nstalled

CentOS 8 のリポジトリ(AppStream)にて、Nginxが提供されています。バージョンは1.14と1.16があり、今回は1.16を使用します。

# dnf module install nginx:1.16/common
  :

サービスを有効化します。

# systemctl enable --now nginx
# systemctl status nginx
● nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2020-04-20 21:33:30 JST; 44s ago
  Process: 26791 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
  Process: 26787 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
  Process: 26785 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
 Main PID: 26792 (nginx)
    Tasks: 3 (limit: 11103)
   Memory: 13.0M
   CGroup: /system.slice/nginx.service
           tq26792 nginx: master process /usr/sbin/nginx
           tq26793 nginx: worker process
           mq26794 nginx: worker process

 4月 20 21:33:30 pan systemd[1]: Starting The nginx HTTP and reverse proxy server...
 4月 20 21:33:30 pan nginx[26787]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
 4月 20 21:33:30 pan nginx[26787]: nginx: configuration file /etc/nginx/nginx.conf test is successful
 4月 20 21:33:30 pan systemd[1]: Started The nginx HTTP and reverse proxy server.

ファイアウォールでhttpとhttpsを許可します。

# firewall-cmd --add-service http --permanent
success
# firewall-cmd --add-service https --permanent
success
# firewall-cmd --reload
success
# firewall-cmd --list-services
cockpit dhcpv6-client http https ssh

ブラウザからポート80にアクセスし、「Welcome to nginx on Red Hat Enterprise Linux!」が表示されたら確認OKです。

nginxの設定

/etc/nginx/conf.d/redmine.conf を新規作成します。

upstream unicorn {
    server unix:/var/run/unicorn/unicorn.sock;
}

server {
    listen 80;
    server_name _;

    root /var/lib/redmine/public;
    client_max_body_size 1G;

    location / {
        try_files $uri/index.html $uri.html $uri @app;
    }

    location @app {
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_connect_timeout 60;
        proxy_read_timeout 60;
        proxy_send_timeout 600;
        proxy_pass http://unicorn;
    }

    error_page 500 502 503 504 /500.html;
}

設定ファイルの検証を行います。

#  nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Nginxでセキュアなアクセス(TLS)を使用

Redmineへのアクセスを https 化(TLS化)します。
検証環境では、然るべきSSLサーバー証明書が用意できないので、自己証明書を作成します。

自己証明書の作成

秘密鍵の作成(パスフレーズなし)

ファイル名:server.key

~$ openssl genrsa -out server.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
................................+++++
.........+++++
e is 65537 (0x010001)
証明書署名要求(CSR:Certificate Signing Request)の作成

ファイル名:server.csr

~$ openssl req -utf8 -new -key server.key -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]: JP                           <--- 国(日本ならJP)
State or Province Name (full name) []:Kanagawa                  <--- 県
Locality Name (eg, city) [Default City]:Kawasaki                <--- 市
Organization Name (eg, company) [Default Company Ltd]:TK        <--- 会社・組織
Organizational Unit Name (eg, section) []:                      <--- 部門(未入力可)
Common Name (eg, your name or your server's hostname) []:alfa.torutk.com    <--- サーバードメイン名(未入力可)
Email Address []:                                               <--- 管理者メール(未入力可)

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:                   <-- (未入力可)
An optional company name []:               <-- (未入力可)   
certs$
  • Common Name(サーバードメイン名)は、次の手順でSANを指定することで無視されるので未入力
Subject Alternative Name(SAN) 情報の作成

ファイル名:san.txt

まず、最近のWebブラウザでは、Subject Alternative Name(SAN) を含めないとアクセスができないのでまずSAN情報をテキストファイルに記述します。

subjectAltName = DNS:*.torutk.com

ワイルドカードを使って複数のホスト名に対応させることができます(DNS指定の場合)。

自己署名証明書の作成

ファイル名:server.crt

~$ openssl x509 -in server.csr -out server.crt -extfile san.txt -req -signkey server.key -days 3650
Signature ok
subject=C = JP, ST = Kanagawa, L = Kawasaki, O = TK, CN = alfa.torutk.com
Getting Private key
certs$ sudo chmod 600 server.key
作成したファイルの配置

上述で作成した秘密鍵と自己署名証明書を、次の場所に配置します。

ファイル名 配置ディレクトリ
server.key /etc/pki/nginx/private
server.crt /etc/pki/nginx
  • nginx とその下の private ディレクトリは配置前に作成しておきます。

Nginx設定ファイルの編集・作成

  • /etc/nginx/nginx.conf から、server {...} の記述を削除
    --- /etc/nginx/nginx.conf.orig  2019-11-23 04:18:57.000000000 +0900
    +++ /etc/nginx/nginx.conf       2020-04-22 10:15:01.376933021 +0900
    @@ -35,26 +35,6 @@
         # for more information.
         include /etc/nginx/conf.d/*.conf;
    
    -    server {
    -        listen       80 default_server;
    -        listen       [::]:80 default_server;
    -        server_name  _;
    -        root         /usr/share/nginx/html;
    -
    -        # Load configuration files for the default server block.
    -        include /etc/nginx/default.d/*.conf;
    -
    -        location / {
    -        }
    -
    -        error_page 404 /404.html;
    -            location = /40x.html {
    -        }
    -
    -        error_page 500 502 503 504 /50x.html;
    -            location = /50x.html {
    -        }
    -    }
    
     # Settings for a TLS enabled server.
     #
    
  • /etc/nginx/conf.d/redmine.conf
    upstream unicorn {
        server unix:/var/run/unicorn/unicorn.sock;
    }
    
    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name _;
    
        return 301 https://$host$request_uri;
    }
    
    server {
        listen 443 ssl http2 default_server;
        listen [::]:443 ssl http2 default_server;
        server_name _;
        root /var/lib/redmine/public;
    
        ssl_certificate "/etc/pki/nginx/server.crt";
        ssl_certificate_key "/etc/pki/nginx/private/server.key";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 10m;
        ssl_ciphers PROFILE=SYSTEM;
        ssl_prefer_server_ciphers on;
    
        include /etc/nginx/default.d/*.conf;
    
        client_max_body_size 1G;
    
        location / {
            try_files $uri/index.html $uri.html $uri @app;
        }
    
        location @app {
            proxy_redirect off;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_connect_timeout 60;
            proxy_read_timeout 60;
            proxy_send_timeout 600;
            proxy_pass http://unicorn;
        }
    
        error_page 404 /404.html;
            location = /40x.html {
        }
    
        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
    

Redmineのデータ移行

旧バージョンのRedmineからデータを引き継いで運用する場合の手順です。
データベースを移行(旧Redmine環境からRDBMSのデータをエクスポートし、新Redmine環境のRDBMSへインポート)すると、プラグインのデータも含まれています。
そこで、Redmine本体とデータベースを使用するプラグインをあらかじめインストールしてからデータベースのマイグレーションを実行します。

プラグインの移行

旧Redmine 3.4 から Redmine 4.1 へバージョンアップする場合の、プラグインの移行についてです。
現在移行対象とする Redmine 3.4 で稼働しているプラグインと、Redmine 4.1対応状況です。

No プラグイン名 稼働Ver. DB? Redmine 4.1対応状況 移行方針
1 clipboard_image_paste 1.13 放棄(画像ペーストが標準機能に登録)
2 redmine_banner 0.1.2 バージョンアップ
3 redmine_github_hook 2.2.1 バージョンアップ
4 redmine_glossary 0.9.2 バージョンアップ1
5 redmine_issue_templates 0.2.2-dev バージョンアップ2
6 redmine_latex_mathjax 0.3.0 バージョンアップ3
7 redmine_startpage 0.1.0 放棄4
8 redmine_wiki_extensions 0.8.2 バージョンアップ
9 redmine_wiki_lists 0.0.9 バージョンアップ
10 redmine_xls_export 0.2.1.t11 同一バージョン使用5
11 sidebar_hide 0.0.8 同一バージョン使用6
12 view_customize 2.1.0 バージョンアップ

1 本家は未対応、フォーク版がいくつかあり、4.x対応
https://github.com/torutk/redmine_glossary

2 IE11対応が必要なら0.3-stableブランチ、不要なら1.0にバージョンアップ

3 Redmine本家サイトのPlugin Directoryには、Redmine 4対応のフォーク版が掲載(2020-04-22現在)

4 プラグインの数を減らすため放棄、routes設定で直接制御
https://torutk.hatenablog.jp/entry/20130701/p1

5 URLは https://github.com/two-pack/redmine_xls_export
類似プラグインに Redmine XLSX format issue exporter あり

6 Redmine 4対応はうたっていないので、だめな場合の代替手段も検討する
類似プラグイン Redmine Toggle Sidebar
View Customizeプラグインで制御 Redmineでサイドバーを開閉可能とし、かつ縦スクロールでも隠れないようにする

移行しないプラグインについて、データベースを使用しないプラグインは、Redmine 4.1で入れないだけで問題はありません。データベースを使用するプラグインは、データベースを元に戻す必要があります。これは移行作業の際に旧Redmine 3.4上で当該プラグインのmigrateでバージョン0に戻す(プラグインが変更したデータベーススキーマを戻す)ことで対処します。

データベースを使用するプラグインのインストール

  • redmine_banner
  • redmine_issue_templates
  • redmine_wiki_extensions
  • view_customize
  • redmine_glossary
~$ cd /var/lib/redmine/plugins
plugins$ git clone https://github.com/akiko-pusu/redmine_banner.git
  :
plugins$ git clone https://github.com/akiko-pusu/redmine_issue_templates.git
  :
plugins$ git clone https://github.com/haru/redmine_wiki_extensions.git
  :
plugins$ git clone https://github.com/onozaty/redmine-view-customize.git view_customize
  :
plugins$ git clone https://github.com/torutk/redmine_glossary.git
  :
plugins$ ls
README          redmine_glossary         redmine_wiki_extensions
redmine_banner  redmine_issue_templates  view_customize

プラグインのデータベースマイグレーションは、後の手順で実施します。

データ移行

旧Redmineから新Redmineへデータ移行をするには次を実施します。

  • データベース移行
  • 添付ファイル移行
  • リポジトリ移行

データベース移行

まずはRedmine 3.4のデータベースをエクスポートします。

~$ mysqldump -uredmine redmine > mysql_dump.sql

エクスポートしたデータベースを、Redmine 4.1のデータベースにインポートします。

~$ mysql -u redmine -D redmine < mysql_dump.sql

データベースの内容を移行したら、続いてデータベースのマイグレーションを実施します。
それには、まずRedmine本体のマイグレーションを行い、次にプラグインのマイグレーションを実施します。

Redmine本体のマイグレーション
~$ cd /var/lib/redmine-4.1-stable
redmine-4.1-stable$ bundle exec rails db:migrate RAILS_ENV=production
  :
Redmineプラグインのマイグレーション
redmine-4.1-stable$ bundle exec rails redmine:plugins:migrate RAILS_ENV=production
文字コード(character set)、照会順序(collocation)の変更

MariaDBの設定では、文字コードをutf8mb4(各文字は最大4バイトで補助文字を含む)としましたが、過去のデータベースデータはutf8(各文字は最大3バイト)である場合、インポートした結果は過去の設定であるutf8のままとなります。このテーブルに4バイトからなるUTF-8文字を書き込むとエラーになります。Redmineの場合は、例えば「絵文字を含む文章をWiki記述して保存したところInternal Errorが返ってきた」という状況になります。

この場合、インポートした後に各テーブルの文字コード、照会順序を変更します。

指定したテーブルとそのカラムの文字コード、照会順序を変更するコマンドを次に示します。

> ALTER TABLE versions CONVERT TO CHARACTER SET utf8mb4;

versionsテーブルのCHARACTER SETはutf8mb4に、同テーブルのCOLLATIONはutf8mb4_generic_ciになりました。

RedmineデータベースのすべてのテーブルのCHARACTER SETをutf8からutf8mb4に変更するのに、テーブル1つ1つについて上述のコマンドを手打ちするのは大変です。そこで、ごそっとスクリプトを作って実行します。

~$ for t in $(mysql -uredmine -pxxxxxxxx redmine -e "show tables" -s -N); ~
do echo "ALTER TABLE $t CONVERT TO CHARACTER SET utf8mb4;"; done > alter_all_tables_charset_utf8mb4.sh

生成したスクリプトファイルの中身は次のようになります(抜粋)。

ALTER TABLE ar_internal_metadata CONVERT TO CHARACTER SET utf8mb4;
ALTER TABLE attachments CONVERT TO CHARACTER SET utf8mb4;
ALTER TABLE auth_sources CONVERT TO CHARACTER SET utf8mb4;
ALTER TABLE banners CONVERT TO CHARACTER SET utf8mb4;
ALTER TABLE boards CONVERT TO CHARACTER SET utf8mb4;
  :

実行します。

~% mysql -uredmine -pxxxxxxxx redmine < alter_all_tables_charset_utf8mb4.sh

添付ファイルの移行

redmineインストールディレクトリの下のfilesディレクトリの内容を旧Redmineから新Redmineへコピーします。

リポジトリの移行

旧Redmineと同じマシン上にあるSubversionとGitとをバックアップし、新Redmineのマシンに移行します。

Subversionの移行

旧subversionリポジトリをバックアップします。

~$ svnadmin dump /var/lib/svn/my_repo > svn_my_repo-`date +%Y%m%d`.dump
  • 設定ファイル(リポジトリのconfディレクトリ以下)に手を入れていたら、上述dumpではバックアップされないので手動でコピーしておきます。

新subversionリポジトリに復元します。
/var/lib/svn、/var/lib/subversion/repo、/var/www/svn にはSELinuxでHTTPサーバーからアクセスできる設定が定義済みなので、このいずれかにsubversionリポジトリを設けるとよいでしょう。

# mkdir /var/lib/svn
# svnadmin create /var/lib/svn/my_repo
# svnadmin load /var/lib/svn/my_repo < svn_my_repo-202004231030.dump

TODO: apache httpdをインストール後、/var/lib/svn以下のオーナー、グループをapacheに変更し、またhttpdからRedmine認証を経てアクセスする設定を設ける

Gitの移行

旧Gitリポジトリをバックアップします。

~$ git clone --mirror /var/lib/git/my_repo.git git_my_repo-`date +%Y%m%d`.git
~$ tar czf git_my_repo-202004231100.tgz git_my_repo-202004231100.git

新gitリポジトリに復元します。
CentOS 8のSELinuxポリシーでは、/var/lib/git にはSELinuxのgit_sys_content_t タイプが、/var/www/git にはSELinuxのgit_content_t タイプが定義されています。

apache httpd から smart http 経由でgitリポジトリにアクセスする際は、gitリポジトリへの読み書き、および各リポジトリディレクトリのhooksディレクトリにあるスクリプトの実行が必要です。そこで、SELinuxポリシーの設定を変更します。

# semanage fcontext -m -t git_rw_content_t '/var/lib/git(/.*)?'
# semanage fcontext -a -t git_script_exec_t '/var/lib/git/[^/]+/hooks(/.*)?'
# restorecon -R /var/lib/git

残りのプラグインのインストール

データベースを伴わないプラグインをインストールします。

  • redmine_github_hook
  • redmine_latex_mathjax
  • redmine_wiki_lists
  • redmine_xls_export
  • sidebar_hide

redmine_github_hook

redmine_latex_mathjax

redmine_wiki_lists

redmine_xls_export

sidebar_hide

plugin$ git clone https://gitlab.com/bdemirkir/sidebar_hide.git

旧Redmineのプラグインの代替

次のプラグインはRedmine 4.1に対応していないので、代替手段を講じます。

  • clipboard_image_paste
  • redmine_startpage

clipboard_image_paste

Redmine 4.1からは、画像データをクリップボードにコピーした状態でWiki編集領域にペースト操作をすると、添付ファイルに画像ファイルが自動で追加され、Wiki編集領域には画像ファイルの表示記法(!でファイル名を挟んだもの)が挿入されます。

なお、ファイル名は自動で付与され、後から名前を変えるのは簡単ではなさそうです。

redmine_startpage

本プラグインの使用はやめて、以下の修正を直接Redmineに入れます。

  • config/routes.rb を変更
     Rails.application.routes.draw do
    -  root :to => 'welcome#index', :as => 'home'
    +  root :to => 'wiki#show', :project_id => 'swe', :as => 'home'
    

ここで、sweはプロジェクト識別子です。

テーマ

旧Redmineで使用していた Gitmike テーマが、sidebar hideプラグインと干渉しているので、別なテーマを探します。

候補の調査

  • PurpleMine2
    サイドバーを左側に配置し、折り畳み機能を持つ。このテーマを使うときは機能が被るのでsidebar hideプラグインは削除する。
  • Blueclair
    Redmineのオリジナルテーマを基調に見やすく改善。sidebar hideと共存可。

落ち穂ひろい

Redmine 4.1系をCentOS 8で動かすにあたり、残る雑多なことをメモ

ImageMagickを入れる

CentOS 8(Red Hat Enterprise 8)では、OS提供パッケージからImageMagickが削除されています。
ImageMagickを入れない状態では、thumbnailマクロが機能しない等の制約があります。
EPELリポジトリからImageMagickが提供されているので、制約が受け入れられない場合(例えば本サイト。thumbnailを使いまくっています)、EPELからImageMagickをインストールします。

# dnf --enablerepo=epel install ImageMagick
  :

なお、Ruby gemモジュールを入れるときに、--withoutオプションでrmagickを除外していましたが、最近のRedmineはrmagickを使わずmini_magickを使っているのでbundleの入れ直し等は不要でした。


ほぼ4年前に更新