

# 在 AWS Glue 中使用 Python 虚拟环境
<a name="aws-glue-programming-python-virtual-environments"></a>

 自 AWS Glue 5.0 起，您可以在 Python 虚拟环境（.venv）中运行 ETL 作业。使用虚拟环境后，无需在作业运行时解析依赖项，从而确保每次运行均使用相同的软件包，并防止上游软件包发生更改而导致故障。

 AWS Glue 支持两种使用虚拟环境的方式：
+ **服务生成的虚拟环境**——在 AWS Glue 6.0 及更高版本中可用。您添加 `--python-virtual-env-storage-prefix` 参数后，AWS Glue 会为您构建虚拟环境，并将其缓存在 Amazon S3 中，供后续运行作业时使用。无需在本地构建。
+ **手动构建的虚拟环境**——在 AWS Glue 5.0 及更高版本中可用。您可以在本地计算机或 CI/CD 管道中构建虚拟环境，将其上传至 Amazon S3，然后通过 `--python-virtual-env` 参数引用该虚拟环境。

 本主题介绍如何将使用 `--additional-python-modules` 的作业迁移到上述任一方式。有关管理 Python 依赖项的其他方法，请参阅 [将 Python 库与 AWS Glue 结合使用](aws-glue-programming-python-libraries.md)。

## 与 --additional-python-modules 的主要区别
<a name="python-venv-key-differences"></a>

下表对 `--additional-python-modules` 与手动构建的虚拟环境进行了比较。


| 功能 | `--additional-python-modules` | `--python-virtual-env` | 
| --- | --- | --- | 
| 基础容器库（boto3、numpy、pandas 及其他） | 自动提供 | 不可用。您必须在 venv 中包含所有必需的软件包。 | 
| 依赖关系解析 | 在运行时进行 | 在您的计算机上构建时进行 | 
| 运行时隔离 | 部分。在基础库的基础上安装软件包。 | 完全。完全取代 Python 环境。 | 

**重要**  
迁移到 `--python-virtual-env` 时，必须在虚拟环境中包含作业所需的所有 Python 软件包。这包括以前可从 AWS Glue 基础容器中获得的软件包，例如 boto3、numpy 和 pandas。这些软件包不再隐式可用。

## 选择方法
<a name="python-venv-choosing-an-approach"></a>

请参考下表，确定哪种方法适合您的作业。


| 场景 | 推荐方法 | 
| --- | --- | 
| 仅使用少量 pip 软件包且希望避免构建开销的简单作业 | 服务生成的 venv（添加 `--python-virtual-env-storage-prefix`）。需要 AWS Glue 6.0 或更高版本。 | 
| 复杂的依赖树、完全可重现性，或通过 CI/CD 管道构建 venv | 手动构建的 venv（`--python-virtual-env`）。需要 AWS Glue 5.0 或更高版本。 | 
| 只需最少更改即可从 `--additional-python-modules` 迁移 | 服务生成的 venv（添加 `--python-virtual-env-storage-prefix`）。需要 AWS Glue 6.0 或更高版本。在 AWS Glue 5.0 和 5.1 上，请改用手动构建的虚拟环境。 | 
| 包含自定义软件包的私有 PyPI 索引 | 两种方法均可。服务生成的 venv 需要 AWS Glue 6.0 或更高版本，并且可与 [--python-modules-installer-option](aws-glue-programming-etl-glue-arguments.md#python-modules-installer-option) 配合使用。 | 

## 使用服务生成的虚拟环境及 Amazon S3 缓存
<a name="python-venv-service-generated"></a>

自 AWS Glue 6.0 起，您可以使用 `--python-virtual-env-storage-prefix` 参数，让 AWS Glue 构建虚拟环境并将其缓存到 Amazon S3 中。这种方法将 `--additional-python-modules` 的简单性与缓存虚拟环境的性能优势相结合。

### 工作原理
<a name="python-venv-service-generated-how-it-works"></a>

当您提供 `--python-virtual-env-storage-prefix` 时，AWS Glue 会执行以下操作：
+ **首次运行（缓存未命中）**——AWS Glue 使用 `--system-site-packages` 创建虚拟环境，该环境会继承 numpy、pandas 和 pyarrow 等容器软件包。AWS然后，Glue 使用 pip 安装 [--additional-python-modules](aws-glue-programming-etl-glue-arguments.md#additional-python-modules) 中的软件包，将虚拟环境打包为 `.tar.gz` 文件，并将其上传至您的 Amazon S3 前缀，供后续重复使用。
+ **后续运行（缓存命中）**——AWS Glue 从 Amazon S3 下载并解压缓存的 `.tar.gz` 文件，然后配置 Spark 驱动程序和执行器使用该虚拟环境。无需进行 pip 安装。

### 与手动构建的虚拟环境的区别
<a name="python-venv-service-generated-comparison"></a>

下表对服务生成的虚拟环境与手动构建虚拟环境的方法进行了比较。


| 功能 | 服务生成的 venv | 手动构建的 venv | 
| --- | --- | --- | 
| 构建职责 | AWS Glue 会自动构建 venv | 您在 Docker 中构建 venv | 
| 容器软件包 | 通过 `--system-site-packages` 继承 | 必须显式包含所有软件包 | 
| 首次运行延迟 | 需要额外时间进行 pip 安装、打包并上传至 Amazon S3 | 无，因为 venv 已预先构建 | 
| 后续运行延迟 | 从 Amazon S3 下载并解压需要额外时间 | 从 Amazon S3 下载并解压需要额外时间 | 
| 确定性 | 我们建议您锁定软件包版本 | 具有完全确定性，因为版本已在构建时锁定 | 
| PyPI访问 | 首次运行时需要 | 无需联网，因为 venv 已离线构建 | 

### 配置服务生成的虚拟环境
<a name="python-venv-service-generated-usage"></a>

`--python-virtual-env-storage-prefix` 参数用于指定 AWS Glue 存储其所构建虚拟环境的 Amazon S3 位置，格式为 `s3://{{path}}/`。AWSGlue 在首次运行作业时将虚拟环境缓存到此前缀，并在后续运行中重复使用。

要启用服务生成的虚拟环境，请将 `--python-virtual-env-storage-prefix` 参数添加到您的作业中，并保留现有的 `--additional-python-modules` 参数。

```
"--additional-python-modules": "requests==2.32.3,scikit-learn==1.5.0"
"--python-virtual-env-storage-prefix": "s3://{{amzn-s3-demo-bucket}}/venv-cache/"
```

您还可以使用以下可选参数：
+ `--python-virtual-env-version` —— 缓存虚拟环境的版本标识符。更改此值可使缓存失效，并强制 AWS Glue 重新构建虚拟环境。该值是一个字符串，因此您可以采用适合自身工作流的任意版本控制方案，例如递增编号、日期或构建标识符。默认值为 `0`。
+ [--python-modules-installer-option](aws-glue-programming-etl-glue-arguments.md#python-modules-installer-option) —— 向 pip 传递选项，例如 `--no-deps` 或 `--index-url`。

要为现有作业启用缓存，请添加存储前缀参数。首次运行耗时较长，因为 AWS Glue 需要构建并上传虚拟环境；后续运行则会使用缓存的虚拟环境，无需进行 pip 依赖项解析。

```
# Before
"--additional-python-modules": "requests==2.32.3"

# After
"--additional-python-modules": "requests==2.32.3"
"--python-virtual-env-storage-prefix": "s3://{{amzn-s3-demo-bucket}}/venv-cache/"
```

### AWS Glue 如何缓存虚拟环境
<a name="python-venv-service-generated-caching"></a>

AWS Glue 根据作业配置生成缓存键。配置包括 `--additional-python-modules` 中的模块、`--python-modules-installer-option` 的值、AWS Glue 版本以及 `--python-virtual-env-version` 的值。

配置未发生变化时，即可命中缓存。如果您更改上述任意值，AWS Glue 将构建新的虚拟环境并创建新的缓存条目。

AWS Glue 将每个缓存的虚拟环境分别存储在存储前缀下的独立键中。使用相同模块和安装程序选项的作业共享相同的缓存条目。

### 限制
<a name="python-venv-service-generated-limitations"></a>
+ 需要 AWS Glue 6.0 或更高版本。
+ 首次运行时需要访问 PyPI 或您的私有索引，以便进行依赖项解析。
+ 系统会继承 numpy 和 pandas 等容器软件包，但不会锁定其版本。如果您的作业需要使用特定版本的容器软件包，请改用 `--python-virtual-env`。
+ 缓存键由配置决定。更改任何模块或版本都会创建新的缓存条目；先前的缓存条目会一直保留在 Amazon S3 中，直至您将其删除。

## 自行构建虚拟环境
<a name="python-venv-migration-procedure"></a>

在 AWS Glue 5.0 及更高版本中，您可以自行构建虚拟环境，并使用 `--python-virtual-env` 参数引用该虚拟环境。如果您要求环境完全可重现、需要使用特定版本的容器软件包，或者需要通过 CI/CD 管道执行构建，请使用此方法。

### 先决条件
<a name="python-venv-prerequisites"></a>

开始之前，请确保已具备以下条件：
+ 从 Docker 网站获取并安装在本地计算机上的 [Docker](https://docs.docker.com/get-docker/)，以便您能够在与 AWS Glue 兼容的环境中构建虚拟环境
+ 用于上传已打包虚拟环境的 Amazon S3 存储桶
+ 已配置相应权限的 AWS CLI，可用于上传至 Amazon S3 并更新 AWS Glue 作业参数

有关每个 AWS Glue 版本的 Python 版本和平台兼容性详情，请参阅 [附录 B：AWS Glue 环境详细信息](aws-glue-programming-python-libraries.md#glue-python-libraries-environment-details)。

### 第 1 步：创建 requirements 文件
<a name="python-venv-step-1"></a>

创建两个 requirements 文件，用于指定虚拟环境所需的软件包。

1. 从 GitHub 网站上的 aws-glue-libs 存储库下载适用于您的 AWS Glue 版本的 `base-requirements.txt`。此文件列出了标准 AWS Glue 容器提供的软件包。要查看本指南中的同一列表，请参阅 [AWS Glue 中已提供的 Python 模块](aws-glue-programming-python-libraries.md#glue-modules-provided)。
   + AWSGlue 5.0 —— GitHub 网站上的 [base-requirements.txt](https://raw.githubusercontent.com/awslabs/aws-glue-libs/glue-5.0/base-requirements.txt)
   + AWSGlue 5.1 —— GitHub 网站上的 [base-requirements.txt](https://raw.githubusercontent.com/awslabs/aws-glue-libs/glue-5.1/base-requirements.txt)
   + AWSGlue 6.0 —— GitHub 网站上的 [base-requirements.txt](https://raw.githubusercontent.com/awslabs/aws-glue-libs/main/base-requirements.txt)

1. 创建`additional-requirements.txt`。将现有 `--additional-python-modules` 参数中的软件包逐行添加，每行一个。例如：

   ```
   cryptography
   requests-oauthlib
   sqlalchemy
   ```

**重要**  
如果您的作业使用 AWS Glue Python 库（例如 `GlueContext` 或 `DynamicFrame`），则还必须添加 PyPI 网站上提供的 [AWSGlueDataplanePython](https://pypi.org/project/AWSGlueDataplanePython/) 软件包。请按照下表，使用与您的 AWS Glue 版本相匹配的软件包版本。


| AWS Glue 版本 | 软件包版本 | 
| --- | --- | 
| 5.0 | `AWSGlueDataplanePython==5.0.0` | 
| 5.1 | `AWSGlueDataplanePython==5.1.0` | 
| 6.0 | `AWSGlueDataplanePython==6.0.0` | 

### 步骤 2：创建 Dockerfile
<a name="python-venv-step-2"></a>

创建一个与目标 AWS Glue 版本的运行环境相匹配的 Dockerfile。有关平台和 Python 版本的详细信息，请参阅 [附录 B：AWS Glue 环境详细信息](aws-glue-programming-python-libraries.md#glue-python-libraries-environment-details)。

AWSGlue 5.0 和 5.1 在 Amazon Linux 2023 上使用 Python 3.11。

```
FROM --platform=linux/amd64 public.ecr.aws/amazonlinux/amazonlinux:2023-minimal

RUN dnf install -y python3.11 zip && \
    dnf clean all

WORKDIR /build
```

AWSGlue 6.0 在 Amazon Linux 2023 上使用 Python 3.13。

```
FROM --platform=linux/amd64 public.ecr.aws/amazonlinux/amazonlinux:2023-minimal

RUN dnf install -y python3.13 zip && \
    dnf clean all

WORKDIR /build
```

### 步骤 3：构建并启动容器
<a name="python-venv-step-3"></a>

构建 Docker 镜像。然后启动一个容器，并将 requirements 文件和作业脚本挂载到该容器中。

```
docker build --platform linux/amd64 -t glue-venv-builder .

docker run --platform linux/amd64 \
  -v $(pwd)/base-requirements.txt:/working_dir/base-requirements.txt:ro \
  -v $(pwd)/additional-requirements.txt:/working_dir/additional-requirements.txt:ro \
  -v $(pwd)/my_glue_script/:/working_dir/my_glue_script/:ro \
  -v $(pwd):/output \
  -w /working_dir \
  -it glue-venv-builder bash
```

此命令会挂载您的 requirements 文件以及 AWS Glue 作业脚本目录。下一步将使用该脚本目录进行导入分析。

### 第 4 步：构建临时 venv 并确定所需的软件包
<a name="python-venv-step-4"></a>

在容器内，构建一个与 AWS Glue 运行时环境相匹配的临时 venv。然后使用静态分析确定作业所需的最小软件包集合。

对于使用 Python 3.11 的 AWS Glue 5.0 和 5.1，请运行以下命令。

```
# Create a temporary venv to reproduce the AWS Glue runtime environment
python3.11 -m venv temp_venv
source temp_venv/bin/activate

python3.11 -m pip install --upgrade pip

# Install base container libraries (mirrors what the AWS Glue container provides)
python3.11 -m pip install -r base-requirements.txt

# Install additional Python modules on top (mirrors how AWS Glue installs them at runtime)
python3.11 -m pip install -r additional-requirements.txt

# Freeze the full resolved environment
pip freeze > full-requirements.txt

# Install analysis tools
python3.11 -m pip install pipreqs pip-tools

# Use pipreqs to discover what the script actually imports
# --mode no-pin outputs package names without versions
pipreqs --mode no-pin --savepath discovered-requirements.txt /working_dir/my_glue_script

# Remove packages provided by the Spark runtime
sed -i '/pyspark/d' discovered-requirements.txt
sed -i '/py4j/d' discovered-requirements.txt

# Remove awsglue - install AWSGlueDataplanePython in Step 5 instead
sed -i '/awsglue/d' discovered-requirements.txt

# Use pip-compile to resolve the full dependency tree of the discovered packages,
# constrained to the versions from the temporary venv
pip-compile discovered-requirements.txt -c full-requirements.txt -o final-requirements.txt

echo "=== Final requirements.txt ==="
cat final-requirements.txt

# Deactivate and discard the temporary venv
deactivate
rm -rf temp_venv
```

对于使用 Python 3.13 的 AWS Glue 6.0，请运行以下命令。

```
# Create a temporary venv to reproduce the AWS Glue runtime environment
python3.13 -m venv temp_venv
source temp_venv/bin/activate

python3.13 -m pip install --upgrade pip

# Install base container libraries (mirrors what the AWS Glue container provides)
python3.13 -m pip install -r base-requirements.txt

# Install additional Python modules on top (mirrors how AWS Glue installs them at runtime)
python3.13 -m pip install -r additional-requirements.txt

# Freeze the full resolved environment
pip freeze > full-requirements.txt

# Install analysis tools
python3.13 -m pip install pipreqs pip-tools

# Use pipreqs to discover what the script actually imports
# --mode no-pin outputs package names without versions
pipreqs --mode no-pin --savepath discovered-requirements.txt /working_dir/my_glue_script

# Remove packages provided by the Spark runtime
sed -i '/pyspark/d' discovered-requirements.txt
sed -i '/py4j/d' discovered-requirements.txt

# Remove awsglue - install AWSGlueDataplanePython in Step 5 instead
sed -i '/awsglue/d' discovered-requirements.txt

# Use pip-compile to resolve the full dependency tree of the discovered packages,
# constrained to the versions from the temporary venv
pip-compile discovered-requirements.txt -c full-requirements.txt -o final-requirements.txt

echo "=== Final requirements.txt ==="
cat final-requirements.txt

# Deactivate and discard the temporary venv
deactivate
rm -rf temp_venv
```

**注意**  
检查 `final-requirements.txt`，确认其内容正确无误。如果您的作业使用动态导入或条件导入，pipreqs 可能无法检测到这些导入。手动将这些软件包添加到文件中。

### 第 5 步：构建生产环境 venv
<a name="python-venv-step-5"></a>

创建最终的 venv，其中仅包含作业所需的软件包。然后将其打包成 tarball 文件。

对于使用 Python 3.11 的 AWS Glue 5.0 和 5.1，请运行以下命令。

```
python3.11 -m venv pyspark_venv
source pyspark_venv/bin/activate

python3.11 -m pip install --upgrade pip
python3.11 -m pip install -r final-requirements.txt

# Install the AWS Glue Python library that matches your AWS Glue version (see the version
# table in Step 1). Use 5.0.0 for AWS Glue 5.0, or 5.1.0 for AWS Glue 5.1.
python3.11 -m pip install AWSGlueDataplanePython=={{5.0.0}}

python3.11 -m pip install venv-pack
venv-pack -f -o pyspark_venv.tar.gz

cp pyspark_venv.tar.gz /output/
exit
```

对于使用 Python 3.13 的 AWS Glue 6.0，请运行以下命令。

```
python3.13 -m venv pyspark_venv
source pyspark_venv/bin/activate

python3.13 -m pip install --upgrade pip
python3.13 -m pip install -r final-requirements.txt

# Install the AWS Glue Python library (see version table in Step 1)
python3.13 -m pip install AWSGlueDataplanePython==6.0.0

python3.13 -m pip install venv-pack
venv-pack -f -o pyspark_venv.tar.gz

cp pyspark_venv.tar.gz /output/
exit
```

### 步骤 6：上传到 Amazon S3
<a name="python-venv-step-6"></a>

将打包后的虚拟环境上传至您的 Amazon S3 存储桶。

```
aws s3 cp pyspark_venv.tar.gz s3://{{amzn-s3-demo-bucket}}/{{path}}/pyspark_venv.tar.gz
```

### 第 7 步：更新作业参数
<a name="python-venv-step-7"></a>

更新您的 AWS Glue 作业配置，改用 `--python-virtual-env`，不再使用 `--additional-python-modules`。

移除 `--additional-python-modules` 参数，然后添加 `--python-virtual-env` 参数，并使其指向您已上传的 tarball 文件。

```
# Before
"--additional-python-modules": "cryptography"

# After (remove --additional-python-modules entirely)
"--python-virtual-env": "s3://{{amzn-s3-demo-bucket}}/{{path}}/pyspark_venv.tar.gz"
```

## 使用 Kiro 实现迁移自动化
<a name="python-venv-kiro-migration"></a>

如果您倾向于采用自动化方式，可以使用 Kiro 网站上提供的 [Kiro](https://kiro.dev)，从命令行执行 [自行构建虚拟环境](#python-venv-migration-procedure) 中所述的迁移。使用 Kiro 技能后，Kiro 会分析您的 AWS Glue 作业配置，在 Docker 中构建虚拟环境，并生成打包好的 tarball 文件。

### 工作原理
<a name="python-venv-kiro-how-it-works"></a>

当您让 Kiro 将您的 AWS Glue 作业从 `--additional-python-modules` 迁移到 `--python-virtual-env` 时，Kiro 将执行以下操作：

1. 从您的请求中提取 AWS Glue 版本、`--additional-python-modules` 的值和作业脚本。

1. 从 AWS Glue 文档中检索适用于您的 AWS Glue 版本的基础容器模块列表。

1. 在工作目录中创建构建构件，包括 `base-requirements.txt`、`additional-requirements.txt`、Dockerfile 和构建脚本。

1. 为与 AWS Glue 兼容的环境构建 Docker 镜像。

1. 在非交互式容器中运行发现与打包工作流。

1. 生成 `pyspark_venv.tar.gz`，提示您指定 Amazon S3 目标位置，然后上传 tarball 文件。

1. 显示更新后的作业参数。

### 示例请求
<a name="python-venv-kiro-example"></a>

提供您的 AWS Glue 版本、额外的 Python 模块以及作业脚本。例如：

```
I have a Glue 5.1 job with the following:
--additional-python-modules: ephem, awscli

Glue job script:
import awscli
import ephem

Help me migrate to using --python-virtual-env.
```

### 获取 Kiro 技能
<a name="python-venv-kiro-skill"></a>

`venv-migration` 技能文件在 aws-glue-libs 存储库中维护，本指南不提供该文件。有关技能文件及安装说明，请参阅 GitHub 网站上的 [venv-migration 技能](https://github.com/awslabs/aws-glue-libs/blob/main/.kiro/skills/venv-migration/skill.md)。

### 限制
<a name="python-venv-kiro-limitations"></a>
+ 使用 Kiro 时，命令行环境中必须提供 Docker。
+ 系统无法自动检测脚本源代码中未明确显示的动态导入和条件导入。检查生成的 `final-requirements.txt` 文件，并手动添加缺失的软件包。
+ 如果您的作业使用带有 `--index-url` 的私有 pip 索引，则必须在 Docker 容器中配置该索引的网络访问权限。
+ 构建期间出现的 pip 冲突可能需要手动解决。有关更多信息，请参阅 [问题排查](#python-venv-troubleshooting)。

## 问题排查
<a name="python-venv-troubleshooting"></a>

请参阅以下各节，解决在 AWS Glue 中使用 Python 虚拟环境时遇到的常见问题。

### 解决 pip 版本冲突
<a name="python-venv-troubleshooting-pip-conflicts"></a>

pip 版本冲突是指两个软件包要求同一依赖项使用彼此不兼容的版本。要查找并修复冲突，请执行以下操作：

1. 查看 pip 错误输出。解析彻底失败时，输出会列出每项相互冲突的依赖项要求，以及引入该要求的软件包。

1. 在不安装任何内容的情况下，预览 pip 的依赖项解析结果。按照以下示例，将 `--dry-run --report install-report.json` 添加到安装命令中。

   ```
   pip install -r additional-requirements.txt --dry-run --report install-report.json
   ```

1. 检查`install-report.json` 该报告会列出 pip 选择的所有软件包，从而显示其中是否存在静默降级。

1. 放宽对非关键软件包的版本锁定，或移除相关约束。

### 解决 ModuleNotFoundError
<a name="python-venv-troubleshooting-module-not-found"></a>

此错误表示您的虚拟环境中缺少必需的软件包。常见原因包括以下各项：
+ 您未包含作业所需的基础容器库。手动构建的虚拟环境不会继承 AWS Glue 容器中的软件包。
+ 您的作业使用了 pipreqs 在静态分析期间无法检测到的动态导入。
+ 作业的执行器节点需要 PySpark 依赖项。

要解决此问题，请添加缺失的软件包并重建虚拟环境。具体步骤取决于您的作业所采用的方法。
+ **手动构建 venv** —— 将软件包添加到 `final-requirements.txt`，然后重新构建虚拟环境并再次上传。
+ **服务生成的 venv** —— 将软件包添加到 `--additional-python-modules`。新的模块列表会更改缓存键，因此 AWS Glue 会在下一次运行作业时构建新的虚拟环境。

### 减小 venv tarball 文件的大小
<a name="python-venv-troubleshooting-large-tarball"></a>

如果打包后的虚拟环境过大，请采用以下方法减小其大小：
+ 移除脚本未导入的非必要软件包，例如测试框架和开发工具。
+ 对于需要手动控制传递依赖项的软件包，请使用 `pip install --no-deps`。
+ 仅包含脚本直接导入的软件包，并让 pip-compile 解析最低限度的必要传递依赖项。

### 解决平台兼容性错误
<a name="python-venv-troubleshooting-platform-errors"></a>

如果 venv 中的软件包是针对其他操作系统或系统架构构建的，就会出现此类错误。为避免此类错误，请执行以下操作：
+ 始终使用 `--platform linux/amd64` 标志在 Docker 容器内构建虚拟环境。
+ 确认 wheel 平台标签与目标 AWS Glue 版本相匹配。例如，AWS Glue 5.0 和 5.1 需要 `manylinux2014_x86_64` 或兼容的平台标签。
+ 如果没有 Docker，请勿直接在 macOS 或 Windows 上构建虚拟环境。