とあるSESの現場では本番リリースの時期が近づいてきており、僕を含めた数人のエンジニアは間に合いそうもない残作業の開発を進めたり、本番で使うためのデータの整備を本番サーバー内で行ったりしていた。ほとんどがその案件のために集められたメンバーだったため特に和気あいあいとするでもなく、エアコンの風の音が響く小さなオフィスの片隅で静かに作業をしていた。
業務上のやりとりもRedmineで行われており、声を発するのもたまにメンバー同士で話をしたり、クライアントから電話がかかってきた時だけ。その日もメールで通知が届いてきており、確認してみるとRedmineで僕が関係しているチケットにコメントが届いているという通知だった。
通知のURLをクリックしてRedmineのチケットを確認してみる。
それによると一旦本番サーバー上に存在するデータの中の一部の主要データをCSV形式で送ってほしいという依頼だった。無茶なスケジュールな中で残作業を残していた状況だったため、正直ちょっとそんなことをやっている場合じゃないだろうと苛ついた。しかもそのCSVを大元の依頼主の方で調整し、またその変更をインポートし直すという追加作業付きだったため心底うんざりした。
とはいえしばらく手を止めて考えを巡らせたあと、リリース工程のためにはやむを得ない作業であることは分かったため、表情も変えず「かしこまりました」と入力して返信した。
CSVの作成
とにかくバタバタしていて忙しかった事もあったため、CSVにエクスポートするためのプログラムを書くということは避けたかった。可能であればMySQLのクエリかコマンド一発で対応したいと思ったため、ネット上でやり方を探した。検索結果の最初の数結果目のページを開くと丁度良さそうな情報がすぐに見つかった。SELECT文にINTO OUTFILE '/tmp/users.csv'
という記述を追記することにより出力できるらしい。1
早速対応しようと思い、まずはローカル環境のphpMyAdmin上で目的のSQLを組み立てる。すぐに出来あがり、特に問題もなさそう。実際に本番で試してみようと思い本番サーバーにSSH接続する。この本番サーバーは一つのサーバーの中にWebサーバーとMySQLサーバーが同居している。サーバーにログイン後、mysqlコマンドでMySQLのクライアントにログインした。そして先程作ったSQLを実行してCSVをエクスポートする。
しかし、実際に出力されたものはCSVではなくエラーメッセージだった。権限がなく出力できないという。なぜ?
よくよく考えてみると、Linuxのユーザーとしての権限は問題ないのだが、SQLでファイル名を指定しておりそれを実行するのはMySQLのサーバー側のため、ファイル出力はmysqlユーザーで行われているのではないか、と考えた。たしかに見つけたサンプルは/tmpディレクトリに出力していたのだが、今回はSSHでログインしているユーザーのディレクトリに出力しようとしていたので、そう考えると書き込み権限があるわけがない。
そのためログインしているユーザーのホームディレクトリに書き込み権限を与え、再度OUTFILEを試してみたところ正常にCSVを出力することが出来た。僕はそれをRedmineのチケットに添付し、依頼主にメッセージを送った。そういうことで突然入ってきた急な業務が問題なく完了したので、引き続き残っているチケットを進めることにした。
静かに訪れる
デスクの右側には街が見渡せる大きな窓がついており、差し込んでくる光は既に自然光から人工光に変わっていた。そろそろ最後の作業を終えて帰宅する頃。
先程の作業とは別に、ちょっと前に本番のデータを調整するための対応を行っており、一通り対応はできていたためクライアントに確認の依頼をしていた。丁度Redmineに連絡が届き、確認が完了したため本番に反映してもよいという話になった。その日最後の作業を行う。本番でSeederを実行するため、いつものようにSSH接続を行う。
しかしいつもならばすぐに接続できるのだが、なぜかなかなか反応が返ってこない。なにか調子が悪いのだろうかと思いながらしばらく待った。しかし、最終的に接続に失敗してしまう旨のメッセージが表示されてしまった。もしかしたら何か他の件のVPNに接続したままだったかなと思い確認してみるが、特にそういうわけでもなかった。しばらく試行錯誤を繰り返す。
ふと、嫌な予感が頭をよぎった。
SSH接続が出来ない、つまりSSHに関する何かが変わってしまっている可能性がある? しかしSSHの設定自体は何も変えていない。SSH……、SSH鍵……、.sshディレクトリ……。
いつもとなにか変わっていることと言えば、そういえば先程ホームディレクトリに書き込み権限を追加した。ホームディレクトリと言えば当然中に.sshディレクトリが含まれている。もしかしてそれが何か関係しているのだろうか……?
試しにホームディレクトリの属性を変えてみたいと考えたが、不可能だった。この本番サーバーには他にユーザーが存在しなかったのだ。セキュリティ設定が雑でもしかするとrootで直接ログインできるのではないかと試してみたが、さすがに不可能だった。もしかしたらSSH接続したままのクライアントが残っていないかとPCを調べたが、全て切断済みだった。
最悪の状況を招いてしまいたのではないかという考えが頭を巡っていた。いつもは少しだるい感じで仕事をしているのだが、やけに頭が冴えてしまっていた。こころなしか胸の鼓動がいつもよりよく聞こえる気がした。
何か解決方法がないのか、このあとどうするべきかを考えていた。ふと思いつく言葉。ほうれん草。
とりあえずクライアントに状況を報告するため電話をした。普段であればなぜ自分が電話代を払わなければならないのかと思い電話を待つ側に徹しているのだがそんな事を気にしている場合ではない。すぐ電話をした。そしてすぐに着信音がやみ、クライアントの声が聞こえてくる。
「もしもしー」
「もしもし……」
クライアントのプロジェクトマネージャは技術的な話も通じるため、恐らく昔から自分で手を動かしてきたタイプの人であるような印象を持っていた。僕はインフラについては専門的にはやっておらず、たまたま持っている知識だけでなんとかやっているだけなのだが、そのクライアントは恐らく色々と詳しいであろうと思っていた。報告することで何か解決することがあれば、と淡い期待を抱いてもいたかもしれない。
「あー、それはだめですね」
期待はかき消され、現実を叩きつけられた2。クライアントは僕がやった手順やなぜそれをやったのかを確認しつつ、考え込んでいるようだった。そして決定的な言葉を投げかけてきた。
「もう二度とサーバーに接続できないってことですよ。うーん」
僕は「ああ……」とつぶやいた。そのあと何と言ったかは覚えていないが、「ですよね」だと責任を感じていないぶっきらぼうな言い方な気がして言うべきではないと思ったし、では何と言うべきかを一瞬の間にぐるぐると考え続けていた気がする。もしかすると「はい……」とよく分からない事を言ったのかもしれないし、考える事に時間を使いすぎて結局何も言ってなかったのかもしれない。
「まあちょっとなんとか出来るか確認してみるわ」
そういってクライアントとの電話を終わった。なんとか出来るかと言っても、そもそももう接続できないのになんとかなるのか、と思った。とにかくもうどうしようもならないとんでもないことをしてしまったことだけは分かっていた。
その後はどうしようもないので他にユーザーがいなかったか、他に何か解決できる方法はないのかを調べたりしていた。時間はどんどん無情に過ぎていき、当然定時も過ぎているのだがもちろん帰るわけにはいかない。もうオフィスには他のエンジニアはいない。僕は今日はいつ帰ることが出来るのだろうか。
というかそもそも接続できないということがはっきり周知された場合どうなるのだろうか。ソース自体はGitのリモートリポジトリにあるから別のサーバーを用意して復元する? しかしデータは既に依頼元が直接本番で調整しているため、復元しようと思ったら再度依頼元に対応をしてもらうことになってしまう。時間とそれに伴う人件費の損失。そもそもそうするとしても今日はどうするのだろう? どうしようもないし帰ります、と言って帰るのだろうか? それともそんな無責任なことは出来ないからもう解決するまで何日も帰れないのだろうか?
そんな事を考えたりよく分からない調べ物をしたりしていると、突然クライアントから電話がかかってきた。なんだろう。
「ちょっとサーバーに接続してみてもらえますか」
サーバーに接続……? なぜ……? まさか……?
その言葉に微かな期待を抱きながら半信半疑でSSH接続を行ってみた。すると。まさか。繋がったのだ。
「接続できました……!」
「あー、できた?」
「はい、どうやったんですか……?」
「シングルユーザーで接続した」
シングルユーザー……。聞いたことのない言葉だったので聞き間違いかもしれないと思い「ィンぐるユーザーですか……」とぼやかしながら聞き返した。気にはなったが後で調べればいいし、こんなバタバタさせてしまった状況でいちいち技術的な事を質問するのも失礼にあたると思いひとまず聞き流した。
あとは書き込み属性を与えてはいけないということを軽く念押しだけされ、電話を切った。
何にしろ、助かった……。
シングルユーザーモードとは
あとで調べてみたところ、Windowsのセーフモードのようなものということだった。つまり物理サーバーがどこか触れるところにあったということだろうか。不幸中の幸いだ。
このようなトラブルを招かないために
とにかくSSH接続できるユーザーは複数用意しておくほうが良い。今回のような古き良きサーバーの場合、Webサーバー、データベースサーバー、アップロードファイルなど、あらゆるものがサーバー上に詰め込まている。こういった場合、サーバーが何かしらの原因で接続できなくなった場合、あらゆる本番のデータを失ってしまう。今回の場合はリリース前ということで最悪全員が苦労して復旧すればなんとかなるのだが、リリース後に実際のユーザーが登録されてしまっている場合はそうともいかない。
とはいえ、この構成だと故障してしまった時に結局どうしようもなくなり全てを紛失してしまう。最近ではAWSやGCPなどのクラウドサービスを使い、通常のWebサーバーとは別に、データベースは他のデータベースサービス、アップロードファイルはストレージサービスを用いてWebサーバーの外に保存する仕組みで運用することができる。こうするとWebサーバー内には失ってはまずいデータが存在しなくなるため、トラブルがあった際には単にサーバーを一度捨てて作り直すだけで解決する。
帰ろう
クライアントのおかげであらゆる問題が解決した。あとは止まってしまった残作業を再開し、完了したら無事に家に帰ることが出来る。ちょっと遅くなってしまったが、最悪もう二度と家に帰ることができないかもとも思っていたため助かった。
本番のデータを更新するため、SSH接続を行い、アプリケーションの存在するディレクトリへ移動する。よし、これで終わりだ。さっさと終わらせて帰ろう。僕は他の誰かがtruncate文を埋め込んでいたことも知らないままSeederを実行した。
-
元々.sshディレクトリ以下が書き込み可能になっていたか、もしくはsshdの設定でStrictModesが有効になっている場合はホームディレクトリに書き込み権限を付与するだけで認証できなくなる。とにかくLinux上で何かしらの役割のあるファイルやディレクトリの権限を変更してはいけない。 ↩