The Nightmare Monkey before Christmas

本稿は[Livesense - 自 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/livesense-ji)の16日目の記事です。
リブセンスでアルコール駆動インフラエンジニアをやっている @Ets です。

# はじめに

## 背景
2017年末の現在、リブセンスのサービスは障害はもちろん、アラートすらほとんど発生しない環境となりました。
これは、全社のエンジニアの努力の積み重ねの賜物です。心から感謝。

しかし我々インフラグループは、<font color="Red">**未知のアラートが飛んできたときに慌てずに対処すること**</font>が求められるグループです。
あまりにもアラートが無いと、**いざ本当の障害という時に焦ってしまうかもしれません。**

焦ってしまうのは、新人が配属されたときも同様でしょう。
初めてのアラート対応が本番というのも可哀想なもの。

だから、いつの頃からか、こんなことを言うようになりました。

<font color="Red">**『開発環境で障害対応訓練をできないだろうか』**

## 擬似的な障害を起こす
Netflixの[Chaos Monkey](https://github.com/Netflix/chaosmonkey)はご存知でしょうか。
ITエンジニア、運用系に携わる人なら聞いたことがあるかもしれませんね。
どんなものかは、[Publickeyのこの記事](http://www.publickey1.jp/blog/12/chaos_monkeynetflix.html)にわかりやすくまとまっています。(以下引用)

> あえてわざと管理された範囲内の障害を日常的に引き起こし、それに日常的に対応できていることを証明し続けることで、本物の障害が起きても問題なく対処できることが証明できる

しかし、Chaos Monkeyが対象としているのは基本的にインスタンス単位の障害です。
もっと小さな、プロセス単位で障害を起こしたいものです。

**無いなら作るしかないですね。**

# Nightmare Monkey概要

<font color="Red">**Nightmare Monkey**</font>はChaos Monkeyにインスパイアされた、**OSに悪夢を招く**プログラムです。

- https://github.com/etsxxx/nightmare-monkey

OS上でdaemonとして動作し、**ランダムにOSに不具合を発生**させていきます。
*※ 本稿執筆時点では、ランダムにプロセスをKILLする機能しか実装されていません。*

## 実装
Go言語で書かれています。
実装には特にテクニカルな面はないので、説明は割愛します。詳しく知りたいならGitHubを見てもらいたいところですが、このアドベントカレンダーのネタに思いついて急いで書いたコードなので**考慮も実装もテストコードすらも不足**しています。

CentOS上でのみ動作テストをしているので、その辺もご容赦ください。

## 簡単な使い方
簡単も何も・・・そもそも簡単な機能しか実装してないんですけどね。

### Dryrun
オプションを特に何も指定せず実行すると**Dryrunモード**で動きます。
まずはこれで様子見してください。

```bash:Dryrun実行例1
$ nightmare-monkey

2017/12/14 16:32:02 Nightmare Monkey is playing from 11:00 to 18:00, Mon thru Fri.
2017/12/14 16:32:02 Next: 4365 seconds after
```

上記例では随分先に事故が起きそうです。
Dryrunでは、もう少し短い間隔（オプションについては後述）を指定したほうが良いでしょう。

```bash:Dryrun実行例2
$ nightmare-monkey --min-interval 1 --max-interval 2

2017/12/14 16:32:40 Nightmare Monkey is playing from 11:00 to 18:00, Mon thru Fri.
2017/12/14 16:32:40 Next: 1 seconds after
2017/12/14 16:32:41 (Dryrun) Kill: sshd(PID:23183 PPID:1) - by monkey
2017/12/14 16:32:41 Next: 1 seconds after
2017/12/14 16:32:42 (Dryrun) Kill: rsyslogd(PID:1013 PPID:1) - by monkey
2017/12/14 16:32:42 Next: 1 seconds after
2017/12/14 16:32:43 (Dryrun) Kill: acpid(PID:1156 PPID:1) - by monkey
2017/12/14 16:32:43 Next: 1 seconds after
```

ランダム時間経過するたびにプロセスをKILLする様子が見れますが、Dryrunなので何も起きません。


### 通常動作
動きそうな気がしたら、`--execute`オプションを付けましょう。
**「OSを不安定にする」**という機能のため、<font color="Red">**root権限で実行**</font>しましょう。
`nohup`などと組み合わせてdaemon化しておき、ログは答え合わせ程度に利用するのがお勧めです。

`--execute`オプションで起動すると不規則にプロセスを殺そうとします。
完全なランダムではなく、以下の条件で動くようにはしています。

* 指定した最小間隔(`min-interval`, default:3600=1時間)と最大間隔(`max-interval`, default:21600=6時間)の間
* 指定した曜日(`day`, default:1-5=月〜金)
* 指定した時間(`time`, default:11:00-18:00)

つまり、ゆるめに平日業務時間をデフォルトにしています。

なお、以下では`min-interval`と`max-interval`にひどい数字を指定して酷い目にあったログを貼っておきます。


```bash:実行ログ
$ sudo nohup nightmare-monkey --execute --min-interval 1 --max-interval 2 > /vagrant/nightmare.log 2>&1 &
[1] 11103

$ tail -n 1000 -f /vagrant/nightmare.log

2017/12/14 16:39:00 Nightmare Monkey is playing from 11:00 to 18:00, Mon thru Fri.
2017/12/14 16:39:00 Next: 1 seconds after
2017/12/14 16:39:01 Kill: kacpid(PID:17 PPID:2) - by monkey
2017/12/14 16:39:01 Next: 1 seconds after
2017/12/14 16:39:02 Kill: usbhid_resumer(PID:41 PPID:2) - by monkey
2017/12/14 16:39:02 Next: 1 seconds after
2017/12/14 16:39:03 Kill: ata_sff/0(PID:21 PPID:2) - by monkey
2017/12/14 16:39:03 Next: 1 seconds after
2017/12/14 16:39:04 Kill: mingetty(PID:23607 PPID:1) - by monkey
2017/12/14 16:39:04 Next: 1 seconds after
2017/12/14 16:39:05 Kill: rpc.idmapd(PID:1107 PPID:1) - by monkey
2017/12/14 16:39:05 Next: 1 seconds after
2017/12/14 16:39:06 Kill: rpciod/0(PID:1103 PPID:2) - by monkey
2017/12/14 16:39:06 Next: 1 seconds after
2017/12/14 16:39:07 Kill: sshd(PID:10423 PPID:23183) - by monkey
2017/12/14 16:39:07 Next: 1 seconds after
2017/12/14 16:39:08 Kill: ksuspend_usbd(PID:22 PPID:2) - by monkey
2017/12/14 16:39:08 Next: 1 seconds after
2017/12/14 16:39:09 Kill: khubd(PID:23 PPID:2) - by monkey
2017/12/14 16:39:09 Next: 1 seconds after
2017/12/14 16:39:10 Kill: scsi_eh_1(PID:333 PPID:2) - by monkey
2017/12/14 16:39:10 Next: 1 seconds after
2017/12/14 16:39:11 Kill: acpid(PID:1156 PPID:1) - by monkey
2017/12/14 16:39:11 Next: 1 seconds after
2017/12/14 16:39:12 Kill: mingetty(PID:23603 PPID:1) - by monkey
2017/12/14 16:39:12 Next: 1 seconds after
2017/12/14 16:39:13 Kill: hald(PID:1165 PPID:1) - by monkey
2017/12/14 16:39:13 Next: 1 seconds after
2017/12/14 16:39:14 Kill: rsyslogd(PID:1013 PPID:1) - by monkey
2017/12/14 16:39:14 Next: 1 seconds after
2017/12/14 16:39:15 Kill: khungtaskd(PID:28 PPID:2) - by monkey
2017/12/14 16:39:15 Next: 1 seconds after
Connection to 127.0.0.1 closed by remote host.
Connection to 127.0.0.1 closed.
```

最後にはsshdが殺されてセッションが切られました。
**現実でこんなにプロセスが死んでたら悪夢ですね。**


### 外部トリガー動作
Nightmare MonkeyはRESTのAPIとして動作させており、外部から悪夢を引き起こすことも出来ます。

KILLコマンドを発行するためには、`{{HOST}}/api/kill`というURLに`POST`を投げます。
この際、メモ的に使える`desc`パラメータを必須にしています。
*※ この辺は特に設計もせずコードを書いているので突っ込まないでください。*

以下はNightmare MonkeyにプロセスをKillするリクエストを送り込んでいる例です。

```bash:KILLリクエストをcurlで送る
$ curl http://{{HOST_ADDR}}:8080/api/kill -X POST -d '{"desc":"I like kill!"}'
```

成功すると何を殺したかの応答が返ってきます。

```json:KILL応答例
{"name":"crond","desc":"I like kill!","pid":1966,"ppid":1}
```

今回は`crond`が死にました。
**障害対応の訓練という用途では、この応答はそのまま捨てるのがお勧めです。**
あるいはログに留め、答え合わせに利用しましょう。

この例を見ていてわかると思いますが、 <font color="Red">**認証もセキュリティもあったもんじゃありません。**</font>
本番環境で動作はさせないでくださいね。

# 活用例
さて、ここまでは駆け足でNightmare Monkeyの実装の紹介をしてきました。
<font color="Red">**さぁここからがアドベントカレンダーです。**</font>

## 外部トリガー実装例(1)
hubotにこんなコードを入れておきましょう。

```coffeescript:チャットからの接続の実装例
  robot.hear /(destroy)|(ですとろい)|(デストロイ)/, (msg) ->
    data = JSON.stringify({
      desc: "request from hubot"
    })
    robot.http("http://{{HOST_ADDR}}:8080/api/kill")
      .header('Content-Type', 'application/json')
      .post(data) (error, response, body) ->
      msg.send "(emoticon)< ﾃﾞｽﾄｲｲｲ!!!!"
```

これでチャットで「destroy」と呟くたびにプロセスが死んでいきます。

![hubot 接続例.png](https://qiita-image-store.s3.amazonaws.com/0/82212/1c0a7441-bc3f-2d59-110a-0022a939a02d.png)


```bash:nightmare-monkeyのログ
2017/12/16 11:54:49 Kill: drbd0_submit(PID:1484 PPID:2) - request from hubot
```

おっと、「destroy」なんて普段発しない言葉でしたね。( *しかもタイポしてますね！* )
<font color="Red">どうせなら**「出社」**に反応するようにして朝から悪夢を見せるのも良いし、**「おつかれさま」**に反応して疲れてる人に追い討ちを掛けるのも良いですね。</font>

## 外部トリガー実装例(2)
BASHでこんなコードを書き、金曜日に飲みに行く前に慌ててCronに「月曜日から動くように」仕込んでおきました。

### コード
```bash:トリガー
#!/bin/bash -
URL="https://qiita.com/api/v2/items/c31be8235354a3e21945"
TMPFILE="/var/tmp/tmp.next"
if [ -f ${TMPFILE} ]; then
  NEXT=$(cat ${TMPFILE})
else
  NEXT=3
fi

LIKE=$(curl ${URL} 2>/dev/null | jq .likes_count)
[[ ${LIKE} == "null" ]] && exit 0

if [[ ${LIKE} -ge ${NEXT} ]]; then
  echo "$(( ${NEXT} + 3 ))" > ${TMPFILE}
  curl http://localhost:8080/api/kill -X POST -d '{"desc":"Congraturations you have got '${LIKE}' like"}' 2>/dev/null
fi
```

さて、これは何でしょうか。

### 解説
2行目にURLが見えますね。
**Qiita API経由の、この記事のURLです。**

下から2行目にどこかで見たcurlが見えますね。
**localhostで動いているNightmare MonkeyへのKILLリクエストです。**

その前に`if`条件がありますね。
簡単に言うと、3,6,9,...と、**3いいね毎に条件がtrueになります。**

以上から、<font color="Red">**この記事のいいねが増えるたびに、とあるサーバーでNightmare Monkeyが悪夢を起こします。**</font>

## つまり
**押すなよ！絶対に押すなよ！**


# 最後に
と言うわけで、**自**己研鑽のために**自**動化された**自**己破壊的なプログラムを作った話でした。

* <font color="Red">**絶対に本番で動かさないでくださいね**</font>
* さすがに上司に怒られるので、「とあるサーバー」は本番ではありません。
* 慌てて書いたコードなので色々変ですがご容赦を。
* 今後機能をもっと実装して行きたいと思ってます。
* いいねのトリガーが無事に動いたら、追記しますね。
* こんなにクリスマス前に「殺す」とか「死ぬ」とか「悪夢」とか書いているの俺だけじゃないか？ ( *BANされないよね？* )

## 追記: 顛末その1
この投稿の2日後、月曜日朝時点で「30いいね」頂きました。
ありがとうございます。

そしてCron設定された時間を迎え、トリガーはちゃんと動作しました。

```text:log
2017/12/18 11:00:02 Kill: kworker/1:2H(PID:13669 PPID:2) - Congraturations you have got 30 like
2017/12/18 11:10:02 Kill: agetty(PID:1308 PPID:1) - Congraturations you have got 30 like
2017/12/18 11:20:02 Kill: ksmd(PID:40 PPID:2) - Congraturations you have got 30 like
2017/12/18 11:30:02 Kill: sshd(PID:23713 PPID:23711) - Congraturations you have got 30 like
2017/12/18 11:40:02 Kill: drbd_r_r0(PID:1507 PPID:2) - Congraturations you have got 30 like
2017/12/18 11:50:01 Kill: kdmflush(PID:685 PPID:2) - Congraturations you have got 30 like
2017/12/18 12:00:02 Kill: arc_reclaim(PID:1540 PPID:2) - Congraturations you have got 30 like
2017/12/18 12:10:02 Kill: rpciod(PID:484 PPID:2) - Congraturations you have got 30 like
2017/12/18 12:20:02 Kill: drbd0_submit(PID:1484 PPID:2) - Congraturations you have got 30 like
2017/12/18 12:30:05 Kill: xfs-buf/vda1(PID:637 PPID:2) - Congraturations you have got 30 like
```

しかし・・・10回のKILLが走りましたが、まだこれと言って
面白い
障害は発生していません。
中途半端なので、引き続きこのまま放置しておきます。