ECS/FargateでEFSが127.0.0.1にマウントされる|NFSの接続先がlocalhostになる理由

トラブル

ECS/FargateのタスクからEFSをマウントしている環境で、コンテナ内からマウント情報を確認すると、EFSのDNS名でもマウントターゲットのIPアドレスでもなく、127.0.0.1が表示されることがあります。

私が確認した環境でも、タスク定義にはEFSボリュームを定義しており、アプリケーションからEFS上のファイルを正常に読み書きできているにもかかわらず、マウント元はlocalhostになっていました。

EFSへ接続しているはずなのに、なぜ接続先が127.0.0.1なのか。クロスアカウントでEFSを利用していることが原因なのか、Fargate特有の動作なのか、マウント情報だけでは分かりませんでした。

結論から書くと、これはEFSの転送時暗号化(TLS)で使われる仕組みによるものです。

NFSクライアントはEFSへ直接接続するのではなく、ローカルで待ち受けるプロキシへ接続します。そのプロキシがEFSとのTLS通信を担当するため、NFSクライアントから見た接続先として127.0.0.1が表示されます。

この記事では、実際に確認した127.0.0.1という表示を起点に、EFSの転送時暗号化でどのような通信経路が作られるのか、AWSサポートがEC2基盤のECSタスクで確認したログも交えて整理します。

EFSをマウントしているのに127.0.0.1が表示された

今回確認したのは、ECS/Fargateから別AWSアカウントのEFSを利用する構成です。

AWSアカウントA

ECS / Fargate
      │
      │ EFSマウント
      ▼

AWSアカウントB

EFS

タスク定義にはEFSボリュームを定義し、コンテナの所定のディレクトリへマウントしていました。アプリケーションからEFS上のファイルを読み書きできているため、EFSへの接続自体は成立しています。

ところが、コンテナ内からマウント状態を確認すると、次のように表示されました。

127.0.0.1:/ on /mnt/xxxx type nfs4 (...,port=2xxxx,...,addr=127.0.0.1)

通常のNFSマウントをイメージすると、接続先にはEFSのDNS名やマウントターゲットが見えるように思えます。実際に表示されているのは127.0.0.1、コンテナから見たlocalhostです。

この結果だけを見ると、「EFSではなくローカルのファイルシステムを見ているのではないか」「クロスアカウントEFSだから特殊なマウントになっているのではないか」と考えたくなります。

原因はクロスアカウント構成そのものではありませんでした。

NFSクライアントはローカルポートへ接続している

EFSへの通信経路は、転送時暗号化の有無によって変わります。

転送時暗号化を利用しない場合、NFSクライアントはEFSマウントターゲットへ直接NFS通信を行います。転送時暗号化を有効にすると、NFSクライアントとEFSの間に、TLS通信を担当するプロキシが入ります。

図:EFSの転送時暗号化なし/ありで変わる通信経路

転送時暗号化を有効にした場合、NFSクライアントから見た接続先はEFSではなく、ローカルで待ち受けているプロキシです。NFSクライアントは127.0.0.1のローカルポートへ接続し、efs-proxyがその通信をTLSでEFSへ中継します。

このため、mountなどで確認したマウント情報には、EFSのDNS名ではなく127.0.0.1が現れます。127.0.0.1はEFSそのものを示しているのではなく、NFSクライアントから見たローカル側の接続先です。

AWSのEFSドキュメントでも、EFS mount helperを使用して転送時暗号化を行う場合、NFSクライアントはローカルポートへマウントするよう構成され、マウント情報では127.0.0.1として見えることが説明されています。

Encrypting data in transit – Amazon Elastic File System

127.0.0.1が表示されたこと自体は、EFSへの接続異常を示しているわけではありません。

なぜTLS通信ではlocalhostを経由するのか

NFSクライアントからEFSへの通信をTLSで暗号化するには、NFS通信とTLS通信の間を仲介するコンポーネントが必要になります。

EFSではamazon-efs-utilsに含まれるEFS mount helperが、このマウント処理を支援します。現在のamazon-efs-utilsではefs-proxyが利用され、2.0.0より前のバージョンではstunnelが利用されていました。

古いEFSの記事ではstunnel、比較的新しい記事ではefs-proxyという名前が出てくることがありますが、これはamazon-efs-utilsのバージョンによる違いです。amazon-efs-utils 2.0.0以降では、従来のstunnelに代わって、性能を改善したefs-proxyが利用されています。

今回なぜ転送時暗号化が有効になっていたのか

ここまでの仕組みは、EFSの転送時暗号化を利用していることが前提です。

ECSのタスク定義では、EFSボリュームのtransitEncryptionによって転送時暗号化を指定します。transitEncryptionを省略した場合の既定値はDISABLEDです。

IAM認可またはEFS Access Pointを利用する構成では、transitEncryptionを明示的に有効にする必要があります。自動的にTLSが有効になるわけではありません。

たとえばIAM認可を利用する場合、タスク定義のvolumes内の該当部分は次のような設定になります。

{
  "efsVolumeConfiguration": {
    "fileSystemId": "fs-xxxxxxxx",
    "transitEncryption": "ENABLED",
    "authorizationConfig": {
      "iam": "ENABLED"
    }
  }
}

IAM認可と転送時暗号化の関係や、transitEncryptionの既定値についてはAWSのECSドキュメントでも確認できます。

Specify an Amazon EFS file system in an Amazon ECS task definition

EFSを利用している環境で127.0.0.1が表示された場合、クロスアカウント構成かどうかだけを見るのではなく、タスク定義のtransitEncryptionauthorizationConfigを確認する必要があります。

今回見えていた127.0.0.1は、クロスアカウントだから発生したものではなく、EFSをTLSでマウントする通信経路の一部でした。

EC2基盤のmount.logではefs-proxyの起動を確認できる

Fargateでは利用者がホストOSへログインできないため、EFSのマウント処理をホスト側から直接確認できません。

AWSサポートへ問い合わせたところ、EC2基盤のECSタスクで同様のEFSマウントを確認した結果として、/var/log/amazon/efs/mount.logのログが提示されました。

一部を簡略化すると、次のような内容です。

INFO - binding <local port>
INFO - Starting efs-proxy:
       nsenter --net=/proc/xxxx/ns/net ... /sbin/efs-proxy ...
INFO - Started efs-proxy, pid: <pid>

ログには、ローカルポートの確保、efs-proxyの起動、指定されたネットワーク名前空間への切り替えが記録されています。nsenter --net=が使われていることから、efs-proxyは指定されたネットワーク名前空間内で起動されていることが確認できます。

これを通信経路と対応させると、mountで見えていた127.0.0.1と、mount.logに記録されたローカルポートの確保、efs-proxyの起動、EFSへのTLS接続が一つの流れとして理解できます。

図:EC2基盤のmount.logとefs-proxyによる通信経路の対応

EC2基盤では、127.0.0.1というマウント表示が単なるlocalhostを示しているのではなく、その先でefs-proxyを介してEFSへ接続するためのローカルエンドポイントであることを、ログと通信経路を対応させて確認できます。

Fargateではどこまで確認できるのか

FargateではホストOSがAWSによって管理されているため、利用者がmount.logやホスト側のプロセス一覧を直接確認することはできません。

FargateでEFSボリュームを利用すると、AWSはEFSボリュームを管理するためのaws-fargate-supervisorというsupervisorコンテナを作成します。

このコンテナは、タスクメタデータエンドポイント バージョン4(Task Metadata v4)から確認できるほか、CloudWatch Container Insightsでもaws-fargate-supervisorというコンテナ名で確認できます。

aws-fargate-supervisorはタスクに割り当てられたCPUとメモリを少量使用します。EFSを利用するFargateタスクでは、アプリケーションコンテナ以外にもEFSボリューム管理のためのリソースが消費されることになります。

この仕様はAWSのECSドキュメントに記載されています。

Amazon EFS volumes – Amazon Elastic Container Service

ここで注意したいのは、aws-fargate-supervisorefs-proxyを同じものとして扱わないことです。

AWS公式ドキュメントでは、aws-fargate-supervisorがEFSボリュームの管理を担当することまでは説明されていますが、supervisorコンテナとefs-proxyの具体的な関係までは明示されていません。

今回、FargateタスクではEFSのマウント元が127.0.0.1として表示されました。EFSの公式仕様では、TLSマウント時にNFSクライアントをローカルポートへ接続する方式が説明され、EC2基盤で確認されたログでは実際にefs-proxyが起動しています。

これらからFargateでも同様の通信方式が使われていると考えられますが、Fargate内部でEC2基盤とまったく同じプロセス起動シーケンスが実行されているところまで、今回直接確認したわけではありません。

127.0.0.1が見えたとき、何を切り分けるのか

mountの結果に127.0.0.1が表示されても、それだけでEFSへの接続障害とは判断できません。転送時暗号化を利用している場合、localhostは正常な通信経路の一部だからです。

アプリケーションからEFS上のファイルを正常に読み書きできているのであれば、127.0.0.1を理由にネットワーク障害を疑う必要はありません。

EFSをマウントできない、あるいはアクセス時にエラーになる場合は、localhostより先の経路を確認します。主な確認対象は次のとおりです。

  • ECSタスクとEFSマウントターゲット間のセキュリティグループ
  • NFS(TCP/2049)の通信可否
  • 利用するAZのEFSマウントターゲット
  • IAM認可
  • EFSファイルシステムポリシー
  • Access Pointを利用している場合はその設定

EC2基盤であれば、/var/log/amazon/efs/mount.logも有力な調査材料になります。Fargateではホスト側のmount.logを直接確認できないため、タスクの停止理由やECSのイベント、EFS側のネットワーク・認可設定など、利用者から確認できる範囲で切り分けます。

ここで重要なのは、

127.0.0.1という表示だけでは、EFSへの到達性が正常か異常かは判断できない

ということです。

設定値と実行時の状態は同じではない

今回の調査で改めて感じたのは、マネージドサービスでは設定として見えているものと、その設定を実現するために内部で動いている処理が同じ粒度ではないということです。

ECSのタスク定義には、EFSのファイルシステムIDやマウント先、転送時暗号化、認可方式などを設定します。そこに「127.0.0.1へNFSマウントする」「efs-proxyを起動する」と書くわけではありません。

利用者が指定した設定を実現するため、基盤側では別の処理が動きます。通常はそこまで意識する必要がないこと自体がFargateの利点ですが、障害調査では、この隠蔽が逆に分かりにくさにつながることがあります。

タスク定義ではEFSを指定しているのに、実行時には127.0.0.1が見える。設定だけを見ていれば、この二つが同じEFSマウントを表しているとは直感的に理解しにくいからです。

以前、ECS/Fargateで非rootコンテナを動かした際にも、Dockerfileでchownしたディレクトリと、ECSがボリュームをマウントした後の実行時状態が異なることで、Permission deniedが発生しました。
この事象については、以下の記事で実際の原因と切り分けを整理しています。

ECS/FargateでDockerfileのchownが効かない|非rootコンテナがPermission deniedになる理由
ECS/Fargateで非rootコンテナを実行した際、Dockerfileでchown済みのログディレクトリがPermission deniedになった実例をもとに、空のデータVolume(Empty Volume)とbind mount、VOLUMEの仕様、対策、設計レビュー、事前PoCで確認すべき点を解説します。

今回も問題の種類は違いますが、見るべきポイントは似ています。

設定ファイルに何を書いたかだけではなく、その設定によって実行時にどのような状態が作られるのかを見る。

Fargateのように基盤側が隠蔽されているサービスほど、この視点が障害調査や設計レビューで重要になります。

この違いを設計書や設定値だけで確認するには限界があります。

特にクラウド移行では、本番相当の構成で実際に動かし、設計時の想定と実行時の状態に差がないかをPoCで確認することが重要です。PoCで何を確認すべきか、確認不足が後工程でどのような手戻りにつながるのかは、以下の記事で整理しています。

【第1部】クラウドリフトの失敗は構築前に始まっている|PoC不足が手戻り・コスト超過・納期遅延を招く理由
クラウド移行(AWSへのクラウドリフト)で起きる手戻り・コスト超過・納期遅延は、構築中ではなくPoC不足から始まります。Auto Scaling、EFS/EBS、RDS、監視、バックアップ、ライセンスなど10の設計リスクを現場目線で整理します。

まとめ

ECS/FargateからEFSをマウントしているにもかかわらず、マウント情報に127.0.0.1が表示されるのは、EFSの転送時暗号化で利用される通信方式によるものです。

NFSクライアントはEFSへ直接接続せず、ローカルポートで待ち受けるプロキシへ接続します。現在のamazon-efs-utilsではefs-proxyがEFSとのTLS通信を担当するため、NFSクライアントから見た接続先としてlocalhostが表示されます。

127.0.0.1が見えたこと自体はEFSへの接続障害を意味しません。マウントやアクセスに問題がある場合は、localhostという表示ではなく、EFSまでのネットワーク経路やIAM認可、ファイルシステムポリシーなどを切り分ける必要があります。

設定として書いた内容と、それを実現するために実行時に作られる状態は、別に確認する必要があります。

参考文献

コメント

タイトルとURLをコピーしました