# Amazon AWS S3

本指南描述了将 [Amazon AWS S3](https://aws.amazon.com/s3/) 配置为 Alluxio 底层存储系统的说明。\
Amazon AWS S3，即 Amazon Simple Storage Service，是一种对象存储服务，提供行业领先的可扩展性、数据可用性、安全性和性能。\
有关 Amazon AWS S3 的更多信息，请阅读其[文档](https://docs.aws.amazon.com/s3/index.html)。

## 先决条件

准备将Amazon AWS S3与Alluxio一起使用时，请遵循以下步骤。

| `<S3_BUCKET>`        | [创建一个新的S3 bucket](https://docs.ceph.com/en/quincy/radosgw/s3/bucketops/) 或者使用一个现存的 bucket                                                                |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<S3_DIRECTORY>`     | 你想在容器中使用的目录，创建一个新目录，或者使用一个现有的目录。                                                                                                                         |
| `<S3_ACCESS_KEY_ID>` | 用于对 AWS 发出的请求进行签名计算。请参阅 [如何获取 Access Key ID 和 Secret Access Key](https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html)  |
| `<S3_SECRET_KEY>`    | 用于对 AWS 发出的请求进行签名计算。请参阅 [如何获取 Access Key ID 和 Secret Access Key](https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html)。 |

## 基础设置

使用 [挂载表操作](https://documentation.alluxio.io/ee-ai-cn/ai-3.6/overview/namespace#挂载表操作)\
来增加一个新的挂载点, 指定Alluxio路径在其上创建挂载，指定S3的路径作为UFS URI。\
密钥和配置选项也可以通过指定 `--option` 标志作为挂载命令的一部分来指定，如[配置挂载点](https://documentation.alluxio.io/ee-ai-cn/ai-3.6/overview/namespace#对不同挂载点使用不同的配置)所述。

以下是通过 Operator 创建挂载点的 `ufs.yaml` 文件示例：

```yaml
apiVersion: k8s-operator.alluxio.com/v1
kind: UnderFileSystem
metadata:
  name: alluxio-s3
  namespace: alx-ns
spec:
  alluxioCluster: alluxio-cluster
  path: s3://<S3_BUCKET>/<S3_DIRECTORY>
  mountPath: /s3
  mountOptions:
    s3a.accessKeyId: <S3_ACCESS_KEY_ID>
    s3a.secretKey: <S3_SECRET_KEY>
    alluxio.underfs.s3.region: <S3_REGION>
```

不使用 Operator 的情况下, 将 `s3://<S3_BUCKET>/<S3_DIRECTORY>` 挂载到 `/s3` 的示例命令：

```shell
bin/alluxio mount add --path /s3/ --ufs-uri s3://<S3_BUCKET>/<S3_DIRECTORY> \
  --option s3a.accessKeyId=<S3_ACCESS_KEY_ID> --option s3a.secretKey=<S3_SECRET_KEY>
```

请注意，如果您想挂载S3 bucket的根，请在bucket名称后面添加一个斜杠（例如`s3://S3_BUCKET/`).

对于其他设置AWS凭证的方法，请参阅[高级设置](#高级凭证设置)中的凭证章节。

## 高级设置

### 配置 v1 AWS SDK

在访问 S3 bucket时, 配置 AWS SDK 版本。默认使用的版本是 v2。\
如果您想将版本设置为 v1，请在 conf/alluxio-site.properties 文件中添加以下配置。

```properties
alluxio.underfs.s3.sdk.version=1
```

请注意SDK V2有更好的内存管理和更高的吞吐性能。

### 配置S3 Region

配置访问 S3 bucket时的 S3 Region会提高性能。\
否则，将启用全局 S3 bucket访问，这会引入额外的请求。\
可以用配置属性 `alluxio.underfs.s3.region` 来设置 S3 Region。

```properties
alluxio.underfs.s3.region=us-west-1
```

注意如果设置了[S3 endpoint](#为非标准配置指定endpoint)，那么上述的配置属性将被忽略，转而优先使用这个endpoint特定的区域属性。

### 高级凭证设置

你可以用不同的方式指定凭证（credentials），优先级由高到低排列：

你可以使用多种不同的方式指定凭证，从高优先级到低优先级分别是：

1. 作为挂载选项指定 `s3a.accessKeyId` 和 `s3a.secretKey`
2. 作为 Java 系统属性指定 `s3a.accessKeyId` 和 `s3a.secretKey`
3. 在 `alluxio-site.properties` 中指定 `s3a.accessKeyId` 和 `s3a.secretKey`
4. 在 Alluxio 服务器上使用环境变量 `AWS_ACCESS_KEY_ID` 或者 `AWS_ACCESS_KEY`（两者之一即可）以及`AWS_SECRET_ACCESS_KEY` 或者 `AWS_SECRET_KEY`（两者之一即可）
5. 包含凭证的配置文件位于 `~/.aws/credentials`
6. 如果您使用的是 EC2 实例，则使用 AWS 实例配置凭证

当使用 AWS 实例配置文件提供凭证时：

* 创建一个具有访问挂载存储桶权限的 [IAM Role](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
* 创建一个[实例配置文件](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#ec2-instance-profile)作为已定义 IAM role的容器
* 使用创建的配置文件启动一个 EC2 实例

请注意，IAM role需要访问存储桶中的文件以及存储桶本身，以确定存储桶的所有者。\
通过设置属性 `alluxio.underfs.s3.inherit.acl=false` 可以避免自动分配存储桶所有者。

有关更多详细信息，请参阅 [Amazon 的文档](http://docs.aws.amazon.com/java-sdk/latest/developer-guide/credentials.html#id6)。

### 禁用 HTTPS

默认情况下，启用 HTTPS 协议与 S3 进行安全通信。要禁用此功能，请配置以下属性：

```properties
alluxio.underfs.s3.secure.http.enabled=false
```

### 开启服务端加密

您可以对存储在 S3 中的数据进行加密。\
加密仅对 S3 中静态数据有效，当客户端读取数据时将以解密形式传输。\
请注意，启用此功能还会启用 HTTPS 以符合读取/写入对象的要求。

通过配置 `conf/alluxio-site.properties` 文件启用此功能：

```properties
alluxio.underfs.s3.server.side.encryption.enabled=true
```

### DNS-Buckets

默认情况下，指向名为"mybucket"的bucket的请求将发送到名为"mybucket.s3.amazonaws.com"的主机。\
您可以启用 DNS-Buckets 以使用路径风格的数据访问，例如："<http://s3.amazonaws.com/mybucket"，通过设置以下配置：>

```properties
alluxio.underfs.s3.disable.dns.buckets=true
```

### 通过proxy访问S3

在 `conf/alluxio-site.properties` 中加入以下选项，用以支持使用proxy与S3交互:

```properties
alluxio.underfs.s3.proxy.host=<PROXY_HOST>
alluxio.underfs.s3.proxy.port=<PROXY_PORT>
```

`<PROXY_HOST>` 和 `<PROXY_PORT>` 需要用你的proxy的主机名和端口代替。默认情况下，使用HTTPS协议访问proxy；如果proxy仅支持HTTP，请务必设置 `alluxio.underfs.s3.secure.http.enabled=false`。

### 为非标准配置指定endpoint

如果您想访问特定的endpoint，例如 AWS VPC 端点或非 Amazon 的服务提供商，请修改 `conf/alluxio-site.properties` 文件并添加以下内容：

```properties
alluxio.underfs.s3.endpoint=<S3_ENDPOINT>
alluxio.underfs.s3.endpoint.region=<S3_ENDPOINT_REGION>
```

endpoint和region都需要进行设置。\
请注意，当设置了endpoint时，`alluxio.underfs.s3.region=<S3_REGION>` 将不再生效。

如果使用非 Amazon 的服务提供商，请将 S3 服务的主机名和端口设置为 `<S3_ENDPOINT>`。

### 连接到 Oracle Cloud Infrastructure (OCI) 对象存储

endpoint和region值都需要被更新为使用non-home region。

```properties
alluxio.underfs.s3.endpoint=<S3_ENDPOINT>
alluxio.underfs.s3.endpoint.region=<S3_ENDPOINT_REGION>
```

所有的OCI对象存储region都需要使用 `PathStyleAccess`

```properties
alluxio.underfs.s3.disable.dns.buckets=true
alluxio.underfs.s3.inherit.acl=false
```

### 使用v2 S3签名

一些 S3 服务提供商仅支持 v2 签名。\
对于这些 S3 提供商，您可以通过设置 `alluxio.underfs.s3.signer.algorithm` 为 `S3SignerType` 来强制使用 v2 签名。

### \[实验性] S3 流式上传功能

由于 S3 作为对象存储的特性，文件上传时会被从客户端发送到Worker节点，并被存储在本地磁盘的临时目录中，默认在 `close()` 方法中被上传到S3。

要启用 S3 流式上传，您需要修改 `conf/alluxio-site.properties` 文件，添加以下内容：

```properties
alluxio.underfs.s3.streaming.upload.enabled=true
```

默认的上传过程更安全，但存在以下问题：

* 上传时间慢。文件必须先发送到 Alluxio worker，然后由 Alluxio worker负责将文件上传到 S3。这两个过程是顺序执行的。
* 临时目录必须有足够的容量来存储整个文件。
* `close()` 方法执行缓慢。`close()` 方法的执行时间与文件大小成正比，与带宽成反比，即 O(FILE\_SIZE/BANDWIDTH)。`close()` 方法执行缓慢是预期之外的，并且已经成为 Alluxio FUSE 中的一个瓶颈。\
  Alluxio FUSE 方法调用 `close()` 是异步的，因此如果通过 Alluxio FUSE 将大文件写入 S3，FUSE 写操作会在文件实际写入 S3 之前很久就返回。

S3 流式上传功能解决了上述问题，并且基于 [S3 低级别的分段上传](https://docs.aws.amazon.com/AmazonS3/latest/dev/mpListPartsJavaAPI.html)。

S3 流式上传具有以下优点：

* 更快的上传时间：文件可以直接从客户端流式上传到 S3，而无需先发送到 Alluxio 工作节点。
* 减少本地存储需求：无需在临时目录中存储整个文件，减少了对本地磁盘空间的需求。
* 更快的 `close()` 方法：`close()` 方法执行时间大大缩短，因为文件的上传在写入过程中已经完成。

如果 S3 流式上传被中断，可能会有中间分段上传到 S3，并且 S3 将为这些数据收费。\
为了减少费用，用户可以修改 `conf/alluxio-site.properties` 文件，添加以下内容：

```properties
alluxio.underfs.cleanup.enabled=true
```

所有非只读 S3 挂载点中超过clean age（由 `alluxio.underfs.s3.intermediate.upload.clean.age` 配置）\
的中间分段上传文件将在达到清理时间间隔（由 `alluxio.underfs.cleanup.interval` 配置）时被清理。

### \[实验性] S3 多部分上传

默认的上传方法一次性从头到尾上传一个完整的文件。\
我们使用分段上传方法将一个文件分成多个部分进行上传，每个部分将通过一个线程上传。上传过程中不会生成任何临时文件。*它会消耗更多内存，但比流式上传模式更快。*

要启用 S3 分段上传，您需要修改 `conf/alluxio-site.properties` 文件，添加以下内容：

```properties
alluxio.underfs.s3.multipart.upload.enabled=true
```

可以在 `conf/alluxio-site.properties` 文件中指定其他参数，让上传过程更快更好。

```properties
# Timeout for uploading part when using multipart upload.
alluxio.underfs.object.store.multipart.upload.timeout
```

```properties
# Multipart upload partition size for S3. The default partition size is `16MB`
alluxio.underfs.s3.multipart.upload.partition.size
```

### 设置请求重试策略

有时可能会因为服务器暂时无法响应，而在访问UFS时出现错误。您可以为UFS请求配置一个重试政策。

每一个发往UnderFS的请求，例如getObjectData、getObjectStatus、listObjects, Alluxio 都会检查响应结果。\
如果响应是一个错误，并且错误代码表明可以重试，则将根据配置中的重试策略重新提交请求。Alluxio 将会持续重试直到请求成功或者达到了重试的最大次数。\
连续的成功重试间的等待间隔时间将会逐步从配置的基础休眠时间增长到最大休眠时间。

以下错误码被归类为可重试错误码：`500 HTTP_INTERNAL_ERROR`，`502 HTTP_BAD_GATEWAY`，`503 HTTP_UNAVAILABLE`，`503 Slow Down`，和 `504 HTTP_GATEWAY_TIMEOUT`。

**注意：**

* 4xx 错误码 通常代表客户端错误，比如：NOT\_FOUND，PERMISSION\_DENIED，UNAUTHENTICATED等等。此类错误不应重试，因为问题出在客户端。
* 5xx 错误码 通常代表服务端错误，但是不是所有的 5xx 错误都需要被重试。比如 `501 HTTP_NOT_IMPLEMENTED` 不应该被重试。

如果你想设置 UFS 访问请求的重试策略，您需要修改 `conf/alluxio-site.properties` 文件，添加以下内容：

```properties
# 一个 UnderFS 访问请求的最大重试次数。
alluxio.underfs.business.retry.max.num=10

# 第一次失败后两次尝试之间的初始睡眠时间
alluxio.underfs.business.retry.base.sleep=30ms

# 两次尝试之间的最大睡眠时间
alluxio.underfs.business.retry.max.sleep=30s
```

### 高并发调优

如果在每个 Alluxio server上使用大量client访问 S3，则可以下面调整这些参数，以开启Alluxio 使用针对 S3 的特定后端优化。

如果 S3 连接较慢，则可以尝试设置较大的超时时间：

```properties
alluxio.underfs.s3.socket.timeout=500sec
alluxio.underfs.s3.request.timeout=5min
```

如果需要大量并发元数据操作：

```properties
alluxio.underfs.s3.admin.threads.max=80
```

如果元数据+数据操作的总数很大：

```properties
alluxio.underfs.s3.threads.max=160
```

在worker端，这个参数的含义是worker到S3并发写的最大数量；在coordinator端，这个参数的含义是在一个文件夹中并发rename 文件的最大线程数。

```properties
alluxio.underfs.s3.upload.threads.max=80
```

这个参数的含义是在coordinator端提交delete和rename操作的线程池的大小。

```properties
alluxio.underfs.object.store.service.threads=80
```

### 用空占位文件优化元数据列举

与传统文件系统不同，许多对象存储没有目录这一基础概念。\
一些通用文件系统操作（例如检查目录是否存在，检索目录元数据或遍历目录）必须转化成基于前缀的对象列举操作来完成。

当一个目录包含大量对象并且缺少目录占位符对象时（大小为0并以前缀作为目录名称的对象称为为 "breadcrumbs" 对象），\
这些操作可能会变得更加昂贵。为了减轻此开销，通常引入占位符对象以明确表示目录，来改善与目录相关的元数据操作的性能。

Alluxio提供了一个选项通过使用 "breadcrumb" 对象来减轻这些开销， "breadcrumb" 对象是用来代表目录的轻量级占位符。\
默认情况下Alluxio会主动在UFS中创建 breadcrumb 对象以表示目录。要关闭这项功能，需要增加如下配置：

```properties
alluxio.underfs.object.store.breadcrumbs.enabled=false
```

Breadcrumb 对象有一下优点：

* 性能的提高：一旦创建了 breadcrumbs ，就可以有效地访问目录元数据，从而避免重复进行全量列举。
* 缓存协同作用：与元数据缓存和预加载策略一起优化寒冷启动表现。

Breadcrumb 有以下几点需要考虑：

* 需要写入权限：启用此功能需要写入对象存储的写权限。
* 潜在的数据影响：担心数据纯度或对象存储被外部修改的用户可能更希望禁用它。
* 冷加载开销：创建 breadcrumb 对象时，初始目录加载时可能会产生一个一次性的写入成本。

推荐建议:

* 如果 UFS 允许写入，并且您正在优化性能（尤其是在列表性能较差的对象存储中），建议启用。
* 如果 UFS 是只读的，或者修改 UFS 的数据由于合规或审计原因是不可接受的，建议关闭

## S3 Objects的身份验证和访问控制

[S3 的身份验证和访问管理](https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-access-control.html)\
与传统的 POSIX 权限模型非常不同。\
例如，S3 ACL 不支持组或目录级别的设置。\
Alluxio 尽最大努力从 S3 ACL 信息中继承权限信息，包括文件所有者、组和权限模式（permission mode）。

### 为什么返回403 Access Denied Error

在 Alluxio 配置中设置的 S3 credentials 对应于一个 AWS 用户。\
如果此用户没有访问 S3 bucket或object所需的权限，则将返回 403 permission denied错误。

如果在访问 S3 服务时在 Alluxio 服务器日志中看到 403 错误，请务必检查：

1. 您是否使用了正确的 AWS credentials。请参阅 [凭证设置](#高级凭证设置).
2. 您的 AWS 用户是否具有访问挂载到 Alluxio 的存储桶和对象的权限。

阅读更多关于 AWS 403 错误的[故障排除指南](https://aws.amazon.com/premiumsupport/knowledge-center/s3-troubleshoot-403/)。

### 文件所有者和组

Alluxio 文件系统根据在 Alluxio 中配置的用于连接到 S3 的 AWS 账户设置文件所有者。\
由于 S3 ACL 中没有组，因此所有者被设为文件的group。

默认情况下，Alluxio 提取此 AWS 账户的显示名称作为文件所有者。\
如果此显示名称不可用，则将使用此 AWS 用户的[规范用户 ID](https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html)。\
此规范用户 ID 通常是一个长字符串（例如 `79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be`），\
因此在实践中往往不方便阅读和使用。\
可选地，属性 `alluxio.underfs.s3.owner.id.to.username.mapping` 可用于指定从规范用户 ID 到 Alluxio 用户名的预设映射，格式为 "id1=user1;id2=user2"。\
例如，编辑 `alluxio-site.properties` 使之包含如下内容：

```properties
alluxio.underfs.s3.owner.id.to.username.mapping=\
79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be=john
```

这个配置帮助 Alluxio 将所有由此 AWS 账户拥有的对象识别为 Alluxio 命名空间中的用户 `john` 拥有。\
要找出您账户的 AWS S3 规范用户 ID，可以查看控制台 `https://console.aws.amazon.com/iam/home?#/security_credentials`，\
展开 "Account Identifiers" 选项卡，然后参考 "规范用户 ID"。

### 修改权限

Alluxio目录和文件的 `chown`, `chgrp`, 和 `chmod` 不会被传递到底层的S3 buckets或者objects。

## 问题排查

### 启用 AWS-SDK 调试级别

如果在使用 S3 后端时遇到问题，可以启用额外的日志记录来跟踪 HTTP 流量。\
修改 `conf/log4j2.xml` 文件，添加以下属性：

```properties
<Logger name="software.amazon.awssdk" level="warn" />
<Logger name="software.amazon.awssdk.request" level="debug" />
<Logger name="org.apache.http.wire" level="debug" />
```

查阅[Amazon文档](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/logging-slf4j.html)查看详细情况。

如果正在使用 AWS SDK V1，请修改 `conf/log4j2.xml` 文件，添加以下属性：

```properties
<Logger name="com.amazonaws" level="warn"/>
<Logger name="com.amazonaws.request" level="debug"/>
<Logger name="org.apache.http.wire" level="debug"/>
```

查阅[Amazon文档](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-logging.html)查看详细情况。
