誰向けか
まだ仕方なくAWS SDK for Ruby V1を使っていて、かつクレデンシャルを環境変数でなくEC2のインスタンスメタ情報から取得している人。あんまりいないかもしれん。
なにが起こるか
クレデンシャルのリフレッシュ時に、AWSの障害などで一度でもメタ情報の取得に失敗すると、二度とリカバーしない。結果こんなエラーが出続けることになる。
AWS::Errors::MissingCredentialsError (
Missing Credentials.
なぜそうなるか
AWS SDK for Ruby V1の実装があんまよくない。
credential_providers.rb
module AWS
module Core
module CredentialProviders
module Provider
def credentials
raise Errors::MissingCredentialsError unless set?
@cached_credentials.dup
end
def set?
@cache_mutex ||= Mutex.new
unless @cached_credentials
@cache_mutex.synchronize do
@cached_credentials ||= get_credentials
end
end
!!(@cached_credentials[:access_key_id] &&
@cached_credentials[:secret_access_key])
end
...
class DefaultProvider
def credentials
providers.each do |provider|
if provider.set?
return provider.credentials
end
end
raise Errors::MissingCredentialsError
end
...
リフレッシュ時に取得に失敗すると、@cached_credentialsに空のハッシュが代入されるので、以後get_credentialsが呼ばれることはない。
どうするのがよいのか
AWS SDK for Ruby V2にUpgradeする
これが最もよい。V2の実装では、一度リフレッシュに失敗しても次回の実行時にリカバーされるようになる。
EC2Providerのオプションにリトライをセットする
リトライをセットすることで、リフレッシュが失敗する確率を下げることができる。が100%ではない。
SDKにパッチを当てる
すぐにSDKをアップグレードできない事情がある場合には、短期的にSDKにパッチを当てることで回避できる。
monkey_patches/credential_providers.rb
module AWS
module Core
module CredentialProviders
# The original CredentialProvider has a serious bug that keeps credentials empty
# when retrieving the ec2 meta infomation was failed on refreshing.
class EC2Provider
def initialize options = {}
@mutex = Mutex.new
@ip_address = options[:ip_address] || '169.254.169.254'
@port = options[:port] || 80
@retries = options[:retries] || 5
@http_open_timeout = options[:http_open_timeout] || 1
@http_read_timeout = options[:http_read_timeout] || 1
@http_debug_output = options[:http_debug_output]
end
def credentials
if near_expiration?
@mutex.synchronize do
refresh_credentials if near_expiration?
end
end
super
end
def set?
unless @cached_credentials
@mutex.synchronize do
refresh_credentials
@available_provider = credentials_set?
end
end
if @available_provider && !credentials_set?
@mutex.synchronize do
refresh_credentials
end
end
credentials_set?
end
private
def credentials_set?
!!(@cached_credentials[:access_key_id] &&
@cached_credentials[:secret_access_key])
end
def refresh_credentials
@cached_credentials = get_credentials
end
end
end
end
end