Why aws-sdk-js-v2 get token so slow in k8s Node
Why aws-sdk-js-v2 get sts assume-role token so slow in k8s node
Published Dec 17, 2023
Why aws-sdk-js-v2 get sts assume-role token so slow in k8s Node
前言: 工作上遇到的問題,花點時間解決,並解記錄下來。
開門見山直接看有問題的 code.
aws-sdk-js
version:2.1026.0
可以看到光是執行
assumeRole()
就花費了 4.668s
的時間!!經過另一個方式用測試,採用直接塞 AKSK(
ACCESS-KEY
)(SECRET-ACCESS-KEY
)的方式去執行 assumeRole()
以下為示意code:output:
可以看到直結解果有明顯縮短,到
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
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 就很慢呢,可以在此 repo 中找到此 issue,原來在 aws-sdk version
v2.575.0
之後,預設先嘗試走 IMDSv2
流程進行拿取 instance metadata
的 token,這樣做是為了加強安全性,IMDSv2
更新的流程需要在能夠調用任何元 metadata endpoint 之前獲得令牌。那麼
IMDSv2
又是什麼呢 ?我們可以從 EC2 的文件中得知: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
後就可以透過他去存取 metadata endpoint回到這次的事件
為什麼透過
IMDSv2
拿取的速度這麼慢勒,原因為此環境為 k8s 的 node,也就是 application 會是以 Container ( pod ) 方式跑在此 EC2 中,然而當前的 k8s 使用的網路,如果沒有指定
kubelet
網絡插件,則使用 noop
插件,它設置 net/bridge/bridge-nf-call-iptables=1
以確保簡單的配置(例如帶有 bridge
的 Docker
)與 iptables
代理一起正常工作。Network Plugins: https://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
- Launch EC2 InstanceMetadataOptions.HttpPutResponseHopLimit setting to
1
andMetadata version
useV1 and V2(token optional)
- Connect to Instance
- Request a token from IMDSv2 endpoint
- Modify EC2 Instance
InstanceMetadataOptions.HttpPutResponseHopLimit
to2
Modify instance metadata
http-put-response-hop-limit
to 1
Try run container with host network…
Request
http://169.254.169.254/latest/api/token
Get timeout with not use host network.- Request
http://169.254.169.254/latest/api/token
Not get timeout with use host network.
- 強制 SDK 使用 IMDSv1 的方式存取 metadata endpoint (不建議比較不安全,往後的 instance 應該也會淘汰v1 的方式去存取metadata endpoint )。
- 透過調整 response hop limits 的上限,可以透過 awscli modify-instance-metadata-options將上限調整到 2 以上,或是 Launch Template or Launch Configuration 預設在起動 EC2 時進行調整。
- 使用 VPC-CNI: Amazon EKS 會透過 Kubernetes 專用 Amazon VPC 容器網路介面 (CNI) 外掛程式支援原生 VPC 聯網。使用此外掛程式可讓 Kubernetes Pod 擁有與他們在 VPC 網路上 Pod 內相同的 IP 地址,這樣也解決 response hop limit 的問題。
- 使用 IRSA 給予 application 去取得權限與 AWS Service 進行互動。
將 IAM 角色與 Kubernetes 服務帳戶建立關聯。然後,此服務帳戶可以為使用該服務帳戶之任何 Pod 中的容器提供 AWS 許可。
- Use IMDSv2 - Use IMDSv2 - Amazon Elastic Compute Cloud: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
- 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
- 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
- AWS 中的錯誤重試與指數退避 - AWS 中的錯誤重試與指數退避 - AWS 一般參考: https://docs.aws.amazon.com/zh_tw/general/latest/gr/api-retries.html