メインコンテンツまでスキップ

Docker上にAWS Glueの開発環境を構築する方法

· 約10分
moritalous

Glueジョブをローカル環境で開発する方法がないか調べていたところ、Dockerイメージが提供されていることがわかりました。ローカルの開発環境として使用できるか、調査しました。

概要
  • Glueジョブの開発用にDockerコンテナが用意されている
  • ジョブのスクリプトを単体で実行したり、pytestで実行することが可能
  • Jupyter Labも付属しており、ブラウザでの開発も可能。他にはVSCodeからの接続方法も。

環境構築

Dockerイメージの取得

DockerイメージをPullします。

docker pull amazon/aws-glue-libs:glue_libs_3.0.0_image_01
ヒント

コンテナには以下のものが含まれています。

  • Amazon Linux
  • AWS Glue ETL ライブラリ (aws-glue-libs)
  • Apache Spark 3.1.1
  • Spark 履歴サーバー
  • Jupyter ラボ
  • Livy
  • その他のライブラリ依存関係 (AWS Glue ジョブシステムのライブラリセットと同じです)

AWS認証情報の作成

AmazonS3ReadOnlyAccessだけの権限を持つIAMユーザーを作成し、認証情報を生成します。

  • 空ファイルの作成
mkdir .aws
touch .aws/config .aws/credentials
  • aws configureの実行
docker run -it --rm \
-v $PWD/.aws:/root/.aws \
amazon/aws-cli configure
  • ウィザードに回答
AWS Access Key ID [None]: [アクセスキーを入力]
AWS Secret Access Key [None]: [シークレットアクセスキーを入力]
Default region name [None]: [リージョンを入力(ap-northeast-1など)]
Default output format [None]:
  • 環境変数の設定
PROFILE_NAME=default

ジョブの直接実行(spark-submit)

spark-submitコマンドを使ってAWS Glue のジョブを実行できます。

サンプルプログラムの作成

サンプル用に公開されているpersons.jsonをS3から取得し、スキーマを出力します。

persons.json
  • 1行目のみフォーマットして記載
  • 実際は1行に1レコードで、全部で1961行
{
"family_name": "Collins",
"name": "Mac Collins",
"links": [
{
"note": "Wikipedia (de)",
"url": "https://de.wikipedia.org/wiki/Mac_Collins"
},
{
"note": "Wikipedia (en)",
"url": "https://en.wikipedia.org/wiki/Mac_Collins"
},
{
"note": "Wikipedia (sv)",
"url": "https://sv.wikipedia.org/wiki/Mac_Collins"
},
{
"note": "website",
"url": "http://www.house.gov/maccollins"
}
],
"gender": "male",
"image": "https://theunitedstates.io/images/congress/original/C000640.jpg",
"identifiers": [
{
"scheme": "bioguide",
"identifier": "C000640"
},
{
"scheme": "everypolitician_legacy",
"identifier": "C000640"
},
{
"scheme": "freebase",
"identifier": "/m/0255g_"
},
{
"scheme": "google_entity_id",
"identifier": "kg:/m/0255g_"
},
{
"scheme": "govtrack",
"identifier": "400078"
},
{
"scheme": "house_history",
"identifier": "11254"
},
{
"scheme": "icpsr",
"identifier": "29340"
},
{
"scheme": "nndb",
"identifier": "229/000036121"
},
{
"scheme": "opensecrets",
"identifier": "N00002556"
},
{
"scheme": "snac",
"identifier": "w6086f24"
},
{
"scheme": "thomas",
"identifier": "00222"
},
{
"scheme": "uscongress",
"identifier": "C000640"
},
{
"scheme": "viaf",
"identifier": "258630693"
},
{
"scheme": "wikidata",
"identifier": "Q1882459"
},
{
"scheme": "wikipedia",
"identifier": "Mac Collins"
}
],
"other_names": [
{
"lang": "bar",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "ca",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "da",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "de",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "en",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "es",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "fa",
"note": "multilingual",
"name": "\u0645\u06a9 \u06a9\u0627\u0644\u06cc\u0646\u0632"
},
{
"lang": "fi",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "fr",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "hu",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "it",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "lb",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "nb",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "nds",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "nl",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "nn",
"note": "multilingual",
"name": "Mac Collins"
},
{
"lang": "sv",
"note": "multilingual",
"name": "Mac Collins"
}
],
"sort_name": "Collins, Michael",
"images": [
{
"url": "https://theunitedstates.io/images/congress/original/C000640.jpg"
},
{
"url": "https://upload.wikimedia.org/wikipedia/commons/2/26/MacCollins.JPG"
}
],
"given_name": "Michael",
"birth_date": "1944-10-15",
"id": "0005af3a-9471-4d1f-9299-737fff4b9b46"
}
  • src/sample.py
import sys
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
from awsglue.utils import getResolvedOptions


class GluePythonSampleTest:
def __init__(self):
params = []
if '--JOB_NAME' in sys.argv:
params.append('JOB_NAME')
args = getResolvedOptions(sys.argv, params)

self.context = GlueContext(SparkContext.getOrCreate())
self.job = Job(self.context)

if 'JOB_NAME' in args:
jobname = args['JOB_NAME']
else:
jobname = "test"
self.job.init(jobname, args)

def run(self):
dyf = read_json(self.context, "s3://awsglue-datasets/examples/us-legislators/all/persons.json")
dyf.printSchema()

self.job.commit()


def read_json(glue_context, path):
dynamicframe = glue_context.create_dynamic_frame.from_options(
connection_type='s3',
connection_options={
'paths': [path],
'recurse': True
},
format='json'
)
return dynamicframe


if __name__ == '__main__':
GluePythonSampleTest().run()

実行

  • 環境変数のセット
WORKSPACE_LOCATION=$PWD
SCRIPT_FILE_NAME=sample.py
  • 実行
docker run -it --rm \
-v $PWD/.aws:/home/glue_user/.aws \
-v $WORKSPACE_LOCATION:/home/glue_user/workspace/ \
-e AWS_PROFILE=$PROFILE_NAME \
-e DISABLE_SSL=true \
-p 4040:4040 \
-p 18080:18080 \
--name glue_spark_submit \
amazon/aws-glue-libs:glue_libs_3.0.0_image_01 \
spark-submit /home/glue_user/workspace/src/$SCRIPT_FILE_NAME

ログの合間にdyf.printSchema()の結果が出力されれば成功です。

root
|-- family_name: string
|-- name: string
|-- links: array
| |-- element: struct
| | |-- note: string
| | |-- url: string
|-- gender: string
|-- image: string
|-- identifiers: array
| |-- element: struct
| | |-- scheme: string
| | |-- identifier: string
|-- other_names: array
| |-- element: struct
| | |-- lang: string
| | |-- note: string
| | |-- name: string
|-- sort_name: string
|-- images: array
| |-- element: struct
| | |-- url: string
|-- given_name: string
|-- birth_date: string
|-- id: string
|-- contact_details: array
| |-- element: struct
| | |-- type: string
| | |-- value: string
|-- death_date: string

インタラクティブシェルの実行(pyspark)

pysparkでインタラクティブシェルを起動できます。

実行

  • 実行
docker run -it --rm \
-v ~/.aws:/home/glue_user/.aws \
-e AWS_PROFILE=$PROFILE_NAME \
-e DISABLE_SSL=true \
-p 4040:4040 \
-p 18080:18080 \
--name glue_pyspark \
amazon/aws-glue-libs:glue_libs_3.0.0_image_01 pyspark

インタラクティブシェルが開始されます。

Welcome to
____ __
/ __/__ ___ _____/ /__
_\ \/ _ \/ _ `/ __/ '_/
/__ / .__/\_,_/_/ /_/\_\ version 3.1.1-amzn-0
/_/

Using Python version 3.7.10 (default, Jun 3 2021 00:02:01)
Spark context Web UI available at http://69025ae1b71c:4040
Spark context available as 'sc' (master = local[*], app id = local-1664369431065).
SparkSession available as 'spark'.
>>>
注意

pysparkの知識がゼロのためこの先何をすればいいのかわからず😫

ユニットテストの実行(pytest)

pytestコマンドでPyTestによるユニットテストが実行できます。

テストコードの作成

  • test/test_sample.py
import pytest
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
from awsglue.utils import getResolvedOptions
import sys
from src import sample


@pytest.fixture(scope="module", autouse=True)
def glue_context():
sys.argv.append('--JOB_NAME')
sys.argv.append('test_count')

args = getResolvedOptions(sys.argv, ['JOB_NAME'])
context = GlueContext(SparkContext.getOrCreate())
job = Job(context)
job.init(args['JOB_NAME'], args)

yield(context)

job.commit()


def test_counts(glue_context):
dyf = sample.read_json(glue_context, "s3://awsglue-datasets/examples/us-legislators/all/persons.json")
assert dyf.toDF().count() == 1961

実行

  • 環境変数のセット
WORKSPACE_LOCATION=$PWD
SCRIPT_FILE_NAME=sample.py
UNIT_TEST_FILE_NAME=test_sample.py
  • 実行
docker run -it --rm \
-v $PWD/.aws:/home/glue_user/.aws \
-v $WORKSPACE_LOCATION:/home/glue_user/workspace/ \
-e AWS_PROFILE=$PROFILE_NAME \
-e DISABLE_SSL=true \
-p 4040:4040 \
-p 18080:18080 \
--name glue_pytest \
amazon/aws-glue-libs:glue_libs_3.0.0_image_01 -c "python3 -m pytest"

テストが成功することを確認します。

=============================================== test session starts ================================================
platform linux -- Python 3.7.10, pytest-6.2.3, py-1.11.0, pluggy-0.13.1
rootdir: /home/glue_user/workspace
plugins: anyio-3.6.1
collected 1 item

test/test_sample.py . [100%]

================================================= warnings summary =================================================
test/test_sample.py::test_counts
/home/glue_user/spark/python/pyspark/sql/context.py:79: DeprecationWarning: Deprecated in 3.0.0. Use SparkSession.builder.getOrCreate() instead.
DeprecationWarning)

../.local/lib/python3.7/site-packages/_pytest/cacheprovider.py:428
/home/glue_user/.local/lib/python3.7/site-packages/_pytest/cacheprovider.py:428: PytestCacheWarning: could not create cache path /home/glue_user/workspace/.pytest_cache/v/cache/nodeids
config.cache.set("cache/nodeids", sorted(self.cached_nodeids))

../.local/lib/python3.7/site-packages/_pytest/stepwise.py:49
/home/glue_user/.local/lib/python3.7/site-packages/_pytest/stepwise.py:49: PytestCacheWarning: could not create cache path /home/glue_user/workspace/.pytest_cache/v/cache/stepwise
session.config.cache.set(STEPWISE_CACHE_DIR, [])

-- Docs: https://docs.pytest.org/en/stable/warnings.html
========================================== 1 passed, 3 warnings in 45.15s ==========================================

Jupyter Labを起動

Jupyter Labを起動するスクリプトが用意されています。

準備

Jupyter Labで使用するワークスペースを作成します。

JUPYTER_WORKSPACE_LOCATION=$PWD/jupyter_workspace
mkdir -p $JUPYTER_WORKSPACE_LOCATION
sudo chown 10000.0 $JUPYTER_WORKSPACE_LOCATION
注意

コンテナ内ではuid=10000(glue_user) gid=0(root) groups=0(root)で実行しているため、ワークスペースをこのユーザーに合わせた権限にします。

実行

  • 実行
docker run -it --rm \
-v $PWD/.aws:/home/glue_user/.aws \
-v $JUPYTER_WORKSPACE_LOCATION:/home/glue_user/workspace/jupyter_workspace/ \
-e AWS_PROFILE=$PROFILE_NAME \
-e DISABLE_SSL=true \
-p 4040:4040 \
-p 18080:18080 \
-p 8998:8998 \
-p 8888:8888 \
--name glue_jupyter_lab \
amazon/aws-glue-libs:glue_libs_3.0.0_image_01 \
/home/glue_user/jupyter/jupyter_start.sh

起動後、http://localhost:8888/labにアクセスします。

image.png

PySparkのNotebookを開き、spark.sql("show tables")を入力し実行します。

image.png

注意

S3へのアクセス件しかない認証情報でのアクセスのため、エラーとなりました。Glueへの権限があれば正しく結果が取得できるものと思われます。

VSCodeから接続

最後はVSCodeからコンテナに接続する方法です。

VSCodeにはPython拡張とリモートコンテナ拡張をインストールしておきます。

設定

vscodeの設定ファイルを作成します。

  • .vscode/settings.json
{
"python.defaultInterpreterPath": "/usr/bin/python3",
"python.analysis.extraPaths": [
"/home/glue_user/aws-glue-libs/PyGlue.zip:/home/glue_user/spark/python/lib/py4j-0.10.9-src.zip:/home/glue_user/spark/python/",
]
}

起動

コンテナを起動します。

docker run -it \
-v $PWD/.aws:/home/glue_user/.aws \
-v $JUPYTER_WORKSPACE_LOCATION:/home/glue_user/workspace/jupyter_workspace/ \
-e AWS_PROFILE=$PROFILE_NAME \
-e DISABLE_SSL=true \
--rm -p 4040:4040 \
-p 18080:18080 \
--name glue_pyspark \
amazon/aws-glue-libs:glue_libs_3.0.0_image_01 pyspark

インタラクティブシェルの待ち状態になります。

Welcome to
____ __
/ __/__ ___ _____/ /__
_\ \/ _ \/ _ `/ __/ '_/
/__ / .__/\_,_/_/ /_/\_\ version 3.1.1-amzn-0
/_/

Using Python version 3.7.10 (default, Jun 3 2021 00:02:01)
Spark context Web UI available at http://75628d1103ba:4040
Spark context available as 'sc' (master = local[*], app id = local-1664373292151).
SparkSession available as 'spark'.
>>>

この状態でVSCodeのリモートエクスプローラーを開き、amazon/aws-glue-libs:glue_libs_3.0.0_image_01を右クリックし、Attach to Containerを選択します。

image.png

新しいウィンドウが起動し、Glueのコンテナに接続した状態となります。

image.png

src/sample.pyを作成し、実行してみます。

python3 src/sample.py
注記

pythonだとpython2が起動しますので、python3を指定します

root        
|-- family_name: string
|-- name: string
|-- links: array
| |-- element: struct
| | |-- note: string
| | |-- url: string
|-- gender: string
|-- image: string
|-- identifiers: array
| |-- element: struct
| | |-- scheme: string
| | |-- identifier: string
|-- other_names: array
| |-- element: struct
| | |-- lang: string
| | |-- note: string
| | |-- name: string
|-- sort_name: string
|-- images: array
| |-- element: struct
| | |-- url: string
|-- given_name: string
|-- birth_date: string
|-- id: string
|-- contact_details: array
| |-- element: struct
| | |-- type: string
| | |-- value: string
|-- death_date: string

これでGlueのコンテナ内で開発ができます。

ヒント

Dockerコンテナを使用したGlueのローカル環境での開発方法の紹介でした。 PySparkの勉強をしようと思います。。

参考サイト

https://docs.aws.amazon.com/ja_jp/glue/latest/dg/aws-glue-programming-etl-libraries.html#develop-local-docker-image