はじめに
過去に、閉域ネットワーク上の AWS Glue Python Shell から Snowflake に接続した際、ジョブがコード実行前に失敗した経験があります。CloudWatch を見ると PyPI への接続タイムアウトが発生しており、Snowflake の接続設定以前の問題でした。
今回、改めて最小構成の検証環境を構築し、Python 用の Snowflake コネクタ(snowflake-connector-python) を使って閉域 VPC から Snowflake へ接続できるか検証を行いました。
その結果、同じく PyPI(pypi.org)への接続タイムアウトでジョブが失敗しました。
本記事では、
- なぜ閉域 VPC で失敗するのか(失敗ケース)
- どうやって依存を閉域内で解決するか(成功ケース)
- 他にどんな選択肢があるか
を、再現検証を題材に整理します。
本記事は pip / 追加ライブラリの閉域対応が主題です。Snowflake PrivateLink の構築手順や接続パラメータの解説は意図していません。
経緯
事象の流れは以下のとおりです。
- Snowflake へは AWS PrivateLink で接続
- Glue ジョブ(Python Shell) に
--additional-python-modules=snowflake-connector-pythonを指定 - ジョブがタイムアウト(原因は前述の PyPI 接続)
- 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 | 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です。

なぜ失敗するのか
- Glue は 追加ライブラリを PyPI に取りに行く
- 閉域 VPC には PyPI へのルートがない
- したがって スクリプトが実行される前に失敗する
コードや Snowflake の接続情報の問題ではなく、ネットワークと pip の取得先の問題です。
成功 ケース — wheel + pip --no-index
方針
-
インターネット接続できる環境で
pip downloadし、Glue 向け manylinux wheel を S3 に配置 - Glue ジョブでは
--additional-python-modulesを使わない - スクリプト内で S3 から wheel を取得し、
pip install --no-index --find-links ...でインストール - 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 download → aws 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.
補足(検証でハマった点)
記事の本筋ではないので詳細は割愛しますが、以下の事象にも遭遇しました。
urllib3 / OpenSSL(Glue 3.9)
- 事象: Glue 同梱の
urllib3v2 と 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-optionは Python 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-modules に s3://.../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 のデフォルトは外に向く——過去の経験で印象に残った点です。
同じ状況の方の参考になれば幸いです。
参考
-
AWS Glue ジョブでジョブパラメータを使用する —
--additional-python-modules/--python-modules-installer-optionの公式説明(後者は Python 3.9 非対応の記載あり) -
AWS Glue での Python シェルジョブに関するジョブプロパティの設定 — Python Shell の追加ライブラリ(
--additional-python-modules、wheel 配布) - AWS Glue での Python ライブラリの使用 — pip による追加モジュールのインストール全般
- AWS Glue の PyPI に接続する VPC のセットアップ — 閉域 VPC で PyPI / S3 index / CodeArtifact を使う際の公式ガイド
- AWS PrivateLink と Snowflake — Snowflake 側 PrivateLink の公式 docs

