[AWS Glue]ジョブ失敗時の通知設定をCloudFormationで実装してみた

2020.12.28

こんにちは、CX事業本部の若槻です。

AWSのETLサービスであるAWS Glueのジョブ失敗時の通知はCloudWatch Eventsを使用することにより実装が可能です。以下の記事ではコンソールを用いた設定方法が紹介されています。

今回は、上記で紹介されているAWS Glueのジョブ失敗時の通知設定をCloudFormationで実装してみました。

作ってみた

CloudFormationテンプレート

Glueジョブ含めたリソース定義を行っています。

% touch template.yaml

template.yaml

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  RawDataBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties: 
      BucketName: !Sub raw-data-${AWS::AccountId}-${AWS::Region}

  GlueJobExecuteRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - glue.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: etl-glue-job-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              -
                Effect: Allow
                Action:
                  - glue:*
                  - cloudwatch:PutMetricData
                  - s3:ListAllMyBuckets
                  - s3:ListBucket
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
                Resource: "*"
              -
                Effect: Allow
                Action: 
                  - logs:CreateLogStream
                  - logs:CreateLogGroup
                  - logs:PutLogEvents
                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws-glue/jobs/*
              -
                Effect: Allow
                Action: 
                  - s3:PutObject
                  - s3:GetObject
                  - s3:DeleteObject
                Resource:
                  - !Sub arn:aws:s3:::${RawDataBucket}/*/*
                  - !Sub arn:aws:s3:::${GlueJobTempDirBucket}/admin/*
                  - !Sub arn:aws:s3:::${GlueJobScriptBucket}/admin/*

  GlueJobTempDirBucket:
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub glue-job-temporary-${AWS::AccountId}-${AWS::Region}

  GlueJobScriptBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub glue-job-script-${AWS::AccountId}-${AWS::Region}

  SJIStoUTF8GlueJob:
    Type: AWS::Glue::Job
    Properties:
      Name: sjis-to-utf8-job
      Command:
        Name: pythonshell
        PythonVersion: 3
        ScriptLocation: !Sub s3://${GlueJobScriptBucket}/admin/sjis-to-utf8.py
      DefaultArguments:
        --TempDir: !Sub s3://${GlueJobTempDirBucket}/admin
        --BUCKET_NAME: !Sub ${RawDataBucket}
        --SRC_OBJECT_KEY: sjis-data/raw-sjis-data.csv
        --SRC_FILE_ENCODING: shift_jis
        --DEST_OBJECT_PREFIX: utf8-data
      ExecutionProperty:
        MaxConcurrentRuns: 1
      MaxRetries: 0
      Role: !Ref GlueJobExecuteRole

  GlueJobFailureNotificationTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: job-failure-notification-topic

  SJIStoUTF8JobFailureEventRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.glue
        detail-type: 
          - Glue Job State Change
        detail:
          state:
            - FAILED
          jobName:
            - !Ref SJIStoUTF8GlueJob
      State: ENABLED
      Targets:
        - 
          Arn: !Ref GlueJobFailureNotificationTopic
          Id: !GetAtt GlueJobFailureNotificationTopic.TopicName

  GlueJobFailureEventTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      Topics: 
        - !Ref GlueJobFailureNotificationTopic
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: sns:Publish
            Resource: !Ref GlueJobFailureNotificationTopic

Glueジョブスクリプト

スクリプトは、試しにエラーを起こせれば何でも良いですが、今回は以下記事のスクリプトを流用します。

% touch sjis-to-utf8.py
import boto3
import sys
import uuid
from awsglue.utils import getResolvedOptions

# ジョブパラメータの読み込み
args = getResolvedOptions(sys.argv, ['BUCKET_NAME', 'SRC_OBJECT_KEY', 'SRC_FILE_ENCODING', 'DEST_OBJECT_PREFIX'])

# S3 Service Resource 準備
s3 = boto3.resource('s3')

# ファイルを文字コード変換してロード
src_obj = s3.Object(args['BUCKET_NAME'], args['SRC_OBJECT_KEY'])
body = src_obj.get()['Body'].read().decode(args['SRC_FILE_ENCODING'])

# ファイルを保存
dest_obj_file_name = str(uuid.uuid4())
dest_obj = s3.Object(args['BUCKET_NAME'], args['DEST_OBJECT_PREFIX'] + '/' + dest_obj_file_name)
dest_obj.put(Body = body)

# ファイルを削除
src_obj.delete()

このスクリプトはS3バケットに特定のキーのファイルが存在しなければエラーとなります。

デプロイ

CloudFormationスタックをデプロイします。

% aws cloudformation deploy \
  --template-file template.yaml \
  --stack-name Glue-Job-Failure-Notify-Stack \
  --capabilities CAPABILITY_NAMED_IAM \
  --no-fail-on-empty-changeset

GlueジョブのスクリプトをS3バケットにアップロードします。

% ACCOUNT_ID=<Account ID>
% AWS_REGION=<AWS Region>
% aws s3 cp sjis-to-utf8.py s3://glue-job-script-${ACCOUNT_ID}-${AWS_REGION}/admin/sjis-to-utf8.py

サブスクリプション登録

SNSトピックのサブスクリプションに通知先のメールアドレスを登録します。

% to_address=<Mail Address>
% aws sns subscribe \
  --topic-arn arn:aws:sns:${AWS_REGION}:${ACCOUNT_ID}:job-failure-notification-topic \
  --protocol email \
  --notification-endpoint ${to_address}

上記コマンドを実行すると下記のような登録確認メールが届くので、Confirm subscriptionをクリックして登録を完了させます。 image

動作確認

通知を発生させてみる

ジョブを実行します。

% aws glue start-job-run --job-name sjis-to-utf8-job

すると下記のようなメールが届きます。

開始	AWS Notifications <no-reply@sns.amazonaws.com>
宛先:	<xxxxxxxxxxxxxxxxxx>
件名	AWS Notification Message

{"version":"0","id":"e240736b-0903-610f-1ce8-e6f1eeef7b7d","detail-type":"Glue Job State Change","source":"aws.glue","account":"0123456789","time":"2020-12-28T13:16:53Z","region":"ap-northeast-1","resources":[],"detail":{"jobName":"sjis-to-utf8-job","severity":"ERROR","state":"FAILED","jobRunId":"jr_f41dd4aeed489bff491de6cc8f1d9b73383b601d174e180a87450ceb4f5664a8","message":"NoSuchKey: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist."}}

detail - jobNameよりエラー通知がsjis-to-utf8-jobジョブで発生したことが分かります。またdetail - messageにNoSuchKey: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.とあるため、S3バケットからオブジェクト取得時に指定のキーが存在しなかったためエラーとなっていることが分かります。

ジョブ失敗のメトリクスの確認

CloudWatchのマネジメントコンソールで該当のルールを開き、[ルールのメトリクスを表示]をクリックします。 image

メトリクス名TriggeredRulesにチェックを入れると、グラフにジョブ失敗のメトリクスが記録されていることが確認できます。 image

おわりに

AWS Glueのジョブ失敗時の通知設定をCloudFormationで実装してみました。

Glueジョブを使用する際は是非ともセットで実装したいですね。

参考

以上