Level10

とあるSEの備忘録

AWS のメンテナンス情報で対象リソースを含めて Slack に通知する

概要

AWS における EC2 のメンテナンス情報は AWS アカウントのメールアドレスに送信されます。
メールだと気づきづらいので、Slack 通知したい!! と思うかもしれません。
その実装に関して、@hayao_k さんの記事がとても参考なります!!

qiita.com

今回は上記コードに情報を足して EC2 に関するリソース情報を含めて Slack へ通知してみたいと思います。

前提

  • Python 3.7 で動作確認
  • 対象となるのは EC2 のリソースのみです ( RDS 等は対応していません )

サンプルコード

サンプルコードはこちらになります

import json
import os
import logging
import boto3
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

logger = logging.getLogger()
logger.setLevel(logging.INFO)
ec2client = boto3.client('ec2')
iamclient = boto3.client('iam')

def lambda_handler(event, context):
  webhook_url = os.environ['WEBHOOK_URL']
  account_alias = iamclient.list_account_aliases()['AccountAliases'][0]

  message = 'AWS Healthイベントを検知しました。対象のリソースを確認してください。' 
  
  logger.info(event)

  # リソース情報が含まれているかどうかを判定
  if 'resources' in event:
      if len(event['resources']) > 0:
          resource = event['resources'][0]
          resource_tags = 'nothing'

          # リソースが "i-" であればEC2のHostnameタグ情報を取得する
          type_check_str = resource[:2]
          if type_check_str == 'i-':
              response = ec2client.describe_instances(
                  InstanceIds=[
                      resource
                  ])
          
              for instance in response['Reservations']:
                  #print(instance['Tags'])
                  for tag in instance['Instances'][0]['Tags']:
                      #print(tag['Key'])
                      if tag['Key'] == 'Name':
                          #print(tag['Value'])
                          resource_tags = tag['Value']
      else:
          resource = 'nothing'
          resource_tags = 'nothing'
  else:
      resource = 'nothing'
      resource_tags = 'nothing'
  
  slack_message = {
    'username': 'AWS Health Event Notification',
    'icon_emoji': ':warning:',
    'text': message,
    'attachments': [
      {
        'fallback': 'AWS Health Event Description.',
        'color': 'warning',
        'title': event['detail']['eventTypeCode'],
        'title_link': 'https://phd.aws.amazon.com/phd/home',
        'fields': [
          {
            'title': 'Account ID',
            'value': event['account'],
            'short': True
          },
          {
            'title': 'Account Alias',
            'value': account_alias,
            'short': True
          },
          {
            'title': 'Region',
            'value': event['region'],
            'short': True
          },
          {
            'title': 'Service',
            'value': event['detail']['service'],
            'short': True
          },
          {
            'title': 'Start Time',
            'value': event['detail']['startTime'],
            'short': True
          },
          {
            'title': 'Resource',
            'value': resource,
            'short': True
          },
          {
            'title': 'ResourceTags',
            'value': resource_tags,
            'short': True
          },
          {
            'title': 'Description',
            'value': event['detail']['eventDescription'][0]['latestDescription'][0:511],
            'short': False
          }
        ]
      }
    ]
  }

  req = Request(webhook_url, json.dumps(slack_message).encode('utf-8'))
  try:
    response = urlopen(req)
    response.read()
    logger.info("Message posted.")
  except HTTPError as e:
    logger.error("Request failed: %d %s", e.code, e.reason)
  except URLError as e:
    logger.error("Server connection failed: %s", e.reason)

解説

下記が追加部分です。 通知メッセージに resources が含まれるかどうかを判定し、 含まれている場合には EC2 に設定されている Name タグから値を取得しています。 対象外であれば nothing を表示します。

  # リソース情報が含まれているかどうかを判定
  if 'resources' in event:
      if len(event['resources']) > 0:
          resource = event['resources'][0]
          resource_tags = 'nothing'

          # リソースが "i-" であればEC2のHostnameタグ情報を取得する
          type_check_str = resource[:2]
          if type_check_str == 'i-':
              response = ec2client.describe_instances(
                  InstanceIds=[
                      resource
                  ])
          
              for instance in response['Reservations']:
                  #print(instance['Tags'])
                  for tag in instance['Instances'][0]['Tags']:
                      #print(tag['Key'])
                      if tag['Key'] == 'Name':
                          #print(tag['Value'])
                          resource_tags = tag['Value']
      else:
          resource = 'nothing'
          resource_tags = 'nothing'
  else:
      resource = 'nothing'
      resource_tags = 'nothing'

結果

こんな感じで Slack に通知が来ます!! f:id:genkeyx:20190604001811p:plain

AccountAlias : 通知を受け取ったアカウントのエイリアスを取得して表示
Resource : 通知内のリソース情報を表示します ( 例: i-0000000000000000 )
ResourceTags ' 通知内のリソース情報に設定されている Name タグの値を表示します ( 例 : ec2-instance-name )

参考情報 : 受信 Evetnt 例

CloudWatch からは下記形式のメッセージが来ます ( 一例 Lambda の動作確認をする場合には本 JSON をもじってテストイベントとして設定見てください!!

{
  "version": "0",
  "id": "00000000-0000-0000-0000-000000000000",
  "detail-type": "AWS Health Event",
  "source": "aws.health",
  "account": "000000000000",
  "time": "YYYY-MM-DDTHH:MM:SSZ",
  "region": "ap-northeast-1",
  "resources": [
    "i-0000000000000000"
  ],
  "detail": {
    "eventArn": "arn:aws:health:ap-northeast-1::event/EC2/AWS_EC2_PERSISTENT_INSTANCE_RETIREMENT_SCHEDULED/AWS_EC2_PERSISTENT_INSTANCE_RETIREMENT_SCHEDULED",
    "service": "EC2",
    "eventTypeCode": "AWS_EC2_PERSISTENT_INSTANCE_RETIREMENT_SCHEDULED",
    "eventTypeCategory": "scheduledChange",
    "startTime": "Sun, 11 Jan 2018 11:00:00 GMT",
    "endTime": "Sun, 11 Jan 2018 11:00:00 GMT",
    "eventDescription": [
      {
        "language": "en_US",
        "latestDescription": "EC2 has detected degradation of the underlying hardware hosting your Amazon EC2 instance associated with this event in the ap-northeast-1 region. Due to this degradation, your instance could already be unreachable. After 2018-1-11 11:00 UTC your instance, which has an EBS volume as the root device, will be stopped.\\n \\nYou can see more information on your instances that are scheduled for retirement in the AWS Management Console (https://console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#Events)\\n \\n* How does this affect you?\\n \\nYour instance will be stopped after the specified retirement date, but you can start it again at any time. Note that if you have EC2 instance store volumes attached to the instance, any data on these volumes will be lost when the instance is stopped or terminated as these volumes are physically attached to the host computer\\n \\n* What do you need to do?\\n \\nYou can wait for the scheduled retirement date - when the instance is stopped - or stop the instance yourself any time before then. Once the instances has been stopped, you can start the instance again at any time. For more information about stopping and starting your instance, and what to expect when your instance is stopped, such as the effect on public, private and Elastic IP addresses associated with your instance, see Stop and Start Your Instance in the EC2 User Guide (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Stop_Start.html).\\n \\n* Why retirement?\\n \\nAWS may schedule instances for retirement in cases where there is an unrecoverable issue with the underlying hardware. For more information about scheduled retirement events please see the EC2 user guide (http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-retirement.html).\\n \\nIf you have any questions or concerns, you can contact the AWS Support Team on the community forums and via AWS Premium Support at: http://aws.amazon.com/support"
      }
    ],
    "affectedEntities": [
      {
        "entityValue": "i-00000000000000000",
        "tags": {}
      }
    ]
  }
}

Athena で VPC Flow Logs 結果 を JST で表示する

概要

VPC Flow Logs は S3 に出力して Athena で解析することができます。 詳細は AWS 公式ドキュメントを参照ください。

docs.aws.amazon.com

VPC Flow Logs の日時情報 ( starttime, endtime ) はデフォルトでは Unixtime になっています。*1 今回は Unixtime を JST 表示にするクエリの紹介です。

前提

クエリ

特定の通信開始時刻 ( JST ) を抽出する場合のクエリになります。

SELECT 
 version,
 account,
 interfaceid,
 sourceaddress,
 destinationaddress,
 sourceport,
 destinationport,
 protocol,
 numpackets,
 numbytes,
 DATE_FORMAT(FROM_UNIXTIME(starttime, 'Asia/Tokyo') ,'%Y-%m-%d %H:%i:%s') as starttimeJST,
 DATE_FORMAT(FROM_UNIXTIME(endtime, 'Asia/Tokyo') ,'%Y-%m-%d %H:%i:%s') as endtimeJST,
 action,
 logstatus,
 dt
FROM "DB名"."Table名"
WHERE dt='YYYY-MM-DD'
AND  DATE_FORMAT(FROM_UNIXTIME(starttime, 'Asia/Tokyo') ,'%Y-%m-%d %H:%i:%s') = 'YYYY-MM-DD HH:MM:SS'
ORDER BY starttimeJST
limit 100;

解説

Athena では、オープンソースの分散型クエリエンジンである Presto を使用してクエリを実行します。 そのため、DATE_FORMATFROM_UNIXTIMEをそれぞれ利用しています。

おわり

JST でのデータ抽出にご活用ください(๑•ω•́ฅ✧

Lambda から Athena の WorkGroup 指定実行で出力するエラー対処法

概要

Lambda を用いて Athena を実行しようとしたところ、下記エラーメッセージを出力しました。

[ERROR] ParamValidationError: Parameter validation failed:
Unknown parameter in input: "WorkGroup", must be one of: QueryString, ClientRequestToken, QueryExecutionContext, ResultConfiguration
Missing required parameter in ResultConfiguration: "OutputLocation"
Traceback (most recent call last):

原因

stackoverflow に該当する投稿がありました。

stackoverflow.com

The bundled version of boto3 in the Lambda execution environment is not up to date with the latest boto3 release.

You can make a newer version available either by includin it in the deployment package or adding it as a Lambda Layer.
Lambda実行環境のboto3のバンドル版は、最新のboto3リリースでは最新のものではありません。

新しいバージョンを展開パッケージに含めるか、Lambda Layerとして追加することで、新しいバージョンを利用可能にすることができます。

ということで、Lambda 実行環境の boto3 が Athena の WorkGroup 指定に対応していないのが原因のようです。

クラメソすずきさんの記事として Athena の WorkGroup ではないですが、waf-regional 非対応だった時の対応記事がありました。

dev.classmethod.jp

対処法

対処法としては 2 つあります。

  1. 新しいバージョンの boto3 をパッケージに含める
  2. Lambda Layer として追加する

新しいバージョンの boto3 をパッケージに含める

以前からある方法です。 上記、クラメソすずきさんの記事を参考にしてみてください

Lambda Layer として追加する

Qiita @hayao_k さんの記事が参考になります

qiita.com

おわり

Lambda Layer を利用することで複数の Lambda で共通利用するライブラリをパッケージに含めなくて済むのは便利です。 Lambda にはデプロイパッケージサイズの上限もあります。*1そのためデプロイパッケージサイズを小さくできる点でも利点がありそうです。 積極的に使っていきたいですね。

LambdaでLambda の function一覧を取得する

概要

Lambda で Python ( boto3 ) を使って Lambda の function 名一覧を取得する方法です。

ポイント

boto3 での Lambda function の情報を取得するためには list_functions を使用します。 list_functions では取得されたオブジェクト件数が、リクエストパラメータで指定した MaxItems 以上存在した場合には、NextMarker という値がレスポンスで返却されます。 その値を次のAPIコールにMarkerパラメータとして与えることで残りの値を取得することができます。

前提

サンプルコード

import boto3

client = boto3.client('lambda')
ary_function_name = []
    

def function_list(max_items=50, next_marker=None):
    if next_marker:
        r = client.list_functions(MaxItems=max_items, Marker=next_marker)
    else:
        r = client.list_functions(MaxItems=max_items)
    
    for functions in r['Functions']:
        ary_function_name.append(functions['FunctionName'])

    if 'NextMarker' in r:
        return function_list(max_items=max_items, next_marker=r['NextMarker'])
    else:
        return

def lambda_handler(event, context):
    
    r = function_list()
    print(ary_function_name)
    
    return

まとめ

これで function 名の取得ができます。 他にも boto3 を利用していると Marker を使用するケースがあると思うので参考にしてみてください。