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
にアクセスします。
PySparkのNotebookを開き、spark.sql("show tables")
を入力し実行します。
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
を選択します。
新しいウィンドウが起動し、Glueのコンテナに接続した状態となります。
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の勉強をしようと思います。。