
深入探讨 kubecost 是如何获取整个 AWS 账号的未使用磁盘信息
本文将深入研究 kubecost 是如何收集整个 AWS 账号中未使用磁盘的信息。kubecost 是一个广受欢迎的工具,用于管理 Kubernetes 环境的成本和资源。本文将解释 kubecost 如何与 AWS API 进行互动,并使用各种技术和方法来识别和收集未使用的磁盘资源。
Kubecost 简介
Kubecost 是 Stackwatch 开发的项目,专门用于监控和分析 Kubernetes 的成本。并且提供开源版本(Opencost)以及付费订阅的版本(Kubecost Enterprise)。其主要用例,在于实现 Kubernetes 集群的整体成本可见度。Kubecost 的开源版本将计费结果的存储周期限定为 15 天。如果需要将指标保留 15 天以上,您必须升级至付费版本。
Amazon EKS 免费支持由 AWS 与 Kubecost 协同合作提供自定义版本的 Kubecost(Amazon EKS-optimized Kubecost),通过 Kubecost 可以查看集群的成本分布,并了解哪些应用或服务在消耗最多的资源,可让监控按 Kubernetes 资源(包括 Pods、节点、命名空间和标签)进行细部的成本监控和分析,帮助团队视觉化 Amazon EKS 费用明细、分配成本,以及向应用程序团队等组织单位收取费用。
此外,Kubecost 还可以提供未使用的磁盘空间信息,这对于资源管理和成本控制非常有帮助。
Kubecost 如何获取未使用磁盘信息
不过 kubecost 的未使用磁盘信息功能设计让我也收到一个很有趣的问题,有使用者在安装 Amazon EKS 安装优化版本的 kubecost 后,安装后他发现 kubecost 可以获得整个账号的未使用磁盘信息。
他怀疑是否默认的权限是否过大了,而且除了依照 EKS 官方的文件安装 kubecost,他并没有提供任何账号等级的权限,因此特别好奇是否存在安全性疑虑,而提出这项问题:
helm upgrade -i kubecost oci://public.ecr.aws/kubecost/cost-analyzer --version kubecost-version \
--namespace kubecost --create-namespace \
-f https://raw.githubusercontent.com/kubecost/cost-analyzer-helm-chart/develop/cost-analyzer/values-eks-cost-monitoring.yaml
要了解 kubecost 其中获取 AWS 账号底下所有 EBS 磁盘的机制,就得先分析 kubecost 实际是如何获取磁盘信息,并且了解其中是否涉及相关的权限设定。
为了帮助分析,本文将以 kubecost 的基础开源 opencost 项目的进行分析,以 2.2 版本为例,获取是否存在闲置 AWS EBS 磁盘的相关分析被定义在 GetOrphanedResources()
方法中 1:
func (aws *AWS) GetOrphanedResources() ([]models.OrphanedResource, error) {
volumes, volumesErr := aws.getAllDisks()
addresses, addressesErr := aws.getAllAddresses()
...
for _, volume := range volumes {
if aws.isDiskOrphaned(volume) {
cost, err := aws.findCostForDisk(volume)
if err != nil {
return nil, err
}
...
如果再近一步检视,可以注意到 getAllDisks()
作为要方法通过循环遍历了所有账号底下 AWS 区域,并且执行另一个操作为 getDisksForRegion()
获取单一区域中的 2,这里使用了 EC2 提供的 DescribeVolumes
API 3 操作获取这些信息:
func (aws *AWS) getDisksForRegion(ctx context.Context, region string, maxResults int32, nextToken *string) (*ec2.DescribeVolumesOutput, error) {
aak, err := aws.GetAWSAccessKey()
if err != nil {
return nil, err
}
cfg, err := aak.CreateConfig(region)
if err != nil {
return nil, err
}
cli := ec2.NewFromConfig(cfg)
return cli.DescribeVolumes(ctx, &ec2.DescribeVolumesInput{
MaxResults: &maxResults,
NextToken: nextToken,
})
}
func (aws *AWS) getAllDisks() ([]*ec2Types.Volume, error) {
regions := aws.Regions()
volumeCh := make(chan *ec2.DescribeVolumesOutput, len(regions))
// Get volumes from each AWS region
for _, r := range regions {
// Fetch volume response and send results and errors to their
// respective channels
go func(region string) {
...
// Query for first page of volume results
resp, err := aws.getDisksForRegion(context.TODO(), region, 1000, nil)
...
因此,答案似乎显而易见,kubecost 在底层实现上仍必须依赖 AWS 提供的 DescribeVolumes
获取这项信息,但这个权限是哪里来的呢?
在 kubecost 的部署中,cost-analyzer 包含了 cost-model
其中一个容器应用,负责前面提到的副程序进行相关 AWS API 的调用:
NAME READY STATUS RESTARTS AGE
pod/kubecost-cost-analyzer-688d699b88-w5d8f 0/4 Pending 0 21s
pod/kubecost-forecasting-6c6456668f-tlhv7 0/1 Pending 0 20s
pod/kubecost-prometheus-server-6b584dc478-6kd7q 0/1 Pending 0 20s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubecost-aggregator ClusterIP 10.100.10.104 <none> 9004/TCP 23s
service/kubecost-cloud-cost ClusterIP 10.100.193.45 <none> 9005/TCP 23s
service/kubecost-cost-analyzer ClusterIP 10.100.26.236 <none> 9003/TCP,9090/TCP 22s
service/kubecost-forecasting ClusterIP 10.100.169.241 <none> 5000/TCP 22s
service/kubecost-prometheus-server ClusterIP 10.100.185.108 <none> 80/TCP 21s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kubecost-cost-analyzer 0/1 1 0 22s
deployment.apps/kubecost-forecasting 0/1 1 0 22s
deployment.apps/kubecost-prometheus-server 0/1 1 0 21s
NAME DESIRED CURRENT READY AGE
replicaset.apps/kubecost-cost-analyzer-688d699b88 1 1 0 22s
replicaset.apps/kubecost-forecasting-6c6456668f 1 1 0 21s
replicaset.apps/kubecost-prometheus-server-6b584dc478 1 1 0 21s
因此,在这种情况下,我们很大概率可以看看是否跟两个权限设定有关了
- Service Account:是否使用了 EKS 支持的 IAM Role for Service Account(IRSA)功能
- Worker Node(EC2 instance)本身的 IAM 权限(Instance Profile IAM Role)
在前面的 Helm 安装步骤中,我们并未设置相关 IRSA 的权限,看了看果然符合预期,并没有关联任何的 IAM Role。默认的 Service Account 是用于赋予 kubecost 进行 Kubernetes 资源访问权限的设置(例如:获取 Pod, Node, PV, PVC 等等资源的权限),并且在相关的 Helm Chart 中被定义 4:
$ kubectl get pod/kubecost-cost-analyzer-688d699b88-w5d8f -n kubecost -o yaml | grep -i serviceaccount
serviceAccount: kubecost-cost-analyzer
serviceAccountName: kubecost-cost-analyzer
$ kubectl describe sa/kubecost-cost-analyzer -n kubecost
Name: kubecost-cost-analyzer
Namespace: kubecost
Labels: app=cost-analyzer
app.kubernetes.io/instance=kubecost
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=cost-analyzer
helm.sh/chart=cost-analyzer-2.2.0
Annotations: meta.helm.sh/release-name: kubecost
meta.helm.sh/release-namespace: kubecost
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: <none>
Events: <none>
既然不是 IRSA 的设置,这样我们可以更确定可能跟 EC2 instance 本身关联的 IAM 权限有关了。默认情况下,每个 EKS 的 Worker Node 都会使用默认的一些 IAM 权限设置(IAM Policy),包含 5:
这些权限目的是可以让运行在 EC2 上面的必要 Kubernetes 应用可以正常工作(例如:kubelet 和 CNI Plugin)等。因此,可以预期在运行 kubecost 部署到 Worker Node 上面运行,其通常将使用默认对应 EKS 节点的 IAM 权限,从默认的策略 AmazonEKSWorkerNodePolicy
可以注意到以下信息:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeVolumes",
...
],
"Resource": "*"
}
]
}
为了验证是否跟上述权限有关,我在 Worker Node 对应的 IAM Role 中加入拒绝策略进行测试:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TestKubeCostFindAllDisks",
"Effect": "Deny",
"Action": "ec2:DescribeVolumes",
"Resource": "*"
}
]
}
当 Kubecost 获取磁盘数据时,可以看到预期的错误:
$ kubectl logs $KUBECOST_POD cost-model -n kubecost
INF unable to get addresses: %s logged 5 times: suppressing future logs
WRN unable to get disks: operation error EC2: DescribeVolumes, https response error StatusCode: 403, RequestID: b1eb2ada-8813-441c-a2f9-64847e14f6cd, api error UnauthorizedOperation: You are not authorized to perform this operation.
WRN unable to get disks: operation error EC2: DescribeVolumes, https response error StatusCode: 403, RequestID: 46053624-5c2d-4ef3-a145-f01917eeec84, api error UnauthorizedOperation: You are not authorized to perform this operation.
因此我们可以了解 kubecost 是如何是在无需赋予相关权限的情况下获取所有区域的 EBS 磁盘资源。默认的 ec2:DescribeVolumes
权限确保 kubelet 运行和 EKS-Optimized AMI 中相应组件必须的运作权限,以获取 EC2 instance 和该区域关联资源的运作信息(例如:使用 EBS CSI Driver 部署 EBS Volume 的需求),并作为相关 Kubernetes 物件资源 Label 数据的一部分(例如:PersistentVolume 以及 PersistentVolumeClaim),并且只提供读取权限,未包含相关的写入操作,这项权限无法修改任何 EBS 资源。
Kubecost 在运作阶段通过使用 Worker Node 本身的 IAM 身份和权限扫描 AWS 账户区域中闲置的EBS 资源(未附加至任何 EC2 instance 的 EBS 卷),将这些资源筛选出来进行成本节省的优化建议。
总结
本文深入探讨了 Kubecost 如何收集 AWS 账号中未使用磁盘的信息,分享了使用者可能在安装 Kubecost 后对于 Kubecost 可以获得整个账号的未使用磁盘信息的疑虑。除了进行相关的程序代码分析,也解释了 Kubecost 如何与 AWS API 互动,以及如何使用各种技术和方法来识别和收集未使用的磁盘资源。
经过深入分析和验证,我们确认 Kubecost 是使用 Worker Node 本身的 IAM 身份和权限来获取 AWS 账号中未使用的 EBS 磁盘信息。这种做法并没有越权或违反安全原则,因为 Kubecost 仅使用了默认的 ec2:DescribeVolumes
权限,这是为了确保 Kubernetes 运行所必要的权限,并且只有读取权限,没有修改任何 EBS 资源的写入操作。
希望通过本篇文章,能让读者对 Kubecost 如何获取 AWS 未使用磁盘信息有更深入的理解,并澄清对相关安全性的疑虑。
参考资料