目次
はじめに
【追記:2021年8月23日】
Redshiftクラスタの起動に15分以上かかる場合があり、Lambdaがタイムアウトしてアラートが抑止できていない状況が発生していました。
この課題解決のため、IAMポリシーとLambdaのソースコードを修正しました。
今回はAWS Lambdaを使用してAmazon Redshiftのスケジュール停止・再開を制御するという内容です。
背景として、Redshiftクラスタの停止・再開タイミングでCloudWatchアラームが不要なアラートメールを送信してしまうという状況がありました。
Lambdaでクラスタ停止・再開とCloudWatchアラーム無効化・有効化を制御することで、アラートを抑止したいというのが目的です。
設計
・Lambda関数を1つ用意します。
・CloudWatchルールはRedshiftクラスタ1つにつき最低2つ用意し、ターゲットとしてLambdaを指定します。(ルールはRedshiftクラスタ停止、再開でそれぞれ1つずつ)
設定手順
1. IAMポリシーの作成
まずは、IAMポリシーを作成します。
このポリシーは、Lambdaで使用するIAMロールにアタッチします。
JSONは以下の記載内容を参考にしてください。
ポリシー名は「redshift-alert-reduction_policy」としました。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowRedshiftClusterManagement",
"Action": [
"redshift:ResumeCluster",
"redshift:PauseCluster"
],
"Resource": [
"arn:aws:redshift:ap-northeast-1:[アカウントID]:cluster:[クラスタ名]"
],
"Effect": "Allow"
},
{
"Sid": "AllowDescribeRedshiftClusters",
"Action": [
"redshift:DescribeClusters"
],
"Resource": [
"*"
],
"Effect": "Allow"
},
{
"Sid": "AllowInvokeLambdaFunction",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:ap-northeast-1:[アカウントID]:function:redshift_alert_reduction"
],
"Effect": "Allow"
},
{
"Sid": "AllowCWAlarmManagement",
"Action": [
"cloudwatch:DisableAlarmActions",
"cloudwatch:EnableAlarmActions"
],
"Resource": [
"arn:aws:cloudwatch:ap-northeast-1:[アカウントID]:alarm:*"
],
"Effect": "Allow"
}
]
}
2. IAMロールの作成
続いて、IAMロールを作成します。
ここではロール名を「lambda-alert-reduction-role」としました。
ロール作成時の「ユースケースの選択」でLambdaを選択してください。
ポリシーは以下の2つをアタッチします。
・AWSLambdaBasicExecutionRole
・redshift-alert-reduction_policy
3. Lambda関数の作成
Lambda関数を作成します。ランタイムはPython3.8です。
関数名は「redshift_alert_reduction」としました。
関数には作成したロール「lambda-alert-reduction-role」を設定します。
タイムアウトを15分に設定してください。
ソースコード
import json
import boto3
import logging
import time
def get_vein_logger():
import logging
log_format = '[VEIN-%(levelname)s][%(aws_request_id)s][%(funcName)s:%(lineno)d]\t%(message)s'
formatter = logging.Formatter(log_format)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
for handler in logger.handlers:
handler.setFormatter(formatter)
return logger
logger = get_vein_logger()
def control_cloudwatch_alarm(cluster_name, action):
client = boto3.client('cloudwatch')
alarm_disk = f'VEIN-ALERT Redshift {cluster_name} - Disk Space Used'
alarm_health = f'VEIN-ALERT Redshift {cluster_name} - Health Status'
alarm_cpu = f'VEIN-ALERT Redshift {cluster_name} - CPU Utilization'
if (action):
try:
client.enable_alarm_actions(
AlarmNames=[
alarm_disk,
alarm_health,
alarm_cpu
]
)
logger.info("Enabling CloudWatch Alarm")
except Exception as e:
logger.error("Exception: {}".format(e))
else:
try:
client.disable_alarm_actions(
AlarmNames=[
alarm_disk,
alarm_health,
alarm_cpu
]
)
logger.info("Disabling CloudWatch Alarm")
except Exception as e:
logger.error("Exception: {}".format(e))
def control_redshift_cluster(cluster_name, action):
client = boto3.client('redshift')
if (action):
try:
cluster_status = check_redshift_cluster_status(cluster_name)
if cluster_status == 'Available':
#sleep 10 minutes and enable cloudwatch alarm
time.sleep(600)
control_cloudwatch_alarm(cluster_name, action)
return
elif cluster_status == 'Modifying':
pass
elif cluster_status == 'Paused':
client.resume_cluster(
ClusterIdentifier=cluster_name
)
logger.info("Resuming Redshift cluster")
else:
#sleep 10 minutes and enable cloudwatch alarm
time.sleep(600)
control_cloudwatch_alarm(cluster_name, action)
return
#sleep 8 minutes and check status of redshift cluster
time.sleep(480)
cluster_status = check_redshift_cluster_status(cluster_name)
#judge if cloudwatch alarm can be enabled
if cluster_status == 'Available':
#sleep 5 minutes and enable cloudwatch alarm
time.sleep(300)
control_cloudwatch_alarm(cluster_name, action)
else:
logger.info("Invoking additional Lambda function to prevent 15-minute timeout")
invoke_lambda_function(cluster_name)
except Exception as e:
logger.error("Exception: {}".format(e))
else:
try:
client.pause_cluster(
ClusterIdentifier=cluster_name
)
logger.info("Pausing Redshift cluster")
except Exception as e:
logger.error("Exception: {}".format(e))
def check_redshift_cluster_status(cluster_name):
client = boto3.client('redshift')
response = client.describe_clusters(
ClusterIdentifier=cluster_name
)
cluster_status = response['Clusters'][0]['ClusterAvailabilityStatus']
return cluster_status
def invoke_lambda_function(cluster_name):
event_dict={
"cluster_name": cluster_name,
"action": True
}
event = json.dumps(event_dict)
response = boto3.client('lambda').invoke(
FunctionName='vein_redshift_alert_reduction',
InvocationType='Event',
Payload=event
)
def lambda_handler(event, context):
#redshift cluster name
cluster_name = event['cluster_name']
#if action is true, resume cluster and enable cloudwatch alram
action = event['action']
if action == True:
control_redshift_cluster(cluster_name, action)
elif action == False:
control_cloudwatch_alarm(cluster_name, action)
#wait until cloudWatch alarm becomes disabled
time.sleep(60)
control_redshift_cluster(cluster_name, action)
ソースコードの補足
・12~14行目でCloudWatchのアラーム名を指定しています。これらのアラームを抑止しますので、適宜アラーム名を変更してください。
・63行目に”time.sleep(480)”とありますが、Redshiftクラスタ再開から8分待機し、クラスタがAvailableになったらCloudWatchアラームを有効化します。
・RedshiftクラスタはAvailableになった直後にCloudWatchアラームを有効化してもアラートが上がる場合があります。そのため、アラーム有効化の前に69行目で5分の待機時間を入れています。
・1回の処理で待機時間の合計が15分を超えてしまうとLambdaがタイムアウトするため、待機時間は少し余裕を持たせて8分と5分の計13分としています。
・クラスタ再開から8分経過した時点でAvailableになっていない場合は、再度自身(redshift_alert_reduction)を呼び出し、Availableになるまで処理を繰り返します。
これにより、Lambdaの15分タイムアウトを回避しつつ、アラート抑止を機能させます。
4. CloudWatchルール設定
設計の説明でも記載した通り、Redshiftクラスタ1つにつき、ルールは最低2つ設定します。
停止・再開タイミングが複数ある場合は、その分だけルール設定が必要となります。
ターゲットには作成したLambda関数を指定します。
入力の設定で「定数 (JSONテキスト)」を選択し、以下のJSONを入力します。
jsonで指定するactionがTrueの場合 :Redshiftを起動、CloudWatchアラームを有効化
jsonで指定するactionがFalseの場合:Redshiftを停止、CloudWatchアラームを無効化
●定数に入力するJSON(クラスタ起動の場合)
{ "cluster_name": "クラスタ名", "action": true}
●定数に入力するJSON(クラスタ停止の場合)
{ "cluster_name": "クラスタ名", "action": false}
ルール名は以下のようにしました。
ルール①:redshift_[クラスタ名]_resume
ルール②:redshift_[クラスタ名]_pause
以上で設定は完了です。
おわりに
今回はLambdaを使用して、RedshiftやCloudWatchを制御することでアラートを抑止し、重要なアラートを見落とさないようにするという対応を実施しました。
記事投稿時点ではCloudWatchにアラートを抑止するための機能が組み込まれていないため、ここに記載した内容が少しでもお役に立てれば幸いです。
- カテゴリー