AWS Lambda(Python)によるサーバー簡易監視と、Slackでの結果通知


## 実現したかったこと

- 運用中サーバーの簡易監視として、定期的にHTTPアクセスを行い、結果を確認したい。
- サーバーレスで実行したい。
	- 対象サーバーにアクセスするIPを制限しているため、監視元は固定IPの必要がある。
- 完全自動化し、監視結果だけが通知されて欲しい。

## 最終的に実現したこと

- 全体イメージ
![全体イメージ](https://qiita-image-store.s3.amazonaws.com/0/40651/e7ed1c39-3d74-8ffa-5483-b31d558e9be0.png "AWS+Slack.png")


---

- `S3`に監視先サーバーデータを配置する。
	- データの形式は以下の通り。

		```json:servers.json
		{
		    "servers": [
		        {"name": "Google", "url": "http://www.google.co.jp"},
		        {"name": "Yahoo", "url": "http://www.yahoo.co.jp"}
		    ]
		}
		```

- `AWS Lambda(Python)`から上記のデータを読み込み、各サーバーにHTTPアクセスを行う。

- 監視結果を`Slack`に通知する。
- 上記を、`CloudWatch Events - Schedule`で定期実行する。

## 設定詳細

### VPC設定

- 実現したかったことが、以下で完全に網羅されていたため、参照。
	- https://www.joyzo.co.jp/blog/2325

### IAM設定

- `Lambda`作成時に、roleのtemplateとして、`Simple Microservice permissions`を選択。
- `S3`にアクセスするため、`AmazonS3ReadOnlyAccess`のポリシーをアタッチ。
- `Lambda`を`VPC`上で実行するため、`AWSLambdaVPCAccessExecutionRole`のポリシーをアタッチ。

### CloudWatch Events - Schedule設定

- [Tutorial: Schedule Lambda Functions Using CloudWatch Events - Amazon CloudWatch Events](http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/RunLambdaSchedule.html)

### Slack

- [Incoming Webhooks](https://api.slack.com/incoming-webhooks)を参照して、`Slack`への通知用URLを取得。

### AWS Lambda(Python)

- エラーハンドリング周りは改善の余地あり。

```python:lambda_function.py
import json
import requests
import boto3

BUCKET_NAME = 'xxxxxxxxxx'
OBJECT_NAME = 'xxxxxxxxxx/servers.json'
SLACK_POST_URL = 'https://hooks.slack.com/services/xxxxxxxxxx/xxxxxxxxxx/xxxxxxxxxxxxxxxxxxxx'

def lambda_handler(event, context):
    json_data = __getServers()
    __check_server(json_data)

def __getServers():
   s3 = boto3.resource('s3')
   obj = s3.Object(BUCKET_NAME, OBJECT_NAME)
   response = obj.get()
   body = response['Body'].read()
   return body.decode('utf-8')

def __check_server(json_data):
    data = json.loads(json_data)
    servers = data['servers']

    has_error = False

    for server in servers:
        name = server['name']
        url = server['url']
        print("Check: " + name)

        try:
            r = requests.get(url)
            if r.status_code != 200:
                __send_error_message(name, url)
                has_error = True
        except requests.exceptions.RequestException as e:
            __send_request_error_message(name, url)
            has_error = True

    if has_error == False:
        __send_success_message()

def __send_error_message(name, url):
    payload = {
        "text": name + '\n' + url + '\n' + '*ERROR!*',
        "icon_emoji": ":x:"
    }
    __send_message(payload)

def __send_request_error_message(name, url):
    payload = {
        "text": name + '\n' + url + '\n' + '*Request Error!*',
        "icon_emoji": ":warning:"
    }
    __send_message(payload)

def __send_success_message():
    payload = {
        "text": "All Servers OK!",
        "icon_emoji": ":o:"
    }
    __send_message(payload)

def __send_message(payload):
    try:
        return requests.post(SLACK_POST_URL, json=payload)
    except requests.exceptions.RequestException as e:
        return None


```

### AWS Lambda(Python)設定時の注意点

- ライブラリはソースコードと同じディレクトリに配置する。

```bash
pip install requests -t .
```

- ソースコードとライブラリをまとめて、zipにして配置する。

```bash
zip -r lambda_function.zip *
```

- [Programming Model for Authoring Lambda Functions in Python - AWS Lambda](http://docs.aws.amazon.com/lambda/latest/dg/python-programming-model.html)

## 雑感
- `AWS Lambda`にVPCを設定できるようになったことで、データ送信元を固定IPアドレスにしてLambdaを実行できるようになったため、使いどころが広がった。
- 手軽な通知先として、`Slack`との連携が簡単で便利すぎ。
- servers.jsonの中身をbodyに詰めた、HTTP POST通信をトリガーに、`API Gateway`経由で実行させる方法も試したが、最終的に定期実行がラクな現在の形とした。