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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容