プライム・ストラテジー「KUSANAGI」開発チームの石川です。
KUSANAGIは複数のオープンソースのソフトウェアを組み合わせて提供している製品です。
当社で開発しているコンポーネントもありますが、NginxやPHPのようにオープンソースのコミュニティで開発されているコンポーネントも多くあります。
「バグのないソフトウェアはない」と言われるようにKUSANAGIでも不具合が見付かることはあります。その場合に、当社で開発しているコンポーネントについてはすぐに対応することができます。しかし、オープンソースのコミュニティで開発されているコンポーネントは当社で対応するのではなく、コミュニティに修正をお願いすることになります。
今回は昨年に ユーザーフォーラム に報告があった cURL の不具合を例に、オープンソースのコミュニティへ不具合を報告する際に当社で注意していることを説明します。
不具合が発生した環境を正確に報告する
ユーザーフォーラムで報告のあった内容は、一部のサイトで以下のようなエラーメッセージが表示され、その結果アップデートなどができないという問題でした。
cURL error 56: OpenSSL SSL_read: Connection closed abruptly, errno 0 (Fatal because this is a curl debug build)
この不具合が cURL にあることは明確なのですが、その条件が何であるかが問題でした。
cURLでエラーが起きるようになりました。
と一言で言われても、cURLを実行する方法はいくつかあります。
- curlコマンドを実行する
- PHP の curl 関数を実行する
開発元でいくつかの方法で curl コマンドと PHP の curl 関数の両方のテストケースを作ったのですが、どうしてもユーザーフォーラムで報告のあったメッセージが得られなかったのです。
正直言って、開発元で不具合を再現できない限りは修正する方法がありません。
不具合を見付けた方は、以下をできるだけ正確に報告していただけると、よりスムーズに原因特定につなげられます。
これは ユーザーフォーラム の「できるだけ多くの情報を含める」に詳しく書かれていますので、一読ください。
- 不具合が発生した環境の情報
kusanagi status
コマンドの実行結果kusanagi --version
コマンドの結果yum check-update
コマンドの結果
- 不具合が発生した具体的な操作
- コマンドのエラーであれば、コマンドとオプション全て、および、コマンドの結果
- WordPressなどウェブ画面での不具合であれば、画面のスクリーンショット、および、具体的な操作内容
条件が分からないと調査は困難を極める
ユーザーフォーラムで報告いただいた一文だけから、どのような操作をしたのかを想像する必要があります。
cURL のSSLに関するエラーであることから、どこかのサイトに HTTPS で接続した際に起きたのではないかと考えました。そこから2つのケースをそれぞれ実行してみました。
- curlコマンドを実行する
- PHP の curl 関数を実行する
curlコマンドを実行する
KUSANAGI 9 では kusanagi-libcurl のRPMを提供していますが、curlコマンドは含まれていません。
そのため、curlコマンドは KUSANAGI 9 のものではなく、OS (CentOS Stream あるいは AlmaLinux) のコマンドとなります。
これらのバージョンは変更がないため影響は考え難いですが、テストケースを作りました。
#!/bin/bash
curl --verbose https://www.google.com >/dev/null
しかし、いくつかのサイトで試してみても再現できませんでした。
PHP の curl 関数を実行する
次に可能性が高いのは PHP の curl 関数を実行することです。
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, 'https://www.google.com');
var_dump(curl_exec($ch));
var_dump(curl_errno($ch));
var_dump(curl_error($ch));
KUSANAGI 9 では複数のPHPのバージョン (7.4, 8.0, 8.1, 8.2) が使えるため、それぞれのバージョンでも試しましたが、はやり再現できませんでした。
WordPressから実行する
再現できずに困っているところで、幸か不幸か当社の環境でも同様のエラーメッセージが出力される環境を見付けることができました。
具体的な操作は以下でした。
- WordPressの管理画面にログインする
- GenerateBlocks Proプラグインをインストールする
- GenerateBlocksの管理画面より License Key を入力して Save する
おそらくユーザーフォーラムの報告者が行った操作とは違うとは思いますが、似たような現象を再現することができました。これでやっと一歩進めることができます。
このプラグインの内部の処理を調べていったところ、WordPressの wp_remote_post()
関数で起きることまで特定できました。
<?php
require_once __DIR__ . '/wp-load.php';
$response = wp_remote_post(
'https://generatepress.com',
[
'timeout' => 30,
'sslverify' => false,
'body' => [
'edd_action' => 'activate_license',
'license' => 'aaa',
'item_name' => 'GP Premium',
'url' => 'https://example.com',
]
]
);
ちなみに、この時点で PHP のバージョンに依存することなく、どのバージョンでも発生することが分かったので、以後の調査は PHP 8.1 だけで行っています。
発生するバージョンを特定する
ユーザーフォーラムで報告のあったエラーは cURL のバージョンが上がったことで生じたものです。
そこで kusanagi-libcurl のバージョンを変えて、WordPressの環境で操作を行いました。
- kusanagi-libcurl 7.86.0-1.el8 : 発生しない
- kusanagi-libcurl 7.87.0-1.el8 : 発生する
これで cURL 7.87.0 から発生することが突き止められました。
オープンソース側の変更を調べる
オープンソースのソフトウェアの多くは github で公開されており、ソースコードはもちろんのこと、変更履歴などを見ることもできます。
ますは Releases から changelog を見ていきます。
しかし、SSLの処理に関して何か変更が加えられているようには見えませんでした。
続いてソースコードの 差分 を調べていきます。
すると cURL error 56: OpenSSL SSL_read: Connection closed abruptly, errno 0 (Fatal because this is a curl debug build)
のメッセージを出している ソースコード に変更があることが分かりました。
これでオープンソース側の変更による影響が濃厚であることが見えてきました。
再現する方法を用意する
オープンソースのコミュニティに不具合を報告する上で重要なことは、KUSANAGI の不具合を報告する場合と基本的には変わりません。
- 不具合が発生した環境の情報
- 不具合が発生した具体的な操作
しかし、大きな違いはオープンソースのコミュニティ (今回の場合は curl) は KUSANAGI のことなど知らないということです。 (いわゆる「おま環-おまえの環境の問題だろう?-」というやつです)
今回の不具合は curl コマンドや、PHP の curl 関数でも再現せず、WordPress からの場合のみで起きていることから、WordPressではない環境でも再現させる必要が出てきます。
ここが非常にやっかいでした。
WordPressの wp_remote_post()
関数はカプセル化されているので、内部でどのようなパラメータを PHP の curl 関数に設定しているのかが、簡単には見えないからです。
WordPress内で curl_setopt()
している値を全て抜き出して順番に組み合わせを試すことで、ようやく PHP 単体で発生させる条件を見付けられました。
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, 'https://www.google.com');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Connection: close',
]);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
var_dump(curl_exec($ch));
var_dump(curl_errno($ch));
var_dump(curl_error($ch));
Connection: close
を設定していることと、HTTP/1.1 であること、が条件でした。
次に curl コマンド単体で再現させる方法を作ります。
PHP の curl_setopt()
は curl コマンドのオプションにほぼ対応しているので、これは容易です。
#!/bin/bash
curl \
--verbose \
--http1.1 \
-H 'Connection: close' \
https://www.google.com >/dev/null
ようやくコミュニティに報告できる再現方法を確立することができました。
不具合をコミュニティに報告する
コミュニティには不具合を受け付けるページが大抵は存在します。
今回は github.com 上なので、Issues から報告を行いました。
コミュニティによって若干の差はありますが、Issueを作ると発生する環境や条件、再現方法を書くためのテンプレートが用意されていますので、それに従って記入して送信します。
ちなみに こちら が実際の Issue になります。
コントリビュート(貢献)する意識を持つ
コミュニティはサポートデスクではありません。
言うまでもないことですが、多くのオープンソースコミュニティはボランティア活動で成り立っています。
問題の解決に対して、オープンで協力的ではありますが、サポートを行う義務はありません。有償のサポートサービスのような対応を期待してはいけません。
お互いにコントリビュート(貢献)しようという意思を示すことが大事です。
そして、不具合の早期解決のためには
- 不具合が発生した環境の情報
- 不具合が発生した具体的な操作
- 不具合を再現させる方法
が非常に大事であるということです。
もしも、不具合に遭遇してしまってユーザーフォーラムに投稿しようと思うことがあったら、上記の情報をできるだけ書くように心掛けてください。