🐳

ローカルJupyterLab環境でGlueジョブを開発・実行する

2021/09/23に公開

はじめに

記事概要

  • Glue ジョブをローカルの Jupyter Lab で開発・テスト実行できる環境を整備します。
  • ローカル開発環境 (Docker コンテナ) から、S3上のサンプルデータを Spark DataFrame として読み込みます。

a. 想定読者

  • Glue ジョブをもっと便利に開発したい方
  • Docker について一定の知識・スキルがある方

b. 前提条件

  • Docker がインストールされており、起動していること
  • AWS CLI がインストールされており、設定が完了していること

c. 動作確認環境

筆者が動作確認を行った環境は以下の通りです。

# OS
% sw_vers
ProductName:	macOS
ProductVersion:	11.6
BuildVersion:	20G165

# Docker
% docker --version
Docker version 20.10.8, build 3967b7d

# Python
% python --version
Python 3.7.6

d. リソース

本記事で使用するリソースは、GitHub に公開しています。

目次

  1. 経緯
  2. 実施手順
  3. 機能概要
  4. 未採択要件
  5. 落穂拾い

1. 経緯

【既に Glue バリバリ使ってるよ!という方は読み飛ばしてください】

Amazon Web Service が提供するマネージドなETLサービスとして AWS Glue があります。Glue は「データカタログ」や「クローラ」など様々な機能を備えていますが、「Glueジョブ」もまた Glue の中核をなすサービスの一つとなっています。開発者は実行基盤を意識することなく、サーバーレスに ETL ワークロードを実装・実行することができます。

Glue ジョブを開発する手段としては、以下が一般的だと思います。

(1)Glue ジョブのスクリプトエディタで、直接スクリプトを編集する。
(2)Glue Notebook インスタンスのノートブック上で、処理ロジックを記述する。

ぶっつけ本番で開発に着手できる人は(1)を選ぶでしょうし、一旦ノートブックでロジックの妥当性を確認したい人は(2)を選ぶでしょう。

しかし、いずれにしても AWS マネジメントコンソールにログインする必要があり、(特に MFA をきちんと設定している場合などには)開発着手までが若干手間です。また、開発時にインターネット接続が必須になってしまうのもあまり好ましくありません。ローカル環境で Glue ジョブをテスト実行する術はないでしょうか。

結論から言ってしまうと、一応あります。AWS が公式に Glue ジョブ実行ランタイム用の Docker イメージを公開しており、これを Pull & Run することで、awsglue ライブラリが事前インストールされた開発用の Glue ジョブ実行環境が手に入ります。詳細な手順は、以下の公式ブログに記載されています。

ブログの手順通りに進めれば、とりあえず Jupyter Notebook 上で Glue ジョブを開発できるようになります。ただ、本記事執筆時点(2021.9.23時点)で公開されているイメージには Jupyter Lab が事前インストールされておらず、また、開発対象の ETL ワークロード毎に必要なライブラリも色々と入っていない可能性があります。Jupyter Notebook を起動し、コマンドプロンプトでインストールでも良いでしょうが、今後も使い回すことを考えれば、必要なライブラリを一式揃えた Docker イメージを新たに Build する運用を整えた方が楽でしょう。

ということで前置きが長くなりましたが、以降で AWS 提供のコンテナイメージをベースに Jupyter Lab 等のライブラリをインストールした新たなイメージをビルドしていきます。また、Amazon S3 に格納されたサンプルデータを読み込み、Spark DataFrame として表示できるかどうかの確認までを行います。

2. 実施手順

2-1. 事前準備

適当なディレクトリ(以下、$WORK_DIR)配下で Git リポジトリをクローンし、移動します。

% cd $WORKDIR
% git clone git@github.com:roki18d/glue-job-local-execution.git
% cd glue-job-local-execution

必要に応じて、pyenv や conda 等の Python 仮想環境にスイッチします。本記事では glueenv という pyenv 仮想環境を使用します。

% pyenv local glueenv
% python --version
Python 3.7.6

必要な Python ライブラリを pip インストールします。

% pip install -r requirements.txt

2-2. 設定

config_sample.json をもとに config.json をコピー作成します。環境に合わせて設定値を編集します。(ビルドした新規イメージを Docker Hub に Push する必要がない場合は、DOCKER_HUB_SECRET の編集は不要です)

% cp config_sample.json config.json
% vi config.json
{
    "GENERAL": {
        "BASE_IMAGE_NAME": "amazon/aws-glue-libs:glue_libs_1.0.0_image_01", 
        "DOCKERFILE_LOCATION": ".", 
        "MY_IMAGE_NAME": "my-aws-glue-libs"
    }, 
    "LOGGING": {
        "CONFIG_FILE_LOCATION": "./logging.conf", 
        "SEPARATOR_CHAR": "-", 
        "SEPARATOR_NUM_REPEAT": 60
    }, 
    "DOCKER_HUB_SECRET": {
        "USERNAME": "<your username>", 
        "PASSWORD": "<your password>", 
        "EMAIL": "<your email>", 
        "REGISTRY": "https://index.docker.io/v1/"
    }, 
    "DOCKER_CONTAINER_CONFIG": {
        "NAME": "glue_jupyter", 
        "PORT_ON_HOST_JUPYTER_NOTEBOOK": 18888, 
        "PORT_ON_HOST_SPARK_UI": 14040
    }
}
Key Default Value Description
GENERAL.BASE_IMAGE_NAME "amazon/aws-glue-libs:glue_libs_1.0.0_image_01" Base image name with tag provided by AWS.
GENERAL.DOCKERFILE_LOCATION "." Dockerfile location
GENERAL.MY_IMAGE_NAME "my-aws-glue-libs" Custom image name
LOGGING.CONFIG_FILE_LOCATION "./logging.conf" Logging configuration file locaion.
LOGGING.SEPARATOR_CHAR "-" The character to be used for logging separator.
LOGGING.SEPARATOR_NUM_REPEAT 60 The length of logging separator.
DOCKER_HUB_SECRET.USERNAME "your username" Docker Hub username
DOCKER_HUB_SECRET.PASSWORD "your password" Docker Hub password
DOCKER_HUB_SECRET.EMAIL "your email" Docker Hub E-mail Address
DOCKER_HUB_SECRET.REGISTRY "https://index.docker.io/v1/" Docker Hub Registry
DOCKER_CONTAINER_CONFIG.NAME "glue_jupyter" Container name to run locally.
DOCKER_CONTAINER_CONFIG.PORT_ON_HOST_JUPYTER_NOTEBOOK 18888 Host-side port number for Jupyter Notebook/Lab.
DOCKER_CONTAINER_CONFIG.PORT_ON_HOST_SPARK_UI 14040 Host-side port number for Spark UI.

2-3. Build & Run

Dockerfile を編集します。本リポジトリでは例として、ベースイメージに対して pip の更新、および Jupyter Lab のインストールのみ行っています。

% vi Dockerfile
FROM amazon/aws-glue-libs:glue_libs_1.0.0_image_01

RUN pip install -U pip
RUN pip install jupyterlab

scripts/main.py を実行し、カスタムイメージを Build & Run します。-t オプションには、カスタムイメージに対するタグを指定します。タグフォーマットは vX.Y (X: Major Version, Y: Minor Version) です。

# Tag Format: vX.Y
#     - X: Major Version
#     - Y: Minor Version

% python scripts/main.py -t vX.Y 

2-4. 動作確認

まず、Docker コンテナが起動されているか確認します。docker ps コマンドを実行すると、確かに glue_jupyter という名前のコンテナが起動していることが分かります。

% docker ps
CONTAINER ID   IMAGE                    COMMAND                  CREATED          STATUS          PORTS                                                                                                          NAMES
1d77a2b0aadb   my-aws-glue-libs:v0.98   "/home/jupyter/jupyt…"   53 seconds ago   Up 52 seconds   8080/tcp, 8998/tcp, 0.0.0.0:14040->4040/tcp, :::14040->4040/tcp, 0.0.0.0:18888->8888/tcp, :::18888->8888/tcp   glue_jupyter

ホスト側ポート番号 18888 は Docker 側ポート番号 8888 (Jupyter Notebook 用) にバインディングされています。ブラウザから http://localhost:18888/lab にアクセスすると、Jupyter Lab 環境にアクセスできることを確認できます。

Kernel として "PySpark" を選択し、ノートブックを新規作成します。ファイル名は適当に test_notebook.ipynb としておきます。

AWS がサンプルとして提供している S3 上の JSON ファイルを Spark DataFrame として読み込んでみます。作成したノートブックのセルに以下をコピー&ペーストし、実行します。

from pyspark import SparkContext
from awsglue.context import GlueContext

glueContext = GlueContext(SparkContext.getOrCreate()) 
inputDF = glueContext.create_dynamic_frame_from_options(
    connection_type = "s3", 
    connection_options = {"paths": ["s3://awsglue-datasets/examples/us-legislators/all/memberships.json"]}, 
    format = "json")

inputDF.toDF().show()

JSON ファイルの中身を DataFrame としてきちんと表示できているようです。セル実行の際、裏側では Spark Job が起動しており、これに関する情報を Spark UI 上で確認できます。ブラウザから http://localhost:14040/jobs/ にアクセスすることで、Spark UI を確認できます。(設定でバインディングポート番号を変更した場合は、適宜読み替えてください)

以上で Glue ジョブのローカル実行の動作確認は完了です。

3. 機能概要

本章では、機能面の概要説明を付しておきたいと思います。(詳細はソースコードをご覧ください)

3-0. Main 関数の動作

以下は、scripts/main.py の抜粋です。次の流れで動作します。

  1. ベースイメージを Pull する。
  2. カスタムイメージを Build する。
  3. カスタムイメージを Push する。
  4. コンテナを Run する。
def main():

    # parse agguments
    parser = argparse.ArgumentParser()
    parser.add_argument("-t", "--tag", help="")
    args = parser.parse_args()
    new_image_version = args.tag

    # pull base image, if not exists
    base_image = pull_image(image_name=base_image_name)

    # build my image
    built_image = build_image(
        my_image_name=my_image_name, 
        new_image_version=new_image_version, 
        dockerfile_location=dockerfile_location)

    # get new image tag
    new_image_tag = ":".join([my_image_name, new_image_version])

    # push my image
    push_image(my_image_name, new_image_version)

    # run container from built image
    containers = run_container(new_image_tag, restart=True)

    # end program with exit code 0
    logging.info(logging_separator)
    logging.info('This program completed successfully.')
    logging.info(logging_separator)
    sys.exit(0)

3-1. Pull

ローカルにベースイメージが存在しない場合、これを Pull します。既に存在する場合はスキップされます。

3-2. Build

設定で指定されてカスタムイメージ名、および -t オプションで指定されたタグを付し、Dockerfile の内容をもとにビルドします。

タグはローカルに既存のバージョンよりも大きなものを指定する縛りを設けています。(特定バージョンが上書かれたり、 "untagged" が大量発生するのを防ぐため)

  • 🙆‍♂️ : v0.2 --> v1.0
  • 🙆‍♂️ : v0.2 --> v0.4
  • 🙅‍♂️ : v0.2 --> v0.2
  • 🙅‍♂️ : v0.2 --> v0.1

3-3. Push

設定で指定された認証情報を用いて Docker Hub にログインし、Build 済みカスタムイメージを Push します。ログインや Push の実行中に何かしらの問題が発生した場合、Push を自動的にスキップします。

3-4. Run

カスタムイメージからコンテナを起動します。既に指定されたコンテナ名がローカルに存在する場合、エラーを出して処理を中断するか、実行中コンテナ停止・削除した上で再起動するかを選択できます。再起動する場合は、run_container(tag, restart) 関数の引数に restart=True を与えます。

4. 未採択要件

執筆時点 (2021.9.23時点) で、以下の要件は未採択です。(そのうち取り込むかもしれません)

  • 引数の追加、説明付与
  • 自動バージョニング ... タグを指定しなかった場合の自動的にタグを付与する
  • ビルドスキップ ... 最新カスタムイメージから変更がない場合にビルドをスキップする
  • カスタムイメージクリーンアップ ... 指定したバージョン以下のカスタムイメージを削除する

5. 落穂拾い

5-1. AWS 上の Glue ジョブでライブラリを追加する

本記事では、Glue ジョブ実行ランタイムに含まれない Python ライブラリは Dockerfile で追加しました。ローカル環境で開発した Glue ジョブを実際に AWS の環境で実行する際には、これらを外部ライブラリとして Glue ジョブが読み込めるように設定する必要があります。手順の詳細は以下に記載されています。

How do I use external Python libraries in my AWS Glue 1.0 or 0.9 ETL job? - AWS

5-2. Zeppelin Notebook を使用する

本記事では、Jupyter Notebook, Jupyter Lab を使用しましたが、Zeppeline Notebook を使用することもできるようです。その場合、以下を参考にポート番号や実行コマンドを Zeppelin 用のものに読み替えてください。

docker run -itd -p 8080:8080 -p 4040:4040 -v ~/.aws:/root/.aws:ro --name glue_zeppelin amazon/aws-glue-libs:glue_libs_1.0.0_image_01 /home/zeppelin/bin/zeppelin.sh

さいごに

本記事では、Glue ジョブのテスト実行環境をローカルに構築しました。Glue ジョブそのものというよりは、Docker イメージ・コンテナ管理に主眼を置いた記事でした。以前はシェルスクリプトで管理したりもしていましたが、"Docker SDK for Python" の存在を知ってからは専ら Python 管理になりました。Python, Docker、便利ですね。

最後までご覧頂き、ありがとうございました。

参考


EOF

Discussion