[!] この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
この記事は、OpenStack の DBaaS(Trove)導入において、データベースサーバーが正常動作しなかった場合の解析・対処について説明します。問題解析編 1 の続きとなっていますので、読んでいない方はそちらからご覧ください。
PostgreSQL サーバーが正常に起動しない問題の解析(後半)
発生事象
- 本問題の前半部分にあたる解析・対処を行ったが、PostgreSQL データストアを指定して Trove インスタンスを起動してもまだ正常起動しない。
- Dashboard 画面上で Trove インスタンスがしばらく「作成中」状態のままとなり、その後「失敗」状態となる。
- Dashboard のコンピュートインスタンスの画面に同名のインスタンスができるが、こちらは「実行中」状態となっている。
問題の解析
Trove プロセスのログ調査
前半と同様に、まず OpenStack サーバーホスト上の各 Trove プロセスのログから確認していきます。
コントローラーノード:
/var/log/trove/trove.log
ログから、前半と同様の流れが見えてきます。 trove-api は、Create database instance 要求を受けて、trove-taskmanager に処理を渡しています。 また、要求の受付は 200 OK で返しています。
POST リクエストは Trove の API 一覧 ⧉と比較して、Create database instance という要求であるとわかります。
《ログを開く/閉じる》
trove-taskmanager の API で create_instance という要求を出しています。
《ログを開く/閉じる》
POST リクエストの受け付けは 200 OK となっています。
《ログを開く/閉じる》
trove-taskmanager 側のログを確認していきます。
コントローラーノード:
/var/log/trove/trove-taskmanager.log
ログから、前半と同様に進んでいます。 trove-taskmanager は、Trove インスタンス用の仮想マシンを作成し、trove-guestagent に prepare 要求を出しますが、 時間経過後にエラーとなっています。
まず、Trove インスタンスの作成が開始され、Neutron(ネットワーク)やボリュームの処理を呼び、その後、仮想マシンインスタンスを作成しています。
《ログを開く/閉じる》
新規インスタンス上にいる、trove-guestagent の API で prepare を要求しています。
《ログを開く/閉じる》
このあたりから処理の完了待ちです。
《ログを開く/閉じる》
約 11 分経過後、前半と同様に、エラーになっています。他のログの調査も必要です。
《ログを開く/閉じる》
trove-conductor のログを確認します。
コントローラーノード:
/var/log/trove/trove-conductor.log
ログから、最終的にやはり failed to spawn 状態の通知を受けています。
はじめに、building 状態の通知です。
《ログを開く/閉じる》
failed to spawn 状態の通知です。また、Database failed to stop.
のメッセージが見えています。
《ログを開く/閉じる》
データベース操作の詳細は trove-guestagent が担当しているため、Trove インスタンスに入って確認していきます。
Trove インスタンスの調査
Trove インスタンスにログインし、状況を確認していきます。
trove-guestagent のログ確認
Trove インスタンス:
/var/log/trove/logfile.txt
ログから、エラーの発生は前半と同様になっているように見えます。
trove-guestagent は prepare 処理を開始し、設定変更のため一旦 PostgreSQL サーバーを停止して状態監視を行いますが、10 分後にタイムアウトして停止の失敗としてエラーになっています。
まずは、prepare 処理の開始です。
《ログを開く/閉じる》
設定変更のためにデータベースサービスを停止しています。
《ログを開く/閉じる》
pg_isready コマンドで PostgreSQL サーバーの状態を確認し、shutdown 状態になるのを待っています。
《ログを開く/閉じる》
約 10 分後にエラーとなりました。running 状態から shutdown 状態に変わるのを待っていたがタイムアウトしたと出ています。
《ログを開く/閉じる》
前半で実施した PostgreSQL サーバー停止に関する修正が効かなかったのかどうかを調べていきます。
postgresql / postgresql@9.6-main サービス状態確認
systemctl コマンドで、PostgreSQL サービスの状態を確認します。
postgresql.service は停止、postgresql@9.6-main.service は動作中の状態です。
PostgreSQL 応答確認
ログに出力されていたコマンドで PostgreSQL サーバーの生存を確認してみます。
PostgreSQL サーバーは動作中に見えます。
PostgreSQL プロセス確認
PostgreSQL のプロセスを確認します。
PID 1211 の postgres プロセスが実行中です。
syslog 確認
念のため確認すると、/var/log/syslog には、前半で修正前に出ていたエラーメッセージ postgresql@9.6-main.service: Can't open PID file /run/postgresql/9.6-main.pid (yet?) after start: No such file or directory
は出ていません。
PID file は /run/postgresql/postgresql.pid で、中身のプロセス ID も合っています。
systemd の PostgreSQL 用設定確認
前半で確認したように、PostgreSQL のサービスは postgresql.service と postgresql@9.6-main.service に分かれています。
/lib/systemd/system/postgresql.service
/lib/systemd/system/postgresql@.service
/lib/systemd/system/postgresql@.service ファイルの PIDFile 設定行は、前半での対処により、PIDFile=/var/run/postgresql/postgresql.pid
に正しく修正されています。
Trove guest agent 処理の調査
前半で PostgreSQL サービス停止時の不具合に関する最初の問題対処をしたものの、/var/log/syslog ファイルのエラーが消えた以外に大きな変化は見られず、ログからはこれ以上確認できない方法で制御にまだ失敗しているようなので、PostgreSQL サービス停止部分の処理について詳しく調べてみます。
走行コードの抽出・トレース
trove-guestagent のログで、
の部分が実際にうまく停止制御できていなかった部分のログにあたりますが、タイムアウト以外のエラーは特に出ていませんでした。
また、前半で修正した systemd 管理用の /lib/systemd/system/postgresql@.service ファイルについては、修正の結果エラーが出なくなったことを確認できましたが、上記ログでは sudo service postgresql stop
コマンドで停止を実施したような出力になっており、systemd ではない別のサービス管理手段で行っているように見えるため少し違和感を感じます。二者に何か関連があるのでしょうか?
ここからは、Trove インスタンス中の ~/trove/、またはコントローラー上の Trove guest agent ファイル格納場所にあるソースを参照し調べていきます。
Trove のログには、基本的にはログ出力したコードのソースの場所と行番号が付加されているので、ソースを参照しメッセージ出力行を確認しつつ、追っていけばよいでしょう。ログのメッセージから離れてトレースしていくときは、共通処理部分のコードまたは PostgreSQL 専用の処理コードという条件で絞って探していきます。
例えば、
については、以下のコードが見つかります。
trove/guestagent/datastore/experimental/postgresql/manager.py
《ソース詳細を開く/閉じる》
上記最後の処理にある self.app は、以下コードにより初期化され、PgSqlApp のインスタンスとなっています。
trove.guestagent.datastore.experimental.postgresql.service の PgSqlApp クラスは以下に見つかります。
trove/guestagent/datastore/experimental/postgresql/service.py
stop_db から PgSqlAppStatus の stop_db_service を呼んでいますが、PgSqlAppStatus クラスのコードに stop_db_service メソッドは無く、ベースクラスの BaseDbStatus に見つかります。
trove/guestagent/datastore/service.py
ここでも、ログに記録されているメッセージ出力が見つかります。
ここで先に、サービス停止の完了判定方法を見ておくと、以下のように、サービスが目的の状態に変化するか、タイムアウトするまで監視する処理が入っています。
trove/guestagent/datastore/service.py
PostgreSQL の状態は、以下のように pg_isready コマンドの実行結果で判定しています。
trove/guestagent/datastore/experimental/postgresql/service.py
pg_isready を使って状態を判定しましたが、動作中(RUNNING)で返っていたため、タイムアウトするまで停止を待ちました。生存中の postgres プロセスが、ps コマンドでも見えていました。
次に、問題のサービス停止処理を確認します。
《ソース詳細を開く/閉じる》
trove/guestagent/common/operating_system.py
service = service_discovery(service_candidates)
の結果で見つかったサービス管理方法を使い、service_candidates (ここでは 'postgresql')のサービスを操作(停止)しています。
service_discovery の処理を見ると、システムに特徴的なファイルが存在しているかどうかにより、どのサービス管理用コマンドを利用できるのかを決定していることがわかります。 実際にファイルを確認してみると、以下のように sysvinit と systemd の判定で確認しているファイルがそれぞれ見つかりますが、ソースでの処理順上先にチェックしている sysvinit の方が採用されていると思います。
sysvinit が選ばれた場合、sudo systemctl stop postgresql
コマンドではなく、以下コードにしたがい、ログにあったように sudo service postgresql stop
コマンドでサービス停止が行われることとなっています。
trove/guestagent/common/operating_system.py
service コマンドの調査
使用されているのが systemctl でなく service コマンドでよいのか、という疑問を持ちつつ、service コマンドの詳細を確認していきます。
service コマンドは上記にあるシェルスクリプトとなっています。
《ソース詳細を開く/閉じる》
/usr/sbin/service
と、いきなり冒頭で systemd のチェックをしており、systemd を考慮に入れた処理になっているようです。
以降のコードは、
と続き、さらに
と、結局 systemctl コマンドによる停止方法を選択して実行するルートを通るようです。
ただし、上記コードで重要な注意点が 1 つありました。
コードをよく見ると、システムの立ち上げ/シャットダウン時のような、multi-user.target が ACTIVE でないタイミングでは、systemctl コマンドに --job-mode=ignore-dependencies
パラメータが追加されて実行されるようになっていました。
このパラメータの効果により、systemd による制御が行われる際に、ユニットの依存関係が無視されて実行されるということです。
これが、PostgreSQL サービスの制御にとって致命的な問題を引き起こしていました。
《問題原因》
既に見たように、postgresql.service は postgresql@.service と依存関係があり、postgresql.service の中身はほとんど空で、postgresql@.service 側に実際のプロセス停止などの有効な処理が全て定義されていました。
systemctl の --job-mode=ignore-dependencies
オプション付きで postgresql.service に対し stop を行うと、postgresql.service 自体に定義された処理は正常に完了するものの、ユニットの依存関係は無視されるため、postgresql@.service 側の処理が実行されず、その結果 PostgreSQL のプロセスを停止する実処理も起動されないため正しい停止状態にならないようです。
解析結果のまとめ
注目すべき原因と結果の対応がいくつかあるため、注意してまとめてみます。
まず Trove guest agent コードから PostgreSQL サービスを停止するために、システムに存在しうる特徴的なファイルの有無を判定して、使用可能なサービス管理手段を判断しますが、候補として upstart、sysvinit、systemd があるうちの sysvinit、systemd が該当しますが、判定処理の順番により、sysvinit が選択され、service コマンドによるサービス停止が決定されます。
service コマンドによる PostgreSQL の停止では、service コマンドに systemd 管理の存在を考慮するコードがあり、systemctl が利用できると判断されるため、最終的には service コマンドの中から systemctl コマンドを使って PostgreSQL を停止するルートの処理が走ります。ただしシステム立ち上げ時やシャットダウン時には --job-mode=ignore-dependencies
オプションが自動的に systemctl に追加され、ユニットの依存関係を無視する動作モードで実行されます。
systemd の PostgreSQL 関係のサービス定義については、直接制御される postgresql.service と、その依存サービスの定義である postgresql@.service があり、postgresql.service の中身はほとんど空で、postgresql@.service の中にすべての実処理が定義されるというかたちになっています。ユニットの依存関係を無視して postgresql.service に対する停止を行うと、postgresql.service 自体は正常に完了するがほとんど何も処理が発生せず、本来 postgresql@.service 側で実行されるはずだった実際のプロセスの停止処理は動かないため、全体として正しい停止状態とはならず、PostgreSQL のプロセスは動作したままとなります。
問題の対処
対処案 1 - Trove guest agent コードを修正し systemd 管理を選択させる
trove/guestagent/common/operating_system.py の service_discovery コードを修正し、使用できるサービスの判定を現状の upstart、sysvinit、systemd の順にするのではなく、sysvinit より先に systemd を判定するコードにします。これにより、悪影響のあった --job-mode=ignore-dependencies
オプションを使用せずに systemctl コマンドを実行できるようになります。
懸念事項として、service_discovery は Ubuntu や PostgreSQL 等での利用時以外にも、Trove のデータベースサーバー起動イメージ作成時に選択できる各種 OS やデータストアからも利用される共通コードとなっており、他に影響がある可能性が考えられます。そのためできれば変更したくないコードです。
対処案 2 - service コマンドコードを変更して --job-mode=ignore-dependencies を追加しない処理とする
/usr/sbin/service のコードを変更し、systemctl コマンドを起動時に --job-mode=ignore-dependencies
を自動的に追加しないための変更を行う。
懸念事項として、service コマンドが PostgreSQL 以外のサービス管理時にも利用されることがあるかもしれず、その場合に影響がある可能性が考えられます。そのためできれば変更したくないコードです。
対処案 3 - service_discovery の判定結果が変わるような状態にイメージの環境を変更する
service_discovery では、/etc/init.d/postgresql ファイルが環境にあることにより sysvinit を使用可能と判定しているため、PostgreSQL サーバー起動イメージ内の /etc/init.d/postgresql を隠すことにより、sysvinit を使用可能と判定させず、代わりに systemd のみが使用可能と判定させるようにします。
この変更は PostgreSQL 用の起動イメージ内のみに影響が限定され、他のデータストア使用時の動作には影響しません。 既に動作確認済のデータストア用イメージがある場合にも、修正の影響を与えないため安心して適用できます。
対処案の選択
他への影響が少ない対処方法として、 対処案 3 の、PostgreSQL サーバー起動イメージ内で /etc/init.d/postgresql ファイルを隠す方法を選択しました。
Diskimage-builder による PostgreSQL サーバー起動イメージビルド時のログを参考に、 Trove 用の elements で PostgreSQL 環境の作成に大きく関わっていると思われる部分のみ取り出してみると、それらは以下の順番でコードが実行されていることが分かります。
-
extra-data.d フェーズ
(省略) -
pre-install.d フェーズ
:
- pre-install.d/10-postgresql-repo (elements: ubuntu-postgresql)
※ PostgreSQL のリポジトリを登録
:
- pre-install.d/10-postgresql-repo (elements: ubuntu-postgresql)
-
install.d フェーズ
:
- install.d/30-postgresql (elements: ubuntu-postgresql)
※ PostgreSQL をインストール - install.d/31-fix-init-script (elements: ubuntu-xenial-postgresql)
※ PostgreSQL 用の初期化ファイル(systemd ユニットファイル)を修正
:
- install.d/30-postgresql (elements: ubuntu-postgresql)
-
post-install.d フェーズ
(省略)
上記より、ubuntu-postgresql/install.d/30-postgresql で postgresql-9.6 のインストール後に ubuntu-xenial-postgresql/install.d/31-fix-init-script で関連ファイルの変更を行うタイミングがあることが分かるので、ここに対処となる処理を追加すればよいと思われます。
イメージビルド環境にて、対処を以下のように行います。
ubuntu-xenial-postgresql/install.d/31-fix-init-script
対処が完了したら、PostgreSQL サーバー起動イメージを再ビルドして Trove に登録・設定し、再度 PostgreSQL を使用した Trove インスタンスを起動してみます。
対処結果
対処の結果、PostgreSQL を使用した Trove インスタンスは無事、起動に成功しました。
おわりに
全 4 回にわたり、OpenStack Trove 導入のポイントについて見てきました。
今回は OpenStack Queens 版の環境を使用して説明を行いました。 最新版の Trove では、細かい部分が変わっていたり、改善されている部分があるかもしれませんが、Trove による処理の流れや、イメージ作成時の elements の見方、問題の解析手段など、参考にできる部分があると思います。
Trove に興味を持たれた方、Trove 導入の必要がある方などに、この記事が少しでもお役に立てればと願います。