寝落ちを検出して EC2 を自動シャットダウンする方法

# 起動した EC2 を長時間放置してしまう理由

AWS で EC2 を立てて遊び始めたものの、気付いたら数時間放置してしまうことがあります。理由は様々ですが、

 * 寝落ちした。
 * テレビを見始めたら夢中になってしまった。
 * 調べごとのついでに SNS をチェックし始めた。
 * ゲームをやり始めたら、とてもレベルが上がった。
 * ...

といったことがあります。

せっかく立ち上げた EC2 を長時間放置してしまい、後になって「お金の無駄遣いだ..」と後悔することは精神衛生上良くありません。

# 寝落ちを検出する方法の検討

EC2 を長時間放置していることを検出する方法はいくつか考えられます。

 * cron で ```shutdown +TIME```、手動で ```shutdown -c``` する方法
 * cron で .bash_history の更新日時を監視する方法
 * Audit でコマンド実行履歴を記録し、cron で最後にコマンドを実行した日時を監視する方法

## cron で ```shutdown +TIME```、手動で ```shutdown -c``` する方法

例えば ```shutdown +30``` を実行すると 30 分後にシャットダウンするように予約できます。また、```shutdown -c``` で予約されているシャットダウンをキャンセルできます。

これを利用して、cron で定期的に ```shutdown +TIME``` を実行する方法が考えられます。寝落ちしている場合はそのままシャットダウンされ、そうでない場合は ```shutdown -c``` でシャットダウンをキャンセルする方法です。

一応やりたいことは実現できるのですが、cron で ```shutdown``` コマンドが実行されるたびに「The system is going down for maintenance in 3 minutes!」などと端末に表示されるため、いちいち集中力を削がれます。

## .bash_history の更新日時を監視する方法

寝落ちしていないのであれば何らかのコマンドを実行しているはず、そうであれば .bash_history が更新されるはず、という前提に立った方法です。以下のように ```cron``` で .bash_history が一定時間更新されていなければ寝落ちしたとみなしてシャットダウンします。

```
*/10 * * * * history -a && test $(stat --print '%Y' /home/ec2-user/.bash_history) -lt $(date --date '60 minutes ago' '+%s') && sudo shutdown -h now > /dev/null 2>&1
```

この方法は ```HISTCONTROL=erasedups``` のような設定を行っていると、いつまで経っても .bash_history が更新されない可能性があるため、まだ起きているのにシャットダウンされてしまう可能性があります。

## Audit でコマンド実行履歴を記録し、cron で最後にコマンドを実行した日時を監視する方法

Audit は設定したルールに基づいてシステム上のイベントを記録するための仕組みです。これを利用しすると、いつ、誰が、どんなコマンドを実行したのかを記録することができます。

Audit で記録したログから、最後にコマンドを実行した時間を調べ、一定時間以上前だったら ```shutdown``` します。

今回はこの方法を採用しました。

# 寝落ちを検出して EC2 を自動シャットダウンする

## Audit がインストールされていることを確認する

Amazon Linux にはデフォルトで Audit がインストールされています。以下のコマンドで Audit がインストールされているか確認できます。

```bash
$ sudo yum list installed audit
...
audit.x86_64
```

念のため Audit が実行されていることも確認します。

```
$ sudo /etc/init.d/auditd status
auditd (pid  3501) is running...
```

## Audit の設定を変更する

/etc/audit/audit.rules を編集し、「-a never,task」と書かれた行をコメントアウトし、「-a always,exit -F arch=b64 -S execve」を追加します。

```bash
# This file contains the auditctl rules that are loaded
# whenever the audit daemon is started via the initscripts.
# The rules are simply the parameters that would be passed
# to auditctl.

# First rule - delete all
-D

# Increase the buffers to survive stress events.
# Make this bigger for busy systems
-b 320

# Disable system call auditing.
# Remove the following line if you need the auditing.
#-a never,task

# Feel free to add below this line. See auditctl man page
-a always,exit -F arch=b64 -S execve
```

Audit を再起動します。

```bash
$ sudo /etc/init.d/audit restart
```

## Audit のログを確認する

Audit のログは /etc/audit/audit.log に保存されますが、```ausearch``` コマンドを使うと簡単に検索することができます。例えば ec2-user が ```ls``` したログを検索するには次のようにします。

```bash
$ sudo ausearch --uid ec2-user -c ls
...
----
time->Sun Oct 29 13:46:15 2017
type=PROCTITLE msg=audit(1509284775.665:718): proctitle="ls"
type=PATH msg=audit(1509284775.665:718): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=18672 dev=ca:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1509284775.665:718): item=0 name="/bin/ls" inode=262897 dev=ca:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=CWD msg=audit(1509284775.665:718): cwd="/tmp"
type=EXECVE msg=audit(1509284775.665:718): argc=1 a0="ls"
type=SYSCALL msg=audit(1509284775.665:718): arch=c000003e syscall=59 success=yes exit=0 a0=1e87bc0 a1=1e603c0 a2=1e60ce0 a3=7ffc19cbc8b0 items=2 ppid=4023 pid=4046 auid=500 uid=500 gid=500 euid=500 suid=500 fsuid=500 egid=500 sgid=500 fsgid=500 tty=pts6 ses=1 comm="ls" exe="/bin/ls" key=(null)
----
time->Sun Oct 29 13:46:17 2017
type=PROCTITLE msg=audit(1509284777.017:719): proctitle="ls"
type=PATH msg=audit(1509284777.017:719): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=18672 dev=ca:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1509284777.017:719): item=0 name="/bin/ls" inode=262897 dev=ca:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=CWD msg=audit(1509284777.017:719): cwd="/tmp"
type=EXECVE msg=audit(1509284777.017:719): argc=1 a0="ls"
type=SYSCALL msg=audit(1509284777.017:719): arch=c000003e syscall=59 success=yes exit=0 a0=1e604e0 a1=1e60460 a2=1e60ce0 a3=7ffc19cbc8b0 items=2 ppid=4023 pid=4047 auid=500 uid=500 gid=500 euid=500 suid=500 fsuid=500 egid=500 sgid=500 fsgid=500 tty=pts6 ses=1 comm="ls" exe="/bin/ls" key=(null)
----
time->Sun Oct 29 13:46:17 2017
type=PROCTITLE msg=audit(1509284777.369:720): proctitle="ls"
type=PATH msg=audit(1509284777.369:720): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=18672 dev=ca:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1509284777.369:720): item=0 name="/bin/ls" inode=262897 dev=ca:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=CWD msg=audit(1509284777.369:720): cwd="/tmp"
type=EXECVE msg=audit(1509284777.369:720): argc=1 a0="ls"
type=SYSCALL msg=audit(1509284777.369:720): arch=c000003e syscall=59 success=yes exit=0 a0=1e87c20 a1=1e876c0 a2=1e60ce0 a3=7ffc19cbc8b0 items=2 ppid=4023 pid=4048 auid=500 uid=500 gid=500 euid=500 suid=500 fsuid=500 egid=500 sgid=500 fsgid=500 tty=pts6 ses=1 comm="ls" exe="/bin/ls" key=(null)
...
```

## ec2-user が 60 分以上 ls を実行していなければシャットダウンするスクリプトを作成する

```ausearch``` で ec2-user が最後に ```ls``` を実行した日時を取得し、それが 60 分以上前であればシャットダウンするスクリプトを作成します。

```bash:/home/ec2-user/shutdown-if-inactive.sh
#!/bin/bash

LAST_COMMAND_EXECUTED_TIME=$(sudo ausearch --input-logs --uid ec2-user -c ls | grep '^time->' | tail -1 | sed 's/^time->//')

if [ -z "${LAST_COMMAND_EXECUTED_TIME}" ]; then
    exit 0
fi

if [ $(date -d "${LAST_COMMAND_EXECUTED_TIME}" '+%s') -lt $(date -d '60 minutes ago' '+%s') ]; then
    sudo shutdown -h now
fi
```

## crontab に設定する

ec2-user の crontab に以下の内容を設定します。

```
*/10 * * * * bash /home/ec2-user/shutdown-if-inactive.sh > /dev/null 2>&1
```

# まとめ

これで EC2 を長時間放置してしまった場合に、自動でシャットダウンできるようになりました。

Enjoy! AWS