torutkのブログ

ソフトウェア・エンジニアのブログ

さくらVPSのOSをCentOS 8に更新、さらにRedmineを4.1に更新(続々々々)

さくらVPSのOSをCentOS 8に、Redmineを4.1に更新、の続き(5日目)

既に対処が終わっている内容ですが、以下の続きです。

torutk.hatenablog.jp

MariaDB文字コードを、utf8mb4に設定しました。これはUTF-8で1~4バイトの範囲を扱える設定です。一方、MariaDB文字コードをutf8とした場合は、UTF-8で1~3バイトの範囲(基本プレーン)しか扱えません。utf8のデータベースに、例えばUTF-8で4バイトで表現される絵文字をWikiに記載し保存しようとするとエラーとなってしまいます。

ここで、今回の更新で起きた問題は、RedmineのこれまでのデータベースはMySQLのutf8で作成したものを移行してきたことに起因するものです。具体的には、新たにMariaDB文字コードをutf8mb4に設定してもデータベース(テーブル)はutf8のままであり、UTF-8の4バイト文字を入れるとMariaDBの設定自体はutf8mb4であってもテーブルはutf8のためエラーとなるというものです。

今回、MariaDBを設定後にRedmineのデータベースを手動で作成し、その後に旧RedmineMariaDBからダンプしたデータをインポートしたので、データベースはutf8mb4ですが、Redmineの各テーブルがutf8のままとなっている状況です。

utf8からutf8mb4への変更は、UTF-8の1~3バイト文字からUTF-8の1~4バイト文字への拡張のみであるため、文字のデータそのものはまったく変更する必要はなく、単にテーブル(カラム)の文字列の文字コード設定をutf8からutf8mb4へ変更するだけとなります。

1つのテーブルについて、文字コードをutf8からutf8mb4に変更するには次のSQL文を実行します。 例えばattachmentsテーブルの文字コードをutf8mb4に変更する場合のSQL文は次となります。

> ALTER TABLE attachments CONVERT TO CHARACTER SET utf8mb4;

さて、Redmineのデータベースには、テーブルが多数(約70個)あります。 上述のコマンドを各テーブルについて一つ一つ入力して実行するのは大変です。

SQL文で頑張る方法もありそうですが(SELECT CONCAT ...)、ちゃんと動くか不安があったため、Linuxのテキストファイルに落としてから、これを実行することにしました。

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

あとはこのファイルをMariaDBに読み込ませて実行させます。

~$ mysql -uredmine -pxxxxxxxx redmine < alter_tables.sql

IntelliJ IDEAでGradleを使ってJPMSプロジェクトの作成

IntelliJ IDEAでGradleを使ってJPMS(モジュールシステム)対応のプロジェクトを作成

JavaのビルドツールGradleが、まもなくリリースされるバージョン6.4で、Java SE 9で導入されたJava Platform Module System(略称JPMS)に対応します。Java SE 9がリリースされた2017年9月から3年近くが経過し、ようやくGradleがJPMS対応されます。

今回は、IntelliJ IDEA上でGradleをビルドツールに使うJPMS対応のプロジェクトを作成する経緯を記します。

開発環境は次です。

項目 内容
OS Windows 10 Pro 64bit 日本語版
JDK Liberica JDK 14 full 64bit
IntelliJ IDEA Community 2020.1.1
Gradle 6.4 RC-4

Liberica JDK 14のインストール

Liberica JDKの提供元 Bell Software社から JDK 14.0.1 Full versionをダウンロードします。

https://bell-sw.com/

bellsoft-jdk14.0.1+8-windows-amd64-full.zip

Full versionはJavaFXを同梱したJDKとなっています。Windows用のLiberica JDKMSIインストーラー形式とZIP形式とが用意されています。開発環境では各種各バージョンのJDKを多数揃える(同居させる)ので、zip版をダウンロードし展開するのがよいでしょう。

IntelliJ IDEA Community版

IntelliJ IDEA Community版(無料)をインストールします。 https://www.jetbrains.com/ja-jp/idea/download/

Gradle 6.4 RC4のインストール

Gradle Build Tool - Releases を開き、[release candidates]リンクを辿り、Install Manually項の[Binary-only]をクリックすると、Gradle 6.4 RC4のバイナリzipファイルをダウンロードします。

gradle-6.4-rc-4-bin.zip

このファイルをマシン上の適切な場所(C:\Program Files\Java\gradle-6.4-rc-4)に展開しました。

IntelliJ IDEAでJDKの設定

プロジェクト作成前に、IntelliJ IDEA上でLiberica JDK 14 full のJDK定義を作成しておきます。

  • [File]メニュー > [Project Structure] で「Project Structure」画面を開き、左側ペインで[Program Settings] > [SDKs]を選択、中側ペインの[+]をクリックし、[Add JDK]を選択して「Select Home Directory for JDK」画面でJDKのインストールディレクトリを指定します。

  • 「Select Home Directory for JDK」画面の右側ペインでName欄に指定したJDKに適切な名称を記入します(例:Liberica JDK 14 full)。

IntelliJ IDEAでJavaアプリケーション(単一モジュール)プロジェクト

JPMSモジュールを指定して実行するアプリケーションを作成します。今回はJPMSの定義、ビルド、実行に着目するため、依存ライブラリは使用しません。

プロジェクトの作成

IntelliJ IDEA上でGradleを使うプロジェクトを新規作成

まず、IntelliJ IDEAのプロジェクト作成機能でGradleのプロジェクトを作成します。

IntelliJ IDEAを起動、新規プロジェクト(Gradle)を作成します。なお、これで作成したプロジェクトはGradleのバージョンが古い(IntelliJ IDEA 2020.1の場合、Gradle 6.1)ので、後の手順で新しいGradleを使うよう設定を変更します。

  • 「New Project」画面の左側ペインで[Gradle]を選択し、右側ペインでProject SDK欄に前の手順で定義したJDK名(例:Liberica JDK 14 full)を選択します。
  • Additional Libraries and Frameworks欄は、[Java]にチェックを付けて、[Next]ボタンをクリックします。
  • Name欄にプロジェクト名(例:HelloJavaFx)を記入、Location欄にプロジェクトの基点ディレクト1を記入
  • Artifact Coordinatesの左端にある▼印をクリックし、詳細設定項目を展開します。
  • GroupId欄に、成果物のグループ識別子(通常代表モジュールのパッケージ名逆順、例:com.torutk.hello)を記入します。
  • ArtifactId欄に、成果物の名前(デフォルトではプロジェクト名が展開済み)を記入します。
  • Version欄に、バージョン名を記入します。
    f:id:torutk:20200505224804p:plain
    IntelliJ IDEA新規プロジェクト(Gradle)の成果物定義画面
Gradleプロジェクトのディレクトリ構成

IntelliJ IDEAで作成したGradleプロジェクトのディレクトリ・ファイル構成は次です。

HelloJavaFx
│  build.gradle
│  gradlew
│  gradlew.bat
│  settings.gradle
│
├─.gradle
│  ├─6.1
│  │  │  gc.properties
│  │  │
│  │  ├─executionHistory
│  │  │      executionHistory.bin
│  │  │      executionHistory.lock
│  │  │
│  │  ├─fileChanges
│  │  │      last-build.bin
│  │  │
│  │  ├─fileHashes
│  │  │      fileHashes.bin
│  │  │      fileHashes.lock
│  │  │
│  │  └─vcsMetadata-1
│  ├─buildOutputCleanup
│  │      buildOutputCleanup.lock
│  │      cache.properties
│  │      outputFiles.bin
│  │
│  ├─checksums
│  │      checksums.lock
│  │
│  └─vcs-1
│          gc.properties
│
├─.idea
│      .gitignore
│      compiler.xml
│      gradle.xml
│      misc.xml
│      workspace.xml
│
├─gradle
│  └─wrapper
│          gradle-wrapper.jar
│          gradle-wrapper.properties
│
└─src
    ├─main
    │  ├─java
    │  └─resources
    └─test
        ├─java
        └─resources
使用するGradleをIntelliJ IDEA同梱のものから別途インストールしたものへ変更

IntelliJ IDEAの[File]メニュー > [Settings]で「Settings」画面を開き、左側ペインで[Buid, Execution, Deployment] > [Build Tools] > [Gradle]を選択、右側ペインで[Gradle Projects]領域内のUse Gradle from欄をデフォルトの'gradle-wrapper.properties' fileからSpecified locationに変更、右隣の欄にGradle 6.4 RC4ディレクトリパスを設定します。

その下のGradle JVM欄は、プロジェクトで使用するJVM(例:Liberica JDK 14 full)を選択します。

[OK]ボタンを押すと、Gradleの設定ファイルとのSyncが実行されます。

  • 既存のGradleプロジェクトでGradleのバージョンを更新する場合、通常は gradle wrapper --gradle-version 6.4-rc-4 と実行して指定バージョンへ更新するようです。
build.gradleファイルの編集

IntelliJ IDEAが生成したbuild.gradle(次に示す)は、JPMS対応前のバージョン用の定義です。

plugins {
    id 'java'
}

group 'com.torutk.hello'
version '0.1.0'

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

Gradle 6.4のJPMS対応のドキュメント(以下のURL)を参照し、修正をします。 Gradle 6.4-rc-4 Release Notes

GradleプラグインJava一般用からアプリケーション用に変更します。

  plugins {
-     id 'java'
+     id 'application'
  }

実行クラスを含む実行可能モジュールとメインクラスを定義します。

+ application {
+     mainModule = 'com.torutk.hello'
+     mainClass = 'com.torutk.hello.MessageBoard'
+ }

モジュールパス推論をセットします。

  version '0.1.0'

+ java {
+     modularity.inferModulePath = true
+ }

リポジトリ定義、依存関係定義は今回使用しないので削除しておきます。

build.gradle

修正後のbuild.gradleファイルの全体象は次です。

plugins {
    id 'application'
}

group 'com.torutk.hello'
version '0.1.0'

java {
    modularity.inferModulePath = true
}

application {
    mainModule = 'com.torutk.hello'
    mainClass = 'com.torutk.hello.MessageBoard'
}

モジュール定義

src\main\javaディレクトリの下にモジュール定義(module-info.java)を作成します。

HelloJavaFx
└─src
    ├─main
    │  ├─java
    │  │  │  module-info.java

モジュール定義の内容は次です。

module com.torutk.hello {
    requires javafx.graphics;
    opens com.torutk.hello to javafx.graphics;
}
  • 作成するアプリケーションのモジュール名をcom.torutk.helloと定義
  • アプリケーションモジュールcom.torutk.helloは、javafx.graphicsモジュールを使用(requiresの依存関係)
  • このアプリケーションモジュールに含むcom.torutk.helloパッケージは、javafx.graphicsモジュールに対して実行時にのみアクセスを許可(リフレクションでアクセス可能とする)

メインクラスの作成

src\main\javaディレクトリの下に、パッケージcom.torutk.helloに対応するディレクトリを作成し、その中にメインクラスを配置します。

└─src
    ├─main
    │  ├─java
    │  │  │  module-info.java
    │  │  │
    │  │  └─com
    │  │      └─torutk
    │  │          └─hello
    │  │                  MessageBoard.java

メインクラスの最初の雛形ソースコードは次です。

package com.torutk.hello;

import javafx.application.Application;
import javafx.stage.Stage;

public class MessageBoard extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.show();
    }
}
  • ここでは空の(真っ白な)ウィンドウを1つ表示するだけのメインクラスを記述

ビルド

では、早速ビルドしてみます。

IntelliJ IDEAのメニューからビルド

[Build]メニュー > [Build Project] を実行します。

23:40:16: Executing tasks ':classes :testClasses'...

> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
23:40:18: Tasks execution finished ':classes :testClasses'.

メニューからビルドを実行すると、Gradleのタスクのうち classes と testClasses (とその依存タスク)だけを実行しています。 ビルドの結果生成されるディレクトリ・ファイルは次です。クラスファイルのみ生成されています。

├─build
│  ├─classes
│  │  └─java
│  │      └─main
│  │          │  module-info.class
│  │          │
│  │          └─com
│  │              └─torutk
│  │                  └─hello
│  │                          MessageBoard.class
│  │
│  ├─generated
│  │  └─sources
│  │      ├─annotationProcessor
│  │      │  └─java
│  │      │      └─main
│  │      └─headers
│  │          └─java
│  │              └─main
│  └─tmp
│      └─compileJava
Gradleのbuildタスク実行

Gradleペインの中から[Tasks] > [build] > [build] を実行します。 f:id:torutk:20200506234836p:plain

Build結果出力は次です。

23:44:40: Executing task 'build'...

> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :jar
> Task :startScripts
> Task :distTar
> Task :distZip
> Task :assemble
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test NO-SOURCE
> Task :check UP-TO-DATE
> Task :build

BUILD SUCCESSFUL in 504ms
5 actionable tasks: 5 executed
23:44:41: Task execution finished 'build'.

先ほどのclasses、testClassesタスク以外にも、多数のタスクが実行されているのが分かります。 ビルド結果は次です。

├─build
│  ├─classes
│  │  └─java
│  │      └─main
│  │          │  module-info.class
│  │          │
│  │          └─com
│  │              └─torutk
│  │                  └─hello
│  │                          MessageBoard.class
│  │
│  ├─distributions
│  │      HelloJavaFx-0.1.0.tar
│  │      HelloJavaFx-0.1.0.zip
│  │
│  ├─generated
│  │  └─sources
│  │      ├─annotationProcessor
│  │      │  └─java
│  │      │      └─main
│  │      └─headers
│  │          └─java
│  │              └─main
│  ├─libs
│  │      HelloJavaFx-0.1.0.jar
│  │
│  ├─scripts
│  │      HelloJavaFx
│  │      HelloJavaFx.bat
│  │
│  └─tmp
│      ├─compileJava
│      └─jar
│              MANIFEST.MF

クラスファイルだけでなく、tar、zipファイル、jarファイル、それからシェルスクリプト/バッチファイルなども生成されています。

  • distributions のtar/zipファイルには、アプリケーションのjarファイルと実行用スクリプト/バッチファイルが含まれています。
  • libsのアプリケーションjarファイルは、実行可能JARファイルでかつ実行可能モジュールJARファイルとなっています。
コマンドプロンプトからjavaコマンドで実行

Liberica JDK 14にパスを通したコマンドプロンプトからアプリケーションを実行します。

D:\work\HelloJavaFx> java -p build\libs -m com.torutk.hello
  • モジュールJARファイルのあるディレクトリ(build\libs)を-pオプションで指定
  • 実行可能モジュール名(com.torutk.hello)を-mオプションで指定
配布イメージファイルを展開し中のバッチファイルから実行

build\distributions\HelloJavaFx-0.1.0.zip を適当な場所に展開します。

Liberica JDK 14にパスを通したコマンドプロンプトから、上述の展開した中にあるbin\HelloJavaFx.batを実行します。


  1. ここで指定したディレクトリの下に、gradle、srcなどのディレクトリおよびbuild.gradle、gladlewなどのファイル群が生成されます

さくらVPSのOSをCentOS 8に更新、さらにRedmineを4.1に更新(続々々)

さくらVPSのOSをCentOS 8に、Redmineを4.1に更新、の続き(4日目)

先日の続きです。

torutk.hatenablog.jp

Redmineログのローテーション

SELinux下でlogrotateを使ってRedmineのログ・ローテーションを実現するには設定がそれなりに手間なので、Railsの機能を使ってログローテーションを設定します。

Redmineインストールディレクトリのconfig/additional_environment.rb にログ設定を記述

config.logger = Logger.new('log/production.log', 30, 10 * 1024 * 1024)
  • 一つ目の引数は、ログファイルのパスを指定します。Railsアプリケーションのルートディレクトリからの相対パスで指定可。
  • 二つ目の引数は、ローテートするときに残す世代数(ファイル数)です。
  • 三つ目の引数は、ログファイルの容量がこの閾値に達したらローテーションを実施する値です。

gitサーバー

このマシンに共有gitリポジトリを設けて、Redmineから参照し、またHTTP経由でリポジトリのクローンとプッシュをできるようにします。

HTTPからのアクセスにおいて、HTTP用にアカウントを作成し運用管理するのは負担が大きいですから、RedmineのアカウントでHTTPの認証を行うようにします。Redmineには現時点ではApache HTTPD用の認証モジュールが含まれていますがNginxには対応していません。

https://www.redmine.org/issues/7061

そこで、Git(Subversionリポジトリアクセス用にApache HTTPDをインストールします。

gitリポジトリの格納ディレクトリとSELinux許可

gitリポジトリに対しては、読み込み、書き込み、およびスクリプトの実行が必要です。HTTP経由でgitリポジトリにアクセスするには、HTTPのプロセスに与えられるドメインhttpd_tから、gitリポジトリのリソースに対する読み込み(map操作含む)、書き込み、スクリプト実行の操作の許可があるリソースタイプをgitリポジトリに割当てます。

CentOS 8のデフォルトSELinux設定では、gitリポジトリを置くと想定しているディレクトリに対して次のリソースタイプが割当てられています。また、ディレクトリ内のファイルに対して許可される操作も併記します。調査コマンドについては後述します。

基点ディレクト リソースのタイプ ドメインhttpd_tからファイルへの許可された操作
/var/lib/git git_sys_content_t getattr ioctl lock map open read
/var/www/git git_content_t getattr ioctl lock map open read

これらのデフォルトSELinux設定では、書き込み操作およびスクリプトの実行操作が許可されていません。 そこで、書き込み操作と、必要なディレクトリに限定して実行操作を許可するリソースタイプを設定します。

  1. ドメインhttpd_tから/var/lib/git 以下のリソースに対して読み書き許可
  2. ドメインhttpd_tから/var/lib/git/<リポジトリ名>/hooks 以下のリソースに対して実行許可

ドメインhttpd_tから上述の許可を持つリソースタイプを調査したところ、次のタイプが適しています。

  1. git_rw_content_t
  2. git_script_exec_t

SELinuxのポリシー設定を変更します。

/var/lib/git/下のリポジトリディレクトリは、読み書きが可能なgit_rw_content_tを割当てます。デフォルトでルール定義があるので-mオプションで変更するコマンドを実行します。

# semanage fcontext -m -t git_rw_content_t '/var/lib/git(/.*)?'

リポジトリディレクトリ下のhooks/ディレクトリは、この中にあるスクリプトを実行できるようSELinuxのタイプをgit_script_exec_tに割当てます。

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

/var/lib/gitディレクトリを作成し、念のためSELinuxのリソースを再割り当てしておきます。

# mkdir /var/lib/git
# restorecon -R /var/lib/git
リソースに割り当てられるタイプの調査
# semanage fcontext -l | grpe git
  :
/var/lib/git(/.*)?     all files     system_u:object_r:git_sys_content_t:s0
/var/www/git(/.*)?  all files     system_u:object_r:git_content_t:s0
  :
ドメインhttpd_tからリソースへのアクセス可能な操作の調査

ドメインhttpd_tからタイプgit_sys_content_tへの許可を調べるコマンドの実行例を以下に示します。

$ sesearch -A -s httpd_t -t git_sys_content_t
allow httpd_t file_type:dir { getattr open search };
allow httpd_t file_type:filesystem getattr;
allow httpd_t git_sys_content_t:dir { getattr ioctl lock open read search };
allow httpd_t git_sys_content_t:file { getattr ioctl lock map open read };
allow httpd_t git_sys_content_t:lnk_file { getattr read };

ドメインhttpd_tからタイプgit_content_tへの許可を調べるコマンドの実行例を以下に示します。

$ sesearch -A -s httpd_t -t git_content_t
allow httpd_t httpd_content_type:dir { getattr ioctl lock open read search }; [ httpd_builtin_scripting ]:True
allow httpd_t httpd_content_type:dir { getattr open search };
allow httpd_t httpd_content_type:dir { getattr open search }; [ httpd_builtin_scripting ]:True
allow httpd_t httpd_content_type:dir { getattr open search }; [ httpd_builtin_scripting ]:True
allow httpd_t httpd_content_type:dir { getattr open search }; [ httpd_builtin_scripting ]:True
allow httpd_t httpd_content_type:file { getattr ioctl lock map open read };
allow httpd_t httpd_content_type:file { getattr ioctl lock open read }; [ httpd_builtin_scripting ]:True
allow httpd_t httpd_content_type:lnk_file { getattr read }; [ httpd_builtin_scripting ]:True

アトリビュートに束ねられているリソースタイプの確認方法は次です。

$ seinfo --attribute=httpd_content_type -x

Type Attributes: 1
   attribute httpd_content_type;
        apcupsd_cgi_content_t
        apcupsd_cgi_htaccess_t
           :
        git_content_t
        git_htaccess_t
        git_ra_content_t
        git_rw_content_t
        git_script_exec_t
        httpd_sys_content_t
        httpd_sys_htaccess_t
        httpd_sys_ra_content_t
        httpd_sys_rw_content_t
        httpd_sys_script_exec_t
        httpd_user_htaccess_t
        httpd_user_ra_content_t
           :
ドメインhttpd_tから、指定の操作が可能なリソースタイプを調査
$ sesearch -A -s httpd_t -c file -p write
  :
allow httpd_t git_rw_content_t:file { append create getattr ioctl link lock open read rename setattr unlink write }; [ httpd_builtin_scripting ]:True
バックアップしていたgitリポジトリの復元

/var/lib/git ディレクトリの下に、バックアップしていたgitリポジトリを展開します。 (今回はtarで固めたバックアップファイルを単に展開)

# cd /var/lib/git
# tar xzf ~/git_swe_primus-20200423.git.tgz
# ls
swe.primus.git
# restorecon -R .

展開後、SELinuxのタイプが正しく付いていないこともあるので、restoreconで反映します。 また、次の手順でApache httpdをインストールした後で、ファイルのパーミッションapacheに変更します。

Apache httpd のセットアップ

Redmineと連携するリポジトリアクセス用Webサーバーには、Apache HTTPDを使います。 既にWebサーバーにはNginxを稼働させているので、ポート番号をずらしてApache HTTPDを稼働します。

Apache HTTPDのインストール

Apache httpd は、モジュールとして提供されています。

#  dnf module list httpd
CentOS-8 - AppStream
Name        Stream       Profiles                        Summary
httpd       2.4 [d]      common [d], devel, minimal      Apache HTTP Server

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

現時点ではバージョンは1つだけ存在するので、そのままモジュールをインストールします。

# dnf module install httpd
  :
Apache HTTPDの動作方式

Apache HTTPDが同時に複数のリクエストを受けて動作する際の並行処理の動作方式には複数の種類があります。Multi Processing Module: MPMと呼ばれる動作方式には、prefork、worker、eventの3種類があり、今回インストールしたCentOS 8のモジュールではデフォルトがeventになっています。 preforkは古のApache httpdからあるシングルスレッドプロセスを複数稼働させ、1リクエストを1プロセスで受ける方式、workerとeventはマルチスレッドプロセスを複数稼働させ、複数リクエストを1プロセスで受ける方式です。

  • /etc/httpd/conf.modules.d/00-mpm.conf

この設定ファイルで方式に応じたsoをLoadModuleします。

さて、Redmine認証連携のためにmod_perlを使用しますが、このmod_perlは古いので event方式で支障なく動くものなのか確証がありません。そこで今回はMPM方式をpreforkに変更します。

- #LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
+ LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
- LoadModule mpm_event_module modules/mod_mpm_event.so
+ #LoadModule mpm_event_module modules/mod_mpm_event.so 

また、子プロセス数等のpreforkの設定を記述するファイルを用意します。gitリポジトリへのアクセスに使うだけのHTTPDで、仮想マシンでCPU数もほとんどないので、起動プロセス数は最小限にします。

  • /etc/httpd/conf.d/mpm.conf
<IfModule mpm_prefork_module>
  # 起動時に生成する子プロセス数
  StartServers          2
  # アイドルな子プロセスの最小個数
  MinSpareServers       2
  # アイドルな子プロセスの最大個数
  MaxSpareServers       2
  # 子プロセス数の設定可能な上限
  ServerLimit           2
  # 最大同時リクエスト数
  MaxRequestWorkers     2
  # 子プロセスが稼働中に扱うリクエスト数の上限
  MaxConnectionsPerChild 4
</IfModule>
Apache HTTPDのデフォルトポート変更

既にNginxがポート80を使用しているので、Apache httpdはポートをデフォルトの80から8008等に変更します。SELinuxではhttpdプロセス(ドメインhttpd_t)がbindできるポートも制限がかけられています。

SELinuxの設定を変更することなく利用可能なポート番号の調べ方は次です。

http_port_t が対象とするポート番号を確認します。

# semanage port -l 
  :
http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000
  :

ドメインhttpd_t は、http_port_t に対して name_bind 操作が許されています。

$ sesearch -A -s httpd_t
  :
allow httpd_t http_port_t:tcp_socket name_bind;
  :
  • /etc/httpd/conf/httpd.conf を修正します。
-Listen 80
+Listen 8008

-ServerName
+ServerName www.torutk.com:8008
Apache httpdのデフォルト設定変更(不要な設定の削除)

/etc/httpd/conf.d/ にある設定ファイルのうち使用しないものを削除(またはリネーム)します。

# cd /etc/httpd/conf.d
# mv welcome.conf welcome.conf.orig
# mv autoindex.conf autoindex.conf.orig
# mv ssl.conf ssl.conf.orig
# mv userdir.conf userdir.conf.orig

SSLモジュールを読み込まないよう設定を変更します。

- LoadModule ssl_module modules/mod_ssl.so
+ #LoadModule ssl_module modules/mod_ssl.so
Apache httpd経由のGitリポジトリ動作確認

まずはRedmine認証連携の前に、Apache httpdの起動とGitリポジトリ動作確認をします。

  • /etc/httpd/conf.d/git-redmine.conf を新規作成します。
SetEnv GIT_PROJECT_ROOT /var/lib/git
SetEnv GIT_HTTP_EXPORT_ALL
SetEnv REMOTE_USER $REDIRECT_REMOTE_USER

ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/

<LocationMatch "^/git/">
  Require all granted
</LocationMatch>

認証はしないので、Require には"all granted"を記述します。

ポート8008を一時的に開きます。

# firewall-cmd --add-port=8008/tcp

httpd自動起動設定かつ起動します。

# systemctl start --now httpd

リポジトリパーミッションを変更します。

# cd /var/lib/git
# chown -R apache:apache *

リモートからhttp経由でクローンと変更のプッシュを行い、動作確認をします。 まずクローンを実行します。

D:\work> git clone http://www.torutk.com:8008/git/swe.primus.git
Cloning into 'swe.primus'...
remote: Enumerating objects: 739, done.
remote: Counting objects: 100% (739/739), done.
remote: Compressing objects: 100% (279/279), done.
remote: Total 739 (delta 232), reused 739 (delta 232)Receiving objects: 100% (739/739), 20.23 MiB | 20.23Receiving objects: 100% (739/739), 20.67 MiB | 20.06 MiB/s, done.

Resolving deltas: 100% (232/232), done.

変更をローカルでコミット後、プッシュします。

D:\work\swe.primus>git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 6 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 273 bytes | 136.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To http://www.torutk.com:8008/git/swe.primus.git
   40ba3f1..6bd5f2d  master -> master
mod_perlのインストールとRedmine認証設定

Redmineの認証を利用して、Git(Apache HTTPD)の認証を行います。 Redmineに同梱されている Redmine.pm (Perlモジュール)を、httpdmod_perlの検索パスに置きます。

# mkdir -p /etc/httpd/Apache/Authn
# sudo ln -s /var/lib/redmine/extra/svn/Redmine.pm /etc/httpd/Apache/Authn/Redmine.pm

このRedmine.pmをApache HTTPDから利用するために、HTTPDmod_perl モジュールを追加する必要があります。mod_perlは少々古い仕組みで、CentOS 7以降はOS標準には含まれなくなっているので、EPELリポジトリから取得します。

EPELリポジトリ利用設定をインストールします。

# dnf install epel-release

EPELリポジトリからパッケージをインストールするのは限定的とするため、デフォルトではEPELリポジトリを無効とする設定に変更します。

  • /etc/yum.repos.d/epel.repo
  [epel]
  name=Extra Packages for Enterprise Linux $releasever - $basearch
  #baseurl=https://download.fedoraproject.org/pub/epel/$releasever/Everything/$basearch
  metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-$releasever&arch=$basearch&infra=$infra&content=$contentdir
- enabled=1
+ enabled=0
  gpgcheck=1
  gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8

mod_perlをインストールします。

# dnf --enablerepo=epel install mod_perl
  :

perl-Digest-SHAをインストールします。

# dnf install perl-Digset-SHA
  :

/etc/httpd/conf.d/git-redmine.conf に追記

+ PerlLoadModule Apache::Authn::Redmine

  SetEnv GIT_PROJECT_ROOT /var/lib/git
  SetEnv GIT_HTTP_EXPORT_ALL
  SetEnv REMOTE_USER $REDIRECT_REMOTE_USER

  ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/

  <LocationMatch "^/git/">
+   PerlAccessHandler Apache::Authn::Redmine::access_handler
+   PerlAuthenHandler Apache::Authn::Redmine::authen_handler
+   AuthType Basic
+   AuthName "Git Redmine"
+   AuthUserFile /dev/null

+   RedmineDSN "DBI:mysql:database=redmine;host=localhost"
+   RedmineDbUser "redmine"
+   RedmineDbPass "XXXXXX"
+   RedmineGitSmartHttp yes

-  Require all granted
+   Require valid-user
  </LocationMatch>

以前のApache httpd + Gitでは、AuthUserFile /dev/nullは不要だったかと思いますが、現時点ではこの指定が必要です([authn_file:error] AH01619: AuthUserFile not specified in the configurationが出る)。

この設定ファイルにはRedmineMySQLデータベースへの接続情報が記載されるので、アクセス権を厳し目に設定します。

# chmod 0400 /etc/httpd/conf.d/git-redmine.conf

Apache httpdをリロードして動作確認します。

# systemctl reload httpd
Nginxのhttps経由でのアクセス

ここまでの設定でGitリポジトリへの読み書きのアクセスができるようになりました。 しかし、httpプロトコルでポート8008にアクセスしており、認証はBasicのため平文で流れてしまいます。

せっかくサーバーにSSLサーバー証明書を設置し、セキュアなアクセスができるようになっているので、SSLでGitリポジトリにアクセスしたいところです。

既にNginxがhttps(ポート443)を押さえているので、Apache HTTPDSSL設定をすることができません。そこで、Ngixを経由してApacheにアクセスすることでSSLを利用します。

  • /etc/nginx/conf.d/redmine.conf に追記
     location ^~ /.well-known/acme-challenge/ {
         root /usr/share/nginx/html;
     }

+    location ^~ /git/ {
+        proxy_pass http://localhost:8008;
+   }

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

この設定で、Nginxがポート80もしくは443でgitへのアクセスを受けると、localhostの8008に転送します。 しかし、これだけではSELinuxでエラーになってしまいます。ドメインhttpd_tが別なプロセスにTCP接続するには許可が必要になります。

SELinuxのブール値設定で、httpd_can_network_relay を on にします。

# getsebool -a | grep httpd_can_network
httpd_can_network_connect --> off
httpd_can_network_relay --> off
# setsebool -P httpd_can_network_relay on
# getsebool httpd_can_network_relay
httpd_can_network_relay --> on

さくらVPSのOSをCentOS 8に更新、さらにRedmineを4.1に更新(続々)

さくらVPSのOSをCentOS 8に、Redmineを4.1に更新、の続きの続き

昨日の作業の続きです。 torutk.hatenablog.jp

Let's Encrypt の証明書更新

証明書更新の処理確認(エラー対処)

証明書を更新するスクリプトの実行確認(dry-run)をします。

# certbot-auto renew --dry-run
  :
Challenge failed for domain www.torutk.com
http-01 challenge for www.torutk.com
Cleaning up challenges
Attempting to renew cert (www.torutk.com) from /etc/letsencrypt/renewal/www.torutk.com.conf produced an unexpected error: Some challenges have failed.. Skipping.
All renewal attempts failed. The following certs could not be renewed:
  /etc/letsencrypt/live/www.torutk.com/fullchain.pem (failure)
  :
IMPORTANT NOTES:
 - The following errors were reported by the server:

   Domain: www.torutk.com
   Type:   unauthorized
   Detail: Invalid response from
   https://www.torutk.com/.well-known/acme-challenge/***************************************
"<!DOCTYPE
   html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n
   <title>Redmine 404 error</title>\n  <style>\n    body {font-family:
   \"Tr"

   To fix these errors, please make sure that your domain name was
   entered correctly and the DNS A/AAAA record(s) for that domain
   contain(s) the right IP address.

Redmine環境からの移行にあたり、Redmineへのアクセスを完全https対応とし、httpでのリクエストはhttpsへリダイレクトするように設定しました。 このため、Let's Encryptの証明書更新時にドメイン確認のため http でのアクセスがredmineに転送されてしまい、上述のように404エラーとなっています。

対策は、Let's EncryptからのアクセスをRedmineへ転送せず、nginxのデフォルトのdoc rootにアクセスさせるようNginxに設定を追加します。

/etc/nginx/conf.d/redmine.conf

server {
  listen 443 ...
    :
    client_max_body_size 1G;

+    location ^~ /.well-known/acme-challenge/ {
+       root /usr/share/nginx/html;
+    }

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

最初はポート80のserver設定にlocation記述を追加しましたが、return でhttpsへリダイレクトしてしまうのは避けられず証明書更新がエラーに。そこで、ポート443のserver設定に記述することにしました。

location の記述で、^~ を追加すると、この条件に前方一致したときは後続の条件を見ずに適用するとあるので、これを指定しました。

証明書更新の定期実行(cron)

証明書更新を定期的に実行させるため、cronに登録します。

Let's Encryptの証明書の有効期限は90日で、期限30日前から次の更新を受け付けるとあるので、定期処理の間隔は最大1か月以内とします。certbot-autoは、更新対象となる期限前に実行しても証明書更新をスキップするので、毎日実行しても問題はありません。公式サイトにもcron設定の例で1日2回実行する設定が記載されています。

ただ、何か気になるので、週に1回実行することとします。

crontabに記述してもよいのですが、CentOS 8にはanacronという仕組みがあり(もっと前のバージョンからありますが)、毎時、毎日、毎週、毎月の定期処理を行うスクリプトを所定の場所に放り込んでおくとそれを実行してくれます。

今回は、毎週の処理を行う/etc/cron.weeklyディレクトリに、証明書更新のスクリプトを入れておくことにします。

/etc/cron.weekly/letsencrypt_renew.sh

#!/bin/sh
/usr/local/bin/certbot-auto renew -q
/usr/bin/systemctl reload nginx

ファイルには実行権を付けておきます。

# chmod +x /etc/cron.weekly/letsencrypt_renew.sh

テーマの設定

Redmineでは、テーマにGitmikeを使っていました。しかし、Redmine 4.1環境で sidebar_hide プラグインを使うと、サイドバーを開いたときにコンテンツ領域が本来より余分に小さくなってしまいました。(ぱっと見た目でサイドバーの幅の2倍だけ縮んで表示)

そこで、別なテーマを探してみました。

Redmine 4対応を謳っているいるテーマ
  • A1:ダウンロードにユーザー登録必要
  • OpenMind:青系、フラットでシンプル
  • PurpleMine2:紫、カラフル、サイドバーが左側かつ折り畳み可
  • RTMaterial:緑系、割とシンプル
  • MinimalFlat2:紺系、文字大き目、シンプル
  • Bleuclair:青系

Sidebar_hideプラグインとの相性をみつつ、今回はBleuclairを入れることとしました。

Bleuclairテーマ

blog.redmine.jp

Redmineのバージョン毎にブランチが用意されているので、ブランチを指定してクローンします。

~$ cd /var/lib/redmine/public/themes
themes$ git clone -b redmine4.1 https://github.com/farend/redmine_theme_farend_bleuclair.git bleuclair
  :

システム管理者でログインし、[管理] > [設定] > [表示] で、テーマ欄のドロップダウンリストから[Bleuclair]を選択します。

さくらVPSのOSをCentOS 8に更新、さらにRedmineを4.1に更新(続)

さくらVPSのOSをCentOS 8に、Redmineを4.1に更新、の続き

昨日の作業の続きです。 torutk.hatenablog.jp

Unicornのセットアップ

Unicorn gemのインストール

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

gem "unicorn"

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

$ cd /var/lib/redmine-4.1-stable
$ bundle update
  :
Installing kgio 2.11.3 with native extensions
  :
Installing raindrops 0.19.1 with native extensions
  :
Installing unicorn 5.5.4 with native extensions
undle updated!
Gems in the groups development, test and rmagick were not installed.

Unicorn手動起動確認

一時的にファイアウォールのポート3000を開けて、コマンドラインからUnicornを実行します。

# firewall-cmd --add-port=3000/tcp
$ bundle exec unicorn_rails -l 3000 -E production
I, [2020-04-25T09:52:12.230444 #13548]  INFO -- : listening on addr=0.0.0.0:3000 fd=7
I, [2020-04-25T09:52:12.230701 #13548]  INFO -- : worker=0 spawning...
I, [2020-04-25T09:52:12.232185 #13548]  INFO -- : master process ready
I, [2020-04-25T09:52:12.232568 #13549]  INFO -- : worker=0 spawned pid=13549
I, [2020-04-25T09:52:12.232769 #13549]  INFO -- : Refreshing Gem list
I, [2020-04-25T09:52:23.309370 #13549]  INFO -- : worker=0 ready

Webブラウザからポート3000にアクセスしたところ、Redmine画面が表示されました。

気付き事項

ブラウザからのアクセス後、しばらくしてからコマンドラインを見ると、次のエラーメッセージが表示されていました。

E, [2020-04-25T09:53:43.324350 #13548] ERROR -- : worker=0 PID:13549 timeout (61s > 60s), killing
E, [2020-04-25T09:53:43.337562 #13548] ERROR -- : reaped #<Process::Status: pid 13549 SIGKILL (signal 9)> worker=0
I, [2020-04-25T09:53:43.337859 #13548]  INFO -- : worker=0 spawning...
I, [2020-04-25T09:53:43.339628 #13553]  INFO -- : worker=0 spawned pid=13553
I, [2020-04-25T09:53:43.339902 #13553]  INFO -- : Refreshing Gem list
I, [2020-04-25T09:53:49.106922 #13553]  INFO -- : worker=0 ready

タイムアウト60秒を超過したため、ワーカープロセスを再起動しているようです。 これは継続して発生してはおらず、WebブラウザからUnicornにアクセスをした後に発生しています。

Unicornの設定ファイル記述前の手動起動なので問題ないかもしれませんが、一応メモとして残しておきます。

Unicorn設定ファイル記述

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

# -*- coding: utf-8 -*-
# Unicorn設定ファイル

# ワーカープロセスの数。1ワーカーで1つのリクエストを処理する。
# ワーカー数が上限に達すると、先行するリクエストが完了するまで待ちとなる。
worker_processes 2

# リクエスト待ち受け口、TCPとUNIXドメインとが指定可能。
# 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を実行するときにカレントディレクトリが Redmineインストールディレクトリ(/var/lib/redmine-4.1-stable)にあることを前提としています。

worker_processes

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

listen

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

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

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

TCPソケットの場合の例

listen 8282, :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のタスクを早めに起こさずにすませます。

同一マシン上でNginxとUnicornとを連携してサービスを稼働する場合は、TCPソケットは使用しないので、Unicorn単独での動作確認を終えたら削除(またはコメントアウト)しておきます。

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の起動設定(Systemdのサービス定義)

Unicornを、Systemdのサービスとして定義し、マシン起動時に自動で実行されるようにします。また、systemctlコマンドで起動、終了、再読み込みといった制御をできるようにします。

サービス定義のファイルは、/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

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

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

SELinuxの設定上、WorkingDirectoryとPIDFileに設定するパスはシンボリックリンクファイルを含まない実パスで設定します。 WorkingDirectoryを/var/lib下のシンボリックリンクファイルとすると、Systemdから生成したプロセス(タイプinit_t)がCHDIRする際に/var/lib下のリンクファイル(タイプvar_lib_t)への操作許可がないためエラーとなります。通常のファイルへの操作許可はあるので実パスを記述して回避します。

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

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

サービスでのUnicorn起動確認

Systemdのサービスファイルを変更したら、Systemdに設定を再読み込みさせます。

# systemctl daemon-reload

Unicorn単独動作確認のためにポート8282を一時開きます。

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

Unicornサービスを起動します。サービス名は、サービス定義ファイル名から拡張子.serviceを除いた名称です。

# 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 Sat 2020-04-25 11:15:15 JST; 1min 35s ago
  Process: 15536 ExecStartPre=/usr/bin/install -m 755 -o redmine -g redmine -d /var/run/unicorn (code=exited, status=0/SUCCESS)
 Main PID: 15538 (ruby)
    Tasks: 7 (limit: 12523)
   Memory: 119.3M
   CGroup: /system.slice/redmine-unicorn.service
           tq15538 unicorn_rails master -c config/unicorn.rb -E production
           tq15546 unicorn_rails worker[0] -c config/unicorn.rb -E production
           mq15548 unicorn_rails worker[1] -c config/unicorn.rb -E production

 4月 25 11:15:15 www.torutk.com systemd[1]: Starting Redmine Unicorn Server...
 4月 25 11:15:15 www.torutk.com systemd[1]: Started Redmine Unicorn Server.

Webブラウザでポート82828にアクセスします。Redmine画面が表示されれば動作確認OKです。

Nginxのセットアップ

CentOS 8のリポジトリ(AppStream)からNginxのモジュールが提供されています。現時点では、バージョン1.14と1.16の2つが提供されています。

# 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

Nginxは偶数番号のバージョンが安定版で、奇数番号のバージョンが開発版となっています。安定版は1年に1回、毎年4月にリリースされています。1.14は2018年4月、1.16は2019年4月リリースです。

CentOS 8のAppStreamでは、デフォルトは1.14ですが、ここでは1.16をインストールします。

# dnf module install nginx:1.16/common
  :

Nginxのサービスを起動、および自動起動設定します。

# systemctl enable --now nginx
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.

サービスが起動したかどうか確認します。

# 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 Sat 2020-04-25 11:31:58 JST; 1min 5s ago
  Process: 17664 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
  Process: 17662 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
  Process: 17660 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
 Main PID: 17665 (nginx)
    Tasks: 4 (limit: 12523)
   Memory: 14.5M
   CGroup: /system.slice/nginx.service
           tq17665 nginx: master process /usr/sbin/nginx
           tq17666 nginx: worker process
           tq17667 nginx: worker process
           mq17668 nginx: worker process

 4月 25 11:31:58 www.torutk.com systemd[1]: Starting The nginx HTTP and reverse proxy server...
 4月 25 11:31:58 www.torutk.com nginx[17662]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
 4月 25 11:31:58 www.torutk.com nginx[17662]: nginx: configuration file /etc/nginx/nginx.conf test is successful
 4月 25 11:31:58 www.torutk.com systemd[1]: Started The nginx HTTP and reverse proxy server.

NginxへのHTTPおよびHTTPSアクセスを行うため、ファイアウォールにポートを開きます。これは今後継続して有効とするので永続化の指定を追加します。

# firewall-cmd --add-service={http,https} --permanent
success
# firewall-cmd --reload
success
# firewall-cmd --list-services
dhcpv6-client http https

Webブラウザから、httpおよびhttpsでアクセスしてみます。Nginxのデフォルト画面が表示されたら確認OKです。

Let's Encrypt の設定

更新前のサーバでは、SSL証明書の取得にLet's Encryptのサービスを利用していました。ドメイン名(ホスト名)は変更がないため、そのまま引き継いで利用します。

更新前のサーバにある/etc/letsencryptディレクトリ以下を丸ごとバックアップして新しいサーバの/etc/letsencryptに展開します。

# cd /etc
# tar xzfp ~/etc_letsencrypt.tgz
  :

Certbot(Let's EncryptのACMEクライアントツール)を取得します。

$ curl -O https://dl.eff.org/certbot-auto
$ sudo mv certbot-auto /usr/local/bin/
$ sudo chown root:root /usr/local/bin/certbot-auto
$ sudo chmod 0755 /usr/local/bin/certbot-auto

証明書の更新ができるかをテストします。このコマンドを実行する際は、証明書の対象サーバーのFQDN名で httpアクセスが成功する必要があります。

# certbot-auto renew --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/www.torutk.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for www.torutk.com
Using the webroot path /usr/share/nginx/html for all unmatched domains.
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/www.torutk.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/www.torutk.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: systemctl reload nginx

Nginxの設定ファイルを変更

デフォルトの/etc/nginx/nginx.conf には、以下のポート番号80のサーバー設定が含まれているので、まずこれを削除します。

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
          :
    }

それから、/etc/nginx/conf.d/redmine.conf を新規作成し、ここにポート番号80(HTTP)のサーバー設定とポート番号443(HTTPS)のサーバー設定を記述します。

/etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;
}
/etc/nginx/conf.d/redmine.conf

Unicornへのリバースプロキシ設定を記述します。また、ポート80へのアクセスはすべてポート443にリダイレクトします。

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

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name pan;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name www.torutk.com;
    root /var/lib/redmine/public;

    ssl_certificate "/etc/letsencrypt/live/www.torutk.com/fullchain.pem";
    ssl_certificate_key "/etc/letsencrypt/live/www.torutk.com/private/privkey.pem";
    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 /maintenance.html $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 {
    }
}
server_name

SSLサーバー証明書のCN(Common Name)と同じサーバー名を記述します。

ssl_ciphers

SSLで使用する暗号スイートを指定。PROFILE=SYSTEM を指定すると、システム全体のポリシーを適用します。 システム全体のポリシーについては次のドキュメントを参照。

第3章 システム全体の暗号化ポリシーの使用 Red Hat Enterprise Linux 8 | Red Hat Customer Portal

try_files

try_filesの先頭に/maintenance.html を記載しています。これは、ドキュメントルート(/var/lib/redmine/public)にmaintenance.htmlが存在していたら、それを表示します。このファイルを置いている間は基本全てのアクセスに対してmaintenance.htmlの内容が返却されます。 ファイルを削除(リネーム)すると、次のアクセスからは通常の振る舞いをします。

Unicornとの連携

Unixドメインソケットのアクセス

NginxからUnicornが使用するUNIXドメインソケット(/var/run/unicorn/unicorn.sock)をSELinux下でアクセスできるように設定します。 /var/run下のファイルは基本的には var_run_t タイプが割り当てられています。nginxプロセスの httpd_t タイプからvar_run_tタイプのunix_domain_socketにアクセス許可がありません。そこで、/var/run/unicorn以下のファイルについて、var_run_tタイプに替えてhttpd_var_run_tタイプを設定します。

SELinuxでファイルにタイプを設定するには semanage コマンドを使います。コマンドが無ければ次のパッケージをインストールします。

# dnf install policycoreutils-python-utils

/var/run/unicorn 以下のファイルにhttpd_var_run_tタイプを設定します。

# semanage fcontext -a -t httpd_var_run_t '/var/run/unicorn(/.*)?'

# restorecon -R -v /var/run/unicorn
問題!再起動時はSELinuxの設定が失われる

/var/runディレクトリはtmpfsで再起動すると中身が消えてしまいます。/var/run/unicornディレクトリも消えてしまいます。そして、新たにディレクトリを作成すると、そのSELinuxのタイプは親ディレクトリのタイプを引き継ぎます。

つまり、再起動後、/var/run/unicornディレクトリを再度作成したときのSELinuxタイプは、/var/runディレクトリのタイプであるvar_run_tとなってしまいます。

回避策として、unicornサービス起動定義ファイルにrestoreconコマンドを追加します。

  ExecStartPre=/usr/bin/install -m 755 -o redmine -g redmine -d /var/run/unicorn
+ ExecStartPre=/usr/sbin/restorecon -R /var/run/unicorn
  ExecStart=/usr/bin/bundle exec "unicorn_rails -c config/unicorn.rb -E production"
シンボリックリンク作成

Nginxの設定に、/var/lib/redmine/public を記述しています。シンボリックリンクを作成します。

# ln -s /var/lib/redmine-4.1-stable /var/lib/redmine

添付ファイルの復元

昨日のデータ移行で、添付ファイルの復元が作業漏れでした。RedmineRuby on Rails)は添付ファイルを特定のディレクトリに保存しています。バックアップしていたファイルを復元します。

rsyncを使ったバックアップからの復元

バックアップを保存しているマシンから、リモートの新Redmineサーバへファイルを復元します。

rsyncコマンドは、バックアップ保存マシン側だけでなく、新Redmineサーバー側にも必要となります。

Redmineサーバーで実施

# dnf install rsync

バックアップ保存マシンで実施

$ cd backup
$ rsync -av -e "ssh -p 58132" redmine_files/ sau@www.torutk.com:/var/lib/redmine/files/
  :

Redmineプラグインのインストール

Redmineサーバーで利用していたプラグインと、新Redmineサーバーでのプラグイン利用の方針を次に示します。

No. プラグイン 旧稼働Ver. DB利用 最新Ver. Redmine 4.1対応 移行方針
1 clipboard_image_paste 1.13 1.13 放棄しRedmine 4.1標準機能を使用
2 redmine_banner 0.1.2 0.3.1 最新版をインストールしマイグレーション実施
3 redmine_github_hook 2.2.1 3.0.1 最新版をインストール
4 redmine_glossary 0.9.2 1.1.0 最新版をインストールしマイグレーション実施
5 redmine_issue_templates 0.2.2-dev 1.0.1 最新版をインストールしマイグレーション実施
6 redmine_latex_mathjax 0.3.0 --- フォーク版をインストール
7 redmine_startpage 0.1.0 0.0.3 放棄しRedmineのroutes.rbに直接設定
8 redmine_wiki_extensions 0.8.2 0.9.0 最新版をインストールしマイグレーション実施
9 redmine_wiki_lists 0.0.9 0.0.9 Redmine 4.0対応 インストールして動作すれば使用継続
10 redmine_xls_export 0.2.1.t11 0.2.1.t11 Redmine 4.0対応 インストールして動作すれば使用継続
11 sidebar_hide 0.0.8 0.0.8 インストールして動作すれば使用継続
12 view_customize 2.1.0 2.5.0 最新版をインストールマイグレーション実施
  • [1] issueでRedmine 4.0.4で動かない件あり。Redmine 4.1からクリップボードの画像をWiki編集領域でペーストすると添付ファイルと画像インライン記法が生成されます。
  • [4] Redmine 4.x向けの別実装 https://github.com/torutk/redmine_glossary
  • [6] Redmine 4.x向けのフォーク版 https://www.redmine.org/plugins/redmine_latex_mathjax
  • [11]sidebar_hideの代替を事前調査した際、PurpleMine2テーマ(サイドバーを隠す機能あり)、View Customizeでサイドバー開閉の制御をする、が候補になりました。PurpleMine2はややどぎつい色づかい(個人的な印象)、View Customizeはサイドバーがコンテンツの上に重畳されるためコンテンツ右側が隠れてしまう等あり、結果はsidebar_hideの動作を追究し、だめならあきらめることとしました。

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

Redmineサーバーで使用したプラグインのうち、データベース使用が有りのものを移行します。

マイグレートがエラー無く完了すれば移行完了です。

redmine_banner
$ cd /var/lib/redmine/plugins
$  git clone https://github.com/akiko-pusu/redmine_banner.git
  :
$ bundle exec rails redmine:plugins:migrate RAILS_ENV=produ
ction
$
redmine_glossary
$ git clone https://github.com/torutk/redmine_glossary.git
  :
$ bundle exec rails redmine:plugins:migrate RAILS_ENV=production
== 4 RenameGlossaryTermsFromTerms: migrating ==================================
  :(中略)
== 6 RenameGlossaryViewSettingsFromGlossaryStyles: migrated (0.0066s) =========
$
redmine_issue_templates
$ git clone https://github.com/akiko-pusu/redmine_issue_templates.git
  :
$ bundle exec rails redmine:plugins:migrate RAILS_ENV=production
== 20190303082102 CreateNoteTemplates: migrating ==============================
  :(中略)
== 20200314132500 ChangeColumnNoteTemplateDescription: migrated (0.1534s) =====
$
redmine_wiki_extensions
$ git clone https://github.com/haru/redmine_wiki_extensions.git
  :
$ bundle exec rails redmine:plugins:migrate RAILS_ENV=production
$
view_customize
$ git clone https://github.com/onozaty/redmine-view-customize.git view_customize
  :
$ bundle install
  :
Installing activerecord-compatible_legacy_migration 0.1.1
  :
$ bundle exec rails redmine:plugins:migrate RAILS_ENV=production
$

データベースを使用しないプラグインの移行

Redmineサーバーで使用したプラグインのうち、データベース使用が無のものを移行します。

clipboard_image_paste

Redmine 4.1で、クリップボードの画像をWiki編集領域にペーストすると、画像を添付ファイルとし、かつペースト箇所にその画像の添付ファイルのインライン展開記法を挿入します。この機能により、clipboard_image_pasteプラグインの基本機能が代替されます。

そこで、clipboard_image_pasteはRedmine 4.1ではインストールしないこととします。なお、データベースは使用していないので、単に新しいRedmineにインストールしないだけの対処となります。

Redmine 4.1標準機能は、ペースト時に任意矩形範囲をクロップする機能や添付ファイルの名前を変える機能はないようです。

redmine_github_hook

プラグインをインストールします。

$ git clone https://github.com/koppen/redmine_github_hook.git
  :

redmine実行ユーザーでGithubアクセス用のssh鍵認証ファイルを作成します。メールアドレスはGithubアカウントに登録したものを指定します。

~$ ssh-keygen -t rsa -C "torutk@example.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/redmine/.ssh/id_rsa):
Created directory '/home/redmine/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
  :
~$

生成された公開鍵(id_rsa.pub)をcatし表示された文字列をコピーし、GithubSSH鍵管理に登録します。 Githubページ を開き、右上隅のSigned inアイコンをクリックし、[Settings] > [SSH and GPG keys] > [New SSH key] で、Title欄に任意の名称(例:Redmine Github Hook on www.torutk.com)を記入、Key欄に、コピーした文字列を入れ、[Add SSH key]ボタンをクリックします。

登録したSSH鍵で疎通確認をします。

~$ ssh -T git@github.com
  :
Hi torutk! You've successfully authenticated, but GitHub does not provide shell access.

上述のように、"successfully authenticated"と表示されれば疎通確認は良好です。疎通確認のコマンドで指定するアカウントは"git@"です。

GithubリポジトリRedmineマシンにミラー

Githubリポジトリのミラーを保持するディレクトリの基点を作成します。

# mkdir /var/lib/git_mirror
# chown redmine:redmine /var/lib/git_mirror

Githubリポジトリのミラーを作成します。

$ cd /var/lib/git_mirror
$ git clone --mirror git://github.com/torutk/redmine_glossary.git
  :

Redmineでプロジェクトのリポジトリに登録します。

WebブラウザGithubの対象リポジトリを開き、[Settings] > [Webhooks] > [Add webhook] をクリックします。

Payload URL欄には、

https://www.torutk.com/github_hook?project_id=<プロジェクト識別子>&repository_id=<リポジトリ識別子>

と入力します。Githubリポジトリ名とプロジェクト識別子が同一であれば、project_idの指定は省略可能です。プロジェクトにリポジトリが単一であればrepository_idは省略可能です。

Content type欄、Secret欄は指定不要です。

Witch events would you like to trigger this webhook? 欄は、[Just the push event]を選択し、[Add webhook]をクリックします。

redmine_latex_mathjax
$ git clone https://github.com/5inf/redmine_latex_mathjax
  :
redmine_startpage

Redmineの保守管理上、プラグインの数を制限するため、本プラグインの使用はやめて、代わりに以下の修正をRedmineに入れます。

  • config/routes.rb
  Rails.application.routes.draw do
-   root :to => 'welcome#index', :as => 'home'
+   root :to => 'wiki#show', :project_id => 'swe', :as => 'home'
redmine_wiki_lists
$ git clone https://github.com/tkusukawa/redmine_wiki_lists.git
  :
redmine_xls_export
$ git clone https://github.com/two-pack/redmine_xls_export.git
  :
$ bundle install
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
  :
Fetching ruby-ole 1.2.12.2
Installing ruby-ole 1.2.12.2
Fetching rubyzip 2.3.0
Installing rubyzip 2.3.0
Fetching spreadsheet 1.2.6
Installing spreadsheet 1.2.6
Using unicorn 5.5.4
Fetching zip-zip 0.3
Installing zip-zip 0.3
Bundle complete! 38 Gemfile dependencies, 68 gems now installed.
Gems in the groups development, test and rmagick were not installed.
Bundled gems are installed into `../vendor/bundler`
$
sidebar_hide
$ git clone https://gitlab.com/bdemirkir/sidebar_hide.git
  :

sidebar_hideがRedmine 4.1で動作するか事前調査した結果では、一応動作するがテーマにより意図しない振る舞いをするという状況となりました。ちなみに相性が悪いテーマが旧Redmine環境でつかっていたGitmikeテーマです。

Unicornを再読み込みしプラグインを反映
# systemctl reload redmine-unicorn

本日の作業はここまで

残件が少しあります。

  • SubversionおよびGitリポジトリの復元
  • SubversionおよびGitのHTTPアクセス(Redmine連携認証あり)
  • テーマの設定
  • ロールと権限の設定
  • メール通知の設定
  • Let's Encryptの証明書更新の確認、crontへの登録
  • Redmineログのログローテーション

これらは明日以降対応とします。

さくらVPSのOSをCentOS 8に更新、さらにRedmineを4.1に更新

さくらVPSのOSをCentOS 8に、Redmineを4.1に更新

まとまった休みが取れたので、さくらVPSのサーバーのOSをCentOS 7から8に上げて、合わせてRedmineを3.4から4.1に上げる作業を実施しています。更新作業中はRedmineにコンテンツを挙げられないので、はてなに経過をメモすることにします。

CentOS 8への更新

さくらVPSが提供するOSインストール機能で、CentOS 8に上げます。さくらVPSサーバのバージョンはv3なので、標準OSのインストールではCentOS 8は選べません。カスタムOSのインストールでCentOS 8を入れます。

パーティションの設定

仮想ゲストでディスク(イメージファイル)を増やすこともないので、LVMは使用せず、/boot はext4、/はxfs、それとswapの3つのパーティションを作成しました。

マウントポイント 容量(MB) ファイルシステム
/boot 1024 ext4
swap 4096 swap
/ 残り xfs4

インストール時のトラブル

CentOS 8のインストーラーが起動し、Minimal Install でインストールを開始したところ、途中でエラー停止してしまいました。 もう1回インストールをやり直したところエラーにはならなかったので原因はよく分かりません。 なお、同じエラーメッセージが出たとのブログを見つけました。

https://www.softel.co.jp/blogs/tech/archives/6114www.softel.co.jp

このブログによると、Minimalインストールではchronyパッケージがないのでchrony関係設定を使用としてエラーになるとの分析です。

インストール後の設定

さくらVPSでインストールすると、その時点の最新パッケージがインストールされます。さくらVPSで独自のKickstartが提供されているようです。Minumalでインストールしたのですが、chronyやgccなどもインストールされていたので、さくらVPSの設定からの依存関係で追加インストールされた模様です。

気付いた点としては、chronyによる時刻同期設定済み、ネットワークはデバイス名がeth0、IPv6有効、固定IPアドレス(v4、v6)付与済み、ネームサーバ設定済み、カーネルのシリアルコンソールが有効、SELinuxは有効、などでした。

セキュリティ上、SSHのポート番号を変更、SSHでのrootログイン禁止、管理グループ以外のアカウントからのsu禁止、などを設定しました。

Redmine 4.1の稼働にむけて

MariaDBのインストールと設定

# dnf module install mariadb/server

MariaDBは、10.3がインストールされました。設定ファイルには文字コード(最近はutf8mb4を指定)、パフォーマンス設定を記述します。

/etc/my.cnf.d/mariadb-server.cnf
パラメータ名 デフォルト値 設定値
character_set_server latin1 utf8mb4
innodb_buffer_pool_size 128MB 256MB
innodb_log_file_size 48MB 64MB
innodb_flush_method fsync O_DIRECT

VPSはメモリ2GBです。RedmineRuby on Railsアプリケーション)はメモリ喰いなので1GBほど割いておきます。残り1GBをOS、標準サービスとMariaDBとで共用するので、MariaDBはせいぜい512MBメモリを使えればいいでしょう。 そこで、innodb_buffer_pool_sizeはその半分の256MBを割り当てます。innodb_log_file_sizeは最近のバージョンでデフォルト値が増えましたが、ここは過去の設定値の64MBを流用します。 innodb_flush_methodはO_DIRECTを指定することでデータファイルを開くときにOSのディスクキャッシュが使われないのでOS全体のメモリ使用が減ります。その分Redmineにメモリが割当てられます。

/etc/my.cnf.d/mysql-client.cnf
[mysql]
default_character_set = utf8mb4
show-warnings
サービスの登録と起動
# systemctl enable --now mariadb
セキュアな初期設定(mysql_secure_installation)

MariaDBのデフォルト設定で作られるrootアカウントにパスワードを設定、リモートからのrootログイン禁止、匿名アカウントの削除、testデータベースの削除を行います。手作業ではちょっと大変ですが、スクリプト mysql_secure_installationが用意されているのでこれを実行して設定します。

# mysql_sequre_installation
# mysql_secure_installation

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user.  If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.

Enter current password for root (enter for none):
OK, successfully used password, moving on...

Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.

Set root password? [Y/n]
New password:
Re-enter new password:
Password updated successfully!
Reloading privilege tables..
 ... Success!


By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.

Remove anonymous users? [Y/n]
 ... Success!

Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.

Disallow root login remotely? [Y/n]
 ... Success!

By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.

Remove test database and access to it? [Y/n]
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? [Y/n]
 ... Success!

Cleaning up...

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Thanks for using MariaDB!
Redmineから接続するアカウントの作成

MariaDB上にRedmineのデータベースと接続するアカウントを作成します。

MariaDB [(none)]> CREATE DATABASE redmine;
Query OK, 1 row affected (0.002 sec)
MariaDB [(none)]> GRANT ALL ON redmine.* TO 'redmine'@'localhost' IDENTIFIED BY 'tiger' WITH GRANT OPTION;
Query OK, 0 rows affected (0.001 sec)
  • IDENTIFIED BY の後ろにアカウントのパスワードを記述します(ここでは例示のため 'tiger'を指定)。
MariaDB開発パッケージのインストール
# dnf install mariadb-devel

Rubyのインストール

CentOS 8では、パッケージにモジュールという概念が導入されており(MariaDBもモジュール)、Rubyをモジュールでインストールします。 モジュールでは複数バージョンを制御してインストールすることができます。

# dnf module list ruby
CentOS-8 - AppStream                            4.2 kB/s | 4.3 kB     00:01
CentOS-8 - Base                                 4.9 kB/s | 3.9 kB     00:00
CentOS-8 - Extras                               2.6 kB/s | 1.5 kB     00:00
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

現在のCentOS 8では、Rubyは2.5と2.6がモジュールとして提供されています。デフォルトは2.5ですが、ここでは2.6をインストールします。 なお、Redmine 4.0.1でRuby 2.6に対応しています(https://www.redmine.org/issues/30118)。

# dnf module enable ruby:2.6
# dnf module install ruby/common

ruby-develはインストールされないので、個別にインストールします。また、rubyのbundlerもパッケージが用意されているので個別にインストールします。 パッケージの情報を見ると、次のように ruby-2.6に対応したパッケージとなっています。

# dnf info ruby-devel rubygem-bundler
名前         : ruby-devel
バージョン   : 2.6.3
リリース     : 106.module_el8.1.0+249+93480f15
Arch         : x86_64
サイズ       : 243 k
ソース       : ruby-2.6.3-106.module_el8.1.0+249+93480f15.src.rpm
リポジトリー : AppStream
概要         : A Ruby development environment
URL          : http://ruby-lang.org/
ライセンス   : (Ruby or BSD) and Public Domain and MIT and CC0 and zlib and UCD
説明         : Header files and libraries for building an extension library for
             : the Ruby or an application embedding Ruby.

名前         : rubygem-bundler
バージョン   : 1.17.2
リリース     : 106.module_el8.1.0+249+93480f15
Arch         : noarch
サイズ       : 354 k
ソース       : ruby-2.6.3-106.module_el8.1.0+249+93480f15.src.rpm
リポジトリー : AppStream
概要         : Library and utilities to manage a Ruby application's gem
             : dependencies
URL          : http://ruby-lang.org/
ライセンス   : MIT
説明         : Bundler manages an application's dependencies through its entire
             : life, across many machines, systematically and repeatably.
# dnf install ruby-devel rubygem-bundler

Redmineのインストール

Redmineアプリケーションの実行ユーザーはroot権限ではなく専用のユーザーとします。

redmineアカウントの作成
# useradd redmine
# passwd redmine
ユーザー redmine のパスワードを変更。
新しいパスワード:************
新しいパスワードを再入力してください:************
passwd: すべての認証トークンが正しく更新できました。
redmineリポジトリからクローン

Redmineのバージョンアップに追従しやすくするため、Redmineのインストールは tarballを展開するのではなく、リポジトリのクローンを展開します。Redmineの公式リポジトリSubversionですが、最近はgitの方が管理・操作が容易となっているので、Redmineのgitミラーリポジトリからクローンします。ブランチは、リリースブランチを指定します。

# 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のconfig/database.yml を作成します。

production:
  adapter: mysql2
  database: redmine
  host: localhost
  username: redmine
  password: xxxxxxxx
  encoding: utf8mb4

パーミッションを厳しくしておきます。

$ chmod 0600 /var/lib/redmine-4.1-stable/config/database.yml
メール通知その他の設定

今回はメール配信環境、ImageMagick(互換)環境を用意していないので、configuration.ymlの設定は省略します。 後日必要が生じたら設定を行います。

移行前のデータベースを復元

バックアップしていたMariaDBRedmineデータベースを新しいデータベースへインポートします。

$ mysql -uredmine -p redmine < redmine_mysql_www.torutk.com-20200424.dump
Redmineが使用するrubyモジュール群のインストール

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

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

$ cd /var/lib/redmine-4.1-stable
$ bundle install --path vendor/bundler --without development test rmagick
  :
Gems in the groups development, test and rmagick were not installed.
Bundled gems are installed into `./vendor/bundler`
セッションデータ暗号化の鍵生成
$ bundle exec rails generate_secret_token
データベーススキーマの更新

現時点でMariaDBのreadmineデータベースに格納されているデータは、旧Redmine 3.4のデータを復元したものです。 そこで、Redmine 4.1のスキーマに更新する必要があります。

$ bundle exec rails db:migrate RAILS_ENV=production
== 20170723112801 RenameCommentsToContent: migrating ==========================
-- rename_column(:comments, :comments, :content)
   -> 0.0073s
== 20170723112801 RenameCommentsToContent: migrated (0.0076s) =================

== 20180501132547 AddAuthorIdToTimeEntries: migrating =========================
-- add_column(:time_entries, :author_id, :integer, {:default=>nil, :after=>:project_id})
   -> 0.0335s
== 20180501132547 AddAuthorIdToTimeEntries: migrated (0.0485s) ================

== 20180913072918 AddVerifyPeerToAuthSources: migrating =======================
-- change_table(:auth_sources)
   -> 0.0025s
== 20180913072918 AddVerifyPeerToAuthSources: migrated (0.0026s) ==============

== 20180923082945 ChangeSqliteBooleansTo0And1: migrating ======================
== 20180923082945 ChangeSqliteBooleansTo0And1: migrated (0.0000s) =============

== 20180923091603 ChangeSqliteBooleansDefault: migrating ======================
== 20180923091603 ChangeSqliteBooleansDefault: migrated (0.0000s) =============

== 20190315094151 ChangeCustomValuesValueLimit: migrating =====================
-- change_column(:custom_values, :value, :text, {:limit=>16777216})
   -> 0.0289s
== 20190315094151 ChangeCustomValuesValueLimit: migrated (0.0291s) ============

== 20190315102101 AddTrackersDescription: migrating ===========================
-- add_column(:trackers, :description, :string, {:after=>:name})
   -> 0.0201s
== 20190315102101 AddTrackersDescription: migrated (0.0203s) ==================

== 20190510070108 AddUniqueIdToImportItems: migrating =========================
-- change_table(:import_items)
   -> 0.0098s
== 20190510070108 AddUniqueIdToImportItems: migrated (0.0100s) ================

== 20190620135549 ChangeRolesNameLimit: migrating =============================
-- change_column(:roles, :name, :string, {:limit=>255, :default=>""})
   -> 0.0182s
== 20190620135549 ChangeRolesNameLimit: migrated (0.0184s) ====================
一度動作確認を(WEBrick

ここで、いったん動作確認をします。RedmineRuby on Rails)同梱のWebアプリケーションサーバWEBrickRedmineの動作確認をします。

一時的にポート3000を開き、WEBrickサーバーを動かしRedmineが動いているかを確認します。

# firewall-cmd --add-port=3000/tcp
success
$ 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-24 23:25:39] INFO  WEBrick 1.4.2
[2020-04-24 23:25:39] INFO  ruby 2.6.3 (2019-04-16) [x86_64-linux]
[2020-04-24 23:25:39] INFO  WEBrick::HTTPServer#start: pid=31266 port=3000

ここでブラウザからポート3000へアクセスします。Redmine画面が表示されたらOKです。 この時点ではまだRedmineプラグインを入れていないので、プラグインの動作については確認できません。

この後は、Unicorn、Nginxを入れて一通り動作が完了した後にRedmineプラグインを入れることとします。

本日の作業はここまでとし、一時的に開けたポート3000を閉じます。

# firewall-cmd --reload

mavenリポジトリからjarファイルの取得

はじめに

インターネットと常時接続が維持できない環境でプログラミングをする場合、ローカルにライブラリファイル等を保持し、ビルドスクリプトからはローカルのライブラリを参照すれば作業が可能になります。

NetBeans等のIDEでライブラリを利用して開発するときは、そのライブラリのクラスファイルの入ったJARファイル以外にもソースファイルのJARファイルとJavadocのJARファイルがあるとよい感じとなります。

手作業で1つ1つ取得することはできますが、依存関係が複数あると結構な手間となるので、ローカルに保持するライブラリのJARファイル、ソースJARファイル、JavadocのJARファイルをコマンドでまとめてmavenリポジトリから取得する方法がないかを調べました。

pomの作成

取得するライブラリを依存関係として記述したpom.xmlを作成します。

<?xml version="1.0"?>
<project>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.torutk</groupId>
  <artifactId>download</artifactId>
  <version>1</version>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.6.1</version>
    </dependency>
  </dependencies>
</project>
  • project要素の子要素であるmodelVersion、groupId、artifactID、versionは必須のため適当に記載

このpom.xmlのあるディレクトリでmavenを実行します。

D:\work> mvn dependency:copy-dependencies
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< com.torutk:download >-------------------------
[INFO] Building download 1
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:copy-dependencies (default-cli) @ download ---
[INFO] Copying junit-jupiter-5.6.1.jar to D:\work\target\dependency\junit-jupiter-5.6.1.jar
  :
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.950 s
[INFO] Finished at: 2020-04-05T21:38:29+09:00
[INFO] ------------------------------------------------------------------------

ダウンロードしたファイルは次です。

D:\work> dir target\dependency
apiguardian-api-1.1.0.jar
junit-jupiter-5.6.1.jar
junit-jupiter-api-5.6.1.jar
junit-jupiter-engine-5.6.1.jar
junit-jupiter-params-5.6.1.jar
junit-platform-commons-1.6.1.jar
junit-platform-engine-1.6.1.jar
opentest4j-1.2.0.jar

D:\work>

ソースJARファイルをダウンロードする場合は、オプション -Dclassifier=sourcesを指定

D:\work> mvn dependency:copy-dependencies -Dclassifier=sources
  :

ダウンロードしたファイルは次です。

apiguardian-api-1.1.0-sources.jar
junit-jupiter-5.6.1-sources.jar
junit-jupiter-api-5.6.1-sources.jar
junit-jupiter-engine-5.6.1-sources.jar
junit-jupiter-params-5.6.1-sources.jar
junit-platform-commons-1.6.1-sources.jar
junit-platform-engine-1.6.1-sources.jar
opentest4j-1.2.0-sources.jar

JavadocのJARファイルをダウンロードする場合は、オプション-Dclassifier=javadocを指定

D:\work> mvn dependency:copy-dependencies -Dclassifier=javadoc
:

ダウンロードしたファイルは次です。

apiguardian-api-1.1.0-javadoc.jar
junit-jupiter-5.6.1-javadoc.jar
junit-jupiter-api-5.6.1-javadoc.jar
junit-jupiter-engine-5.6.1-javadoc.jar
junit-jupiter-params-5.6.1-javadoc.jar
junit-platform-commons-1.6.1-javadoc.jar
junit-platform-engine-1.6.1-javadoc.jar
opentest4j-1.2.0-javadoc.jar

ダウンロードファイルのディレクトリ指定

デフォルトでは、pom.xmlを置いたディレクトリの下にtarget\dependencyディレクトリが作られ、その中にダウンロードしたJARファイルが保存されます。このディレクトリは、-DoutputDirectory=libs のようにオプション指定で変更することができます。