Amazon AWS 中國區(qū)的那些"坑"

使用AWS 中國區(qū)有一段時(shí)間了, 期間踩過了一些坑. 簡單寫一下, 希望對別人有幫助.
** 文中一些主觀猜測或者AWS 后續(xù)升級, 如有誤導(dǎo), 敬請見諒.

Amazon S3

所有坑中, 最數(shù) S3 坑多. 原因很簡單: EC2的服務(wù)大不了大家在web console 里面點(diǎn)擊鼠標(biāo), S3 更多時(shí)候肯定是用SDK訪問. 因此SDK的各種問題都會提前暴露.

hadoop over S3

問題: 去年12月份左右(具體jets3t 什么時(shí)候fix的這個問題不記得了), hadoop 中使用的library jets3t 不支持中國區(qū)(cn-north-1) , 原因很簡單: S3 的signature 已經(jīng)升級到V4. 但是因?yàn)榧嫒輪栴}, AWS的其他region都兼容V2版本, 中國區(qū)是新的region, 沒有兼容問題, 因此僅僅支持V4. 詳情參見 jets3t 的這個issue

折騰了各種解決辦法, 流水賬的形式寫一下吧.

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

emrfs 就是 EMR 集群中hadoop使用的訪問S3 的方式. 是 Amazon
官方提供的, 不開源. 使用的法子也很簡單: 啟動一個emr 集群, 隨便登陸一臺服務(wù)器, 在 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 可能會發(fā)布新的版本, 這里僅僅是提供一個思路, 列出的文件也是當(dāng)時(shí)版本的emr的實(shí)現(xiàn)

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

  <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>

設(shè)置 EMRFS_HOME 并且把 $EMRFS_HOME/bin 添加到PATH中(后面會用到)

這樣可以保證hadoop 盡快運(yùn)行起來. 但使用 emrfs 也有一些問題:

  1. 沒有源代碼. 官方?jīng)]有計(jì)劃將這個東西開源. 因此除了問題只有反編譯jar包. 還好官方編譯的jar包沒有混淆并且?guī)е?lineNumber 等信息. 曾經(jīng)遇到他代碼里面吃掉異常的情況, 不知道現(xiàn)在是否更新
  2. S3 rename 操作非常耗時(shí). 眾所周知Hadoop Mapreduce 為了保證一致性, 結(jié)果文件都是先寫臨時(shí)文件, 最后 rename 成最終輸出文件. 在 HDFS 上這種模式?jīng)]有問題, 但是 S3 就會導(dǎo)致最后 commit job 時(shí)非常慢, 因此默認(rèn)的committer 是單線程rename文件. 結(jié)果文件大并且多文件的情況下S3 非常慢. 因此 emrfs 做了一個hack: 結(jié)果僅僅寫本地文件, 到 commit 的時(shí)候再一次性上傳結(jié)果文件. 但如果你輸出的一個結(jié)果文件太大會導(dǎo)致本地磁盤寫滿! 不知道哪里是否有參數(shù)配置一下這個最大值.
  3. S3 由于不是FileSystem, 僅僅是一個KV存儲. 因此在list dir 時(shí)會很慢, emrfs 為了優(yōu)化, 用dynamodb做了一層索引.但在某些情況下(我們遇到過)mr job fail 會導(dǎo)致索引和 S3 數(shù)據(jù)不一致. 極端情況下需要使用 emrfs sync path 來同步索引

暫時(shí)記得的關(guān)于 emrfs 就有這么多.

第二個法子: hadoop-s3a

An AWS SDK-backed FileSystem driver for Hadoop

這是github上有人用 AWS-java-SDK 開發(fā)的一個 FileSystem 實(shí)現(xiàn), 雖說是試驗(yàn)情況下, 修改一下還是可以用的. >;<
但是, 這個直接用也是不行的!~~~

坑如下:

  • 中國區(qū) Amazon S3 Java SDK 有一個神坑: 如果不顯示設(shè)置region的 endpoint , 會一直返回 Invalid Request(原因后面解釋), 需要在代碼中添加如下幾行:
// 這里獲取配置文件中的region name的設(shè)置
//  如果獲取不到, 強(qiáng)烈建議獲取當(dāng)前系統(tǒng)所在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);

// 關(guān)鍵是下面這一行, 在除了中國外的其他region, 這行代碼不用寫
s3.setEndpoint(serviceEndpoint);
LOG.info("setting s3 region: " + region + ", : " + serviceEndpoint);
  • S3 rename 操作慢!

    • 需要在 hadoop-s3a 中需要修改rename 方法的代碼, 使用線程池并行rename 一個dir.
    • 需要寫一個 committer, 在MR job 完成的時(shí)候調(diào)用并行rename操作.
  • hadoop-s3a 沒有設(shè)置 connect timeout. 僅僅設(shè)置了socket timetout

  • block size計(jì)算錯誤.
    需要在社區(qū)版本上添加一個 block size 的配置項(xiàng)(跟 hdfs 類似), 并且在所有創(chuàng)建 S3AFileStatus 的地方添加 blockSize 參數(shù). 現(xiàn)在情況下會導(dǎo)致計(jì)算 InputSplit 錯誤(blocksize默認(rèn)是0).

  • 權(quán)限管理
    通常情況下, hadoop 集群使用IAM role 方式獲取accessKey 訪問S3, 這樣會導(dǎo)致之前在 hdfs 中基于用戶的權(quán)限管理失效. 比如, 用戶A 是對一些Table 有讀寫權(quán)限, 但是用戶B 只有只讀權(quán)限. 但EC2 不支持一個instance 掛載兩個不同的 IAM role. 我們的解決方案是在S3FileSystem中判斷當(dāng)前的用戶, 根據(jù)不同的用戶使用不同的AccessKey, 實(shí)現(xiàn)HDFS的權(quán)限管理.

S3 api/client

使用S3 api 或者aws client, 還有一個容易誤導(dǎo)的坑:

你有可能在 cn-north-1 的region 訪問到AWS 美國的S3 !

現(xiàn)象: 比如你按照doc 配置好了aws client(access key 和secret都配置好), 簡單執(zhí)行 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.

上面的錯誤信息非常有誤導(dǎo)性的一句話是:

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), 那邊給的答復(fù)是你本機(jī)的時(shí)間不對

WTF! 服務(wù)器肯定開啟了NTP, 怎么會時(shí)間不對!
其實(shí)你使用 aws s3 --region cn-north-1 ls s3://your-bucket 就不會出錯. 或者在 ~/.aws/config 中 配置:

[default]
region = cn-north-1

但是:

  • support為什么會說我的時(shí)間不對?

  • 為什么 aws client 報(bào)錯是 The AWS Access Key Id you provided does not exist in our records

  • 因?yàn)槟愕恼埱蟮搅薃WS 的美國區(qū)(或者準(zhǔn)確說是非cn-north-1區(qū))!*
    簡單猜測一下原因(純猜測, 猜對了才奇怪!):

** 之前的猜測是錯誤的, S3 不會將數(shù)據(jù)存儲到其他region, 其實(shí)就是因?yàn)?code>cn-north-1區(qū)是非常特殊的區(qū). 而默認(rèn)情況下cli 訪問的都是美國區(qū). (我黨萬歲!) **

  • 默認(rèn)情況下aws s3 的endpoint url 是其他region. 因此那個ls 操作直接請求了非cn-north-1 region.
  • 但是aws cn-north-1 的賬戶系統(tǒng)跟其他region不通, 因此美國區(qū)返回錯誤: The AWS Access Key Id you provided does not exist in our records
  • support 之所以根據(jù)request id 告訴你錯誤是因?yàn)檎埱髸r(shí)間不對, 也很簡單: server端驗(yàn)證了請求的發(fā)起時(shí)間, 由于時(shí)差, 導(dǎo)致時(shí)間肯定是非法的. 因此support 告訴你說你的時(shí)間有問題

感覺客戶端跟support告訴你的錯誤不一致是吧? 我當(dāng)時(shí)就是因?yàn)樗麄兊恼`導(dǎo), 折騰了2天啊!!! 最后加一行代碼解決了問題, 想死的??都有

因此結(jié)論很簡單:

  • 使用awscli 操作 S3 時(shí), 記得帶上 --region cn-north-1
  • 寫代碼訪問S3 時(shí), 顯示調(diào)用 setEndpoint 設(shè)置api地址
// 關(guān)鍵是下面這一行, 在除了中國外的其他region, 這行代碼不用寫
s3.setEndpoint(serviceEndpoint);

S3 一個理解錯誤的坑

S3 是一個KV 存儲, 不存儲在文件夾的概念. 比如你存儲數(shù)據(jù)到 s3://yourbucket/a/b/c/d.txt, S3 僅僅是將s3://yourbucket/a/b/c/d.txt 作為key, value就是文件的內(nèi)容. 如果你想ls s3://yourbucket/a/b 是不存在的key!

S3 定位錯誤的tips

  1. 調(diào)試模式下, 可以考慮關(guān)閉ssl, 并使用 tcpdump 抓包查看數(shù)據(jù)是否正確, 非常實(shí)用
  2. aws 客戶端可以添加 --debug 開啟調(diào)試日志, 出錯后開case時(shí)最好帶著 Request IDExtended Request ID . AWS 幾乎所有服務(wù)的每次請求都是帶有 Request ID 的, 非常便于定位問題. 至于為什么, 建議看Google早年的論文: Dapper, a Large-Scale Distributed Systems Tracing Infrastructure

聊完了 S3, 其他的基本上坑不多, 走過路過也記不得了. 但最深刻的一個關(guān)于 IAM 的要注意.

Amazon IAM 坑

啥是IAM?

AWS Identity and Access Management (IAM) 使您能夠安全地控制用戶對 Amazon AWS 服務(wù)和資源的訪問權(quán)限。您可以使用 IAM 創(chuàng)建和管理 AWS 用戶和群組,并使用各種權(quán)限來允許或拒絕他們對 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 標(biāo)識, 因此對于資源的訪問控制配置文件也要使用 arn 來寫.

arn 的格式如下:

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

比如: 我們想開放某個s3 bucket的讀寫權(quán)限, 可以如下這種寫法:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": ["arn:aws:s3:::your-bucket", "arn:aws:s3:::your-bucket/*"]
    }
 ]
}
  • 上面這行代碼據(jù)說 在AWS 其他region 都可以使用
  • 唯獨(dú)中國區(qū)不能用! 因?yàn)锳WS 中國區(qū)非常特殊, 上述文件中的 aws 要修改成 aws-cn !!!!
    這樣寫在中國區(qū)就可以用:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": ["arn:aws-cn:s3:::your-bucket", "arn:aws-cn:s3:::your-bucket/*"]
    }
 ]
}
  • 不要小看這一點(diǎn)小區(qū)別, 由于AWS 其他region 都是用 aws 就可以, 因此很多開源項(xiàng)目中, 將 arn:aws: XXXX hard code 在代碼里, 導(dǎo)致這些項(xiàng)目用到中國區(qū)會失敗!

  • BTW, 一個小坑: 上面的配置文件中的 "Version": "2012-10-17", 這個日期是必須寫成這個的, 估計(jì)是AWS 的碼農(nóng) hard code 的版本, 不能修改其他任何值 , 千萬別用這個值來作為自己的版本控制(偷笑)

建議程序訪問AWS 資源時(shí), 使用IAM role的方式, 不要使用配置文件中寫入AccessKey/Secret 的方式, 非常不安全.

EC2

EC2 就是虛擬主機(jī). AWS 有兩個概念: Reserved InstanceSpot Instance

Reserved Instance

簡單來說就是包年購買節(jié)點(diǎn). 優(yōu)點(diǎn)肯定是便宜. 缺點(diǎn)就是買了就不能退貨. 但這里最坑(不容易理解)的是:

  • 購買N個T類型的RI后, 其實(shí)僅僅是在RI有效期限內(nèi)計(jì)費(fèi)的時(shí)候, 該類型的節(jié)點(diǎn)中的N 個 T 類型節(jié)點(diǎn)按照打折價(jià)格計(jì)費(fèi).
  • 即使你在RI 期限內(nèi)沒有使用任何該類型的 EC2 節(jié)點(diǎn), 費(fèi)用照常收取, RI 過期后價(jià)格恢復(fù)原價(jià)
  • 其他節(jié)點(diǎn)已久按照正常價(jià)格按小時(shí)收費(fèi)

RI 僅僅是計(jì)費(fèi)單元, 節(jié)點(diǎn)銷毀后重新啟動, 只要不超過 RI 數(shù)量, 都按照打折計(jì)費(fèi)

例如: 購買了3個 t2.micro 類型的RI, 但是你再次期間最多同時(shí)開啟了5個 t2.micro 節(jié)點(diǎn), 那么這其中的3個是按照打折價(jià)格計(jì)費(fèi), 2個節(jié)點(diǎn)按照正常價(jià)格. 如果發(fā)現(xiàn)三臺 t2.micro 配置錯誤, 直接 terminate 后啟動新的instance , 依舊是按照 RI 的價(jià)格計(jì)費(fèi)

Spot Instance

這個就是可以以非常便宜的價(jià)格買到 EC2 節(jié)點(diǎn). 不過迄今未知(2015-08-07) 中國區(qū)沒有該項(xiàng)業(yè)務(wù).

今天太晚了, 回家睡覺去了. 有時(shí)間繼續(xù)寫.
再次重申一下, AWS 是在升級的, 這些問題我肯定是遇到過, 但對于原因很多都是猜測, 畢竟AWS 是個非常復(fù)雜的系統(tǒng), 也不開源, 內(nèi)部如何實(shí)現(xiàn)我也無從得知.

--EOF--

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容