実際の問題:CentOS Stream 9へのMySQL 8インストールで繰り返すトラブル
2021年にCentOS 8がEOLを迎えた際、1週間で5台のサーバーをRocky Linuxに急いで移行しました。その中でも最も痛い教訓だったのがデータベース周りの問題です。それらのサーバーはデフォルトリポジトリのMariaDBを使っていましたが、アプリケーションはMySQL 8専用の書き方をしていました。その結果、ストアドプロシージャの構文が2つのエンジン間で異なること、JSONパス式の動作が不安定なこと、そして誰も移行前に気づかなかった数多くのエッジケースのデバッグに、さらに2日間を費やすことになりました。
その経験から、シンプルなルールを学びました:CentOS Stream 9にMySQL 8をセットアップするなら、最初から正しく行う必要があります。Oracleの公式リポジトリからインストールし、SELinuxを無効化せずに正しく設定し、firewalldを管理し、実際のワークロードに合わせてInnoDBをチューニングする。この記事はまさにそのワークフローを記録したものです。
分析:なぜdnf install mysql-serverは間違いなのか?
CentOS Stream 9でdnf install mysql-serverを実行しても、MySQLはインストールされません――インストールされるのはMariaDB(MySQLのフォーク)です。この2つのエンジンは、いくつかの重要な点で異なります:
- JSONサポート:MySQL 8はgenerated columnによるインデックス付きのネイティブJSONをサポートしていますが、MariaDBの実装は異なります
- ウィンドウ関数:MySQL 8はSQL:2003に完全準拠していますが、MariaDBには一部標準から逸脱した部分があります
- デフォルト認証:MySQL 8は
mysql_native_passwordの代わりにcaching_sha2_passwordを使用します。Connector/J 5.1やlibmysqlclient < 8.0などの古いクライアントは接続エラーが発生します――明示的な設定かドライバのアップグレードが必要です - InnoDBエンジン:MySQL 8にはパラレルread-aheadやアダプティブハッシュインデックスなど、MariaDBにはない多くの改善が含まれています
アプリケーションがMySQL専用に書かれている場合、MariaDBを使うと追跡困難なバグが発生します――特に複雑なトリガーやJSON操作において顕著です。
CentOS Stream 9にMySQL 8をインストールする方法
方法1:MySQL Communityリポジトリ(推奨)
Oracleによる公式の方法です――リポジトリには常に最新バージョンが用意され、GPGキーが自動的に検証され、dnf updateで通常通りアップデートできます。
方法2:RPMパッケージの手動ダウンロード
インターネットに接続できないエアギャップ環境に適していますが、依存関係とバージョンを自分で管理する必要があります――セキュリティパッチ適用時が非常に面倒です。
方法3:ソースからコンパイル
特殊なカスタムビルドフラグ(カスタムTLSライブラリ、非標準パスなど)が必要な場合に限ります。よほど具体的な技術的理由がない限り、本番環境では行わないでください。
最善の方法:MySQL公式リポジトリからインストール
ステップ1:MySQLリポジトリを追加
MySQLのウェブサイトからRPMパッケージをダウンロードします――このパッケージはGPGキーを自動追加し、すべてのチャンネル(community、tools、connectors)を設定します:
# EL9(CentOS Stream 9 / RHEL 9)向けMySQL repoパッケージをダウンロード
sudo rpm -Uvh https://dev.mysql.com/get/mysql84-community-release-el9-1.noarch.rpm
# リポジトリがアクティブかどうかを確認
dnf repolist | grep mysql
MySQL 8.4の代わりにMySQL 8.0をインストールしたい場合は、チャンネルを切り替える必要があります:
# 8.4を無効化し、8.0を有効化
sudo dnf config-manager --disable mysql-8.4-lts-community
sudo dnf config-manager --enable mysql80-community
ステップ2:MySQL Serverのインストール
# MySQL Community Serverをインストール
sudo dnf install mysql-community-server -y
# サービスを起動して自動起動を有効化
sudo systemctl start mysqld
sudo systemctl enable mysqld
# ステータスを確認
sudo systemctl status mysqld
ステップ3:一時パスワードの取得とセキュリティスクリプトの実行
MySQL 8は初回起動時にランダムなパスワードを自動生成します――ログから確認できます:
sudo grep 'temporary password' /var/log/mysqld.log
出力例:A temporary password is generated for root@localhost: Xk9!mNpQ2#rL
セキュリティスクリプトを実行します――本番環境では、どのステップも省略しません:
sudo mysql_secure_installation
- Validate password component:YES(本番環境ではSTRONGレベルを選択)
- Remove anonymous users:YES
- Disallow root login remotely:YES――重要、省略不可
- Remove test database:YES
- Reload privilege tables:YES
MySQL 8のSELinux設定
このセクションはほとんどのadminが見落としがちで、setenforce 0で素早く回避しようとしがちです。これは典型的な間違いです。SELinuxは敵ではありません――正しいコンテキストを設定するだけで、5分もあれば完了します。
SELinuxコンテキストの確認
# MySQLプロセスが正しいコンテキストで実行されているか確認
ps auxZ | grep mysqld
# 期待値:system_u:system_r:mysqld_t:s0
# データディレクトリのコンテキストが正しいか確認
ls -lZ /var/lib/mysql/
# 期待値:system_u:object_r:mysqld_db_t:s0
カスタムデータディレクトリを使用する場合
本番環境でOSボリュームとデータボリュームを分離してデータを別ディスクにマウントする場合(非常に一般的な構成)、MySQLを起動する前にSELinuxコンテキストを設定する必要があります:
# 新しいデータディレクトリを作成
sudo mkdir -p /data/mysql
sudo chown -R mysql:mysql /data/mysql
# SELinuxコンテキストを設定
sudo semanage fcontext -a -t mysqld_db_t "/data/mysql(/.*)?"
sudo restorecon -Rv /data/mysql
# my.cnfに追記
sudo bash -c 'echo "datadir=/data/mysql" >> /etc/my.cnf'
# 新しいデータディレクトリを初期化
sudo mysqld --initialize --user=mysql
SELinuxを通じてMySQLポートを許可
# ポート3306が許可されているか確認
sudo semanage port -l | grep mysqld
# mysqld_port_t tcp 1186, 3306, 63132-63164
# 別のポートを使用する場合(例:3307)
sudo semanage port -a -t mysqld_port_t -p tcp 3307
MySQLのfirewalld設定
本番環境ではポート3306を0.0.0.0に対して絶対に開放しないでください――あるサーバーが継続的なブルートフォーススキャンを受けたのを目の当たりにしましたが、その原因はadminがデバッグのために一時的にMySQLをインターネットに公開し、閉じ忘れたからでした。
特定のIPアドレスのみ接続を許可
# データベーストラフィック用の専用ゾーンを作成(デフォルトゾーンにルールを追加するよりクリーン)
sudo firewall-cmd --new-zone=dbzone --permanent
# アプリサーバーのIPを許可(192.168.1.100を実際のIPに変更)
sudo firewall-cmd --zone=dbzone --add-source=192.168.1.100/32 --permanent
sudo firewall-cmd --zone=dbzone --add-port=3306/tcp --permanent
# リロードして確認
sudo firewall-cmd --reload
sudo firewall-cmd --zone=dbzone --list-all
MySQL接続がブロックされた場合のデバッグ
# MySQLがバインドしているポートを確認
sudo ss -tlnp | grep mysqld
# アプリサーバーからテスト接続
mysql -h <db_ip> -u appuser -p -e "SELECT 1;"
# firewalldがパケットをブロックしているか確認
sudo journalctl -u firewalld -f
本番環境でのMySQL 8パフォーマンス最適化
MySQL 8のデフォルト設定は、約1GB RAMのマシン向けに調整されています。8GBや16GB RAMの本番サーバーでデフォルト設定を使い続けることは、リソースの無駄遣いです。以下のパラメータは、どんな本番サーバーでも最初に調整すべき設定項目です:
[mysqld]
# InnoDBバッファプール — 最重要設定、総RAMの70〜80%を設定
# 8GB RAMサーバー -> 6G、16GB RAMサーバー -> 12G
innodb_buffer_pool_size = 6G
# mutex contentionを減らすためにプールインスタンス数を増加
# ルール:バッファプール1GBあたり1インスタンス、最大64
innodb_buffer_pool_instances = 6
# 再実行ログ容量
# MySQL 8.0.x(8.0.30未満):innodb_log_file_size = 512M を使用
# MySQL 8.0.30以降 / 8.4 LTS:古い変数は廃止、このパラメータを使用(= log_file_size × 2)
innodb_redo_log_capacity = 1G
# ワークロードに応じた最大接続数(高すぎる設定は避ける — 各接続はRAMを消費)
max_connections = 200
# スロークエリログ — クエリのボトルネック検出のため本番環境では常に有効化
slow_query_log = 1
slow_query_log_file = /var/log/mysql-slow.log
long_query_time = 2
# ポイントインタイムリカバリとレプリケーション用バイナリログ
log_bin = /var/lib/mysql/mysql-bin
binlog_format = ROW
binlog_expire_logs_seconds = 604800
設定変更後にMySQLを再起動して確認します:
sudo systemctl restart mysqld
# innodb_buffer_pool_sizeが正しく適用されているか確認
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool%';"
実際のトラフィックが24時間経過した後にmysqltunerを実行するのが筆者の慣例です――推定ではなく実際のトラフィックに基づいて分析してくれます:
curl -L https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl -o mysqltuner.pl
perl mysqltuner.pl --user root --pass <password>
最小権限の原則に従ったデータベースユーザーの作成
-- アプリケーションをrootで接続させない
CREATE USER 'appuser'@'192.168.1.100' IDENTIFIED BY 'StrongP@ssword123!';
-- 特定のデータベースに必要な権限のみ付与
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'appuser'@'192.168.1.100';
-- 設定した権限を確認
SHOW GRANTS FOR 'appuser'@'192.168.1.100';
本番環境への移行前チェックリスト
- MySQLサービスが起動中で、再起動後も自動起動が有効になっている
mysql_secure_installationの実行が完了している- リモートからのrootログインがブロックされている
- データディレクトリのSELinuxコンテキストが正しい(
mysqld_db_t) - firewalldが特定のアプリサーバーIPに対してのみポート3306を開放している
innodb_buffer_pool_sizeが実際のRAMに合わせて設定されている(70〜80%)- スロークエリログが有効になっている
- ポイントインタイムバックアップが必要な場合はバイナリログが有効になっている
- アプリユーザーが必要最小限の権限で作成されている

