1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

閉域 VPC の AWS Glue Python Shell で Snowflake 接続 — 依存ライブラリ問題の対処

1
Posted at

はじめに

過去に、閉域ネットワーク上の AWS Glue Python Shell から Snowflake に接続した際、ジョブがコード実行前に失敗した経験があります。CloudWatch を見ると PyPI への接続タイムアウトが発生しており、Snowflake の接続設定以前の問題でした。

今回、改めて最小構成の検証環境を構築し、Python 用の Snowflake コネクタ(snowflake-connector-python) を使って閉域 VPC から Snowflake へ接続できるか検証を行いました。

その結果、同じく PyPI(pypi.org)への接続タイムアウトでジョブが失敗しました。

本記事では、

  • なぜ閉域 VPC で失敗するのか(失敗ケース)
  • どうやって依存を閉域内で解決するか(成功ケース)
  • 他にどんな選択肢があるか

を、再現検証を題材に整理します。

本記事は pip / 追加ライブラリの閉域対応が主題です。Snowflake PrivateLink の構築手順や接続パラメータの解説は意図していません。


経緯

事象の流れは以下のとおりです。

  1. Snowflake へは AWS PrivateLink で接続
  2. Glue ジョブ(Python Shell) に --additional-python-modules=snowflake-connector-python を指定
  3. ジョブがタイムアウト(原因は前述の PyPI 接続)
  4. S3 wheel + スクリプト内 pip --no-index に切り替え → Snowflake まで到達

「接続設定が悪い」のではなく、ライブラリの取得経路が先に問題になった、という流れでした。


Glue Python Shell の「追加ライブラリ」は何をしているか

--additional-python-modules を指定したとき

Glue は スクリプトを実行する前に、指定パッケージを pip でインストールします。デフォルトの取得先は PyPI です。

項目 内容
いつ ジョブ起動時(スクリプトの main より前)
誰が Glue マネージド側の pip
どこから 基本 PyPI(特に指定がなければ)
依存関係 指定パッケージの 依存も pip が解決

--additional-python-modules を指定しないとき

pip は走りません。 使えるのは Glue ランタイムに 最初から入っているパッケージ(boto3 など)。snowflake-connector-python は同梱されていないため、そのままでは import できません。

閉域 VPC で何が起きるか

プライベートサブネットに Glue を置き IGW / NAT がない構成では、当然ですがインターネット上の PyPI へ届きません。その結果、pip がリトライの末に失敗します。


検証環境の構成

閉域 VPC に Glue Python Shell を置き、Snowflake へは PrivateLink で接続する構成です。

glue-closed-vpc.drawio.png

項目 内容
Glue Python Shell 3.9 / Glue 3.0、ジョブは2つ用意(Step1(失敗ケース)、Step2(成功ケース))
ネットワーク プライベートサブネットのみ、インターネット egress なし
S3 スクリプト・wheel を管理
Glue API Interface VPC Endpoint
Snowflake Interface VPC Endpoint + Route 53(PrivateLink 用 DNS)

ジョブの違い

項目 Step1(失敗) Step2(成功)
--additional-python-modules あり なし
依存の入れ方 Glue 起動時 pip → PyPI S3 の wheel + スクリプト内で pip --no-index

失敗 ケース — --additional-python-modules を指定

ジョブ設定

Glue ジョブのデフォルト引数に以下を指定:

--additional-python-modules=snowflake-connector-python

ライブラリの取得・解決は Glue (起動時 pip)に任せます。
※Snowflake 接続用などの他の引数の記載は省略します。

ソースの要点(Step1)

Python スクリプト — ライブラリは import するだけ:

import sys

from awsglue.utils import getResolvedOptions

# --additional-python-modules で Glue が PyPI から入れる想定
import snowflake.connector


def _connect(args: dict[str, str]):
    connect_kwargs = {
        "account": args["SNOWFLAKE_ACCOUNT"],
        "host": args["SNOWFLAKE_HOST"],
        "user": args["SNOWFLAKE_USER"],
        "password": args["SNOWFLAKE_PASSWORD"],
        "warehouse": args["SNOWFLAKE_WAREHOUSE"],
        "database": args["SNOWFLAKE_DATABASE"],
        "schema": args["SNOWFLAKE_SCHEMA"],
    }
    if args.get("SNOWFLAKE_ROLE"):
        connect_kwargs["role"] = args["SNOWFLAKE_ROLE"]
    return snowflake.connector.connect(**connect_kwargs)


def main() -> None:
    args = getResolvedOptions(sys.argv, REQUIRED_ARGS + ...)
    with _connect(args) as conn:
        with conn.cursor() as cursor:
            cursor.execute("SELECT CURRENT_VERSION()")
            print(f"Snowflake version: {cursor.fetchone()}")

閉域 VPC では この import の前に Glue 側 pip が PyPI へ出て失敗するため、スクリプト本体は実行されません。

結果

ジョブは タイムアウトで失敗。CloudWatch にはスクリプトのログより 先に pip のエラーが出ます。

WARNING: Retrying ... ConnectTimeoutError ... Connection to pypi.org timed out.
ERROR: Could not find a version that satisfies the requirement snowflake-connector-python
ERROR: No matching distribution found for snowflake-connector-python

Glue コンソール上も Run status: Timeoutです。
Step1_ジョブ結果_画像.png

なぜ失敗するのか

  • Glue は 追加ライブラリを PyPI に取りに行く
  • 閉域 VPC には PyPI へのルートがない
  • したがって スクリプトが実行される前に失敗する

コードや Snowflake の接続情報の問題ではなく、ネットワークと pip の取得先の問題です。


成功 ケース — wheel + pip --no-index

方針

  1. インターネット接続できる環境pip download し、Glue 向け manylinux wheel を S3 に配置
  2. Glue ジョブでは --additional-python-modules を使わない
  3. スクリプト内で S3 から wheel を取得し、pip install --no-index --find-links ... でインストール
  4. Snowflake への接続は失敗ケースと同様

wheel の準備(ローカル / CI)

Glue 3.0 / Python 3.9 は OpenSSL 1.0.2 のため、urllib3 2.x や新しい snowflake-connector-python 4.x と相性問題が出ます。検証では以下を固定しました。

snowflake-connector-python==3.12.3
urllib3<2

pip downloadaws s3 sync でバケットへ配置。

ジョブ設定

失敗ケースとの ジョブ設定上の違いは次の2点です。

  • --additional-python-modules付けない
  • 代わりに wheel 用の引数を渡す
--WHEELS_S3_PREFIX=s3://<bucket>/wheels/

ライブラリの取得・解決は スクリプト内の pip--no-index)に任せます。

ソースの要点(Step2)

Python スクリプト:

① 全体の流れ — S3 から wheel を取り、インストールしてから Snowflake へ接続する:

def main() -> None:
    args = _load_args()

    with tempfile.TemporaryDirectory() as wheels_dir:
        with tempfile.TemporaryDirectory() as deps_dir:
            _download_wheels(args["WHEELS_S3_PREFIX"], wheels_dir)   # ① S3 → ローカル
            _install_from_local_wheels(wheels_dir, deps_dir)           # ② pip --no-index
            _run_snowflake_worker(deps_dir, args)                    # ③ Snowflake 接続

② pip — PyPI を見ないよう --no-index を明示(ここが Step1 との違い):

def _install_from_local_wheels(wheels_dir: str, deps_dir: str) -> None:
    subprocess.check_call([
        sys.executable, "-m", "pip", "install",
        "--no-index",
        "--find-links", wheels_dir,
        "--target", deps_dir,
        "--upgrade",
        "urllib3<2",
        "snowflake-connector-python==3.12.3",
    ])

③ Snowflake 接続main() の ③ は _run_snowflake_worker() に委譲します。②で作った deps_dir を別プロセスに渡し、Glue 同梱ライブラリと衝突しない環境で接続します:

def _run_snowflake_worker(deps_dir: str, args: dict[str, str]) -> None:
    worker_path = Path(deps_dir) / "_snowflake_worker.py"
    worker_path.write_text(_SNOWFLAKE_WORKER_SCRIPT, encoding="utf-8")
    env = os.environ.copy()
    env["DEPS_DIR"] = deps_dir
    env["SNOWFLAKE_CFG"] = json.dumps(_snowflake_connect_config(args))
    subprocess.run([sys.executable, "-u", str(worker_path)], env=env, ...)

ワーカー(_snowflake_worker.py)内の接続処理(抜粋):

# _SNOWFLAKE_WORKER_SCRIPT 内(抜粋)
import snowflake.connector

cfg = json.loads(os.environ["SNOWFLAKE_CFG"])
with snowflake.connector.connect(**cfg) as conn:
    with conn.cursor() as cursor:
        cursor.execute("SELECT CURRENT_VERSION()")
        print(f"Snowflake version: {cursor.fetchone()}")

本筋は ①〜②の pip まわりなので、ワーカー分離の詳細はここでは割愛します。

--no-index により PyPI を見に行かないことが重要です。

結果

ジョブは成功。CloudWatch を見ると、Snowflake から情報が取得できています。

pip install --no-index completed (PyPI not used).
Connecting to Snowflake...
Snowflake version: ('10.20.102',)
Snowflake identity: user=..., role=...
Step2 job finished successfully.

Glue コンソール: Succeeded
Step2_ジョブ結果_画像.png

補足(検証でハマった点)

記事の本筋ではないので詳細は割愛しますが、以下の事象にも遭遇しました。

urllib3 / OpenSSL(Glue 3.9)

  • 事象: Glue 同梱の urllib3 v2 と OpenSSL 1.0.2 の組み合わせで import エラー
  • 対応: connector のバージョン固定import 環境の分離(subprocess)

Route 53(CFN で PrivateLink DNS を組む場合)

  • 事象: CNAME の向き先に VPCE の DnsEntries をそのまま入れると名前解決失敗(Name or service not known
  • 補足: CNAME で VPCE の DNS 名を正しく指す構成ならOK
  • 対応: 今回は エイリアスレコード で Endpoint を直接指す形に直した

他の選択肢

過去にこの事象に遭遇した際は、AWS の他のリソースを使うなどの選択が難しい状況でしたが、
閉域 Glue で追加ライブラリが必要なとき、本記事の Step2 実装(S3 wheel + スクリプト内 pip --no-index)とは別に、
以下のような選択肢(未検証です)があると考えられます。

選択肢 1: pip の取得先を閉域内に置く(Glue パラメータ中心)

--additional-python-modules は使いつつ、index を閉域から届く場所に向ける方法です。

方式 概要
S3 静的ホスティング + simple index S3 上に PyPI 互換 index を置き、--python-modules-installer-option で index URL を指定
AWS CodeArtifact プライベート PyPI。VPC Endpoint 経由で閉域から利用
自前 PyPI(devpi 等) VPC 内にミラーを置く

注意(Glue 3.9 / Python Shell): AWS ドキュメントでは、--python-modules-installer-optionPython 3.9 を使う Glue ジョブではサポートされない と明記されています。上表の「installer-option で index を向ける」方式は Glue 4.0+ の Spark ETL 等を想定した例であり、本記事の Glue 3.0 / Python Shell 3.9 ではそのまま使えません。未検証の選択肢として参考程度にお読みください。

Glue のジョブパラメータだけで閉域内 pip を完結させたい場合、(Python バージョンが合う環境では)この方法が有効だと思います。

選択肢 2: ネットワークを足して PyPI へ出す

NAT Gateway / NAT Instance、または社内ネットワーク経由で インターネットまたは社内 PyPI プロキシへ出す方法です。

  • メリット: --additional-python-modules のまま運用しやすい
  • デメリット: 「閉域」の定義が変わる。セキュリティレビューなどが必要

選択肢 3: Glue Python Shell 以外に寄せる

方式 概要
Glue Spark + Snowflake JDBC JAR を S3 から渡し、Python コネクタに依存しない
Lambda + Layer 小規模なら Layer で依存同梱
EC2 / ECS / Batch 閉域内ミラー・wheel を自由に使える

「Glue で Python コネクタ」という前提を変える選択肢です。

試したが今回は不向きだったもの

--additional-python-moduless3://.../xxx.whl を依存モジュール含めてすべて直接指定する方法もありますが、今回の検証では 依存解決のために結局 PyPI を見に行ったため、閉域では不十分でした。メイン wheel だけ S3 でも、依存が足りないと PyPI にフォールバックしやすい点に注意です。


まとめ

ポイント 内容
現象 Glue ジョブ作成時のライブラリ指定で失敗したように見える
本質 Glue は --additional-python-modules 指定時 起動時 pip → 基本 PyPI → PyPI へ届かずスクリプト実行前に失敗
今回の解決方法 S3 wheel + スクリプト内で pip --no-index
他の選択肢 私有 index、NAT 等、別実行基盤

Glue(Python)で「追加ライブラリ」を使うときは、ランタイムに何が入っているかと、pip がどこから何を取るかをセットで理解する必要があります。閉域ネットワークでは後者の設計が必須です。


おわりに

閉域 VPC 上の Glue Python Shell は、追加 Python パッケージの供給経路を自分で設計しないと動きません。Snowflake PrivateLink のように「データプレーンは閉域内に閉じる」構成でも、pip のデフォルトは外に向く——過去の経験で印象に残った点です。

同じ状況の方の参考になれば幸いです。


参考


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?