torutkのブログ

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

さくら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 のようにオプション指定で変更することができます。

Java SE 8の安定感

はじめに

これからソフトウェア開発を始めるプロジェクトでプログラミング言語Javaを使う場合に、Javaのバージョンをどうしようかと調べたことを書き付けます。

開発するソフトウェアは、5年から10年、場合によってはそれ以上使われることが想定されます*1

Javaのバージョンとライフサイクル

OracleRed Hat、Azul Systems、および Bell softwareが提供するJava SE Development Kitのサポート期限を調べてみました。

製品 サポート期限
Oracle Java SE 8 2030年12月*2
Oracle Java SE 11 2026年9月*3
Red Hat OpenJDK 8 2026年5月
Red Hat OpenJDK 11 2024年10月
Azul Zulu Enterprise 8 2030年12月
Azul Zulu Enterprise 11 2027年9月
Azul Zulu Community 8 2026年3月
Azul Zulu Community 11 2027年9月
Liberica JDK 8 2031年3月
Liberica JDK 11 2027年3月

Oracleのサポートは3段階あるうちの2段階目のExtended Supportの期限を記載しています。製品リリース後所定の期間がPremier Support期間で、続いてサポート料金が上済みされるがほぼ同等のサービスを提供するExtended Support期間となります。

参照情報

ビルドツールとの相性

JDK 8環境では、各種ツールとの相性はよかったのですが、JDK 11環境となるとまだまだ整っていない部分があります。

NetBeans IDE

NetBeans IDE 8.2はJava SE8対応ですが、Java SE 11を使うには、Apache NetBeans 10以降を使うことになります。しかしながら、NetBeans は8.2のリリースの後、OracleからApacheへ移管し、Apache incubatorのフェーズを経て2019年4月にApache正式プロジェクトへ昇格しました。

Java SE 8の開発であれば、NetBeans IDE 8.2が使えますが、Java SE 11の開発となると、現時点ではApache NetBeans 11.3 になります。ただ、OracleからApacheへの移行に伴い、プラグインサイトの更新が追い付いていない感があり、安定して利用するには少々時間がかかりそうと思います。

JPMS対応

Apache Ant、Gradleは、JPMS(Java Platform Module Systems)対応が遅れており、Apache Mavenがやっと、というところです。

FindBugs

FindBugsは開発が停滞しており、JDK 11への対応が厳しいかなという状況です。SpotBugsに差し替えるのが有効な解決策の1つですが、NetBeans IDEに組み込むプラグインがまだない模様です。Eclipse用はありますが。

javapackagerの喪失

JDK 11ではjavapackagerが削除され、WindowsインストーラーやLinuxインストーラー(RPMパッケージ他)を作るのが面倒になりました。

外部ライブラリとの相性

Endorsed Standards Override Mechanismの廃止

クラスパスの設定にEndorsed Standards Override Mechanismを使っているライブラリはJDK 11では動かなくなります。(バッチファイル、シェルスクリプトファイルなので、クラスパス指定する等の修正をすれば動作) 例)JacORB 3.9

まとめ

Java SE 11がリリースされてから2年半が経過していますが、まだ周辺ツールが追い付いておらず、開発に使う場合の安定感はJava SE 8の方が高いです。

JDKのライフサイクルを見ると、Azul SystemsとBellSoftwareが提供するJDKについては、2021年9月リリース予定のJDK 17ですらJDK 8よりライフサイクルが短く、2024年9月リリース予定のJDK 23でようやくJDK 8のライフサイクルを超えるサポート期限となります。

もちろん、モジュールシステムや新しいAPI、新しい言語仕様、新しいGCなどの機能がどんどん取り込まれているので使いたい気持ちは山々なのですが、上述のとおりライフサイクルや開発環境(IDEだけでなく、品質チェック、ビルド、リリース等)を考慮すると、Java SE 11に移行すべきと言い切れないというのが現段階です。

*1:ちなみに、12年前に開発に携わったシステムは今も使われています

*2:Extended Support期限

*3:Extended Support期限

Java Module Systemのmain classを持つJARファイル作成の落とし穴

発生した事象

IntelliJ IDEA で作成していたJavaFXアプリケーションがあります。ビルド成果物でJARを作成し、Main Classを指定すると、実行可能JARファイルが生成されます。

このアプリケーションのプロジェクトにmodule-info.javaを追加してJava Module System対応をしました。ビルドでJARファイルを生成し、そのJARファイルをコマンドラインから実行すると、−mオプションに明示的にmain classを指定しないとエラーとなってしまいます。実行可能JAR(-jarオプション)としての実行はできているのですが・・・

D:\work\myapp> java -p out\artifacts\myapp_jar -m com.torutk.myapp
モジュールcom.torutk.myappにModuleMainClass属性がありません。-m <module>/<main-class>を使用してください

生成されたJARファイルのMANIFEST.MFを見ると、Main-Class属性は記述されています。

ここで、エラーメッセージには「ModuleMainClass属性がありません」と出ているのがミソで、実行可能JARファイルのMANIFEST.MFに記述する「Main-Class属性」とは名前が似ているが異なっていた点に注意が向かなかったのが痛かったです。

調査

簡単なモジュールのサンプルを作り、次の2つの方法でJARファイルを生成します。

  1. MANIFEST.MFの雛形にMain-Class属性を記述し、jarコマンドの-mオプションで雛形を指定
  2. jarコマンドの--main-classオプションで実行するメインクラス名を指定
D:\work\myapp> jar --create --file out1\myapp.jar --manifest src\META-INF\MANIFEST.MF -C classes .
D:\work\myapp> jar --create --file out2\myapp.jar --main-class com.torutk.myapp.Main -C classes .

1つ目のjarファイル(out1\myapp.jar)は、エラーとなります。

D:\work\myapp> java -p out1 -m com.torutk.myapp
モジュールcom.torutk.myappにModuleMainClass属性がありません。-m <module>/<main-class>を使用してください

2つ目のjarファイル(out2\myapp.jar)は実行できます。

D:\work\myapp> java -p out2 -m com.torutk.myapp
Hello, Java Module System

この両者の違いを追うべく、2つのJARファイルの違いをwinmergeを使って確認したところ、MANIFEST.MFとmodule-info.classに差異が検出されました。

MANIFEST.MFは属性の記述順が異なるだけでした。 module-info.classは、javacでコンパイルした出力結果をjarコマンドでアーカイブしているだけと思い込んでいたので、jarファイルに含めるときに変更が行われていたとは少々驚きました。

javacでコンパイルしたあとに、jarコマンドでオプションを変えて2種類のjarを生成しています。これらのjarに含まれるmodule-info.classは、もとのjavacでコンパイルしたmodule-info.classとはサイズが異なっており、さらにそれぞれのJARファイルに格納されたmodule-info.classにも違いがありました。

今回の事象の原因となっている違いは、module-info.classファイルの中にModuleMainClass情報が含まれているか否かの違いです。

javapコマンドでmodule-info.classを調べる

jarコマンドの--describe-moduleオプションを指定すると、モジュール情報を確認できます。

D:\work\myapp> jar --describe-module --file out1\myapp.jar
com.torutk.myapp jar:file:///D:/work/myapp/out1/myapp.jar/!module-info.clas
requires java.base mandated
contains com.torutk.myapp
D:\work\myapp> jar --describe-module --file out2\myapp.jar
com.torutk.myapp jar:file:///D:/work/myapp/out2/myapp.jar/!module-info.clas
requires java.base mandated
contains com.torutk.myapp
main-calss com.torutk.myapp.Main

となり、--main-classオプションを指定して生成したJARファイルの中に含まれるmodule-info.classには、メインクラスに関する情報が追加されています。

--main-classオプションを指定して生成したJARファイルの中にあるmodule-info.classを取り出して、バイナリエディタで覗くと「ModuleMainClass」の文字列が確認できます。

また、このJARファイルから取り出したmodule-info.classをjavapにかけてみたところ、次の様にクラスファイルの中にModuleMainClassという情報が追加されていることが分かります。

D:\work\myapp> javap -v module-info.class
Classfile /D:/work/myapp/module-info.class
  Last modified 2020/03/10; size 266 bytes
  MD5 checksum 865c4ebb10b566b887dd80d3783de624
  Compiled from "module-info.java"
module com.torutk.myapp
  minor version: 0
  major version: 55
  flags: (0x8000) ACC_MODULE
  this_class: #2                          // "module-info"
  super_class: #0
  interfaces: 0, fields: 0, methods: 0, attributes: 4
Constant pool:
   #1 = Utf8               module-info
   #2 = Class              #1             // "module-info"
   #3 = Utf8               module-info.java
   #4 = Utf8               com.torutk.myapp
   #5 = Module             #4             // "com.torutk.myapp"
   #6 = Utf8               ModuleMainClass
   #7 = Utf8               com/torutk/myapp/Main
   #8 = Class              #7             // com/torutk/myapp/Main
   #9 = Utf8               ModulePackages
  #10 = Utf8               com/torutk/myapp
  #11 = Package            #10            // com/torutk/myapp
  #12 = Utf8               java.base
  #13 = Module             #12            // "java.base"
  #14 = Utf8               11.0.6
  #15 = Utf8               SourceFile
  #16 = Utf8               Module
{
}
SourceFile: "module-info.java"
Module:
  #5,0                                    // "com.torutk.myapp"
  #0
  1                                       // requires
    #13,8000                                // "java.base" ACC_MANDATED
    #14                                     // 11.0.6
  0                                       // exports
  0                                       // opens
  0                                       // uses
  0                                       // provides
ModuleMainClass: #8                     // com.torutk.myapp.Main
ModulePackages:
  #11                                     // com.torutk.myapp

結論

mainクラスの指定を省略して実行できるモジュールJARを生成するときは、jarコマンドの--main-classオプションを使って生成する必要があります。

この仕組みに言及している文献等

現在Java読書会BOFで主催しているJava読書会で読んでいる書籍「The Java Module System」には、4.5.3項 Defining an entry point に記載がありました(p.101 上4行目からの段落を抜粋)。

When jar is used to package class files into an archive, you can define a main class with --main-class ${class}, where ${class} is the fully qualified name of the class with the main method. It will recorded in the module descriptor and used by defaults as the main class when the module is the initial module for launching an application.

粗訳

jarコマンドを使ってクラスファイル群をアーカイブする際、--main-class ${class}を指定してメインクラスを定義することができます。${class}には、mainメソッドを持つクラスの完全限定クラス名(FQCN)を指定します。そして、これはモジュール記述子に記録され、モジュールがアプリケーション起動時の初期モジュールとなるときのデフォルトのメインクラスとして適用されます。

この箇所は、前回(2月1日)の読書会で読んだ範囲にあるのですが、悲しいかな全く記憶にありませんでした。

この仕組みへのビルドツールの対応状況

NetBeans 8.2のFindBugs Integrationプラグインインストールがエラーに

発生した事象

4,5年前に立ち上げ時期だけ参加していたプロジェクトがありました。そのプロジェクトはもうクローズしていますが、その資産を引き継ぎ新たに開始したプロジェクトがあり、Javaに詳しい開発者がいないとうことで、そのプロジェクトを(本業に差し支えのない範囲で)支援することになりました。

支援先の開発環境が、NetBeans IDE 8.2(とOpenJDK 1.8.0)を使っているので、 それに合わせて開発環境の確認をするため、開発で使用するプラグインNetBeans IDEに順次入れて動作確認をしている中で、FindBugs Integrationプラグインのインストール時にエラーが発生してしまいました。

f:id:torutk:20200301193554p:plain
NetBeans 8.2にFindBugs Integrationプラグインをインストールしようとしてエラー

以前インストールしたときは問題なかったのですが・・・

ということで、どうしてエラーになったのか原因を調べてみました。

FindBugs Integration プラグインの構成

NetBeans IDEプラグインFindBugs Integrationを選択すると、次の2つのプラグインをインストールしようとします。

  1. FindBugs Library Wrapper
  2. FindBugs Integration

ここで、FindBugs Library Wrapper は、FindBugsが依存するライブラリ(ASM、BCEL、Commons Lang、DOM4JFindBugs、JAXEN、jFormatString、JSR305)をインストールするのですが、これらはプラグインのファイルには含まれておらず、インストールを実施するときに外部(mavenリポジトリ)からダウンロードする仕組みとなっています。

ここで、mavenリポジトリから依存するライブラリをダウンロードする際のURLが、ASMの場合次の様に記載されています。

CRC:3011554661
SIZE:380313
URL:http://repo1.maven.org/maven2/org/ow2/asm/asm-debug-all/5.0.2/asm-debug-all-5.0.2.jar
URL:m2:/org:ow2:asm:asm-debug-all:5.0.2:jar

試しにこのURLをWebブラウザで指定してアクセスしてみました。すると、

501 HTTPS Required. 
Use https://repo1.maven.org/maven2/
More information at https://links.sonatype.com/central/501-https-required

とエラーになっています。

mavenリポジトリにアクセスできなくなったのは?

上述でWebブラウザからURLにアクセスしたときのエラーメッセージにある"More information"を見てみます。

  • 2020年1月15日から、The Central Repositoryは、素のHTTPは受け付けず、HTTPSでアクセスする必要がある
  • URLのhttpをhttpsに書き換える
  • どうしてもHTTPでアクセスする必要がある場合、http://insecure.repo1.maven.org/maven2/ へアクセスする

mavenリポジトリがHTTPを受け付けなくなった背景は次に述べられています。

blog.sonatype.com

mavenリポジトリに依存したビルドが、2020年1月15日を境に壊れるという事象が発生しているところがあちらこちらにありそうです。

FindBugs Integrationプラグインをインストールするにはどうすればよいか

理想は、FindBugs IntegrationプラグインHTTPSを使って依存ライブラリをダウンロードするように設定を変更することです。

しかし、アプリケーション開発者の制御範囲外のことですから、プラグイン側が対応するまでの間、回避策が必要です。

回避策1 URLを変更する

まず、FindBugs Integrationプラグインのファイル(nbm: NetBeans Moduleファイル)をダウンロードします。本日時点でNetBeans 8.2でインストールする場合のnbmファイルのURLは次です。

このURLの導出方法を知りたい方は、以前NetBeans 8.0のときに以下のWikiに記載しましたので参照ください。 www.torutk.com

次に、ダウンロードしたorg-netbeans-libs-findbugs.nbmをzipで開き、次のファイルを取り出します。

+-- Info
+-- META-INF
+-- netbeans
      +-- config
      +-- modules
            +-- ext
            |     +-- asm-debug-all-5.0.2.jar.external
            |     +-- bcel-6.0-SNAPSHOT.jar.external
            |     +-- commons-lang-2.6.jar.external
            |     +-- dom4j-1.6.1.jar.external
            |     +-- findbugs.jar.external
            |     +-- jaxen-1.1.6.jar.external
            |     +-- jFormatString.jar.external
            |     +-- jsr305.jar.external

この、externalで終わるファイル名には、jarファイルをmavenリポジトリからダウンロードするURLが記述されています。このURLを、httpからhttpsに書き換えます。

例えば、asm-debug-all-5.0.2.jar.externalは次のテキストが記述されたファイルです。

CRC:3011554661
SIZE:380313
URL:http://repo1.maven.org/maven2/org/ow2/asm/asm-debug-all/5.0.2/asm-debug-all-5.0.2.jar
URL:m2:/org:ow2:asm:asm-debug-all:5.0.2:jar

これを次の様に書き換えます。

CRC:3011554661
SIZE:380313
URL:https://repo1.maven.org/maven2/org/ow2/asm/asm-debug-all/5.0.2/asm-debug-all-5.0.2.jar
URL:m2:/org:ow2:asm:asm-debug-all:5.0.2:jar

同じ要領で、残りのファイル(名前がexternalで終わるもの)についてもhttpをhttpsに書き換えます。

Windows上で7zipツールを使う場合、org-netbeans-libs-findbugs.nbmを7zipで開き、netbeans\modules\extディレクトリを開き、ファイル名がexternalで終わるファイルを右クリックし、[編集]を選択、開いたメモ帳でhttpをhttpsに変更して保存、アーカイブを更新していきます。

次に、このorg-netbeans-libs-findbugs.nbmは署名付きJARファイルとなっているので、内容を変更した結果署名と合わなくなりエラーとなってしまいます。そこで、META-INFの下にある次の署名ファイルを削除します。

+-- META-INF
      +-- ORACLE_C.SF
      +-- ORACLE_C.RSA

すべてのexternalファイルの変更と、署名ファイルの削除が終わったら、NetBeans IDE 8.2上で、[ツール]メニュー > [プラグイン]で「プラグイン」画面を開き、[ダウンロード済]タブを選択し、[プラグインの追加]ボタンを押し、「プラグインの追加」ファイル選択ダイアログが表示されるので修正済みのorg-netbeans-libs-findbugs.nbmと、ダウンロードしたorg-netbeans-modules-findbugs.nbmの2つを選択して[開く]をクリック、「プラグイン」画面に戻るので[インストール]ボタンをクリックしてインストールします。

回避策2 externalを実体に差し替える

org-netbeans-libs-findbugs.nbmの中のexternalファイルを、実際のJARファイルに置き換えます。 NetBeans 8.0のときの作業を先のWikiページに記載しているので、それに倣って対応します(結果は未確認)。

教訓

mavenは一見便利ですが、mavenリポジトリという外部組織が管理するサイトに依存することになるため、ビルドはいつ壊れるか分かりません。

特に、長期間保守をするシステムでは、安易に外部のmavenリポジトリを利用するのではなく、外部組織のサイト管理を常にウォッチしその変更に迅速に対応できる体制を構築して維持するとか(多分無理)、組織内に利用するライブラリ群を保持するmavenミラーリポジトリを用意して管理するとか、依存ライブラリをビルド環境に保持しローカル参照するようなビルドにするといったことが必要です。

ソフトウェア開発環境が含む範囲

ソフトウェア開発環境が含む範囲

本記事は、製品としてソフトウェアを作る場合で、複数の開発者が共同して作業する、企業でのビジネスを想定しています。プログラミング言語は、コンパイラ型でオブジェクト指向プログラミング機能を想定しています。

逆に、サービスの提供が目的でソフトウェア自体が製品ではない場合、開発規模が小さい(ごく少人数の開発で阿吽の呼吸が通じる)場合、既にある製品やサービスを活用し、ちょっとしたアドオンやカスタマイズで実現する場合、などは本記事の想定外となります。

開発環境と一言でいうとどこまでを含むのか

ソフトウェアの開発環境を整備しようとしたら、何を含めるのかについて同じ職場でもコンセンサスがなかなか得られていません。そんなときは両極端な思考をしてみて検討の幅を持たせて適切なところを探ります。

最小限の開発環境

まず、片方の極端な思考として、もっとも少ない開発環境を考えます。ミニマムではソースコードを実行体にコンパイル(ビルド)するのに必要なコンパイラがあればいいでしょう。作業効率も問わなけらば、ソースコードの記述は何でもよくて(メモ帳でもWordPadでも)、ソースファイルは日時のフォルダに整理して保存し、ソースファイルをコンパイルして実行体を生成します。

最大限の開発環境

続いて、もう片方の極端な思考として、もっとも多い開発環境を考えます。

プログラミング作業が用意になるよう統合開発環境IDE:Integrated Develop Environment)を用意します。開発者がそれぞれバラバラなソースコードを記述しないようコーディング規約を策定し、IDEに可能な限り規約に合わせた設定を行い、それを全員に配布し適用します。プロジェクトで定めたファイル先頭コメント(ライセンス記述やプロジェクト記述等)やコメントの雛形等をファイル新規作成時に自動で展開できるようにIDEに設定しておきます。設定を展開するのに必要であればIDEプラグインを作って全員に配布します。

ソースコードは構成管理ツールの下に集約し、ソースコードの作成、修正、履歴管理、リリース管理の方法を構成管理規約を策定し全員に配布します。要件(仕様)からのトレーサビリティを確保するため、要件から展開したタスクをタスク管理システムに登録し、タスク番号をブランチ名としてソースコードをコミットし管理するなども構成管理規約に含めます。

ソースコードの品質を評価する作業も開発の一部として開発環境に組み込みます。品質の代表的な評価手段としてレビュー(インスペクション)とテストがあります。レビューはプロセスやライブラリの外部インタフェース(プロセス間通信、ファイル等のリソースアクセス、APIなど)、プログラムの構造(パッケージ/クラス構造、ライブラリ構成)、ドキュメントコメントから生成する設計情報、各段階のテスト仕様、ソースコードを対象とするので、レビューの実施体制、レビュー基準、レビュー記録などの手順、計画、管理方法などを策定します。

ソースコードに対して静的解析ツールを適用して、予め定義した検査ルールに逸脱する箇所を検出します。検査ルールは、命名規約からネストの深さ、パッケージやクラスの構成(クラス数、メソッド数、ファンイン/ファンアウト数、他)、循環複雑度、凝集性、APIの呼び出し方法、バグパターンなどを定義しておきます。組み込み方も、ある程度開発が進んでから一括して検証するだけでなく、開発者がコードを書いている最中に検証をしておくのが望ましいので、IDEへ組み込めるようにします。

ユニットテストツールを適用して、小さいモジュール単位(クラス/メソッド等)で機能の確認をします。その際、カバレッジツールや動的解析ツールを併用してテストの検証範囲や計算機リソース(CPU、メモリ、ディスク、ネットワーク等)の使用状況を評価します。ユニットテストで不可欠なモックの作成をツールを使って効率化するならモックツールも適用します。

開発されたソースコードをまとめてビルドしテスト環境や納品用のリリース媒体を作るビルド・リリース環境も用意します。ビルドしたソースコードのレビジョンとリリースバージョンの対応付け、バグ管理ツールとの対応(そのリリースで修正されたバグ、既知のバグなど)などを行って、リリースノートへの記載などができるようにします。このビルド環境にも静的解析ツールを組み込んでの評価、ドキュメントコメントからのドキュメント生成などができるようにしておきます。

テストや運用中に発生したバグを管理し、バグの修正コードを記述するために、バグ管理ツールを導入し、バグ管理規約を作成します。バグ管理ツールは構成管理ツールとの紐づけが最低限必要です。

  • IDEとその設定(プラグイン含む)
  • コーディング規約
  • 構成管理ツール
  • 構成管理規約
  • 要件管理ツール(またはタスク管理ツール)
  • レビュー管理ツール
  • レビュー規約
  • 静的解析ツール
  • ユニットテストツール
  • カバレッジツール
  • 動的解析ツール
  • テスト規約(ユニットテスト
  • ビルドツール(自動ビルド)
  • リリースツール(必要ならインストール媒体作成を含む)
  • ドキュメント生成ツール
  • バグ管理ツール
  • バグ管理規約

ソフトウェア開発環境が対象とするもの

ソフトウェア開発環境が対象とするものをソースコードとするのか、製品の媒体(バイナリ、インストーラー等)とするのかもブレがありそうです。

ソースコードまでを範囲とする場合、各開発者が自分の環境でソースコードの作成をすることが主作業で、作業の必要上コンパイルする、ユニットレベルのテストをする、デバッグをする、といった範囲が開発環境となります。ソースコードをテスト環境や運用環境に展開する、あるいはインストール用媒体を生成する、などはおざなりに付く程度です。

実行体までを範囲とする場合は、テスト環境や運用環境に展開する、あるいはインストール用媒体を生成するまでが開発環境となります。

品質活動をどこまで開発環境に取り込むかによって、対象が増減します。

規約とツール

IDEEclipseを使います。ソースコード管理にGitLabを使います。テストはJUnitを使います。ビルドにはJenkinsを使います。バグ管理にはJIRAを使います。とツールだけを決めても開発現場は混乱します。ファイルの改行コード、エンコーディングがバラバラ、ビルドツールはIDE固有の人、Ant使う人、maven使う人などでビルド困難、命名規約がバラバラ、パッケージ単位もバラバラ(やたら細かい人、数十クラスを1つのパッケージにする人)、コメント記述バラバラ、ユニットテストの書き方、メンテナンスがバラバラ、などなど。

しまらないまとめ

ソフトウェア開発環境はピンキリですが、品質保証活動を取り入れた製品の開発を行う場合は、言わば開発対象のビジネス業務を丸ごと一つ業務設計するような決め事が必要になります。これを、ソフトウェアをエンジニアリングすることと言いたいのですが、伝わらないですねぇ。

でも、それをしなないとカオスの渦に飲み込まれてしまいます。