【crontab】EC2のcrontab -> Pythonスクリプト実行 -> RDSのストプロ -> Slackへ通知、という流れを構築する

# 概要
EC2の`crontab` -> Pythonスクリプト実行 -> RDSのストプロ -> Slackへ通知、という流れを構築しました。

具体的には、
「毎日9時に、プロシージャで実施した結果を、指定のSlackチャンネルに通知する」
という流れがゴールです。

今回は、AWSのEC2サーバでクーロンを利用してPythonスクリプトを定期的に実行させる方法になります。Pythonスクリプトの実行はRDSのストプロを呼び出し、SQL結果を特定のSlackチャンネルに通知します。

構築手順と注意点を解説します。

# 手順
まず、EC2サーバに今回のツール用のディレクトリを作成します。
ここでは、とあるDBテーブルからレコード数をカウントするツールを例にしますので、ディレクトリ名は`count-number-tool`とします。

その下に、以下の３つを作成していきます。

```
count-number-tool
├── venv
├── number-counter.py
└── run_script.sh
```

### `venv`の作成
まずは仮想環境を作成します。
サーバ全体に影響を与えずにパッケージをインストールしたいためです。
（個別にインストールするライブラリがない場合は不要です）

以下のコマンドで仮想環境を作成しましょう。

```
python3 -m venv venv
```

※もし、まだpythonがインストールされていない場合は以下のコマンドでインストールしましょう

```
sudo yum install -y python3
```

仮想環境を作成したらアクティベートします。

```
source venv/bin/activate
```

利用したいライブラリをインストールしましょう。
例えば、以下のような感じです。

```
pip install pymysql
pip install requests
```

### `run_script.sh`の作成
`run_script.sh`は、クーロンで呼び出すスクリプトです。
以下のように、仮想環境の起動＆Pythonスクリプトの実行、を記載します。

```run_script.sh
#!/bin/bash
source /home/ec2-user/count-number-tool/venv/bin/activate
python /home/ec2-user/count-number-tool/number-counter.py
deactivate
```

シェルスクリプトには実行権限を与えておきます。

```
chmod +x /home/ec2-user/count-number-tool/run_script.sh
```

### `number-counter.py`の作成

実行したいPythonスクリプトを書きます。
今回は、`count_number`というプロシージャを実行して、その結果をSlackに通知するコードにしています。

```py
import json
import os
from datetime import datetime, timedelta
import pymysql
import requests
import logging
from decimal import Decimal

logger = logging.getLogger()
logger.setLevel(logging.INFO)

rds_host = 'xxx'
rds_user = 'xxx'
rds_password = 'xxx'
rds_db = 'xxx'
slack_webhook_url = 'https://hooks.slack.com/services/xxx'

def send_slack_notification(webhook_url, message):
    payload = {
        "text": message
    }
    response = requests.post(webhook_url, data=json.dumps(payload), headers={'Content-Type': 'application/json'})
    if response.status_code != 200:
        raise ValueError('Request to Slack returned an error {}, the response is:\n{}'.format(response.status_code, response.text))

def decimal_default(obj):
    if isinstance(obj, Decimal):
        return float(obj)
    raise TypeError

def main():
    logger.info("Script started")
    
    today = datetime.now()
    yesterday = today - timedelta(1)
    report_date = yesterday.strftime('%Y-%m-%d')
    logger.info("Report date: {}".format(report_date))
    
    try:
        connection = pymysql.connect(host=rds_host, user=rds_user, password=rds_password, db=rds_db)
        logger.info("Connected to RDS")
    except Exception as e:
        logger.error("Error connecting to RDS: {}".format(e))
        raise
    
    try:
        with connection.cursor() as cursor:
            cursor.callproc('count_number', [report_date])
            result = cursor.fetchall()
            logger.info("Stored procedure result: {}".format(result))
            
            if result:
                row = result[0]
                message = "Report for {}:\n".format(report_date)
                message += "hoge, foo, example, sample, test\n"
                message += "{},{},{},{},{},{}\n".format(row[0], row[1], row[2], row[3], row[4])
                send_slack_notification(slack_webhook_url, message)
                logger.info("Slack notification sent"
            
    finally:
        connection.close()
        logger.info("RDS connection closed")
    
    return {
        'statusCode': 200,
        'body': json.dumps(result, default=decimal_default)
    }

if __name__ == "__main__":
    main()
```

※EC2サーバはRDSへアクセスできる状態である必要があります。セキュリティグループで許容していることを確認してください

### RDSのストプロ例

以下は、sample_tableのcreate_dateカラムが前日のもので、かつ、sample_noカラムに指定の文字列から始まるレコードが何件あるか、というSQLになります。上述のPythonスクリプトで以下のプロシージャを実行します。

```count_number.sql
CREATE DEFINER=`root`@`%` PROCEDURE `sample`.`count_number`(IN report_date DATE)
BEGIN
    SELECT 
        SUM(CASE WHEN sample_no LIKE 'hoge%' THEN 1 ELSE 0 END) AS count_hoge,
        SUM(CASE WHEN sample_no LIKE 'foo%' THEN 1 ELSE 0 END) AS count_foo,
        SUM(CASE WHEN sample_no LIKE 'example%' THEN 1 ELSE 0 END) AS count_example,
        SUM(CASE WHEN sample_no LIKE 'sample%' THEN 1 ELSE 0 END) AS count_sample,
        SUM(CASE WHEN sample_no LIKE 'test%' THEN 1 ELSE 0 END) AS count_test
    FROM mysql57.sample_table  
    WHERE create_date BETWEEN CONCAT(report_date, ' 00:00:00') AND CONCAT(report_date, ' 23:59:59');
END
```

### crontabの設定

EC2サーバで以下を実行し、Cronジョブを設定します。

```
crontab -e
```

> crontab コマンドは、cron ジョブの実行のほか、編集、リスト、除去を行います。 cron ジョブとは、cron デーモンによりスケジュールされ、一定の周期で実行されるジョブです。 cron ジョブの実行を要求するには、crontab コマンドに -e フラグを指定します。 crontab コマンドは、編集セッションを起動します。この編集セッションで、crontab ファイルを作成できます。
https://www.ibm.com/docs/ja/aix/7.2?topic=c-crontab-command

ジョブは以下のように追加します。

```
00 9 * * * /home/ec2-user/count-number-tool/run_script.sh
```

上記は、毎朝9時に、`run_script.sh`を実行する、というもの。
これにより、Pythonスクリプトが定期的に実行され、Slackへ毎朝結果が届くようになりました。

## 参考
AWS Lambdaを利用して構築する手順も以前に記事にしました。ご参考までに。

https://qiita.com/Ryo-0131/items/80616c0cba2999d77c55