【AWS】運用アカウントのDynamoDBから開発アカウントのS3にデータ抽出してみた。


# はじめに
運用AWSアカウントにあるDynamoDBからデータを抽出、
開発AWSアカウントのS3に格納をGlueで行う。
一見そこまで難しくないかなという見積もりだったのですが・・・
これが自分にとってかなりハードルが高かった作業だったので、
備忘録として残しておきたいと思います。

# 目次
1.事前準備及び前提条件
2.ポリシー作成
3.AWS Glue Studioでスクリプトを頂戴する
4.DynamoDBテーブル読み込み
5.DynamoDBとS3のマッピング
6.S3へ出力するためのスクリプト
7.Glue JOB作成
8.処理結果
9.おわりに
10.参考ドキュメント

# 1.事前準備及び前提条件
やりたいことのイメージ図
![イメージ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/251946db-9abb-b38c-6b72-bed4051a5e0b.png)

大事だと思うところ4点挙げておきます。
1.クロスアカウントの設定
運用AWSアカウント側で以下権限を持ったロール作成を行います。※1

    ・DynamoDBの参照権限
    ・信頼されたエンティティに<開発AWSアカウント>を追加
作成後ロールのarnを開発AWSアカウントに連携します。

```py:ロールarn.py
arn:aws:iam::<運用AWSアカウント>:role/role-dynamoDBReadOnlyAccess-SnowFlake
```
2.GlueでPysparkを動作できるようにインストールしておきます。
3.DynamoDB内の情報を取得しておく。
・テーブル名
・リージョン
・カラム名とデータ型
4.S3やGlueはすでに運用されている想定で行います。

# 2.ポリシー作成
<開発AWSアカウント>が<運用AWSアカウント>のDynamoDBへアクセスできるようにポリシーを作成します。※2
![IAMrole.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/ee75691a-d627-1e4a-4fe5-999fb7dab694.png)
AWSコンソール＞IAM＞ロールと降りていき既存のロールにポリシーを作成します。
今回は「GlueDev」ロールにポリシーを作成します。ロールをクリック。

![ポリシー一覧.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/d9e89073-6f41-9b5b-d4ef-41d4430e2e60.png)
今回初めてロールを作る場合は画像にある「タイプ：AWS管理」の3つのポリシーも追加してください。
既存ロールにポリシーを追加する場合は赤枠の「許可を追加」をクリックします。

![インラインポリシーを作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/d1a13a3c-a333-58b3-82c3-556224399103.png)
するとドロップダウンリストが出てきますので、
「インラインポリシーを作成」をクリックします。

![Json.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/3f971ee7-7e58-c2a0-b26a-beb80ddb1817.png)
「ビジュアルエディタ」と「JSON」タブのうちJSONタブを開き、
以下のスクリプトを入力します。
```json:ポリシー.json
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": "sts:AssumeRole",
        "Resource": "arn:aws:iam::<運用AWSアカウント>:role/role-dynamoDBReadOnlyAccess-SnowFlake"
    }
}
```
その後、下にスクロールし「ポリシーの確認」をクリックします。

![ポリシー作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/96133213-7a77-0b9e-1e83-c06c9be55323.png)
ポリシー名を記入します。今回は「dynamodb_cross_account」で作成しました。
「ポリシーの作成」をクリックして完了です。

# 3.AWS Glue Studioでスクリプトを頂戴する
AWSコンソール>AWS Glueを開きます。
![AWS glueにジャンプ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/09dd389e-1195-717a-373e-3fb6713962f7.png)

AWS Glue Studio を開きます。※3
![AWS glue studioをクリック.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/77ea5264-edad-d798-b39a-97e3a6a1a1bb.png)

「View jobs」をクリック
![View jobs選択.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/9fe30228-751f-f000-e0d5-e0d2dab4e3d2.png)

デフォルトでは赤枠の箇所がS3になっていますのでDynamoDBに設定を変更しま
す。
その後「Create」をクリック
![DynamoDBからS3に設定.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/c15a0f6e-f262-8511-d993-8125956ddab0.png)



フロー図が表示されたら「Script」タブをクリックします。
![スクリプト選択.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/7d35d7c1-3eb0-0322-faef-cf1f34e28fc1.png)


スクリプトを全部コピーします。
![フレームワークを使う.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/afd772e1-6a8c-aabf-8309-a40a1d692421.png)



```py:コピーしたスクリプト.py
import sys

from awsglue.transforms import *
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
from awsglue.utils import getResolvedOptions

args = getResolvedOptions(sys.argv, ["JOB_NAME"])
sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args["JOB_NAME"], args)

# Script generated for node DynamoDB table
DynamoDBtable_node1 = glueContext.create_dynamic_frame.from_catalog(
    database="", table_name="", transformation_ctx="DynamoDBtable_node1"
)

# Script generated for node ApplyMapping
ApplyMapping_node2 = ApplyMapping.apply(
    frame=DynamoDBtable_node1, mappings=[], transformation_ctx="ApplyMapping_node2"
)

# Script generated for node S3 bucket
S3bucket_node3 = glueContext.write_dynamic_frame.from_options(
    frame=ApplyMapping_node2,
    connection_type="s3",
    format="json",
    connection_options={"path": "", "partitionKeys": []},
    transformation_ctx="S3bucket_node3",
)

job.commit()

```
上記の取得してきたスクリプトをベースにして編集していきます。

# 4.DynamoDBテーブル読み込み
```py:編集前.py
# Script generated for node DynamoDB table
DynamoDBtable_node1 = glueContext.create_dynamic_frame.from_catalog(
    database="", table_name="", transformation_ctx="DynamoDBtable_node1"
)
```
```py:編集後.py
# Script generated for node DynamoDB table
DynamoDBtable_node1 = glueContext.create_dynamic_frame.from_options(
    connection_type="dynamodb",
    connection_options={
    "dynamodb.region": "<リージョン名>",
    "dynamodb.input.tableName": "<DynamoDBテーブル名>",
    "dynamodb.sts.roleArn": "arn:aws:iam::<運用AWSアカウント>:role/role-dynamoDBReadOnlyAccess-SnowFlake"
    },
    transformation_ctx="DynamoDBtable_node1",
)
```
編集後の構文は参考ドキュメント※2のリンク先を参照してください。
DynamoDBの情報と連携されたロールを設定しています。

# 5.DynamoDBとS3のマッピング
次はDynamoDBとS3のマッピングを行います。

```py:編集前.py
# Script generated for node ApplyMapping
ApplyMapping_node2 = ApplyMapping.apply(
    frame=DynamoDBtable_node1, mappings=[], transformation_ctx="ApplyMapping_node2"
)
```
```py:編集後.py
# Script generated for node ApplyMapping
ApplyMapping_node2 = ApplyMapping.apply(
    frame=DynamoDBtable_node1, mappings=[
        ("DynamoDB側の項目1","データ型","S3側に出力する項目1","データ型"),
        ("DynamoDB側の項目2","データ型","S3側に出力する項目2","データ型"),
        ("DynamoDB側の項目3","データ型","S3側に出力する項目3","データ型"),
        ("DynamoDB側の項目4","データ型","S3側に出力する項目4","データ型"),
        .
        .
        .
        ("DynamoDB側の項目N","データ型","S3側に出力する項目N","データ型")

        ],
        transformation_ctx="ApplyMapping_node2"
)
```
DynamoDB側の項目とターゲットになるS3へ出力する際のマッピング作業になります。
括弧内前半はDynamoDBの内容、後半はS3の内容になります。
今回はDynamoDBの項目名とS3の項目名は同じ名称にしました。

# 6.S3へ出力するためのスクリプト
ここに関しては特に難しいことはありません。
格納するS3のファイルパスを設定します。
```py:編集前.py
# Script generated for node S3 bucket
S3bucket_node3 = glueContext.write_dynamic_frame.from_options(
    frame=ApplyMapping_node2,
    connection_type="s3",
    format="json",
    connection_options={"path": "", "partitionKeys": []},
    transformation_ctx="S3bucket_node3",
)
```
```py:編集後.py
# Script generated for node S3 bucket
S3bucket_node3 = glueContext.write_dynamic_frame.from_options(
    frame=ApplyMapping_node2,
    connection_type="s3",
    format="json",
    connection_options={"path": "<格納したいS3パス>", "partitionKeys": []},
    transformation_ctx="S3bucket_node3",
)
```

# 7.Glue JOB作成
レガシー版でジョブ作成を行いました。（新しいバージョンにも慣れないとですね・・・）
「ジョブの追加」をクリックします。
![レガシージョブ作成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/6cf6d46b-b452-6c32-eba4-5d499fbd73ff.png)

任意のJOB名を記載後、Typeを「Spark」、
Glue versionを「Spark 3.1 Python 3 （Glue Version 3.0）」、
このジョブ実行では「ユーザーが作成する新しいスクリプト」を選択します。
![JOB設定１.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/5d562fb3-d399-cc0f-fa6e-dae5d9a2ebc2.png)

スクリプトが保存されているS3パス・・・こちらに関してはスクリプトを保管している場所を指定します。
今回は下部の一時ディレクトリも同箇所に設定しました。
![JOB設定２.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/de165ea6-b4ec-2b22-3aa8-faae8709a448.png)

設定後、「次へ」クリックします。
![JOB設定３.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/a7478ed2-8971-72f9-2f90-8ac80760449c.png)

下部へスクロールし、「ジョブを保存してスクリプトを編集する」をクリックします。
![JOB設定４.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/5a58ebc2-a882-8b75-c4df-81b388b9d764.png)

4~6で編集したスクリプトを貼り付けて、保存をします。
これにてJOB作成完了しました。
![JOB設定５.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/fe198684-11c1-478b-5434-7f093653a062.png)

# 8.処理結果
処理実行をしまして結果がS3に格納されました。
出力形式はJSONファイルになります。
![処理結果.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2992046/6229c0b5-8c99-faac-3dea-18a33f73c4c2.png)

# 9.おわりに
クロスアカウントの設定をしてGlueでS3に格納という処理が珍しいみたいで、
ネットの海を彷徨ってもなかなか見つからず、
ゴールにたどり着くのに時間がかかってしまいました。

たくさんヒントをいただいたのでなんとか形にできました。
教えてくださった方々にこの場をお借りして感謝を致します。

できるの思い込みは良くないですね・・・
最初は疑ってかからないといけないと反省した事象でした。



https://qiita.com/Lycoris_radiata/items/bb8497542a47b8f28ae9

この後JSONファイルをテーブルデータにしたお話は以前に投稿しました。
Snowflakeに出力したお話なので併せて読んでみてください。

# 10.参考ドキュメント
※1

https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html

※2

https://docs.aws.amazon.com/ja_jp/glue/latest/dg/aws-glue-programming-etl-dynamo-db-cross-account.html

※3

https://docs.aws.amazon.com/glue/latest/ug/tutorial-create-job.html

※4

https://docs.aws.amazon.com/ja_jp/glue/latest/dg/aws-glue-programming-intro-tutorial.html