GitHubで5日以上の空白がある場合もひたすらプログラムを書き続けている私ですが、デプロイにかける時間はなるべく最小限に保ちたいと思っています。

さすがに何度か継続インテグレーション、継続デプロイを繰り返してきてデプロイはもう定型化しつつあるのですが、やっぱりふとした瞬間にハマってなかなか前に進めなくなることあります。今日は自戒の意味も兼ねて今回うまく行かなかった内容について残します。

デプロイはほぼ毎回自宅サーバーのDockerにGitLab経由で行っています。 テストに成功すればGitLabでDockerのイメージをビルドして、コンテナレジストリにpushして、それをサーバーにpullしてデプロイという流れ。 いずれはKubernetesにも挑戦しないとなぁと思いつつも、直感的かつ簡単なのでなかなかDocker Swarmを捨てられません。 前置きが長いのですが、そんなDocker Swarmのログ機能。複数のコンテナからなるログをまとめてくれるのですが、どうやら出力が前後でばらばらになるというかRedisのコンテナに接続しようとしてNode.jsのコンテナが落ちた場合は別のNode.jsコンテナが再起動してからのログはなぜかその前の同じコンテナの出力に上書きされるようなのです。 これがもとになってRedisのコンテナにうまく接続できないのではないかという仮説をたてました。 実際、最初のほうはRedisに接続できていないようなエラーに見えました。しかしコンテナは起動している。これはおかしい。

手元でデバッグするのもそれはそれでややこしいのですが、GitLabでデバッグを行おうとするとprotected variablesという機能があって、CIのときに使われる環境変数はgit push --forceできないブランチじゃないと使われない(injectされない)仕様です。もちろんこれを解除すればいいだけの話なのですが、デバッグだけの無意味なコミットログが増えていくのが気力をどんどん削いでいきます。さすがに何度もやっているうちにprotectedを取り消しましたが、何度も取り消したりしているともともとの意味がないような気がしてそれもどうなんだろうと思ってしまうわけです。

第一の原因としてはパーミッション問題がありました。

Add ability to mount volume as user other than root #2259

ビルドしたコンテナが一般のユーザ(node)のvolumeをマウントしようとするときに、ローカルのディレクトリの所有ユーザがrootだった場合rootになってしまってpermission deniedになってしまうという問題がありました。これの解決は簡単で、単にディレクトリのホストOS側のDockerの実行ユーザに変えれば動きました。ただちょっと見栄えはよくないですけれども仕方がありません。で、これを直した段階でログは変わったのですが、やっぱりうまくいかないままでした。

それで、git branchmasterにあがっているコミットログから最後に動いているであろう箇所を探しました。デプロイしている途中でうまく動かなくなったのではないかという仮説をここでは立てています。ただ、探している途中でどうもしっくりこない。何が原因かもわからないなかで、もともと本当に正しく動いていたのかすらも実は怪しいことに気が付きました。もともと使用していたコンテナはNode.jsのサーバーが1つ、Nginxで1つ、cronの実行用に1つ、Redisで1つという具合に4つだけでこの構成でNode.jsからRedisに繋がらないとずっと悩んでいました。先程のパーミッションの問題は解決したのにまだホストにアクセスできないというエラーが表示され続けている。そこでふとログをDocker SwarmではなくDockerコンテナ単体でログを見てみました。純粋にdocker service logsからdocker logsに変えただけです。

すると、ポート番号がてっきりRedisのコンテナにアクセスできないと思っていたのがPostgresのデータベースにアクセスできていないということが判明しました。よくよく思い返してみるとGitLab側でPostgresのテストは行っているけれど、本番環境で動かすための設定が一切できていませんでした。というのも、普段ローカルではさくっと確認したいのでSQLiteを使っていました。このサイトの投稿を書いているRailsでも今回もそうです。本番環境およびテスト環境ではPostgresに切り替えているんですけれども、そもそも存在しないコンテナにつながりようがないわけです。コンテナが初回起動するときはRedisに繋がらずに何度か再起動するときに出力されるエラーと、そもそもデータベースに繋がらないというエラーが混ざっていました。というわけで、これも直したら当然すんなり繋がりました。 もちろんこうして文章に起こすとたったそれだけのことなのですが、とても悩みました。 この問題を防ぐにはDB Browser for SQLiteの使用禁止と常にdocker-composeでPostgresを使った開発に切り替えなければなりません。ただやっぱりsqliteとpostgresはそれなりに違う部分もあるので最初からずっとPostgresで作るのが理想なのは承知しているつもりですけれども。

データベースの問題も解消し、無事タスクも動き出したけれども今度はNginx側が動きませんでした。 NginxはReactを動かすために利用しているだけなのですが、CORSを有効にするかわりにproxy_passで同一のホストに置けることもあって重宝しています。 そんなただのHTTPサーバーなのに502エラーが出てしまうのはNginxのコンフィグファイルの書き方がよくない以外にありません。 ここはそこまで時間がかかりませんでしたが、単純にホストOSで公開しているポートと、Docker内部でのポート番号があっていなかっただけでした。

振り返ってみるとどれも単純なミスなのですが、ここ最近、少なくとも1ヶ月近くはほぼコピーペーストできており、デプロイで困っていなかったため随分と消耗しました。私はどちらかというとインフラエンジニアよりもプログラミングの方が好きな人間なのでデプロイにあまり時間をかけたいとは思っていません。だからこそ焦ってしまってなかなかうまくデバッグできなかったのかもしれません。何かひとつでも歯車が狂うとうまくいかなくなるのがプログラミングですが、とりあえず今回は一通りデプロイまでできてよかったなと思っています。今回はGitHubで公開予定のないプログラムなので日記に残すだけ程度のお話ですけれども。