Bitbucket Pipelines を使ってて、全く設定を変えていないのに MySQL に繋がったり繋がらなくなったりしていたので、どういうこっちゃ、というメモ。
結論から行くと 3 〜 5 秒くらい待ってあげればよい。
MySQL に繋がらない
エラーとしてはこんな感じ。ちなみにこの問題と直接関係はないが CakePHP 3.5 である。
+ bin/cake migrations migrate
using migration paths
- /opt/atlassian/pipelines/agent/build/config/Migrations
using seed paths
- /opt/atlassian/pipelines/agent/build/config/Seeds
Exception: There was a problem connecting to the database: SQLSTATE[HY000] [2002] Connection refused in [/opt/atlassian/pipelines/agent/build/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php, line 115]
2017-10-16 10:57:41 Error: [InvalidArgumentException] There was a problem connecting to the database: SQLSTATE[HY000] [2002] Connection refused in /opt/atlassian/pipelines/agent/build/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php on line 115
Stack Trace:
#0 /opt/atlassian/pipelines/agent/build/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php(238): Phinx\Db\Adapter\MysqlAdapter->connect()
#1 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/CakeAdapter.php(57): Phinx\Db\Adapter\PdoAdapter->getConnection()
#2 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Command/CommandTrait.php(78): Migrations\CakeAdapter->__construct(Object(Phinx\Db\Adapter\MysqlAdapter), Object(Cake\Database\Connection))
#3 /opt/atlassian/pipelines/agent/build/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php(72): Migrations\Command\Migrate->bootstrap(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#4 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Command/CommandTrait.php(35): Phinx\Console\Command\Migrate->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#5 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Command/Migrate.php(65): Migrations\Command\Migrate->parentExecute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#6 /opt/atlassian/pipelines/agent/build/vendor/symfony/console/Command/Command.php(262): Migrations\Command\Migrate->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#7 /opt/atlassian/pipelines/agent/build/vendor/symfony/console/Application.php(888): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#8 /opt/atlassian/pipelines/agent/build/vendor/symfony/console/Application.php(224): Symfony\Component\Console\Application->doRunCommand(Object(Migrations\Command\Migrate), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#9 /opt/atlassian/pipelines/agent/build/vendor/symfony/console/Application.php(125): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#10 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Shell/MigrationsShell.php(101): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#11 /opt/atlassian/pipelines/agent/build/vendor/cakephp/cakephp/src/Console/Shell.php(508): Migrations\Shell\MigrationsShell->main('migrations', 'migrate')
#12 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Shell/MigrationsShell.php(156): Cake\Console\Shell->runCommand(Array, true, Array)
#13 /opt/atlassian/pipelines/agent/build/vendor/cakephp/cakephp/src/Console/CommandRunner.php(141): Migrations\Shell\MigrationsShell->runCommand(Array, true)
#14 /opt/atlassian/pipelines/agent/build/bin/cake.php(12): Cake\Console\CommandRunner->run(Array)
#15 {main}
script:
- composer install --no-interaction
- chmod +x bin/cake
- chmod -R 777 tmp/ logs/
- bin/cake migrations migrate
- composer test
bitbucket-pipelines.yml に記載した実行スクリプトに関してはこれだけなのだが、マイグレーションのところで、トピックブランチでは通って、マスターブランチでは通らず、なんてことがぼちぼち起きた。
Bitbucket Pipelines 上で MySQL のログが見えるので、これを確認すると、通らなかったときには、通ったときのログが途中できれているようなものだった。
Initializing database
...(中略)...
2017-10-16T01:57:41.831583Z 0 [Note] End of list of non-natively partitioned tables
成功した時はこんな感じの最終行になる。
2017-10-16T01:51:55.506134Z 0 [Note] InnoDB: Starting shutdown...
2017-10-16T01:51:55.606342Z 0 [Note] InnoDB: Dumping buffer pool(s) to /var/lib/mysql/ib_buffer_pool
2017-10-16T01:51:55.606688Z 0 [Note] InnoDB: Buffer pool(s) dump completed at 171016 1:51:55
つまり MySQL コンテナが何かしらの初期化処理をしていて、にも関わらず繋ごうとしたから接続できないエラー?そうして Bitbucket Pipelines がエラーを認識して停止。ふむ、納得出来る気がする。
アプリケーション側で接続時のタイムアウト設定
とりあえず思いつくスマートな方法はこれ。
このプロジェクトは CakePHP3 を使っているが CakePHP3 には接続時のタイムアウト設定な項目はないらしい。かなしい。とはいえ何かあるだろうとソースを読み進めると 最終的に PDO を使っているらしいことがわかる。ので PDO の設定方法を調べてみると PDO::ATTR_TIMEOUT というオプションを設定するとよいらしい。
php - Setting a connect timeout with PDO - Stack Overflow
このオプションを CakePHP3 で設定するには、コンフィグ内の flags にいれると出来そうなソースをしている。
cakephp/PDODriverTrait.php at master · cakephp/cakephp
$connection = new PDO(
$dsn,
$config['username'],
$config['password'],
$config['flags']
);
しかし入れても状況は改善されなかったので、違うらしい。。これはまたそのうち調べよう。。。
bitbucket-pipelines.yml に sleep を入れる
というわけでこっちの方法。yml (の抜粋)はこんな感じ。
script:
- composer install --no-interaction
- chmod +x bin/cake
- chmod -R 777 tmp/ logs/
- sleep 5
- bin/cake migrations migrate
- composer test
これで安定的に動いてくれているので、このままでいいか。
MySQL コンテナの初期化処理
そもそもとして MySQL コンテナの初期化処理って何をしているんだろうか。Dockerfile はここ。どうやら docker-entrypoint.sh が何かしていそうだ。
mysql/Dockerfile at master · docker-library/mysql mysql/docker-entrypoint.sh at master · docker-library/mysql
すごいざっくり読むとこんなことをしているっぽい。
- Docker イメージが提供するのは mysqld の準備~起動、 docker-entrypoint.sh の起動まで
- docker-entrypoint.sh によって初回起動によるデータファイル構築。設定したデータベース、ユーザの準備。ちなみに途中で mysqld の再起動をしている。
なるほど理解。 やっぱりコンテナが立ち上がったと同時に初期化処理が走って、それが終わるまでは接続が出来ないようだ。
ものすごいバッドプラクティスを感じている sleep 5 なのでなんとかしたいものの、どうこうするのがいいのかイマイチわからないので、知見あるひと教えてほしス