AWS GlueのGlueジョブやGlue ConnectionをCloudFormationで構築する方法を書いてみました。必須のもの以外の諸々も全て作成したのでかなりの量です。
やりたいこと
- AWS Glueジョブとそれに必要なものをCloudFormationで構築する
- ymlで記述します
前提
- 転送元データソースはMySQL(EC2,RDS,Aurora問わず)、転送先データソースはS3を想定しています
- そうでない場合でもある程度は使いまわせます
- MySQL側のスキーマ情報を使用するため、Glue Data Catalogは使用しません
- デプロイをする環境ですが、UNIX系を想定しています(MacもOK)
- シェルスクリプトを用いますが、
jq
というコマンド(モジュール)を使用するので、インストールが必要です
CloudFormationで構築する概要
- Glueジョブで使用するIAMロール・IAMポリシー
- S3バケット(Glueジョブのスクリプト配置用)
- S3バケット(Glueジョブの一時ディレクトリ用)
- S3バケット(GlueジョブのETL結果の格納先用)
- Glueコネクションで使用するセキュリティグループ
- Glueコネクション
- Glueジョブ
- 定期実行用Glueトリガー
- ジョブ失敗通知用のイベントルール
- SecurityConfigurationで使用するKMS
- SecurityConfiguration
記事では省略する事前準備
- VPC・サブネットの構築
- MySQL自体の構築
- MySQLユーザの作成
- Secret ManagerでMySQLユーザのSecretの作成
- Glue Connectionにパスワードなどを直書きしないためにSecret Managerを使用して構築します
- Glueジョブの実装・ファイル
方法
ディレクトリ構成
. ├── glue-job-script │ └── job_data_transfer.py ├── shdir │ └── glue.sh └── yamldir └── glue.yaml
CloudFormationテンプレートファイル(yaml)とシェルスクリプトファイルのみ記載します。
GitHubにはジョブファイルも載せています。(DynamoDB->MySQLのGlueジョブ)
CloudFormationテンプレートファイル(yml)
yamldir/glue.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: glue cfn
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "AppName"
Parameters:
- AppName
- Label:
default: "ConnectionName"
Parameters:
- ConnectionName
- Label:
default: "JDBCString"
Parameters:
- JDBCString
- Label:
default: "Secret"
Parameters:
- Secret
- Label:
default: "GlueScriptsBucket"
Parameters:
- GlueScriptsBucket
- Label:
default: "GlueResultsBucket"
Parameters:
- GlueResultsBucket
- Label:
default: "GlueTempBucket"
Parameters:
- GlueTempBucket
- Label:
default: "GlueScriptsFilename"
Parameters:
- GlueScriptsFilename
- Label:
default: "GlueSecurityGroupVpcID"
Parameters:
- GlueSecurityGroupVpcID
- Label:
default: "GlueConnectionSubnetID"
Parameters:
- GlueConnectionSubnetID
- Label:
default: "GlueConnectionSubnetAZName"
Parameters:
- GlueConnectionSubnetAZName
- Label:
default: "SnsTopicArn"
Parameters:
- SnsTopicArn
- Label:
default: "SnsTopicName"
Parameters:
- SnsTopicName
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
AppName:
Type: String
Default: ""
GlueServiceRolePolicyARN:
Type: String
Default: "arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole"
AmazonS3FullAccessPolicyARN:
Type: String
Default: "arn:aws:iam::aws:policy/AmazonS3FullAccess"
ConnectionName:
Type: String
#Default: ""
JDBCString:
Type: String
#Default: ""
Secret:
Type: String
#Default: ""
GlueScriptsBucket:
Type: String
#Default: ""
GlueResultsBucket:
Type: String
#Default: ""
GlueTempBucket:
Type: String
#Default: ""
GlueScriptsFilename:
Type: String
#Default: ""
GlueSecurityGroupVpcID:
Type: String
#Default: ""
GlueConnectionSubnetID:
Type: String
#Default: ""
GlueConnectionSubnetAZName:
Type: String
#Default: ""
SnsTopicArn:
Type: String
#Default: ""
SnsTopicName:
Type: String
#Default: ""
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
GlueRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: !Sub "${AppName}-GlueServiceRole"
Description: "for Glue"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- "glue.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- !Ref GlueServiceRolePolicyARN
- !Ref AmazonS3FullAccessPolicyARN
GluePolicy:
Type: "AWS::IAM::Policy"
Properties:
PolicyName: !Sub "${AppName}-GluePolicy"
Roles:
- !Ref GlueRole
PolicyDocument:
Statement:
- Effect: Allow
Action:
- "logs:AssociateKmsKey"
Resource: "arn:aws:logs:*:*:*"
GlueScriptsS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref GlueScriptsBucket
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
GlueResultsS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref GlueResultsBucket
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
GlueTempS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref GlueTempBucket
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
GlueSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref GlueSecurityGroupVpcID
GroupName: !Sub "${AppName}-Glue-sg"
GroupDescription: "Glue Sg"
Tags:
- Key: "Name"
Value: !Sub "Glue-sg"
SelfRefSecurityGroupIgress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !GetAtt GlueSecurityGroup.GroupId
IpProtocol: tcp
FromPort: 0
ToPort: 65535
SourceSecurityGroupId: !GetAtt GlueSecurityGroup.GroupId
GlueConnection:
Type: AWS::Glue::Connection
Properties:
CatalogId: !Ref AWS::AccountId
ConnectionInput:
Description: "Connect to MySQL database."
ConnectionType: "JDBC"
PhysicalConnectionRequirements:
SecurityGroupIdList:
- !Ref GlueSecurityGroup
SubnetId: !Ref GlueConnectionSubnetID
AvailabilityZone: !Ref GlueConnectionSubnetAZName
ConnectionProperties: {
"JDBC_CONNECTION_URL": !Ref JDBCString,
"USERNAME": !Sub '{{resolve:secretsmanager:${Secret}:SecretString:username}}',
"PASSWORD": !Sub '{{resolve:secretsmanager:${Secret}:SecretString:password}}'
}
Name: !Sub "${ConnectionName}"
GlueJob:
Type: AWS::Glue::Job
DependsOn:
- GlueScriptsS3Bucket
- GlueResultsS3Bucket
- GlueTempS3Bucket
Properties:
Name: !Sub "${AppName}-job"
Connections:
Connections:
- !Sub "${ConnectionName}"
Role: !Ref GlueRole
GlueVersion: "2.0"
Command:
Name: "glueetl"
PythonVersion: "3"
ScriptLocation: !Sub "s3://${GlueScriptsBucket}/${GlueScriptsFilename}"
DefaultArguments:
--JOB_NAME: !Sub "${AppName}-job"
--connection_name: !Sub "${ConnectionName}"
--bucket_root: !Sub "s3://${GlueResultsBucket}"
--TempDir: !Sub "s3://${GlueTempBucket}"
--enable-metrics: ""
--enable-continuous-cloudwatch-log: "true"
--enable-continuous-log-filter: "true"
--continuous-log-logGroup: !Sub "/aws-glue/jobs/${AppName}"
ExecutionProperty:
MaxConcurrentRuns: 1
WorkerType: "G.1X"
NumberOfWorkers: 10
MaxRetries: 1
Timeout: 2880
SecurityConfiguration: !Sub "${AppName}-security-config"
Tags:
Key: Name
Value: !Sub "${AppName}-job"
GlueJobTrigger:
Type: AWS::Glue::Trigger
Properties:
Name: !Sub "${AppName}-trigger"
Schedule: cron(0 16 * * ? *)
Type: SCHEDULED
Actions:
- JobName: !Ref GlueJob
StartOnCreation: true
GlueJobFailureEventRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- aws.glue
detail-type:
- Glue Job State Change
detail:
state:
- FAILED
jobName:
- !Ref GlueJob
State: ENABLED
Targets:
- Arn: !Ref SnsTopicArn
Id: !Ref SnsTopicName
GlueLogsEncryptionKey:
DeletionPolicy: Retain
Type: AWS::KMS::Key
Properties:
Description: "CMK for Glue Job Logs Encryprioin etc"
EnableKeyRotation: true
Tags:
- Key: "Name"
Value: !Sub "GlueLogs-${AppName}-key"
KeyPolicy:
Version: "2012-10-17"
Id: !Sub "GlueLogs-${AppName}-key"
Statement:
- Effect: "Allow"
Principal:
AWS: !Join
- ""
- - "arn:aws:iam::"
- !Ref "AWS::AccountId"
- ":root"
Action: "kms:*"
Resource: "*"
- Effect: "Allow"
Principal:
Service: !Sub "logs.${AWS::Region}.amazonaws.com"
AWS: !GetAtt GlueRole.Arn
Action:
- "kms:Encrypt*"
- "kms:Decrypt*"
- "kms:ReEncrypt*"
- "kms:GenerateDataKey*"
- "kms:Describe*"
Resource: "*"
GlueLogsEncryptionKeyAlias:
Type: "AWS::KMS::Alias"
Properties:
AliasName: !Sub "alias/GlueLogs-${AppName}-key"
TargetKeyId: !Ref GlueLogsEncryptionKey
#### Internal Failureになってエラーになるので、CFn実行後にCLIで作成
#GlueJobSecurityConfiguration:
# Type: AWS::Glue::SecurityConfiguration
# Properties:
# Name: !Sub "${AppName}-security-config"
# EncryptionConfiguration:
# CloudWatchEncryption:
# #CloudWatchEncryptionMode: "SSE-KMS"
# KmsKeyArn: !GetAtt GlueLogsEncryptionKey.Arn
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
GlueLogsEncryptionKeyArn:
Value: !GetAtt GlueLogsEncryptionKey.Arn
Export:
Name: !Sub "GlueLogsEncryptionKey-ARN"
解説
まず、Glueジョブで使用するIAMロール・IAMポリシーです。
GlueRole
にはとりあえずAWSGlueServiceRole、AmazonS3FullAccessという管理ポリシーをアタッチしました。
さらに追加でGluePolicy
をアタッチしていて、これは後述するセキュリティ設定(SecurityConfiguration)でCloudWatch LogをKMSで暗号化するために必要なアクションになります。
"logs:AssociateKmsKey"
のResource: "arn:aws:logs:*:*:*"
部分は、実際は適切に絞るべきです。
GlueRole: Type: "AWS::IAM::Role" Properties: RoleName: !Sub "${AppName}-GlueServiceRole" Description: "for Glue" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - "glue.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" ManagedPolicyArns: - !Ref GlueServiceRolePolicyARN - !Ref AmazonS3FullAccessPolicyARN GluePolicy: Type: "AWS::IAM::Policy" Properties: PolicyName: !Sub "${AppName}-GluePolicy" Roles: - !Ref GlueRole PolicyDocument: Statement: - Effect: Allow Action: - "logs:AssociateKmsKey" Resource: "arn:aws:logs:*:*:*"
次は以下のS3バケットを作成します。細かい設定は適宜変更してください。
GlueScriptsS3Bucket: Type: AWS::S3::Bucket Properties: BucketName: !Ref GlueScriptsBucket BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 GlueResultsS3Bucket: Type: AWS::S3::Bucket Properties: BucketName: !Ref GlueResultsBucket BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 GlueTempS3Bucket: Type: AWS::S3::Bucket Properties: BucketName: !Ref GlueTempBucket BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256
次は、Glueコネクションで使用するセキュリティグループです。VPC IDはシェルからパラメータとして渡します。
具体的な設定は公式ドキュメントで詳細が記載されています。
GlueSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref GlueSecurityGroupVpcID GroupName: !Sub "${AppName}-Glue-sg" GroupDescription: "Glue Sg" Tags: - Key: "Name" Value: !Sub "Glue-sg" SelfRefSecurityGroupIgress: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !GetAtt GlueSecurityGroup.GroupId IpProtocol: tcp FromPort: 0 ToPort: 65535 SourceSecurityGroupId: !GetAtt GlueSecurityGroup.GroupId
ここで重要な、MySQLと接続をするためのGlueコネクションを定義します。
MySQLユーザとそのシークレットは作成済みが前提ですので、そのシークレット名をシェルからパラメータで渡してこちらで指定します。
サブネットも作成済みが前提で、同じくシェルから渡します。
セキュリティグループは先ほど作成したものを指定します。
GlueConnection: Type: AWS::Glue::Connection Properties: CatalogId: !Ref AWS::AccountId ConnectionInput: Description: "Connect to MySQL database." ConnectionType: "JDBC" PhysicalConnectionRequirements: SecurityGroupIdList: - !Ref GlueSecurityGroup SubnetId: !Ref GlueConnectionSubnetID AvailabilityZone: !Ref GlueConnectionSubnetAZName ConnectionProperties: { "JDBC_CONNECTION_URL": !Ref JDBCString, "USERNAME": !Sub '{{resolve:secretsmanager:${Secret}:SecretString:username}}', "PASSWORD": !Sub '{{resolve:secretsmanager:${Secret}:SecretString:password}}' } Name: !Sub "${ConnectionName}"
その次も同じく重要な、Glueジョブです。
色々と設定項目が多いですが、ワーカー・リトライ・タイムアウトの設定などは適宜調整をしてください。
GlueJob: Type: AWS::Glue::Job DependsOn: - GlueScriptsS3Bucket - GlueResultsS3Bucket - GlueTempS3Bucket Properties: Name: !Sub "${AppName}-job" Connections: Connections: - !Sub "${ConnectionName}" Role: !Ref GlueRole GlueVersion: "2.0" Command: Name: "glueetl" PythonVersion: "3" ScriptLocation: !Sub "s3://${GlueScriptsBucket}/${GlueScriptsFilename}" DefaultArguments: --JOB_NAME: !Sub "${AppName}-job" --connection_name: !Sub "${ConnectionName}" --bucket_root: !Sub "s3://${GlueResultsBucket}" --TempDir: !Sub "s3://${GlueTempBucket}" --enable-metrics: "" --enable-continuous-cloudwatch-log: "true" --enable-continuous-log-filter: "true" --continuous-log-logGroup: !Sub "/aws-glue/jobs/${AppName}" ExecutionProperty: MaxConcurrentRuns: 1 WorkerType: "G.1X" NumberOfWorkers: 10 MaxRetries: 1 Timeout: 2880 SecurityConfiguration: !Sub "${AppName}-security-config" Tags: Key: Name Value: !Sub "${AppName}-job"
大事なのがDefaultArguments
で、環境変数のような使い方をする、ジョブ内のコードで使用する各パラメータを指定します。キーにはすべて--
を付けないといけないところは注意です。
DefaultArguments: --JOB_NAME: !Sub "${AppName}-job" --connection_name: !Sub "${ConnectionName}" --bucket_root: !Sub "s3://${GlueResultsBucket}" --TempDir: !Sub "s3://${GlueTempBucket}" --enable-metrics: "" --enable-continuous-cloudwatch-log: "true" --enable-continuous-log-filter: "true" --continuous-log-logGroup: !Sub "/aws-glue/jobs/${AppName}"
ここで補足ですが、--TempDir
、--enable-metrics
、--enable-continuous-cloudwatch-log
、--enable-continuous-log-filter
、--continuous-log-logGroup
はジョブ内のコードで使用するパラメータではなく、Glueジョブの詳細設定に必要な予約パラメータになります。
Argument | 値 | 説明 |
---|---|---|
--TempDir | s3://~~~ | ジョブ内のSparkの一部の処理で使用する一時ディレクトリとしてのS3バケット・キー |
--enable-metrics | ""(空文字) | ジョブに関するメトリクスの収集 |
--enable-continuous-cloudwatch-log | true | リアルタイムの連続ログ記録 |
--enable-continuous-log-filter | true | 標準フィルタ(Apache Spark ドライバー/エグゼキュータや Apache Hadoop YARN ハートビートのログなどを除外する) |
--continuous-log-logGroup | CloudWatch Logグループ名 | 連続ロギングが有効なジョブのカスタム Amazon CloudWatch ロググループ名 |
詳細はこちらをご覧ください。
また、ScriptLocation
はジョブファイルが置かれるS3のパス(バケット+キー)になります。シェルから渡すパラメータで表現しています。
ScriptLocation: !Sub "s3://${GlueScriptsBucket}/${GlueScriptsFilename}"
SecurityConfiguration
はこの後作成するものですが、その名前を指定します。
SecurityConfiguration: !Sub "${AppName}-security-config"
次は、定期実行用Glueトリガーです。Type: SCHEDULED
を前提に説明します。
実行される時刻設定は、cron形式で記入します。(cron(0 16 * * ? *)
)
また、StartOnCreation: true
にしないとトリガーは有効化されず開始されません。
GlueJobTrigger: Type: AWS::Glue::Trigger Properties: Name: !Sub "${AppName}-trigger" Schedule: cron(0 16 * * ? *) Type: SCHEDULED Actions: - JobName: !Ref GlueJob StartOnCreation: true
次は、ジョブ失敗通知用のイベントルールです。
ここでは上記ジョブが失敗したことをトリガーとしてSNSに通知する設定にしています。
GlueJobFailureEventRule: Type: AWS::Events::Rule Properties: EventPattern: source: - aws.glue detail-type: - Glue Job State Change detail: state: - FAILED jobName: - !Ref GlueJob State: ENABLED Targets: - Arn: !Ref SnsTopicArn Id: !Ref SnsTopicName
ここで、後述するSecurityConfigurationで使用する、ログ暗号化に使用するKMSです。本来はAction
をさらに絞った方がセキュアになりますがここではスコープ外なので触れません。
GlueLogsEncryptionKey: DeletionPolicy: Retain Type: AWS::KMS::Key Properties: Description: "CMK for Glue Job Logs Encryprioin etc" EnableKeyRotation: true Tags: - Key: "Name" Value: !Sub "GlueLogs-${AppName}-key" KeyPolicy: Version: "2012-10-17" Id: !Sub "GlueLogs-${AppName}-key" Statement: - Effect: "Allow" Principal: AWS: !Join - "" - - "arn:aws:iam::" - !Ref "AWS::AccountId" - ":root" Action: "kms:*" Resource: "*" - Effect: "Allow" Principal: Service: !Sub "logs.${AWS::Region}.amazonaws.com" AWS: !GetAtt GlueRole.Arn Action: - "kms:Encrypt*" - "kms:Decrypt*" - "kms:ReEncrypt*" - "kms:GenerateDataKey*" - "kms:Describe*" Resource: "*" GlueLogsEncryptionKeyAlias: Type: "AWS::KMS::Alias" Properties: AliasName: !Sub "alias/GlueLogs-${AppName}-key" TargetKeyId: !Ref GlueLogsEncryptionKey
Statement
の2つ目の、Principal
がService: !Sub "logs.${AWS::Region}.amazonaws.com"
とAWS: !GetAtt GlueRole.Arn
になっているのは、SecurityConfigurationで暗号化するCloudWatch LogsとジョブのIAMロールがこのKMSにアクセスできるようにするためです。
詳細はリファレンスにあります。
次は、色々と途中で出てきたSecurityConfigurationです。これはログを暗号化するための設定です。
しかしコメントアウトしています。
#### Internal Failureになってエラーになるので、CFn実行後にCLIで作成 #GlueJobSecurityConfiguration: # Type: AWS::Glue::SecurityConfiguration # Properties: # Name: !Sub "${AppName}-security-config" # EncryptionConfiguration: # CloudWatchEncryption: # #CloudWatchEncryptionMode: "SSE-KMS" # KmsKeyArn: !GetAtt GlueLogsEncryptionKey.Arn
実はコメントアウトしている理由は、これを上記コードでデプロイしたら、GlueJobSecurityConfiguration
の作成がInternal Failure
となってスタック作成に失敗したからです。
この理由がわからず、代わりにシェル内でAWS CLIでやってみたところ成功したので、そのままにしています。
VPC内にKMSのVPCエンドポイントを作成しないといけないのかな?とも思ったけれど、それだとなぜCLIでは成功するのか・・・
あまり調査は出来ていないので、時間があれば調査してみたいと思いますがとりあえず今は暫定でCLIでやります。
最後に、Outputs
でKMSキーのARNを出力しています。
これは、上記のSecurityConfigurationの作成のためにKMSのARNが必要なので、CLIでSecurityConfigurationを作成する際に取得できるようにこちらで出力します。
Outputs: GlueLogsEncryptionKeyArn: Value: !GetAtt GlueLogsEncryptionKey.Arn Export: Name: !Sub "GlueLogsEncryptionKey-ARN"
シェルスクリプト
shdir/glue.sh
#!/bin/bash
set -eu
cd $(dirname $0) && cd ../
CFN_TEMPLATE="./yamldir/glue.yaml"
GLUE_SCRIPTS_DIR="glue-job-script"
SCRIPT_NAME="job_data_transfer.py"
GlueScriptsFilename="${GLUE_SCRIPTS_DIR}/${SCRIPT_NAME}"
APP_NAME="gluetest"
CFN_STACK_NAME="${APP_NAME}"
CFN_REGION="ap-northeast-1"
AWSAccountID=$(aws sts get-caller-identity | jq -r '.Account')
GlueScriptsBucket="${APP_NAME}-${AWSAccountID}-scripts"
GlueResultsBucket="${APP_NAME}-${AWSAccountID}-results"
GlueTempBucket="${APP_NAME}-${AWSAccountID}-temp"
## "(〇〇)"の部分は実際の環境に合わせて埋めてください
ClusterEndpoint="(RDSのDBクラスターのエンドポイント, EC2ではホスト)"
ConnectionSecret="(Glueコネクションで使用するMySQLユーザのシークレット名)"
GlueSecurityGroupVpcID="(Glueで使用するセキュリティグループのVPCのID)"
GlueConnectionSubnetID="(Glue Connectionで使用するサブネットのID)"
GlueConnectionSubnetAZName="(Glue Connectionで使用するサブネットのAZ名 例:ap-northeast-1)"
SnsTopicName="(ジョブ実行失敗を通知するSNSトピック名)"
SnsTopicArn="(ジョブ実行失敗を通知するSNSトピックARN)"
JDBCString="jdbc:mysql://${ClusterEndpoint}:3306/information_schema"
ConnectionName="${APP_NAME}"
aws cloudformation deploy \
--stack-name ${CFN_STACK_NAME} \
--template-file ${CFN_TEMPLATE} \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameter-overrides \
AppName=${APP_NAME} \
ConnectionName=${ConnectionName} \
JDBCString=${JDBCString} \
Secret=${ConnectionSecret} \
GlueScriptsBucket=${GlueScriptsBucket} \
GlueResultsBucket=${GlueResultsBucket} \
GlueTempBucket=${GlueTempBucket} \
GlueScriptsFilename=${GlueScriptsFilename} \
GlueSecurityGroupVpcID=${GlueSecurityGroupVpcID} \
GlueConnectionSubnetID=${GlueConnectionSubnetID} \
GlueConnectionSubnetAZName=${GlueConnectionSubnetAZName} \
SnsTopicArn=${SnsTopicArn} \
SnsTopicName=${SnsTopicName} \
--no-fail-on-empty-changeset
### AWS::Glue::SecurityConfigurationがInternal Failureになってエラーになるので、CFn実行後にCLIで作成
SecurityConfigurationName="${APP_NAME}-security-config"
SecurityExists=`aws glue get-security-configurations \
| jq '.SecurityConfigurations[] | select(.Name == "'${SecurityConfigurationName}'") | .Name' \
| tr -d '"'`
if [ -n "${SecurityExists}" ]; then
aws glue delete-security-configuration \
--name ${SecurityConfigurationName} \
> /dev/null
fi
KmsKeyName="GlueLogsEncryptionKey-ARN"
KmsKeyArn=`aws cloudformation describe-stacks \
--stack-name ${CFN_STACK_NAME} \
| jq '.Stacks[].Outputs[] | select(.ExportName == "'${KmsKeyName}'") | .OutputValue' \
| tr -d '"'`
aws glue create-security-configuration \
--name ${SecurityConfigurationName} \
--encryption-configuration "CloudWatchEncryption={CloudWatchEncryptionMode=SSE-KMS,KmsKeyArn=${KmsKeyArn}}" \
> /dev/null
### ジョブのコードのデプロイ
aws s3 rm s3://${GlueScriptsBucket} --recursive > /dev/null
aws s3 cp ${GLUE_SCRIPTS_DIR} s3://${GlueScriptsBucket}/${GLUE_SCRIPTS_DIR}/ --recursive > /dev/null
解説
GLUE_SCRIPTS_DIR
、SCRIPT_NAME
はそれぞれ最初の方に示したディレクトリ構成に合わせたジョブファイルのディレクトリ・ファイル名です。
GlueScriptsFilename
はS3に置くジョブのコードのパスで、手元の構成に合わせています。
GLUE_SCRIPTS_DIR="glue-job-script" SCRIPT_NAME="job_data_transfer.py" GlueScriptsFilename="${GLUE_SCRIPTS_DIR}/${SCRIPT_NAME}"
次はS3バケット名ですが、グローバルでユニークでないといけないので、AWSアカウントIDを入れています。リージョンごとにGlueを構築する場合は、バケット名にリージョンなどの情報も入れましょう。ただし、バケット名長の上限( 3~63 文字)にはご注意を。
またjq
コマンドを使用しているので、インストールしておく必要があります。
AWSAccountID=$(aws sts get-caller-identity | jq -r '.Account') GlueScriptsBucket="${APP_NAME}-${AWSAccountID}-scripts" GlueResultsBucket="${APP_NAME}-${AWSAccountID}-results" GlueTempBucket="${APP_NAME}-${AWSAccountID}-temp"
以下は事前構築前提のものですので、それぞれリソースを用意して埋めてください。
## "(〇〇)"の部分は実際の環境に合わせて埋めてください ClusterEndpoint="(RDSのDBクラスターのエンドポイント, EC2ではホスト)" ConnectionSecret="(Glueコネクションで使用するMySQLユーザのシークレット名)" GlueSecurityGroupVpcID="(Glueで使用するセキュリティグループのVPCのID)" GlueConnectionSubnetID="(Glue Connectionで使用するサブネットのID)" GlueConnectionSubnetAZName="(Glue Connectionで使用するサブネットのAZ名 例:ap-northeast-1)" SnsTopicName="(ジョブ実行失敗を通知するSNSトピック名)" SnsTopicArn="(ジョブ実行失敗を通知するSNSトピックARN)"
JDBCStringはDBのエンドポイントをもとに構成します。
MySQLのポートを3306から変更している場合は、こちらも合わせて変更が必要です。
スラッシュ以降にinformation_schema
と書いてますがこれはデフォルトDBです。ジョブ内でコネクションの情報に指定している場合は、こちらでは何でもOKです。
JDBCString="jdbc:mysql://${ClusterEndpoint}:3306/information_schema"
ここで、SecurityConfigurationをAWS CLIで作成します。
まず、ymlファイルのGlueジョブ部分で指定した名前を定義します。
### AWS::Glue::SecurityConfigurationがInternal Failureになってエラーになるので、CFn実行後にCLIで作成 SecurityConfigurationName="${APP_NAME}-security-config"
すでに存在していたら作り直す様に、削除します。(ここの作りは少し無理やりなので、アレンジ等お任せします。)
SecurityExists=`aws glue get-security-configurations \ | jq '.SecurityConfigurations[] | select(.Name == "'${SecurityConfigurationName}'") | .Name' \ | tr -d '"'` if [ -n "${SecurityExists}" ]; then aws glue delete-security-configuration \ --name ${SecurityConfigurationName} \ > /dev/null fi
Outputsで出力したKMSのARNをCloudFormationスタックから取得します。
KmsKeyName="GlueLogsEncryptionKey-ARN" KmsKeyArn=`aws cloudformation describe-stacks \ --stack-name ${CFN_STACK_NAME} \ | jq '.Stacks[].Outputs[] | select(.ExportName == "'${KmsKeyName}'") | .OutputValue' \ | tr -d '"'`
そして、SecurityConfigurationを作成します。
aws glue create-security-configuration \ --name ${SecurityConfigurationName} \ --encryption-configuration "CloudWatchEncryption={CloudWatchEncryptionMode=SSE-KMS,KmsKeyArn=${KmsKeyArn}}" \ > /dev/null
最後に、Glueジョブのコードファイルを、S3スクリプトバケットにアップロードをします。
### ジョブのコードのデプロイ aws s3 rm s3://${GlueScriptsBucket} --recursive > /dev/null aws s3 cp ${GLUE_SCRIPTS_DIR} s3://${GlueScriptsBucket}/${GLUE_SCRIPTS_DIR}/ --recursive > /dev/null
実行
上記シェルを実行すると、Glueのデプロイが開始します。
sh shdir/glue.sh
最後に
非常に長くなりました。しかし全部書きたかった・・・!
部分部分でもし参考にしていただけたら幸いです(そもそも読んでもらえただけで嬉しいです)。
途中のSecurityConfigurationのInternal Failureエラー問題はどこかで調査・対処できたらなと思っています。