本番サーバー60台のホスト名を全部 cat にしてしまった話

この記事は、**[本番環境などでやらかしちゃった人 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/yarakashi)** の4日目です。年末進行、いかがお過ごしでしょうか？みなさま無事に仕事が納まることを願っております…

https://qiita.com/advent-calendar/2023/yarakashi

新人インフラエンジニアが、本番ウェブサーバー60台のホスト名を全部 cat にしてしまった話について、ここに供養させていただきたいと思います

# 背景

おそらく今から7年くらい前、インフラエンジニアとして転職してきて1年ほどが経ち、本番環境での作業もこなれてきたなというバッチリのタイミングで事を起こしてしまいました。サーバーは CentOS 6 だったと思います。

職場としてはまだまだベンチャー感にあふれ大きな裁量が与えられスピード感のある環境ながら、サービスの登録ユーザー数は1,000万を超え、本番環境の規模としては既になかなかの大きさがあり、ウェブサーバーだけでも60台くらいあったと思います。ひと山につきロードバランサー配下に30台のサーバーがあり、DNS ラウンドロビンで2山を構成していたはずです。

# シンプルながら強力なスクリプト

本番環境で多くのサーバーを一度に操作するための、**いにしえより伝わる便利 SSH スクリプト** が用意されており、使い心地としてはこのようなイメージでした。例えば、ウェブサーバー全台について NTP サーバーの指定を確認したい場合はこのような具合です。

```console
$ ./ssh-www.sh grep ^server /etc/ntp.conf
192.168.1.1:server 192.168.0.250
192.168.1.2:server 192.168.0.250
192.168.1.3:server 192.168.0.250
(以下略)
```

便利だ。

サーバーの IP アドレス一覧を持っておき、それに対して順に `ssh` を実行する **シンプルながら強力なスクリプト** です。`ssh` コマンドは、リモートで実行するコマンドを引数に取ることができるので、そのコマンドの実行結果をサーバーの IP アドレスと共に出力するようになっていました。

シェルスクリプトはこんなイメージになると思います。

```sh
for s in $servers; do
    echo -n $s: # 対象サーバーのIPアドレスを表示
    ssh $s $@   # シェルスクリプトの引数をそのまま渡す
done
```

# 何でもないある日のこと

何らかのファイルの中身を確認しておきたくて、ウェブサーバーに対して `cat` コマンドを打ちました。

```console
# ./ssh-www.sh cat hogehoge
192.168.1.1:fugafuga
192.168.1.2:fugafuga
192.168.1.3:fugafuga
(以下略)
```

オッケーです。

次に、サーバーのホスト名も確認しておきたいということで、コマンド履歴から1つ前のコマンドを復元（`Ctrl` + `P`）し、ちゃちゃっと編集して **ッターン！** したコマンドが以下でした。

```console
# ./ssh-www.sh hostname cat
192.168.1.1:192.168.1.2:192.168.1.3:(以下略)
```

あれ、なんか出力がヘンだな…… **ア゛ッ！なんかキャットついてた！**／(^o^)＼

血の気がサーッと引いていくのがわかりました。全身から冷や汗が止まらなくなりました。落ち着けおおお落ち着くんだ……

# そしてホスト名が全部 cat になった

本番ウェブサーバー60台のホスト名が全部 cat になってしまって区別がつかなくなりました。すべてのサーバーが、吾輩は猫であると表明しています。

```console
# ./ssh-www.sh hostname
192.168.1.1:cat
192.168.1.2:cat
192.168.1.3:cat
(以下略)
```

こんなに無機質な猫がいるでしょうか…

`hostname` コマンドは、引数に渡された名前をそのサーバーのホスト名として設定する仕様だったのです。もし `hostname -s`（ホスト名が `piyo.example.com` だったらショート版の `piyo` だけを表示する）をミスって `hostname s` と打ったときには、すべてが `s` になります。

# 不幸中の幸いと復旧

ユーザーにサービスを提供しているアプリケーションがホスト名に依存した処理をしていることはなかったので、障害には繋がらなかったことは幸いでした。

また、幸いにも `hostname` コマンドで設定されたホスト名は永続化されるわけではありませんでした。ただ、再起動すれば元に戻るものの、60台を順番に運用から外しながら再起動するのは手間です。設定ファイル（当時は `/etc/sysconfig/network`）などを手がかりにして、再び `hostname` コマンドを使うことで元に戻しました…

それにしても、この `hostname` コマンドには **`crontab -e` を `crontab -r` に打ち間違える**のに次ぐ怖さがあるかもなと思いました…（キーボードで `e` と `r` は隣り合っているというのに、crontab を更新しようと思って1つ間違えると、crontab が真っ白になるやつ）

# 惨劇はなぜおこってしまったのか

- いわゆる、インフラ作業を「完全に理解した」タイミングでの慢心
- 破壊的変更が可能な、シンプルながら強力なスクリプトの存在
- 日常的に root で作業する、ベンチャースピリッツ
- 一社員に与えられた、本番で自由にコマンドを打てるというクソデカ裁量

もう何年も前のやらかしの発生についてですが、振り返ってみるとこれらのようなことがらが事故に繋がった可能性があります。当時にはそれが必要だったからそうなっていたという面もあると思いましたが、すべてはリスクとリターンのトレードオフなのかもなとよく思います。

# 惨劇を起こさないためにできること

- たとえミスがあったとしても耐える、フールプルーフなシステムの設計
- 各員の権限管理を徹底し、スクリプトなどには安全装置を施す
- 本番環境の変更について、適切なレビュー・承認・実行・記録のプロセス

関わる人間が増えてきたり、システムが大きくなってきたり、果ては東証グロース市場に上場するぞというような状況になってくると、ベンチャースピリッツでは誤魔化せないということになってきます。それぞれのフェーズによって求められることが変わりますが、それもエンジニアリングなんだと思いました。

**追記：** 優しいツッコミに感謝です。肝心の、ホスト名を確認したいときに使えるもっと安全なコマンドを書いていませんでした！たとえば `uname -n|--nodename` や、当時より新しい OS でしたら `hostnamectl [status] --static|--transient` もオススメです。

```console
$ uname -n
cat.example.com
$ hostnamectl status --static
cat.example.com
```

`hostnamectl` には、`hostname` と同じくホスト名を変更する機能もありますが、さすがに `hostnamectl set-hostname cat` と打ち間違える心配はないか…いやあるか…… `uname -n` のほうが簡単だし良さそうです

# おわりに

ここまで付き合っていただきありがとうございました…。ほっといても障害は勝手に起こるんですから、できれば自分の手で障害を起こすことのないようにしたいですよね…

**本番環境などでやらかしちゃった人 Advent Calendar**、前営業日の記事は rompasinai さんでした。**クライアントの言うことは絶対** のプレッシャーは想像を絶する予感がします…クライアントワークから逃げ続けてきた自分には耐えられないと思います

https://qiita.com/rompasinai/items/ab8a3069223c725ae876

明日の記事は、waitonly さんの **「ファイルサーバーを業務中に再起動しちゃったの」** です。タイトルを読んだだけでも胃液が上がってくる感じがあります…楽しみですね…

## 宣伝

**本番環境でやらかしちゃった人 Advent Calendar** が好きで、過去の記事について一体どのやらかしが注目を集めたのかというのを先週書きました。よかったらこちらもお願いします！

https://qiita.com/yonex/items/2252260381bb3d4e5d77