• -------------------------------------------------------------
  • ====================================

Amazon AWS 中国区的那些”坑”

生活 dewbay 5年前 (2019-04-12) 3173次浏览 已收录 0个评论 扫描二维码

使用AWS 中国区有一段时间了, 期间踩过了一些坑. 简单写一下, 希望对别人有帮助.
** 文中一些主观猜测或者AWS 后续升级, 如有误导, 敬请见谅.

Amazon S3

所有坑中, 最数 S3 坑多. 原因很简单: EC2 的服务大不了大家在 web console 里面点击鼠标, S3 更多时候肯定是用 SDK 访问. 因此 SDK 的各种问题都会提前暴露.

hadoop over S3

问题: 去年 12 月份左右(具体 jets3t 什么时候 fix 的这个问题不记得了), hadoop 中使用的 library jets3t 不支持中国区(cn-north-1) , 原因很简单: S3 的 signature 已经升级到 V4. 但是因为兼容问题, AWS 的其他 region 都兼容 V2 版本, 中国区是新的 region, 没有兼容问题, 因此仅仅支持 V4. 详情参见 jets3t 的这个 issue

折腾了各种解决办法, 流水账的形式写一下吧.

第一个法子: copy EMR 集群中的 emrfs

emrfs 就是 EMR 集群中 hadoop 使用的访问 S3 的方式. 是 Amazon
官方提供的, 不开源. 使用的法子也很简单: 启动一个 emr 集群, 随便登陆一台服务器, 在 hadoop-env.sh 中可以看到添加了emrfs 的 classpath:

#!/bin/bash

export HADOOP_CLIENT_OPTS="$HADOOP_CLIENT_OPTS -XX:MaxPermSize=128m"
export HADOOP_CLASSPATH="$HADOOP_CLASSPATH:/usr/share/aws/emr/emrfs/lib/*:/usr/share/aws/emr/lib/*"
export HADOOP_DATANODE_HEAPSIZE="384"
export HADOOP_NAMENODE_HEAPSIZE="768"
export HADOOP_OPTS="$HADOOP_OPTS -server"
if [ -e /home/hadoop/conf/hadoop-user-env.sh ] ; then
  . /home/hadoop/conf/hadoop-user-env.sh
fi

注意: EMR 可能会发布新的版本, 这里仅仅是提供一个思路, 列出的文件也是当时版本的 emr 的实现

/usr/share/aws/emr/emrfs 下面的所有文件 copy 出来, 部署到自己的集群并在 core-sites.xml 中添加如下内容:

  <property><name>fs.s3n.impl</name><value>com.amazon.ws.emr.hadoop.fs.EmrFileSystem</value></property>
  <property><name>fs.s3.impl</name><value>com.amazon.ws.emr.hadoop.fs.EmrFileSystem</value></property>
  <property><name>fs.s3.buffer.dir</name><value>/mnt/var/lib/hadoop/s3,/mnt1/var/lib/hadoop/s3</value></property>
  <property><name>fs.s3.buckets.create.region</name><value>cn-north-1</value></property>
  <property><name>fs.s3bfs.impl</name><value>org.apache.hadoop.fs.s3.S3FileSystem</value></property>
  <property><name>fs.s3n.endpoint</name><value>s3.cn-north-1.amazonaws.com.cn</value></property>

设置 EMRFS_HOME 并且把 $EMRFS_HOME/bin 添加到 PATH 中(后面会用到)

这样可以保证 hadoop 尽快运行起来. 但使用 emrfs 也有一些问题:

  1. 没有源代码. 官方没有计划将这个东西开源. 因此除了问题只有反编译 jar 包. 还好官方编译的 jar 包没有混淆并且带着 lineNumber 等信息. 曾经遇到他代码里面吃掉异常的情况, 不知道现在是否更新
  2. S3 rename 操作非常耗时. 众所周知 Hadoop Mapreduce 为了保证一致性, 结果文件都是先写临时文件, 最后 rename 成最终输出文件. 在 HDFS 上这种模式没有问题, 但是 S3 就会导致最后 commit job 时非常慢, 因此默认的 committer 是单线程 rename 文件. 结果文件大并且多文件的情况下 S3 非常慢. 因此 emrfs 做了一个 hack: 结果仅仅写本地文件, 到 commit 的时候再一次性上传结果文件. 但如果你输出的一个结果文件太大会导致本地磁盘写满! 不知道哪里是否有参数配置一下这个最大值.
  3. S3 由于不是 FileSystem, 仅仅是一个 KV 存储. 因此在 list dir 时会很慢, emrfs 为了优化, 用 dynamodb 做了一层索引.但在某些情况下(我们遇到过)mr job fail 会导致索引和 S3 数据不一致. 极端情况下需要使用 emrfs sync path 来同步索引

暂时记得的关于 emrfs 就有这么多.

第二个法子: hadoop-s3a

An AWS SDK-backed FileSystem driver for Hadoop

这是 github 上有人用 AWS-java-SDK 开发的一个 FileSystem 实现, 虽说是试验情况下, 修改一下还是可以用的. >;<
但是, 这个直接用也是不行的!~~~

坑如下:

  • 中国区 Amazon S3 Java SDK 有一个神坑: 如果不显示设置 region 的 endpoint , 会一直返回 Invalid Request(原因后面解释), 需要在代码中添加如下几行:
// 这里获取配置文件中的 region name 的设置
//  如果获取不到, 强烈建议获取当前系统所在 region
AmazonS3Client s3 = new AmazonS3Client(credentials, awsConf);
String regionName = XXXX;
Region region = Region.getRegion(Regions.fromName(regionName));
s3.setRegion(region);
final String serviceEndpoint = region.getServiceEndpoint(ServiceAbbreviations.S3);

// 关键是下面这一行, 在除了中国外的其他 region, 这行代码不用写
s3.setEndpoint(serviceEndpoint);
LOG.info("setting s3 region: " + region + ", : " + serviceEndpoint);
  • S3 rename 操作慢!
    • 需要在 hadoop-s3a 中需要修改 rename 方法的代码, 使用线程池并行 rename 一个 dir.
    • 需要写一个 committer, 在 MR job 完成的时候调用并行 rename 操作.
  • hadoop-s3a 没有设置 connect timeout. 仅仅设置了 socket timetout
  • block size 计算错误.
    需要在社区版本上添加一个 block size 的配置项(跟 hdfs 类似), 并且在所有创建 S3AFileStatus 的地方添加 blockSize 参数. 现在情况下会导致计算 InputSplit 错误(blocksize 默认是 0).
  • 权限管理
    通常情况下, hadoop 集群使用 IAM role 方式获取 accessKey 访问 S3, 这样会导致之前在 hdfs 中基于用户的权限管理失效. 比如, 用户 A 是对一些 Table 有读写权限, 但是用户 B 只有只读权限. 但 EC2 不支持一个 instance 挂载两个不同的 IAM role. 我们的解决方案是在 S3FileSystem 中判断当前的用户, 根据不同的用户使用不同的 AccessKey, 实现 HDFS 的权限管理.

S3 api/client

使用 S3 api 或者 aws client, 还有一个容易误导的坑:

你有可能在 cn-north-1 的 region 访问到AWS 美国的 S3 !

现象: 比如你按照 doc 配置好了 aws client(access key 和 secret 都配置好), 简单执行 aws --debug s3 ls s3://your-bucket/ 确返回如下错误:

2015-08-06 20:54:25,622 - botocore.endpoint - DEBUG - Sending http request: <PreparedRequest [GET]>
2015-08-06 20:54:27,770 - botocore.response - DEBUG - Response Body:
b'<?xml version="1.0" encoding="UTF-8"?>\n<Error><Code>InvalidAccessKeyId</Code><Message>The AWS Access Key Id you provided does not exist in our records.</Message><AWSAccessKeyId>AAABBBBAAAAAA</AWSAccessKeyId><RequestId>111B1ABCFEA8D30A</RequestId><HostId>fPehbRNkUmZyI6/O3kL7s+J0zCLYo/8U6UE+Hv7PSBFiA6cB6CuLXoCT4pvyiO7l</HostId></Error>'
2015-08-06 20:54:27,783 - botocore.hooks - DEBUG - Event needs-retry.s3.ListObjects: calling handler <botocore.retryhandler.RetryHandler object at 0x7f3f8aefd940>
2015-08-06 20:54:27,783 - botocore.retryhandler - DEBUG - No retry needed.
2015-08-06 20:54:27,784 - botocore.hooks - DEBUG - Event after-call.s3.ListObjects: calling handler <awscli.errorhandler.ErrorHandler object at 0x7f3f8b2d4828>
2015-08-06 20:54:27,784 - awscli.errorhandler - DEBUG - HTTP Response Code: 403
2015-08-06 20:54:27,784 - awscli.clidriver - DEBUG - Exception caught in main()
Traceback (most recent call last):
  File "/usr/share/awscli/awscli/clidriver.py", line 187, in main
    return command_table[parsed_args.command](remaining, parsed_args)
  File "/usr/share/awscli/awscli/customizations/s3/s3.py", line 165, in __call__
    remaining, parsed_globals)
  File "/usr/share/awscli/awscli/customizations/s3/s3.py", line 276, in __call__
    return self._do_command(parsed_args, parsed_globals)
  File "/usr/share/awscli/awscli/customizations/s3/s3.py", line 358, in _do_command
    self._list_all_objects(bucket, key)
  File "/usr/share/awscli/awscli/customizations/s3/s3.py", line 365, in _list_all_objects
    for _, response_data in iterator:
  File "/usr/lib/python3/dist-packages/botocore/paginate.py", line 147, in __iter__
    **current_kwargs)
  File "/usr/lib/python3/dist-packages/botocore/operation.py", line 82, in call
    parsed=response[1])
  File "/usr/lib/python3/dist-packages/botocore/session.py", line 551, in emit
    return self._events.emit(event_name, **kwargs)
  File "/usr/lib/python3/dist-packages/botocore/hooks.py", line 158, in emit
    response = handler(**kwargs)
  File "/usr/share/awscli/awscli/errorhandler.py", line 75, in __call__
    http_status_code=http_response.status_code)
awscli.errorhandler.ClientError: A client error (InvalidAccessKeyId) occurred when calling the ListObjects operation: The AWS Access Key Id you provided does not exist in our records.
2015-08-06 20:54:27,877 - awscli.clidriver - DEBUG - Exiting with rc 255
A client error (InvalidAccessKeyId) occurred when calling the ListObjects operation: The AWS Access Key Id you provided does not exist in our records.

上面的错误信息非常有误导性的一句话是:

A client error (InvalidAccessKeyId) occurred when calling the ListObjects operation: The AWS Access Key Id you provided does not exist in our records.

然后你打电话给 support(记住一定要提供 request id), 那边给的答复是你本机的时间不对

WTF! 服务器肯定开启了 NTP, 怎么会时间不对!
其实你使用 aws s3 --region cn-north-1 ls s3://your-bucket 就不会出错. 或者在 ~/.aws/config 中 配置:

[default]
region = cn-north-1

但是:

  • support 为什么会说我的时间不对?
  • 为什么 aws client 报错是 The AWS Access Key Id you provided does not exist in our records
  • 因为你的请求到了 AWS 的美国区(或者准确说是非 cn-north-1 区)!*
    简单猜测一下原因(纯猜测, 猜对了才奇怪!):

** 之前的猜测是错误的, S3 不会将数据存储到其他 region, 其实就是因为cn-north-1区是非常特殊的区. 而默认情况下 cli 访问的都是美国区. (我党万岁!) **

  • 默认情况下 aws s3 的 endpoint url 是其他 region. 因此那个 ls 操作直接请求了非 cn-north-1 region.
  • 但是 aws cn-north-1 的账户系统跟其他 region 不通, 因此美国区返回错误: The AWS Access Key Id you provided does not exist in our records
  • support 之所以根据 request id 告诉你错误是因为请求时间不对, 也很简单: server 端验证了请求的发起时间, 由于时差, 导致时间肯定是非法的. 因此 support 告诉你说你的时间有问题

感觉客户端跟 support 告诉你的错误不一致是吧? 我当时就是因为他们的误导, 折腾了 2 天啊!!! 最后加一行代码解决了问题, 想死的❤️都有

因此结论很简单:

  • 使用 awscli 操作 S3 时, 记得带上 --region cn-north-1
  • 写代码访问 S3 时, 显示调用 setEndpoint 设置 api 地址
// 关键是下面这一行, 在除了中国外的其他 region, 这行代码不用写
s3.setEndpoint(serviceEndpoint);

S3 一个理解错误的坑

S3 是一个 KV 存储, 不存储在文件夹的概念. 比如你存储数据到 s3://yourbucket/a/b/c/d.txt, S3 仅仅是将s3://yourbucket/a/b/c/d.txt 作为 key, value 就是文件的内容. 如果你想 ls s3://yourbucket/a/b 是不存在的 key!

S3 定位错误的 tips

  1. 调试模式下, 可以考虑关闭 ssl, 并使用 tcpdump 抓包查看数据是否正确, 非常实用
  2. aws 客户端可以添加 --debug 开启调试日志, 出错后开 case 时最好带着 Request IDExtended Request ID . AWS 几乎所有服务的每次请求都是带有 Request ID 的, 非常便于定位问题. 至于为什么, 建议看 Google 早年的论文: Dapper, a Large-Scale Distributed Systems Tracing Infrastructure

聊完了 S3, 其他的基本上坑不多, 走过路过也记不得了. 但最深刻的一个关于 IAM 的要注意.

Amazon IAM 坑

啥是 IAM?

AWS Identity and Access Management (IAM) 使您能够安全地控制用户对 Amazon AWS 服务和资源的访问权限。您可以使用 IAM 创建和管理 AWS 用户和群组,并使用各种权限来允许或拒绝他们对 AWS 资源的访问。

唯一大坑: IAM policy file 中 arn 的写法

啥是 arn?

Amazon Resource Names (ARNs) uniquely identify AWS resources. We require an ARN when you need to specify a resource unambiguously across all of AWS, such as in IAM policies, Amazon Relational Database Service (Amazon RDS) tags, and API calls.
具体参加这里

简单来说, arn 就是 AWS 中资源的 uri. 任何 AWS 资源都可以用 arn 标识, 因此对于资源的访问控制配置文件也要使用 arn 来写.

arn 的格式如下:

arn:partition:service:region:account:resource
arn:partition:service:region:account:resourcetype/resource
arn:partition:service:region:account:resourcetype:resource

比如: 我们想开放某个 s3 bucket 的读写权限, 可以如下这种写法:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": ["arn:aws:s3:::your-bucket", "arn:aws:s3:::your-bucket/*"]
    }
 ]
}
  • 上面这行代码据说 在 AWS 其他 region 都可以使用
  • 唯独中国区不能用! 因为 AWS 中国区非常特殊, 上述文件中的 aws 要修改成 aws-cn !!!!
    这样写在中国区就可以用:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": ["arn:aws-cn:s3:::your-bucket", "arn:aws-cn:s3:::your-bucket/*"]
    }
 ]
}
  • 不要小看这一点小区别, 由于 AWS 其他 region 都是用 aws 就可以, 因此很多开源项目中, 将 arn:aws: XXXX hard code 在代码里, 导致这些项目用到中国区会失败!
  • BTW, 一个小坑: 上面的配置文件中的 "Version": "2012-10-17", 这个日期是必须写成这个的, 估计是 AWS 的码农 hard code 的版本, 不能修改其他任何值 , 千万别用这个值来作为自己的版本控制(偷笑)

建议程序访问 AWS 资源时, 使用 IAM role 的方式, 不要使用配置文件中写入 AccessKey/Secret 的方式, 非常不安全.

EC2

EC2 就是虚拟主机. AWS 有两个概念: Reserved InstanceSpot Instance

Reserved Instance

简单来说就是包年购买节点. 优点肯定是便宜. 缺点就是买了就不能退货. 但这里最坑(不容易理解)的是:

  • 购买 N 个 T 类型的 RI 后, 其实仅仅是在 RI 有效期限内计费的时候, 该类型的节点中的 N 个 T 类型节点按照打折价格计费.
  • 即使你在 RI 期限内没有使用任何该类型的 EC2 节点, 费用照常收取, RI 过期后价格恢复原价
  • 其他节点已久按照正常价格按小时收费

RI 仅仅是计费单元, 节点销毁后重新启动, 只要不超过 RI 数量, 都按照打折计费

例如: 购买了 3 个 t2.micro 类型的 RI, 但是你再次期间最多同时开启了 5 个 t2.micro 节点, 那么这其中的 3 个是按照打折价格计费, 2 个节点按照正常价格. 如果发现三台 t2.micro 配置错误, 直接 terminate 后启动新的 instance , 依旧是按照 RI 的价格计费

Spot Instance

这个就是可以以非常便宜的价格买到 EC2 节点. 不过迄今未知(2015-08-07) 中国区没有该项业务.

今天太晚了, 回家睡觉去了. 有时间继续写.
再次重申一下, AWS 是在升级的, 这些问题我肯定是遇到过, 但对于原因很多都是猜测, 毕竟 AWS 是个非常复杂的系统, 也不开源, 内部如何实现我也无从得知.

–EOF–

作者:haitaoyao
链接:https://www.jianshu.com/p/0d0fd39a40c9
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。


露水湾 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Amazon AWS 中国区的那些”坑”
喜欢 (0)
[]
分享 (0)
关于作者:
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址