Amazon AWS 中國區的那些"坑"

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章