Why aws-sdk-js-v2 get sts assume-role token so slow in k8s node

前言: 工作上遇到的問題,花點時間解決,並解記錄下來。

為什麼我的 node application 在 k8s 內部執行時,執行以下 function 時如此的慢,我在地端測試都沒有這個問題??

開門見山直接看有問題的 code.

  • aws-sdk-js version: 2.1026.0
const sts = new AWS.STS({
      endpoint: 'https://sts.us-east-1.amazonaws.com ',
      region: 'us-east-1',
      stsRegionalEndpoints: 'regional',
    });
    const credentials = await sts
      .assumeRole({
        RoleArn: roleArn,
        RoleSessionName: 'RoleSessionName',
      })
      .promise();

可以看到光是執行 assumeRole() 就花費了 4.668s 的時間!!

經過另一個方式用測試,採用直接塞 AKSK(ACCESS-KEY)(SECRET-ACCESS-KEY)的方式去執行 assumeRole() 以下為示意code:

const awsSdk = require('aws-sdk');

const stsClient = new awsSdk.STS({
    endpoint: 'https://sts.us-east-1.amazonaws.com/ ',
    region: 'us-east-1',
    stsRegionalEndpoints: "regional",
    accessKeyId: 'ACCESS-KEY', // <- AK
    secretAccessKey: 'SECRET-ACCESS-KEY' <-SK
});

const credentials = stsClient.assumeRole({
    RoleArn: 'ROLE-ARN',
    RoleSessionName: 'SESSION-NAME'
}).promise();

credentials.then((value) => {
    console.log('Value: ', value);
}).catch((reason) => {
    console.log('Reason: ', reason);
});
[AWS sts 200 0.037s 0 retries] assumeRole({
  RoleArn: 'ROLE-ARN',
  RoleSessionName: 'SESSION-NAME'
})
[AWS sts 200 2.589s 0 retries] assumeRole({
  RoleArn: 'ROLE-ARN',
  RoleSessionName: 'SESSION-NAME'
})
[AWS sts 200 0.055s 0 retries] assumeRole({
  RoleArn: 'ROLE-ARN',
  RoleSessionName: 'SESSION-NAME'
})

可以看到直結解果有明顯縮短,到 2.589s 甚至是 0.055s 這又是為什麼呢?

使用 AWS 時,常常 Application 會需要跟其他 AWS Service 進行互動 比如說最常見的 DynamoDB, S3 etc…

而要與這些服務互動,除了常見的 aws console 登陸後,你的 IAM User 有 attach 特定的 policy 可以讓你透過console 與資源去做互動,而如果開發人員在開發應用時,如果應用要與 AWS 其他的 Service 互動, 勢必要獲得授權,而最簡單獲得授權的方式,就是在 指定的 IAM User 的 security credentials 創建一個 access key

這就是所謂的 AWS Access Key AWS Secret Access Key 俗稱 AKSK ,直接用 AKSK 這個雖然方便,但越是方便的東西越是帶著風險,如果不小心有一天,你的 AKSK 不小信 hardcode 寫在你的 source code ,又不小心 publish 到像是 Github, Gitlab 的 public repo ,都有可能會造成你的 AWS Resource 被有心人士惡意竄改,或是亂開 EC2 來挖礦,你的錢包可以就會傷心了…,所以建議 AKSK 只建議在本機開發使用,千萬不要 hardcode 在 source code 內,建議使用 AWS_PROFILE 來進行管理,基本上 AWS SDK 各語言都可以透過 AWS_PROFILE Named profiles for the AWS CLI - AWS Command Line Interface 來進行授權吃到開發環境的,aws credentials 設定檔 $HOME/.aws/=

在正式環境中,我們使用 AWS EC2 or 其他 Compute Service ,都可以透過該服務的 IAM Role 來授權換取臨時性的 Credential ,而這次的事件也是由這個原因引起的。

先來看一下 Nodejs 應用如何取得 Credential

https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html

Here are the ways you can supply your credentials in order of recommendation:

Loaded from AWS Identity and Access Management (IAM) roles for Amazon EC2

Loaded from the shared credentials file (~/.aws/credentials)

Loaded from environment variables

可以從文件中得知,預設如果是 AWS EC2 環境的話,會先使用 EC2 IAM Role 來獲得 Credential ,再來嘗試 loading ~/.aws/credentials 的檔案,再來才是環境變數,但如果你是直接 hardcode AKSK 在 source code ,當然會直接用 hardcode 內的 AKSK ,所以我們再回到此次的問題,hardcode AKSK 速度極快,為什麼改用 EC2 role 就很慢呢,可以在

High latency inside ecs docker with latest versions From V2.575.0 of aws sdk. It is working till V2.574.0 · Issue #3024 · aws/aws-sdk-js

此 repo 中找到此 issue,原來在 aws-sdk version v2.575.0 之後,預設先嘗試走 IMDSv2 流程進行拿取 instance metadata 的 token,這樣做是為了加強安全性,IMDSv2 更新的流程需要在能夠調用任何元 metadata endpoint 之前獲得令牌。

那麼 IMDSv2 又是什麼呢 ?我們可以從 EC2 的文件中得知:

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html

How Instance Metadata Service Version 2 (IMDSv2) works

IMDSv2 使用 session-oriented requests。對於 session-oriented requests,您可以創建一個 session token 來定義 session 持續時間,該持續時間最短為一秒,最長為六小時(21600s)。在指定的持續時間內,您可以將相同的 session token 用於後續請求。在指定的持續時間到期後,您必須創建一個新的 session token 以用於未來的請求。

以下為請求 session token 的範例,需要在 EC2 執行:

TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` 

拿到 TOKEN 後就可以透過他去存取 metadata endpoint

curl -H "X-aws-ec2-metadata-token: $TOKEN" \
-v http://169.254.169.254/latest/meta-data/

回到這次的事件

為什麼透過 IMDSv2 拿取的速度這麼慢勒,原因為此環境為 k8s 的 node,也就是 application 會是以 Container ( pod ) 方式跑在此 EC2 中,

然而當前的 k8s 使用的網路,如果沒有指定 kubelet 網絡插件,則使用 noop 插件,它設置 net/bridge/bridge-nf-call-iptables=1 以確保簡單的配置(例如帶有 bridgeDocker)與 iptables 代理一起正常工作。

Network Plugins: https://v1-20.docs.kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/ 所以我們來大概看一下 Docker Bridge 的架構,containers 都可以透過 docker0 到 EC2 的eth0 網卡,與外界聯絡。 https://argus-sec.com/docker-networking-behind-the-scenes/

當前環境的 k8s cluster

那麼用 Docker bridge 的模式,為什麼會影響,aws-sdk 存取 IMDSv2 這麼慢呢?因為對於 JavaScript SDK 在獲取 EC2 role 的行為上,首先會使用 MetaData Version 2 (IMDSv2), 當 IMDSv2 無法回應時經過幾次重試,最終使用 IMDSv1 獲取權限。基於您的網路架構,當 Container 要訪問 IMDSv2 時,路徑如下:

[Container] –> [bridge] –> [Instance] –> [IMDSv2]

而訪問 IMDSv2 timeout 的原因是,預設使用 put 請求,拿取 session token 時,網路層的 hop Hop networking limit 是 1,而我們 container 的環境是透過 docker bridge 的方式與外界聯絡(與 IMDSv2),也就是 2 hop ,超過限制時 IMDSv2 的 endpoint 將會拒絕回應,所以造成 timeout。

By default, the response to PUT requests has a response hop limit (time to live) of 1 at the IP protocol level. You can adjust the hop limit using the modify-instance-metadata-options command if you need to make it larger. For example, you might need a larger hop limit for backward compatibility with container services running on the instance. https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html

所以改善此 issue 的方法就是有幾種:

  1. 強制 SDK 使用 IMDSv1 的方式存取 metadata endpoint (不建議比較不安全,往後的 instance 應該也會淘汰v1 的方式去存取metadata endpoint )。

  2. 透過調整 response hop limits 的上限,可以透過 awscli modify-instance-metadata-options將上限調整到 2 以上,或是 Launch Template or Launch Configuration 預設在起動 EC2 時進行調整。

  3. 使用 VPC-CNI: Amazon EKS 會透過 Kubernetes 專用 Amazon VPC 容器網路介面 (CNI) 外掛程式支援原生 VPC 聯網。使用此外掛程式可讓 Kubernetes Pod 擁有與他們在 VPC 網路上 Pod 內相同的 IP 地址,這樣也解決 response hop limit 的問題。

  4. 使用 IRSA 給予 application 去取得權限與 AWS Service 進行互動。

將 IAM 角色與 Kubernetes 服務帳戶建立關聯。然後,此服務帳戶可以為使用該服務帳戶之任何 Pod 中的容器提供 AWS 許可。


  1. Use IMDSv2 - Use IMDSv2 - Amazon Elastic Compute Cloud: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
  2. aws-sdk-js/metadata_service.js at v2.1026.0 · aws/aws-sdk-js: https://github.com/aws/aws-sdk-js/blob/v2.1026.0/lib/metadata_service.js#L123
  3. amazon-ecs-agent/config.go at master · aws/amazon-ecs-agent: https://github.com/aws/amazon-ecs-agent/blob/master/agent/config/config.go#L129
  4. 擷取執行個體中繼資料 - 查詢調節 - https://docs.aws.amazon.com/zh_tw/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html#instancedata-throttling
  5. AWS 中的錯誤重試與指數退避 - AWS 中的錯誤重試與指數退避 - AWS 一般參考: https://docs.aws.amazon.com/zh_tw/general/latest/gr/api-retries.html

20220817 Neil Kuan