Redmineデータ移行

Redmineのデータを旧サーバーから新サーバーへ移行する。

新サーバーのRedmineは『Redmine構築』で作成したDocker構築となっている。

事前容量確認

ディスク容量を確認する。

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2   50G   14G   37G  28% /

ルートマウント(/)に37GBの空きがあるため問題ない。※使用率が80%を超えるようであれば、ボリューム拡張を検討する。

/dev/nvme0n1p2 と /dev/xvda2 の違い

  • /dev/xvda*:Xen ベースの仮想ディスク。
  • /dev/nvme*:NVMe ベースのストレージ。

AWSでインスタンスタイプを変更したため、ストレージインターフェースが変更された。

既存Redmineのデータを取得

以下を取得する

  • DBデータのダンプ
  • 添付ファイルの圧縮データ

バックアップ用ディレクトリを作成

バックアップ用ディレクトリを作成する。

sudo mkdir -p /var/backups/redmine

権限を確認する。

ls -la /var/backups/redmine

DBダンプを取得

DBコンテナの名前(NAME)を確認する。

sudo docker compose ps

NAME               IMAGE
redmine            redmine:5.1.11
redmine-db-1       mysql:8.0
redmine-nginx      nginx:1.27

DBダンプを取得する。

sudo docker exec redmine-db-1 mysqldump \
  --single-transaction \
  --default-character-set=utf8mb4 \
  --no-tablespaces \
  -u root -p"xxx" \
  redmine \
  > /tmp/redmine_dump.sql

ちなみに、具体的には以下のディレクトリのデータをダンプしている。

コンテナ内

/var/lib/mysql/redmine/

ホスト側

/var/lib/docker/volumes/redmine_db_data/_data/redmine/

ダンプファイルを確認

ファイルサイズを確認する。

ls -lh /tmp/redmine_dump.sql

先頭を確認する(正常にダンプできているか)。

head -5 /tmp/redmine_dump.sql

末尾を確認する(最後まで書き出せているか)。

tail -5 /tmp/redmine_dump.sql

期待結果

# ファイルサイズ
-rw-r--r--. 1 root root [XMB] ... /tmp/redmine_dump.sql

# 先頭
-- MySQL dump 10.13  Distrib 8.0.xx, for Linux (x86_64)
-- Host: localhost    Database: redmine

# 末尾
-- Dump completed on YYYY-MM-DD HH:MM:SS

filesアーカイブを取得

ホスト側(実体)の添付ファイルデータを圧縮する。※3~5分かかる

sudo tar czvf /tmp/redmine_files.tar.gz \
  -C /var/lib/docker/volumes/redmine_redmine_files/_data .

圧縮ファイルを確認

ファイルサイズを確認する。

ls -lh /tmp/redmine_files.tar.gz

アーカイブの中身の先頭を確認する。

tar tzf /tmp/redmine_files.tar.gz | head -10

DBマイグレーション

先ほど取得した既存RedmineのDBダンプを新サーバーに展開する。
※ダンプデータは容量が大きく、ヒューマンエラーでコピペ時に破損したりするので、コピー元の容量と、最終的に配置したデータの容量が一致するか確認すること

任意の場所にダンプファイルをアップロードする。

今回は以下とする。

/opt/redmine/migration/

Redmineコンテナを停止

docker-compose.yml を配置したフォルダへ移動する。

cd /opt/redmine
sudo docker compose stop redmine

Redmineデータベースを初期化

MySQLへログインする。

sudo docker exec -it redmine-db-1 mysql -u root -p"password"

-p の後にはスペースを入れないこと。

下記のSQLを実行してデータベースを初期化する。

SHOW DATABASES;

DROP DATABASE redmine;

CREATE DATABASE redmine
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

SHOW DATABASES;
SHOW CREATE DATABASE redmine;

EXIT;

下記のコマンドでリストアする。
※ リストア実行中にエラーが発生した場合は「インシデント1」を参照すること。

DBリストア

以下のコマンドを実行して、ダンプファイルをMySQLへリストアする。

nohup sudo docker exec -i redmine-db-1 mysql \
  --default-character-set=utf8mb4 \
  -u root -p"[root_password]" \
  redmine < /opt/redmine/migration/redmine_dump.sql \
  > /tmp/restore.log 2>&1 &

以下のようなプロセスIDが表示される。

[1] 1458100

プロセスを確認

ps aux | grep "mysql" | grep -v mysqld | grep -v grep

先ほど表示されたプロセスIDでリストアが進行していることを確認する。

ログを確認

tail -f /tmp/restore.log

エラーが出力されていなければ問題ない。

1〜2分経過したら、再度プロセスを確認する。

※ 完了ログは出力されないため、プロセスが終了していることをもって完了と判断する。

ps aux | grep "mysql" | grep -v mysqld | grep -v grep

最終確認

下記のSQLでデータが反映されたか確認する。

sudo docker exec -it redmine-db-1 mysql -u root -p"[root_password]" -e \
"USE redmine;
SELECT COUNT(*) AS table_count
FROM information_schema.tables
WHERE table_schema='redmine';

SELECT COUNT(*) AS projects FROM projects;
SELECT COUNT(*) AS issues FROM issues;
SELECT COUNT(*) AS users FROM users;
SELECT COUNT(*) AS time_entries FROM time_entries;"

projectsissues(チケット)、users が相当数登録されていればOK。

filesの展開

続いて、圧縮した添付ファイルデータを展開する。

先ほど取得した添付ファイルのtar.gzデータを以下に配置したものとする。

/opt/redmine/migration/

まずはアーカイブの構成を確認する。

sudo tar tzf /opt/redmine/migration/redmine_files.tar.gz | head -20

files/xxx のようなパス構成になっていれば最も一般的な構成。

※以下は files/xxx 構成の場合。

下記のコマンドでファイルを展開する。
※ファイル展開中にエラーが発生した場合は「インシデント2」を参照。

sudo tar xzvf /opt/redmine/migration/redmine_files.tar.gz \
  -C /var/lib/docker/volumes/redmine_redmine_files/_data/ \
  --strip-components=1 \
  2>&1 | sudo tee /tmp/tar_extract.log

以下のようなプロセスIDが表示される。

[1] 1458100

プロセスを確認する。

ps aux | grep "tar" | grep -v grep

先ほどのプロセスIDでファイル展開が進行していることを確認する。

ログを監視する。

tail -f /tmp/restore.log

エラーが表示されていなければOK。

1~2分後に改めてプロセスを確認する。

※完了ログは出力されないため、プロセスが終了していることをもって完了と判断する。

ps aux | grep "tar" | grep -v grep

最終確認

tar.gz内のファイル数を確認する。

sudo tar tzf /opt/redmine/migration/redmine_files.tar.gz | grep -v '/$' | wc -l

期待結果

25005

展開済みファイル数を確認する。

sudo find /var/lib/docker/volumes/redmine_redmine_files/_data/ -type f | wc -l

期待結果

25005

両者が一致していればOK。

権限調整

展開されたファイルは所有者が root になっている可能性があるため、状態を確認する。

展開されたファイルの所有者を確認する。
(ホストOSとコンテナで見え方が異なるため、UID・GIDを数値で表示する。)

cd /var/lib/docker/volumes/redmine_redmine_files/_data

sudo ls -lan /var/lib/docker/volumes/redmine_redmine_files/_data/

下記のコマンドでRedmineのUIDとGIDを確認する。

cd /opt/redmine
sudo docker compose up -d redmine
sleep 10
sudo docker exec redmine id redmine

期待結果

uid=999(redmine) gid=999(redmine) groups=999(redmine)

展開したファイルの所有者がRedmineになっていることを確認する。

※他の所有者になっている場合は権限の修正が必要。

起動確認

コンテナの状況を確認する。

cd /opt/redmine
sudo docker compose ps

STATUSUp になっていればOK。

コンテナのログを確認する。

sudo docker logs redmine --tail 30

Listening on http://0.0.0.0:3000 が表示されていればOK。

ブラウザからアクセスできるか確認する。

https://<このサーバーのIP>/redmine

または

https://<このサーバーのIP>/login/
  • 404エラー:インシデント3を参照。
  • ログインできない:インシデント4を参照。

インシデント

リストア実行中にエラー(インシデント1)

ERROR 1118 (42000) at line 1282207: Row size too large (> 8126). Changing some columns to TEXT or BLOB may help. In current row format, BLOB prefix of 0 bytes is stored inline.

1つの行(レコード)のサイズが8126バイトを超えているため発生するエラー。

MySQLのInnoDBには、「1行あたり最大8126バイト」という制限がある。

docker-compose.ymlの設定を変更する

事前にバックアップを取得しておく。

docker-compose.yml に以下の設定を追加する。

db:
  image: mysql:8.0
  environment:
    MYSQL_DATABASE: redmine
    MYSQL_USER: redmine
    MYSQL_PASSWORD: "change_me_db_password"
    MYSQL_ROOT_PASSWORD: "change_me_db_password"
  command: >
    --character-set-server=utf8mb4
    --collation-server=utf8mb4_unicode_ci
    --innodb-strict-mode=0      # 追加
    --max-allowed-packet=256M   # 追加
    --innodb-log-file-size=256M # 追加
  volumes:
    - db_data:/var/lib/mysql
設定 目的 備考
--innodb-strict-mode=0 行サイズエラーを警告に格下げする。 ★★★ 必須(今回のエラー対策)
--max-allowed-packet=256M 大きなSQLステートメントを許可する。 ★★ 保険(大きいダンプ対策)
--innodb-log-file-size=256M 大きなトランザクションのログ領域を確保する。 ★★ 保険

YAML構文をチェックする。

sudo docker compose config

DBコンテナを再起動する。

sudo docker compose up -d db

起動状態を確認する。

sudo docker compose ps

db コンテナの STATUSUp になっていればOK。

設定が反映されたか確認する。下記の確認SQLを実行する。

sudo docker exec -it redmine-db-1 mysql -u root -p"change_me_db_password" -e \
"SHOW VARIABLES LIKE 'innodb_strict_mode';
SHOW VARIABLES LIKE 'max_allowed_packet';
SHOW VARIABLES LIKE 'innodb_log_file_size';"

DBを再作成

以下のSQLを実行

sudo docker exec -it redmine-db-1 mysql -u root -p"[PASSWORD]" -e "
DROP DATABASE redmine;
CREATE DATABASE redmine CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
SHOW DATABASES;"

リストア再実行

以下のSQLを実行

nohup sudo docker exec -i redmine-db-1 mysql \
  --default-character-set=utf8mb4 \
  -u root -p"[PASSWORD]" \
  redmine < /opt/redmine/migration/redmine_dump.sql \
  > /tmp/restore.log 2>&1 &

ファイル展開時にエラー(インシデント2)

ファイル検証

サーバー上の tar.gz ファイルが破損していないか確認する。

sudo tar tzf /opt/redmine/migration/redmine_files.tar.gz | awk -F'/' '{print $2}' | sort -u

実行結果

gzip: stdin: unexpected end of file
tar: Unexpected EOF in archive
tar: Error is not recoverable: exiting now

2018
2019
2020
2021
2022
2023
delete.me

ファイルが破損している。2024年以降のデータも出力されていない。

ローカルの tar.gz の破損有無確認

Git Bashで以下を実行する。

gzip -t redmine_files.tar.gz && echo "GZIP OK" || echo "GZIP NG"

実行結果

gzip: redmine_files.tar.gz: unexpected end of file
GZIP NG

→ ファイルが破損している。

サーバー上の tar.gz とローカルの tar.gz のサイズを比較する。→ 一致。

ローカルの tar.gz と、データ受領元の tar.gz のサイズを比較する。→ 不一致。

これらのことから、データ受領元からローカルへダウンロードした際に破損したと判断できる。

files の再展開

破損していないファイルを再配置する。

ファイルが破損していないか確認する。

sudo gzip -t /opt/redmine/migration/redmine_files.tar.gz && echo "GZIP OK" || echo "GZIP NG"

実行結果

GZIP OK

展開に失敗したファイル群を削除する。

sudo rm -rf /var/lib/docker/volumes/redmine_redmine_files/_data/*
sudo rm -f /var/lib/docker/volumes/redmine_redmine_files/_data/.[!.]*

以下のコマンドで正常に削除されたことを確認する。

# 中身確認(隠しファイルも含む)
sudo ls -la /var/lib/docker/volumes/redmine_redmine_files/_data/

# サイズ確認(ほぼ0になっているはず)
sudo du -sh /var/lib/docker/volumes/redmine_redmine_files/_data/

# ファイル数(0件のはず)
sudo find /var/lib/docker/volumes/redmine_redmine_files/_data/ -type f | wc -l

以下のコマンドで files を再展開する。

sudo tar xzvf /opt/redmine/migration/redmine_files.tar.gz \
  -C /var/lib/docker/volumes/redmine_redmine_files/_data/ \
  --strip-components=1 \
  2>&1 | sudo tee /tmp/tar_extract.log

ログインページでID・パスワードを入力すると404エラー(インシデント3)

ログインページ:https://<IP>/redmine/login

ID・パスワードを入力すると、http://<IP>/login にリダイレクトされ、/redmine が消えている。

docker-compose.yml では以下を設定している。

RAILS_RELATIVE_URL_ROOT: /redmine

これにより、アプリはバックエンドに対し、/redmine を付与してリクエスト・レスポンスを送受信するはずだが、その設定が反映されていない。

Dockerの環境変数として定義されているだけで、Railsが読み込んでいない可能性がある。

明示的にこの変数を読み込む。

以下のコマンドで config.ru を作成する。

sudo tee /opt/redmine/config/config.ru > /dev/null <<'EOF'
# This file is used by Rack-based servers to start the application.

require_relative 'config/environment'

map (ENV['RAILS_RELATIVE_URL_ROOT'] || '/') do
  run Rails.application
end
EOF

sudo chown 999:999 /opt/redmine/config/config.ru

cat /opt/redmine/config/config.ru

map(Rackの機能)で指定パスにアプリをマウントする。これにより、Railsアプリが /redmine 配下で動作するようになる。

以下を参考に docker-compose.yml にマウント設定を追加する。

volumes:
  - redmine_files:/usr/src/redmine/files
  - /opt/redmine/config/config.ru:/usr/src/redmine/config.ru  # 追加

コンテナを再起動する。

sudo docker compose restart redmine

データ移行後にログインできなくなった(インシデント4)

既存環境ではLDAPのSSO認証を使用していたためと思われるが、一応ログを確認する。

sudo docker logs -f redmine

# 出力例
An error occurred when authenticating [username]: LDAP: getaddrinfo: Name or service not known

やはりLDAP認証になっているため、内部認証(Basic認証)へ変更する。

次のコマンドでBasic認証に書き換える。※以下は書き換えること。

  • User.find_by(login: '[username]')
  • TempPass123:任意のパスワード
sudo docker exec -it redmine bash -c "cd /usr/src/redmine && bundle exec rails runner -e production \
\"u = User.find_by(login: 'your_username'); \
u.auth_source_id = nil; \
u.password = 'TempPass123'; \
u.password_confirmation = 'TempPass123'; \
u.must_change_passwd = false; \
u.save!; \
puts 'OK: ' + u.login + ' / auth_source_id: ' + u.auth_source_id.to_s\""

以下、解説。これらを1行で書いたものが上記。

sudo docker exec -it redmine bash
# コンテナ内に入る

bundle exec rails console -e production
# Railsコンソールを起動

u = User.find_by(login: '[username]')
# ユーザーを取得

# u.auth_source_id
# 現在の認証方式を確認したい場合

u.auth_source_id = nil
# Basic認証(内部認証)へ切り替え

u.password = 'TempPass123'
u.password_confirmation = 'TempPass123'

u.save!

コメント

タイトルとURLをコピーしました