モヒカンメモ

髪色が定期的に変わることに定評のある(比較的)若者Webエンジニアの備忘録

ECS上のPHPアプリからAWS SDKを使うとmetadata endpointへアクセスできない問題の備忘録

ECS (Fargate) 上で動くPHPアプリからAWS SDKを使ったとき、metadata endpointへアクセスできない問題を踏んだのでそのことを残しておく。

ざっくり要点まとめ

  • AWS SDKなどがしれっと見に行く metadata endpoint にはEC2用とECS用で分かれており、 AWS_CONTAINER_CREDENTIALS_RELATIVE_URI という環境変数がAWS SDKから見えなければAWS SDKはEC2用metadata endpointに繋ぎに行く
  • php-fpmはデフォルト設定ではサーバ環境変数を引き継がない (exportしない)
  • 上記の仕様が重なって、PHPアプリをECS上で動かすときは明示的にその環境変数をexportしてあげないとEC2用metadata endpointに繋ぎに行って死ぬ

背景

AWSをセキュアに使うために、権限管理は出来るだけアクセストークンを発行せずにTask Role (EC2でいうInstance Profile) を使いたい。

これまでアクセストークン ( AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY ) を使って権限管理を行っていたWebシステムを、Task Roleによる権限管理に移行しようとしたところPHPアプリからAWS SDKを叩いてるところでmetadata endpointへのアクセスができなくなって死んだ。

この原因で死んだときにAWS SDKが吐くエラー:

Error retrieving credentials from the instance profile metadata service. (cURL error 7:  (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://169.254.169.254/latest/meta-data/iam/security-credentials/)

調べたことメモ

FargateにおけるIAM Role

  • Task Execution Role … コンテナを起動する人に与えられるロール
  • Task Role … コンテナそのものに与えられるロール

AWS SDKがどうやってTask Roleを使ってAWS APIを叩くか

AWS SDKがAWS APIを叩くとき、アクセストークン ( AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY ) が必要。それが環境変数などに直接設定されている場合はそれを使うし、されていなければmetadata serviceへ内部的に問い合わせを行ってそこで払い出されたtemporaryなアクセストークンを使ってAPIを叩く振る舞いをする。

docs.aws.amazon.com

AWS SDKが使うアクセストークンを解決する実装は下記のあたり

https://github.com/aws/aws-sdk-php/blob/aefc78c0af15995944ff61c3099e0233d0914e87/src/Credentials/CredentialProvider.php#L114

読み進めると、環境変数に AWS_CONTAINER_CREDENTIALS_RELATIVE_URIAWS_CONTAINER_CREDENTIALS_FULL_URI が設定されている場合はECS用のmetadata endpointに問い合わせるっぽいことが読み取れる

https://github.com/aws/aws-sdk-php/blob/aefc78c0af15995944ff61c3099e0233d0914e87/src/Credentials/CredentialProvider.php#L845

github.com

php-fpmはデフォルト設定では環境変数を引き継がない

php-fpmのデフォルト設定では clear_env = true となっているのでサーバ環境変数を引き継がない。そのため、phpアプリから AWS_CONTAINER_CREDENTIALS_RELATIVE_URI 環境変数を読めない。

PHP: 設定 - Manual

clear_env bool FPM ワーカー内の環境をクリアする。 任意の環境変数が FPM ワーカープロセスに到達してしまうことを防ぐために、 ワーカー内の環境をいったんクリアしてから、このプールの設定で指定された環境変数を追加します。 デフォルト値: Yes

上記から、AWS SDKは自分がECS上で実行されていることを認識できず、ECS用metadata endpointではなくEC2用metadata endpointにつなぎに行ってしまって failed to connect となって死ぬことが分かった。

Aws\Exception\CredentialsException: Error retrieving credentials from the instance profile metadata service. (cURL error 7: (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://169.254.169.254/latest/meta-data/iam/security-credentials/)

対処方法

  1. 設定を clear_env = false とする方法
  2. 設定に ENV パラメータを書いて明示的に引き継がせる方法

1. 設定を clear_env = false とする方法

デフォルト true となっている設定を false へ変更すると、サーバに設定されている環境変数が引き継がれるのでAWS SDK (PHPアプリ) からAWS_CONTAINER_CREDENTIALS_RELATIVE_URI 環境変数が見えるようになる。

PHP: 設定 - Manual

www.conf:

- ;clear_env = no
+ clear_env = no

2. 設定に ENV パラメータを書いて明示的に引き継がせる方法

clear_env = true のままでも下記のような設定を記載すると AWS_CONTAINER_CREDENTIALS_RELATIVE_URI 環境変数だけを引き継がせる (exportする) ことができる。

www.conf:

env[AWS_CONTAINER_CREDENTIALS_RELATIVE_URI] = $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`

参考になった記事

sodane.hokkaido.jp