はじめに
今回は Linux システムにインストールした MySQL をサービスとして起動・停止管理する systemd にまつわる小ネタをご紹介します。
systemd が登場したころの一昔前は、 SysVinit ベース(一時 Upstart もありました)の OS との混在で mysqld サービスの起動や制御方法が異なるため、バージョンアップや運用で systemd そのものの理解や取り扱いに戸惑った方も多かったのではと思います。
現在は systemd ベースの OS が主流になっているかと思いますので systemctl
コマンドもだいぶ浸透してきたのではないでしょうか。
systemd ベースへの RPM インストール時や systemd サービス mysqld.service
の起動時の、あまり他では触れられていない内容を集めてみましたので、
『systemd を用いた MySQL Server の管理』に関しての基本的な内容は、公式マニュアルページに分かり易く書かれていますので、これから内容を知りたい方はまずは以下のリンクからご一読ください。
MySQL :: MySQL 8.4 Reference Manual :: 2.5.9 Managing MySQL Server with systemd
使用環境など
本記事は MySQL 8.4 LTS バージョンをメインに取り扱っていきます。
また、OS は Rocky Linux 8 (RHELディストリビューション) を使用しました。
RPM パッケージインストール時に実行される scriptlet の処理内容について
RPM にはパッケージのインストールや削除、アップグレードの前後でコードを実行することができる scriptlet という機能があります。
MySQL Server(mysql-community-server) のパッケージに仕込まれている scriptlet の処理内容を確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# rpm -qp --scripts mysql-community-server-8.4.0-1.el8.x86_64.rpm preinstall scriptlet (using /bin/sh): ・・・① /usr/sbin/groupadd -g 27 -o -r mysql >/dev/null 2>&1 || : /usr/sbin/useradd -M -N -g mysql -o -r -d /var/lib/mysql -s /bin/false \ -c "MySQL Server" -u 27 mysql >/dev/null 2>&1 || : postinstall scriptlet (using /bin/sh): ・・・② [ -e /var/log/mysqld.log ] || install -m0640 -omysql -gmysql /dev/null /var/log/mysqld.log >/dev/null 2>&1 || : if [ $1 -eq 1 ] ; then # Initial installation systemctl --no-reload preset mysqld.service &>/dev/null || : fi if [ $1 -eq 1 ]; then /usr/bin/systemctl enable mysqld >/dev/null 2>&1 || : fi preuninstall scriptlet (using /bin/sh): ・・・③ if [ $1 -eq 0 ] ; then # Package removal, not upgrade systemctl --no-reload disable --now mysqld.service &>/dev/null || : fi postuninstall scriptlet (using /bin/sh): ・・・④ if [ $1 -ge 1 ] ; then # Package upgrade, not uninstall systemctl try-restart mysqld.service &>/dev/null || : fi |
-
① パッケージインストール前に実行される処理です。
- 起動プロセスやファイルのオーナとなる、
mysql
という名前のグループ・ユーザがここで作成されています。 - グループIDとユーザIDは 27 でハードコードされています。
- 起動プロセスやファイルのオーナとなる、
-
② パッケージインストール後に実行される処理です。
/var/log/mysqld.log
(エラーログファイル) が存在し無かったらファイルが作成されます。- scriptlet の第一引数は規定値になっていて、postinstall の場合、
$1 == 1
ならインストールを意味しています。 systemctl preset
コマンドでまずはベンダーデフォルトの自動起動設定を行っておいて、
改めてsystemctl enable
コマンドを追加実行して、OS 起動時の自動起動を有効にしています。
-
③ パッケージのアンインストール前に実行される処理です。
- preuninstall の場合の第一引数ですが、
$1 == 0
はアンインストールという意味です。 - ここでは
systemctl disable
コマンドで OS 起動時の自動起動を無効にしています。
- preuninstall の場合の第一引数ですが、
-
④ パッケージのアンインストール後に実行される処理です。
- postuninstall の場合の第一引数
$1 == 1
はアップグレードを意味しています。 systemctl try-restart
は実行中なら再起動します(停止中の場合は何もしません)
- postuninstall の場合の第一引数
というように、パッケージのインストール/アンインストール前後で上記の処理が行われているのでした。
これらの説明は実はマニュアルに記載はありますがあっさりとしてた説明になっています。
The installation also creates a user named mysql and a group named mysql on the system.
(…)
If the operating system is systemd enabled, standard systemctl (or alternatively, service with the arguments reversed) commands such as stop, start, status, and restart should be used to manage the MySQL server service. The mysqld service is enabled by default, and it starts at system reboot. Notice that certain things might work differently on systemd platforms: for example, changing the location of the data directory might cause issues. See Section 2.5.9, “Managing MySQL Server with systemd” for additional information.
During an upgrade installation using RPM and DEB packages, if the MySQL server is running when the upgrade occurs then the MySQL server is stopped, the upgrade occurs, and the MySQL server is restarted. One exception: if the edition also changes during an upgrade (such as community to commercial, or vice-versa), then MySQL server is not restarted.
サービスの自動起動設定や OS の mysql ユーザーが作成されるタイミングやその設定内容が具体的になったかと思います。
systemd ユニットファイル内で実行される mysqld_pre_systemd の処理内容について
RPM パッケージをインストールすると、 MySQL Server 用の systemd ユニットファイルが以下の場所に配置されます。
1 2 3 |
# find /usr/lib/systemd/system -name "mysqld*" -print /usr/lib/systemd/system/mysqld.service /usr/lib/systemd/system/mysqld@.service |
mysqld.service
はシングルインスタンス用、 mysqld@.service
はマルチインスタンス用のユニットファイルです。
※マルチインスタンス構成については以下のマニュアルページをご参照ください。
MySQL :: MySQL 8.4 Reference Manual :: 2.5.9 Managing MySQL Server with systemd
ユニットファイルの中身を見てみましょう。
1 |
# cat /usr/lib/systemd/system/mysqld.service |
以下は今回説明する関連箇所の抜粋です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
: [Service] User=mysql Group=mysql Type=notify # Disable service start and stop timeout logic of systemd for mysqld service. TimeoutSec=0 # Execute pre and post scripts as root # hence, + prefix is used # Needed to create system tables ExecStartPre=+/usr/bin/mysqld_pre_systemd # Start main service ExecStart=/usr/sbin/mysqld $MYSQLD_OPTS # Use this to switch malloc implementation EnvironmentFile=-/etc/sysconfig/mysql : |
ExecStart
で /usr/sbin/mysqld
を実行しています。notify
タイプのバックグラウンドサービスとして起動する設定です。
今回取り上げるポイントは ExecStartPre
で設定されている /usr/bin/mysqld_pre_systemd
スクリプトです。
※ ExecStartPre
はその名の通り ExecStart
を実行する前に実行するコマンドやスクリプトを設定します。
以下はマルチインスタンス用 (mysqld@.service
) の設定ですが、インスタンス識別子が引数として渡される設定になっています。
1 2 3 4 5 |
# Needed to create system tables ExecStartPre=+/usr/bin/mysqld_pre_systemd %I # Start main service ExecStart=/usr/sbin/mysqld --defaults-group-suffix=@%I $MYSQLD_OPTS |
mysqld_pre_systemd
スクリプトで実行される内容も、実は公式マニュアルにさらっと記載があります。
At the initial start up of the server, the following happens, given that the data directory of the server is empty:
The server is initialized.
An SSL certificate and key files are generated in the data directory.
validate_password is installed and enabled.
※ An SSL certificate and key files are generated in the data directory.
については 8.4 からは削除されたのですが、ドキュメントがまだ直っていないようです。(これについては後述)
あとここの説明もですね。
MySQL :: MySQL 8.4 Reference Manual :: 2.5.9 Managing MySQL Server with systemd
For platforms that use systemd, the data directory is initialized if empty at server startup. This might be a problem if the data directory is a remote mount that has temporarily disappeared: The mount point would appear to be an empty data directory, which then would be initialized as a new data directory. To suppress this automatic initialization behavior, specify the following line in the /etc/sysconfig/mysql file (create the file if it does not exist):
1 NO_INIT=true
今回はスクリプト自体を穿って見てみましょう。
※ソースコードの所在を載せておきます。
mysql-server/scripts/systemd/mysqld_pre_systemd.in at 8.4 · mysql/mysql-server · GitHub
説明の都合上、スクリプトの処理順に細切れに見ていきます。(左端はコード行番号)
install_db
が引数付きで実行されます。
$1はマルチインスタンス用のユニットファイルから呼び出される場合はインスタンス識別子が渡されます。
1 2 3 4 |
108 109 install_db $1 110 111 exit 0 |
まずは mysql_upgrade_history
ファイルのオーナ設定(&SELinux有効時のコンテキスト一時変更)が行われます。
1 2 3 4 5 6 |
59 install_db () { 60 # Note: something different than datadir=/var/lib/mysql requires SELinux policy changes (in enforcing mode) 61 62 # mysql_upgrade_history file should be owned by mysql user since MySQL 8.4 (new file in 8.4) 63 fix_mysql_upgrade_history 64 |
1 2 3 4 5 6 7 8 9 |
49 fix_mysql_upgrade_history () { 50 datadir=$(get_option mysqld datadir "/var/lib/mysql${instance:+-$instance}" $instance) 51 if [ -d "$datadir" ] && [ -O "$datadir/mysql_upgrade_history" ]; then 52 chown --reference="$datadir" "$datadir/mysql_upgrade_history" 53 if [ -x /usr/bin/chcon ]; then 54 /usr/bin/chcon --reference="$datadir" "$datadir/mysql_upgrade_history" > /dev/null 2>&1 55 fi 56 fi 57 } |
8.0 では mysql_upgrade_info
というファイル名でした(8.0.17 で非推奨となりました)が、8.4 では mysql_upgrade_history
という JSON形式のファイルでインストール情報が記録されるようになりました。
※インストール日などの情報が記録されています。
1 2 3 4 5 6 7 8 9 10 11 12 |
# cat /var/lib/mysql/mysql_upgrade_history | jq -r { "file_format": "1", "upgrade_history": [ { "date": "2024-06-30 01:48:21", "version": "8.4.0", "maturity": "LTS", "initialize": true } ] } |
以下の部分は、前述のマニュアル記載の紹介にあった内容です。
/etc/sysconfig/mysql
ファイルに NO_INIT=true
と設定しておくと、このスクリプトの処理自体がここで終了します。(つまりこの処理をスキップするという意味です)
1 2 3 4 |
65 # No automatic init wanted 66 [ -e /etc/sysconfig/mysql ] && . /etc/sysconfig/mysql 67 [ -n "$NO_INIT" ] && exit 0 68 |
次に、データディレクトリとエラーログが作成されるパートになります。
get_option
という関数で my_print_defaults
ツールを使ってオプションファイル(my.cnfなど)からパラメータ値を取得します。
オプションファイルで datadir
と log_error
の指定がない場合は、デフォルト(/var/lib/mysql
,/var/log/mysql.log
)が作成されます。(いずれも存在し無い場合)
マルチインスタンスの場合は、インスタンス識別子がデータディレクトリ名やログファイル名のサフィックスに設定されるようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
69 local instance=$1 70 datadir=$(get_option mysqld datadir "/var/lib/mysql${instance:+-$instance}" $instance) 71 log=$(get_option mysqld 'log[_-]error' "/var/log/mysql${instance:+-$instance}.log" $instance) 72 73 # Restore log, dir, perms and SELinux contexts 74 75 if [ ! -d "$datadir" -a ! -h "$datadir" -a "x$(dirname "$datadir")" = "x/var/lib" ]; then 76 install -d -m 0751 -omysql -gmysql "$datadir" || exit 1 77 fi 78 79 if [ ! -e "$log" -a ! -h "$log" -a x$(dirname "$log") = "x/var/log" ]; then 80 case $(basename "$log") in 81 mysql*.log) install /dev/null -m0640 -omysql -gmysql "$log" ;; 82 *) ;; 83 esac 84 fi 85 86 if [ -x /usr/sbin/restorecon ]; then 87 /usr/sbin/restorecon "$datadir" 88 [ -e "$log" ] && /usr/sbin/restorecon "$log" 89 for dir in /var/lib/mysql-files /var/lib/mysql-keyring ; do 90 if [ -x /usr/sbin/semanage -a -d /var/lib/mysql -a -d $dir ] ; then 91 /usr/sbin/semanage fcontext -a -e /var/lib/mysql $dir >/dev/null 2>&1 92 /sbin/restorecon -r $dir 93 fi 94 done 95 fi 96 |
SELinux が有効な環境では作成したデータディレクトリとエラーログファイルにコンテキストの設定も行われています。
※MySQL の SELinux コンテキストについては弊社過去ブログ記事をご覧ください。
MySQLのSELinuxでエラーになった際の対処法4つ | スマートスタイル TECH BLOG
そして、ようやくデータディレクトリの初期化を行うセクションです。
1 2 3 4 5 6 7 8 9 10 11 |
97 # If special mysql dir is in place, skip db install 98 [ -d "$datadir/mysql" ] && exit 0 99 100 # Create initial db and install validate_password plugin 101 initfile="$(install_validate_password_sql_file)" 102 /usr/sbin/mysqld ${instance:+--defaults-group-suffix=@$instance} --initialize \ 103 --datadir="$datadir" --user=mysql --init-file="$initfile" 104 rm -f "$initfile" 105 106 exit 0 107 } |
ポイントとしては、既に {datadir}/mysql
ディレクトリ、つまり データディレクトリ内に mysql システムデータベース用ディレクトリが存在する(既にデータディレクトリが初期化済みと見做す)場合は、再度初期化を行わず処理終了となります。
逆の見方をすると、{datadir}/mysql
ディレクトリが無い場合はデータディレクトリの初期化が行われてしまうことになります。
なので、例えば以下のような都合によりこのスクリプトによるデータディレクトリの自動初期化が行われないようにしたい場合は、前述の NO_INIT=true
を /etc/sysconfig/mysql
に設定しておいてスキップするのが役に立ちます。
- データディレクトリの初期化を行う場合は手動で明示的に実施する運用ルールとしている
- (マニュアルに記載の通り)一時的にデータディレクトリのマウントが外れてしまった状態で誤って自動的に初期化が行われないようにしたい、など。
そして、データディレクトリの初期化の際、validate_password
コンポーネントが同時にインストールされます。
※インストールで一時的に生成するファイル名が昔の名残で plugin になっていますが…
1 2 3 4 5 6 7 8 |
40 install_validate_password_sql_file () { 41 local initfile 42 initfile="$(mktemp /var/lib/mysql-files/install-validate-password-plugin.XXXXXX.sql)" 43 chmod a+r "$initfile" 44 echo "SET @@SESSION.SQL_LOG_BIN=0;" > "$initfile" 45 echo "INSERT INTO mysql.component (component_id, component_group_id, component_urn) VALUES (1, 1, 'file://component_validate_password');" >> $initfile 46 echo $initfile 47 } |
初回起動を行う前に手動で mysqld --initialize
を行ってしまった場合は、(必要に応じて)別途手動で validate_password
コンポーネントをインストールしてください。
以上が mysqld_pre_systemd
スクリプトの処理内容でした。
普段何気なく実行している systemd start mysqld
コマンドは、実はこのようなスクリプトが(サービス起動前に)実行されるようになっていたのでした。
(そもそも ExecStartPre
をオーバーライドするかコメントアウトなりでこのスクリプトを実行しないことも可能ですが…)
ちなみに 8.0 の mysqld_pre_systemd
スクリプト にはデータディレクトリ初期化の後に SSL/RSA 証明書を生成するコードがありますが、8.4 では mysql_ssl_rsa_setup
が廃止されました(※)ので該当コードは削除されています。
WL#16205: Remove the deprecated mysql_ssl_rsa_setup · mysql/mysql-server@b75bcb6 · GitHub
※詳細については、先日公開した以下の弊社ブログ記事をご一読ください。
MySQL 8.4 におけるSSL および RSA 証明書とキーの作成 | スマートスタイル TECH BLOG
MySQL 8.2 以降で強化された systemd 通知について
最後に、MySQL 8.2 で追加された機能の紹介です。
mysqld.service
は notify
タイプのサービスとして起動されることを上述しました。
MySQL 8.2 (Innovation release) では、systemd 通知が強化され systemctl status mysqld.service
を実行したときの詳細状況が表示されるようになりました。(勿論、それ以降のバージョンでも表示されます)
Oracle 社の以下ブログ記事で詳細がアナウンスされていますのでこちらをご一読いただくことをお勧めいたします。
MySQLのsystemd通知が機能強化されました | The Oracle MySQL Japan Blog
(※原文ページはこちら)
具体的にどこに表示されるかというと、下記出力の Status: "Server is operational"
の箇所です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# systemctl status mysqld ● mysqld.service - MySQL Server Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled) Active: active (running) since Tue 2024-07-02 13:57:53 JST; 20h ago Docs: man:mysqld(8) http://dev.mysql.com/doc/refman/en/using-systemd.html Process: 2661 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS) Main PID: 2684 (mysqld) Status: "Server is operational" Tasks: 34 (limit: 4915) Memory: 427.2M CGroup: /system.slice/mysqld.service └─2684 /usr/sbin/mysqld Jul 02 13:57:52 mysql-8401-ro8 systemd[1]: Starting MySQL Server... Jul 02 13:57:53 mysql-8401-ro8 systemd[1]: Started MySQL Server. |
Oracle社のブログ記事を元に、分かり易くまとめてみました。
青字が 8.2 以降で追加された通知メッセージになります。
ワークフロー | 通知メッセージ |
---|---|
初期化中の通知 | Server Initialization in progress |
InnoDB Initialization in progress InnoDB Initialization [unsuccessful/successful] |
|
Initialization of MySQL system tables in progress Initialization of MySQL system tables [unsuccessful/successful] |
|
Execution of SQL Commands from Init-file in progress Execution of SQL Commands from Init-file [unsucccessful/successful] |
|
Server Initialization complete [1/0] | |
起動中の通知 | Server startup in progress |
InnoDB Initialization in progress InnoDB Initialization [unsuccessful/successful] |
|
InnoDB crash recovery in progress InnoDB crash recovery [unsuccessful/successful] |
|
Initialization of dynamic plugins in progress Initialization of dynamic plugins [unsuccessful/successful] |
|
Server upgrade in progress Server upgrade complete |
|
Components initialization in progress Components initialization [unsuccessful/successful] |
|
Server is operational | |
シャットダウン中の通知 | Server shutdown in progress |
Graceful shutdown of connections in progress | |
Shutdown of replica threads in progress | |
Forceful shutdown of connections in progress | |
Connection shutdown complete | |
Shutdown of plugins in progress Shutdown of plugins complete |
|
Shutdown of components in progress Shutdown of components complete |
|
Server shutdown complete |
追加されたメッセージの数から分かる通り、非常に細やかな状態遷移を通知してくれるようになりました。
具体的なフローチャートが記事中で公開されていて分かり易いので、こちらも確認してみてください。
[ 引用:Enhancing systemd notifications in MySQL | The Oracle MySQL Blog]
そして、このステータスの遷移状況はエラーログにも記録されるようになりました。
本機能自体の有効化などの追加設定は不要ですが、 systemd 通知メッセージは INFOMATION レベル(Note
) で出力されるので log_error_verbosity
をデフォルトの 2 から 3 に変更する必要があります。
1 2 3 4 5 |
-- オンラインで変更 SET GLOBAL log_error_verbosity=3; -- または、my.cnf の [mysqld] セクションに以下を追記して再起動 log_error_verbosity=3 |
MySQL エラー番号 MY-013930
で出力されるようになります。以下は正常に起動した場合の出力内容です。
メッセージの先頭に systemd notify
と付いていますので分かり易いです。
1 2 3 4 5 6 7 8 9 10 11 |
# grep MY-013930 /var/log/mysqld.log 2024-07-02T13:57:52.200980+09:00 0 [Note] [MY-013930] [Server] systemd notify: STATUS=Server startup in progress 2024-07-02T13:57:52.561958+09:00 1 [Note] [MY-013930] [Server] systemd notify: STATUS=InnoDB initialization in progress 2024-07-02T13:57:53.188352+09:00 1 [Note] [MY-013930] [Server] systemd notify: STATUS=InnoDB initialization successful 2024-07-02T13:57:53.306968+09:00 1 [Note] [MY-013930] [Server] systemd notify: STATUS=InnoDB crash recovery in progress 2024-07-02T13:57:53.378240+09:00 1 [Note] [MY-013930] [Server] systemd notify: STATUS=InnoDB crash recovery successful 2024-07-02T13:57:53.395289+09:00 0 [Note] [MY-013930] [Server] systemd notify: STATUS=Initialization of dynamic plugins in progress 2024-07-02T13:57:53.403364+09:00 0 [Note] [MY-013930] [Server] systemd notify: STATUS=Initialization of dynamic plugins successful 2024-07-02T13:57:53.520408+09:00 0 [Note] [MY-013930] [Server] systemd notify: STATUS=Components initialization in progress 2024-07-02T13:57:53.523242+09:00 0 [Note] [MY-013930] [Server] systemd notify: STATUS=Components initialization successful 2024-07-02T13:57:53.548611+09:00 0 [Note] [MY-013930] [Server] systemd notify: READY=1 STATUS=Server is operational MAIN_PID=2684 |
パフォーマンススキーマの error_log テーブルにも同一内容が記録されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
mysql> SELECT LOGGED, SUBSYSTEM, DATA FROM performance_schema.error_log WHERE ERROR_CODE="MY-013930" LIMIT 10; +----------------------------+-----------+-----------------------------------------------------------------------+ | LOGGED | SUBSYSTEM | DATA | +----------------------------+-----------+-----------------------------------------------------------------------+ | 2024-07-02 13:51:18.415204 | Server | systemd notify: STATUS=Server startup in progress | | 2024-07-02 13:51:18.905427 | Server | systemd notify: STATUS=InnoDB initialization in progress | | 2024-07-02 13:51:19.600105 | Server | systemd notify: STATUS=InnoDB initialization successful | | 2024-07-02 13:51:19.725325 | Server | systemd notify: STATUS=InnoDB crash recovery in progress | | 2024-07-02 13:51:19.797191 | Server | systemd notify: STATUS=InnoDB crash recovery successful | | 2024-07-02 13:51:19.814817 | Server | systemd notify: STATUS=Initialization of dynamic plugins in progress | | 2024-07-02 13:51:19.823862 | Server | systemd notify: STATUS=Initialization of dynamic plugins successful | | 2024-07-02 13:51:19.945796 | Server | systemd notify: STATUS=Components initialization in progress | | 2024-07-02 13:51:19.948924 | Server | systemd notify: STATUS=Components initialization successful | | 2024-07-02 13:51:19.976001 | Server | systemd notify: READY=1 STATUS=Server is operational MAIN_PID=2385 | +----------------------------+-----------+-----------------------------------------------------------------------+ 10 rows in set (0.00 sec) |
デフォルトよりもログ出力量が多少増えてしまいますが、起動や停止が通常よりも時間がかかっている、なかなか終わらないような状況に陥ったときのようなトラブルシューティングに非常に有益な情報となります。
ログ監視しておくのも良いでしょう。
ちなみに…
標準バイナリ(tarball)パッケージでも以下の MySQL Secure Deployment Guide の手順に沿って systemd が利用可能なようにセットアップすることも可能ですが、
MySQL :: MySQL Secure Deployment Guide :: 5 Post Installation Setup
今回ご紹介した systemd 通知は、確認したところ標準バイナリパッケージでも表示されましたのでご安心ください。
まとめ
ちょっと細かい内容でしたが、意外と知っていると役立つかもしれない systemd mysqld サービスの小ネタ紹介でした。
インストール作業や運用で気になったときやトラブルシューティングのときなどに、ふと思い出してもらえたら幸いです。