qtatsuの週報

初心者ですわぁ

【AWS Glue】開発エンドポイントをAWS CLIで構築してSageMaker Notebookを使う

前書き

AWS Glueの開発エンドポイントを利用すると、コードを一行ずつお試し実行しながらETLスクリプトを作成することが可能です。

Developing Scripts Using Development Endpoints - AWS Glue

実行環境としては、以下の二種類から選ぶことができます。

  • Zeppelin Notebook
  • SageMaker Notebook

SageMakerはjupyter Notebookをベースにしていて扱いやすかったです(個人的な感想)。

本記事では開発エンドポイント/SageMaker Notebookの環境構築をAWS CLIを使って行う方法をまとめました(※ SageMaker Notebookの立ち上げはGUIで行います)。

注意: 開発エンドポイントの料金

料金 - AWS Glue | AWS

開発エンドポイントはオプションで、ETL コードをインタラクティブに開発することを選択した場合にのみ課金が適用されます。開発エンドポイントは、開発エンドポイントのプロビジョニングに使用されたデータ処理ユニットの時間に基づいて課金されます。AWS Glue の開発エンドポイントには最低で 2 個の DPU が必要です。デフォルトでは、AWS Glue は各開発エンドポイントに 5 個の DPU を割り当てます。DPU 時間あたり 0.44 ドルが 1 秒単位で課金され、最も近い秒単位に切り上げられます。プロビジョニングされた開発エンドポイントごとに 10 分の最小期間が設定されます。

  • 開発エンドポイントは存在している間、常に課金され続けます。
  • デフォルトではDPUs(Data processing units)が5に設定されます。(2020年11月28日現在)

「DPU時間当たり0.44ドルが1秒単位で課金され」とは、1時間につき、DPU一つに対して0.44ドルが課金されるということです。 何も考えずにデフォルト(DPUs-> 5個)で開発エンドポイントを作成すると、一時間当たり2.2ドル課金されます。

仮に1ヶ月そのままにしていれば、来月の請求には1580ドルほどになります。

(ちなみにマネジメントコンソール(GUI)から開発エンドポイントのタブを開くと「使わない時も課金されるから、削除してね!(意訳)」というメッセージが表示されます。親切ですね。)


月18万円!AWS Glueの開発エンドポイントで破産しないために - Qiita

↑こちらの記事では、落とし忘れた開発エンドポイントをLambdaによって通知する仕組みを提案されています。

環境

AWS CLIの実行環境はCloud9です。

ローカルでAWS CLIコマンドを打っても、同じようにできるはずです(未確認)

開発エンドポイント/SageMaker Notebookの環境構築手順

事前準備

  • AWS CLIが使える環境を用意すること(お勧めはCloud9)。
  • AWS CLIを実行するロール or ユーザ(グループ)に以下のポリシーをアタッチしておくこと。
    1. IAMFullAccess ※ これは最強なので、使用後は外しておくことをお勧めします。
    2. AmazonS3FullAccess
    3. AWSGlueServiceRole
    4. AmazonSageMakerFullAccess

IAMロールの作成

必要なIAMリソースを作成していきます。AWS Glueの公式ドキュメントにも詳しく書かれていますが少々記述が不親切なところがあるので、その都度読み替えて適応します。 本手順では、以下のリソースが必要になります。

  1. 開発エンドポイント用のロール
    • ETLスクリプトを実行するジョブに付加するロールと同じ権限を与えます。今回はAWS managed policyを利用してGlueへの基本的な操作権限を与え、加えてS3バケットを新規に作成 & そのバケットへのアクセス権限を追加します。
    • デフォルトでは、AWSGlueServiceRoleから始まる名前のロールにする必要があります。
    • 公式ドキュメント
  2. SageMaker Notebook用のロール
    • SageMakerの実体はEC2インスタンスです。ここから、開発エンドポイントにアクセスして使います。
    • デフォルトではAWSGlueServiceSageMakerNotebookRoleから始まる名前のロールにする必要があります。
    • 公式ドキュメント

IAMロール作成手順

1. S3バケットと、バケットへのアクセス権限を定義したIAM ポリシーの作成

デフォルトリージョンを指定しておきます。

export AWS_DEFAULT_REGION=ap-northeast-1

自分のアカウントのAWS_IDを取得し、これをリソース名に含めるようにします。(JAWS-UG CLI専門支部 - connpass様のハンズオンを大いに参考にさせていただきました。)

AWS_ID=`aws sts get-caller-identity --query 'Account' --output text` && echo ${AWS_ID}

aws s3api create-bucket --bucket "tmp-test-${AWS_ID}" --create-bucket-configuration "LocationConstraint=ap-northeast-1"


cat << EOS > tmp-s3-access-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::tmp-test-${AWS_ID}/*"
            ]
        }
    ]
}
EOS

# jsonが壊れてないかチェック
cat tmp-s3-access-policy.json | python3 -m json.tool

# ポリシー作成
POLICY_NAME=tmp-s3-access-policy
aws iam create-policy --policy-name ${POLICY_NAME} --path "/tmp-test/" --policy-document file://tmp-s3-access-policy.json

# 作成したポリシーが存在することを確認する。
aws iam list-policies --scope Local --path-prefix /tmp-test/ --query "Policies[*].PolicyName" 

2. 開発エンドポイント用のロールの作成

繰り返しになりますが、基本的に「開発エンドポイント」につけるロールは、Glueで実行するジョブに付加するロールと同じ権限のものにします。 すでにジョブ実行に使用しているIAMロールがあれば、そちらを使うようにしてもOKです。

ロールの作成時には、「このロールはGlueにつかえますよ〜」というtrust relationship(Principalと呼ばれているもの)を定義したpolicy documentを作成する必要があります。これは以下に示す形式で、「Service」のところだけglueとなるように変更します。

(余談)厄介なのですが、このサービス指定部分の一覧は、公式ドキュメントにも整備されていないようです。

一度マネコンから作成したロールを確認するか、非公式の一覧(有志がまとめたもの)を参照するしかないようです。 -> List of AWS Service Principals · GitHub

# ロールの作成
$ cat << EOS > role-for-glue.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "glue.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOS

# jsonが壊れていないか確認する。
cat role-for-glue.json | python3 -m json.tool

GLUE_ROLE_NAME='AWSGlueServiceRole-Tmp-Test' && echo $GLUE_ROLE_NAME
aws iam create-role --role-name $GLUE_ROLE_NAME --assume-role-policy-document file://role-for-glue.json --path /tmp-test/

# ロールができているか確認
aws iam list-roles --path /tmp-test/ --query "Roles[?RoleName=='${GLUE_ROLE_NAME}']"

3. 開発エンドポイント用のロールに、ポリシーをアタッチする

AWSGlueServiceRoleについては公式ドキュメントに詳しく記述されています。(AWSのmanaged policy。名前が「Role」なのでわかりづらいですが、これはポリシーです。)

AWSGlueServiceRole(AWSのmanaged policy)で基本的なGlueの操作の権限を与え、自作のtmp-s3-access-policyを使ってs3バケットへのアクセス権限を渡します。

# 先ほど作ったs3アクセス用ポリシーのARN
POLICY_ARN=`aws iam list-policies --scope Local --path-prefix /tmp-test/ --query "Policies[?PolicyName=='${POLICY_NAME}'].Arn" --output text`

# AWSGlueの基本的な権限を持つ、AWS managed policy も取得しておく。
GLUE_POLICY_ARN=`aws iam list-policies --scope AWS --query "Policies[?PolicyName=='AWSGlueServiceRole'].Arn" --output text`
aws iam attach-role-policy --role-name $GLUE_ROLE_NAME --policy-arn $GLUE_POLICY_ARN
aws iam attach-role-policy --role-name $GLUE_ROLE_NAME --policy-arn $POLICY_ARN

# アタッチしたポリシーが存在することを確認しておく。
aws iam list-attached-role-policies --role-name $GLUE_ROLE_NAME

4. SageMaker Notebook用のロールを作成する

SageMaker Notebookは、PrincipalでServiceを"sagemaker.amazonaws.com"に設定します。

cat << EOS > role-for-sagemaker.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "sagemaker.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOS

cat role-for-sagemaker.json | python3 -m json.tool

# 必ず、AWSGlueServiceSageMakerNotebookRole というprefixにします。
SAGEMAKER_ROLE_NAME='AWSGlueServiceSageMakerNotebookRole-Tmp-Test' && echo $SAGEMAKER_ROLE_NAME
aws iam create-role --role-name $SAGEMAKER_ROLE_NAME --assume-role-policy-document file://role-for-sagemaker.json --path /tmp-test/

# ロールができているか確認
aws iam list-roles --path /tmp-test/ --query "Roles[?RoleName=='${SAGEMAKER_ROLE_NAME}']"

5. SageMaker Notebook用のロールに、ポリシーを作成 & アタッチする

ポリシーに、かなりクセがあります。 どうしても失敗するようならマネジメントコンソールでSageMaker Notebookのインスタンスをマネジメントコンソールから作成するときに、ロールを新規作成するオプションがあるので、そちらを選んだ方が簡単かもしれません。

Step 6: Create an IAM Policy for SageMaker Notebooks - AWS Glue

こちらのドキュメントに記載されているように作成しますが、bucket-nameに指定するのはaws-glue-jes-prod-ap-northeast-1-assetsとなります。 (AWS glueに関係する設定ファイルなどが置かれている、公共のs3バケットのようです)

あとは自分のAWS IDとリージョン(ap-northeast-1など)を適切な場所に置換します。

(余談ですが、AmazonSageMakerFullAccessを与えてもうまく行きません。他のサービスと異なり、Glue関連は"FullAccess"系のポリシーだけでは動かないものが多いです。)

AWS_ID=`aws sts get-caller-identity --query 'Account' --output text` && echo ${AWS_ID}


cat << EOS > tmp-sagemaker-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::aws-glue-jes-prod-${AWS_DEFAULT_REGION}-assets"
            ]
        },
        {
            "Action": [
                "s3:GetObject"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::aws-glue-jes-prod-${AWS_DEFAULT_REGION}-assets*"
            ]
        },
        {
            "Action": [
                "logs:CreateLogStream",
                "logs:DescribeLogStreams",
                "logs:PutLogEvents",
                "logs:CreateLogGroup"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:logs:${AWS_DEFAULT_REGION}:${AWS_ID}:log-group:/aws/sagemaker/*",
                "arn:aws:logs:${AWS_DEFAULT_REGION}:${AWS_ID}:log-group:/aws/sagemaker/*:log-stream:aws-glue-*"
            ]
        },
        {
            "Action": [
                "glue:UpdateDevEndpoint",
                "glue:GetDevEndpoint",
                "glue:GetDevEndpoints"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:glue:${AWS_DEFAULT_REGION}:${AWS_ID}:devEndpoint/*"
            ]
        },
        {
            "Action": [
                "sagemaker:ListTags"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:sagemaker:${AWS_DEFAULT_REGION}:${AWS_ID}:notebook-instance/*"
            ]
        }
    ]
}
EOS

# jsonが壊れてないかチェック
cat tmp-sagemaker-policy.json | python3 -m json.tool

# ポリシー作成
SAGE_POLICY_NAME=tmp-sagemaker-policy
aws iam create-policy --policy-name ${SAGE_POLICY_NAME} --path "/tmp-test/" --policy-document file://tmp-sagemaker-policy.json


# ポリシーのアタッチ
SAGE_POLICY_ARN=`aws iam list-policies --scope Local --query "Policies[?PolicyName=='${SAGE_POLICY_NAME}'].Arn" --output text`
aws iam attach-role-policy --role-name $SAGEMAKER_ROLE_NAME --policy-arn $SAGE_POLICY_ARN

# アタッチされていることを確認
aws iam list-attached-role-policies --role-name $SAGEMAKER_ROLE_NAME

開発エンドポイントの作成

公式ドキュメント(下のリンク)には、マネジメントコンソール(GUI)とAWS CLI、それぞれを用いた開発エンドポイントの作成方法が簡単に記載されています。こちらを元に、手順を作りました。

Adding a Development Endpoint - AWS Glue

考慮すべきパラメータは以下の通りです。

  • --number-of-nodes: (e.g.) '2'
    • 最初に述べたDPUs(Data Processing Units)。時間当たりの課金額を決める要員。
    • 最小で2に設定できる。デフォルトは5。 最低の2に設定すれば、一時間に100円以下(正確には0.88ドル)の課金となるので安心。


  • --glue-version: (e.g.) '1.0'
    • 開発エンドポイントでは0.9 or 1.0しか選択できない。
    • 実際には、現在は最新の2.0でjobを作ることが望ましい。


  • --arguments: (e.g.) 'GLUE_PYTHON_VERSION=3'
    • --glue-versionを1.0にした場合、Pythonのバージョンを2 or 3で選択できる。
    • json形式で記載することもできる('{"GLUE_PYTHON_VERSION": "3"}')


開発エンドポイント作成手順

パラメータが多いので、引数を準備 & 確認してから実行します。

AWS_ID=`aws sts get-caller-identity --query 'Account' --output text` && echo ${AWS_ID}
GLUE_ROLE_NAME='AWSGlueServiceRole-Tmp-Test' && echo $GLUE_ROLE_NAME



ENDPOINT_NAME="endpoint-tmp-${AWS_ID}"
# 先ほど作成した、開発エンドポイント用のロールを選択します。
ROLE_ARN=`aws iam list-roles --query "Roles[?RoleName=='${GLUE_ROLE_NAME}'].Arn" --output text`
NUMBER_OF_NODES=2  #  Data Processing Units (DPUs) 2以上。
GLUE_VERSION=1.0   # 0.9か1.0
ARGUMENTS="GLUE_PYTHON_VERSION=3"
REGION='ap-northeast-1'

# きちんと定義できていることを確認しておく。
cat << EOS
 ENDPOINT_NAME: $ENDPOINT_NAME
 ROLE_ARN: $ROLE_ARN
 NUMBER_OF_NODES: $NUMBER_OF_NODES
 GLUE_VERSION: $GLUE_VERSION          
 ARGUMENTS: $ARGUMENTS   
 REGION: $REGION
EOS

開発エンドポイント一覧を確認 & 新規作成。 この瞬間から課金されるので、開発エンドポイントは忘れずに削除する必要があります。

# 現在の開発エンドポイント一覧を確認する。
aws glue list-dev-endpoints

# 開発エンドポイントの作成
aws glue create-dev-endpoint --endpoint-name $ENDPOINT_NAME --role-arn $ROLE_ARN --number-of-nodes $NUMBER_OF_NODES --glue-version $GLUE_VERSION --arguments $ARGUMENTS  --region $REGION

# 完了確認。
aws glue get-dev-endpoint --endpoint-name $ENDPOINT_NAME

エンドポイント作成後、利用可能になるまでに数分〜10分程度かかります。

以下のコマンドの結果が、PROVISIONINGの間は待ちましょう。 READYになれば、次の手順に進めます(SageMaker Notebook作成)

aws glue get-dev-endpoint --endpoint-name $ENDPOINT_NAME --query "DevEndpoint.Status"

SageMaker Notebookの作成

SageMakerのインスタンスと、開発エンドポイントを紐つける処理を指定するのは少し面倒です。 (SageMakerのLifecycle configurationsから、インスタンス起動時のShellScriptをbase64エンコードしてから指定する必要があります。)

そのため、この部分はマネジメントコンソール(GUI)から行うことにします(SageMaker自体もマネジメントコンソールから開く必要があります)

インスタンスタイプの指定などはCLIからupdateコマンドで行う必要があるようです(GUIからは指定することができません) (余談)インスタンスの状態がFailになってしまうとCLIからでないとリスタートできなかったり、現状はいろいろと不便なUIです。

SageMaker Notebook作成手順

AWS Glueの画面にアクセスし、作成した開発エンドポイントを選択してSageMaker Notebookのインスタンスを作成します。 この手順で作成すると、自動で開発エンドポイント周りの設定を行うスクリプトを作成してくれます。(インスタンス作成後、SageMakerのマネジメントコンソール-> Lifecycle configurationsからスクリプトを確認可能)

f:id:Qtatsu:20201129180234p:plain
SageMaker Notebookインスタンス作成


以下の様に設定し、ページ下のCreate Notebookを押せば完了です。 もし10分ほどしてインスタンスの状態がFailとなってしまう場合は、SageMaker Notebookに付加したIAMロールの権限を間違えている可能性が高いです。その場合、「Create an IAM role」を選択すると自動で正しいロールを作成してくれます。

f:id:Qtatsu:20201129180335p:plain
SageMaker Notebookインスタンス作成2

立ち上がるのには結構時間がかかります。下手すると10分くらいかかっているようです。

なお、IAMなどの設定ミスでインスタンス立ち上げがFailするとGlueのマネジメントコンソールからはstartできなくなります。 Amazon SageMakerのマネコン(もしくはCLI)を使ってstartする必要があります。

ETLスクリプトをSageMaker Notebookで試す

公式ドキュメントに、以下のような手順書が書かれています。こちらにしたがって試してみます。(上記でスクショを記載した、SageMaker Notebookインスタンスの作成手順も下のリンクに書いてあります。) Tutorial: Use an SageMaker Notebook with Your Development Endpoint - AWS Glue

f:id:Qtatsu:20201129180945p:plain
スクリプト実行1

pysparkを選び、ノートを作成します。

f:id:Qtatsu:20201129181025p:plain
スクリプト実行2

sparkと打ち込み、実行すれば準備はOKです。

f:id:Qtatsu:20201129181241p:plain
スクリプト実行3

上記スクショのように、ETLスクリプトと同じようにコードが実行できます。クローラを実行してGlueのデータベースを作っていれば、from_catalogメソッドを使ってS3バケットからの読み込みもできます。 AWSのリソース(S3など)にETLスクリプトと同じようにアクセスできるため、コードのデバッグに集中できます。

なお、sc = SparkContext()は実行するとエラーになります。最初のsparkコマンドですでにscは作成済みだからです。

作成したリソースの後始末

作成したリソースを削除していきます。

SageMaker Notebook

インスタンスを停止しておきます。(停止すれば課金されません。コードを残しておけるので、stopにしておきます) 開発エンドポイントを削除しても、同じ名前の開発エンドポイントを再度作成すれば、SageMaker Notebookのインスタンスを再度立ち上げることができます。

不要ならデリートしてもOKです。

f:id:Qtatsu:20201129181512p:plain
SageMakerインスタンスの停止

開発エンドポイント

絶対に消し忘れてはいけません!!課金されてしまいます。

# 一覧確認
aws glue list-dev-endpoints 

# 開発エンドポイント名を取得する
ENDPOINT_NAME=`aws glue list-dev-endpoints --query "DevEndpointNames[0]" --output text` && echo $ENDPOINT_NAME

# 削除する
aws glue delete-dev-endpoint --endpoint-name $ENDPOINT_NAME

# 削除されたことを確認
aws glue list-dev-endpoints 

IAMリソース

不要ならば削除しておきます。IAMリソースは残しておくと増えていきがち & 使用目的を忘れると消すことができなくなるので、その場で消すのが良いと思います。

  • 今回作成したポリシーとロールの一覧

ポリシーもロールも、/tmp-test/というパスを指定して作成しました。パスを使えば、作成したIAMリソースの一覧を簡単に取得できます。(そのほかuserやgroupなども含め、IAMリソースはpathをつけておいた方が扱いやすいです。)

削除漏れ防止のために、確認しておきます。

# 作成したロール名一覧の確認方法
aws iam list-roles --path-prefix /tmp-test/ --query "Roles[*].RoleName"

# 作成したポリシー名一覧の確認方法
aws iam list-policies --scope Local --path-prefix /tmp-test/ --query "Policies[*].PolicyName"
  • SageMaker Notebookのロール(AWSGlueServiceSageMakerNotebookRole-Tmp-Test)からポリシーをデタッチする。
SAGEMAKER_ROLE_NAME=AWSGlueServiceSageMakerNotebookRole-Tmp-Test
aws iam list-attached-role-policies --role-name $SAGEMAKER_ROLE_NAME

SAGE_POLICY_NAME=tmp-sagemaker-policy
SAGE_POLICY_ARN=`aws iam list-policies --scope Local --path-prefix /tmp-test/ --query "Policies[?PolicyName=='${SAGE_POLICY_NAME}'].Arn" --output text`
aws iam detach-role-policy --role-name $SAGEMAKER_ROLE_NAME --policy-arn $SAGE_POLICY_ARN

tmp-sagemaker-policyは自作のポリシーなのでこれも削除しておきます。

# 存在することを確認。
SAGE_POLICY_NAME=tmp-sagemaker-policy
aws iam list-policies --scope Local --path-prefix /tmp-test/ --query "Policies[?PolicyName=='${SAGE_POLICY_NAME}'].Arn" --output text

# ポリシーの削除
SAGE_POLICY_ARN=`aws iam list-policies --scope Local --path-prefix /tmp-test/ --query "Policies[?PolicyName=='${SAGE_POLICY_NAME}'].Arn" --output text`
aws iam delete-policy --policy-arn $SAGE_POLICY_ARN
  • SageMaker Notebookのロール(AWSGlueServiceSageMakerNotebookRole-Tmp-Test)を削除
# 削除対象のロールを確認
SAGEMAKER_ROLE_NAME=AWSGlueServiceSageMakerNotebookRole-Tmp-Test
aws iam list-roles --path-prefix /tmp-test/ --query "Roles[?RoleName=='${SAGEMAKER_ROLE_NAME}'].RoleName"

# 削除。上記手順(list-roles)を再度実行し、削除成功したかを確認しておく。
aws iam delete-role --role-name $SAGEMAKER_ROLE_NAME
  • 開発エンドポイントのロールからポリシーをデタッチする
GLUE_ROLE_NAME=AWSGlueServiceRole-Tmp-Test
aws iam list-attached-role-policies --role-name $GLUE_ROLE_NAME

# アタッチされていたポリシーを、デタッチする。(POLICYのARNは、上記list-attached-role-policiesコマンドの結果からARNを探し、コピペで貼ると楽)
aws iam detach-role-policy --role-name $GLUE_ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole
aws iam detach-role-policy --role-name $GLUE_ROLE_NAME --policy-arn arn:aws:iam::${AWS_ID}:policy/tmp-test/tmp-s3-access-policy

# ロールを削除する。
aws iam delete-role --role-name $GLUE_ROLE_NAME

# 削除できたことを確認。
aws iam list-roles --path-prefix /tmp-test/ --query "Roles[?RoleName=='${GLUE_ROLE_NAME}'].RoleName"

デタッチしたポリシーのうち、tmp-s3-access-policyは自作のポリシーなのでこれも削除しておく。

# 存在することを確認。
POLICY_NAME=tmp-s3-access-policy
aws iam list-policies --scope Local --path-prefix /tmp-test/ --query "Policies[?PolicyName=='${POLICY_NAME}'].Arn" --output text

# ポリシーの削除
POLICY_ARN=`aws iam list-policies --scope Local --path-prefix /tmp-test/ --query "Policies[?PolicyName=='${POLICY_NAME}'].Arn" --output text`
aws iam delete-policy --policy-arn $POLICY_ARN
# 中身を空にしておきます。
aws s3 rm --recursive "s3://tmp-test-${AWS_ID}"

# バケットを削除します。
aws s3 rb "s3://tmp-test-${AWS_ID}"

参考にさせていただいたリソースへのリンク