在 kubernetes 中找出过度使用资源的 namespaces
2022-3-28
我们知道, 在 kubernetes 中, namespace 的资源限制在 ResourceQuota 中定义, 比如我们控制 default 名称空间使用 64核80G 的资源:
$ kubectl get resourcequota not-best-effort -oyaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: not-best-effort
spec:
hard:
limits.cpu: "64"
limits.memory: 80G
status:
hard:
limits.cpu: "64"
limits.memory: 80G
used:
limits.cpu: 30500m
limits.memory: 59G
通常来讲, 由于 kubernetes 的资源控制机制, .status.used
中资源的值会小于 .status.hard
中相应资源的值. 但也有特例.
特殊情况 #
当我们开始定义了一个较大的资源限制, 待应用部署完毕, 资源占用了很多之后, 这时调低资源限制. 此时就会出现 .status.used
中的值超过 .status.hard
中相应值的情况, 尤其是内存的限制. 比如下面个:
...
spec:
hard:
limits.cpu: "1"
limits.memory: 1G
status:
hard:
limits.cpu: "1"
limits.memory: 1G
used:
limits.cpu: 15600m
limits.memory: 26044M
找出这些名称空间 #
在集群管理的过程中, 往往我们需要找出这些过度使用资源名称空间, 这可能是因为用户会为资源付费. 所以, 我尝试使用 golang 开发一个小工具, 找出这些名称空间. 现在代码已经写好了, 这个工具有很少的代码, 直接将全部代码贴出来:
package main
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)
func main() {
fi, err := os.Open("./resourcequotas.txt")
if err != nil {
fmt.Printf("Error: %s\n", err)
return
}
defer fi.Close()
br := bufio.NewReader(fi)
for {
l, _, c := br.ReadLine()
if c == io.EOF {
break
}
fmt.Print(string(l))
s := strings.Split(string(l), "limits.memory:")
mem := strings.Trim(s[1], " ")
s = strings.Split(mem, "/")
mused := formatUnits(s[0])
mhard := formatUnits(s[1])
fmt.Printf("(%v/%v)", mused, mhard)
musedf, _ := strconv.ParseFloat(mused, 64)
mhardf, _ := strconv.ParseFloat(mhard, 64)
if musedf > mhardf {
fmt.Println(" <== this line")
} else {
fmt.Println("")
}
}
}
func formatUnits(s string) string {
if strings.Index(s, "G") >= 0 {
s = strings.Trim(s, "G")
s = strings.Trim(s, "Gi")
return s
}
if strings.Index(s, "M") >= 0 {
s = strings.Trim(s, "M")
s = strings.Trim(s, "Mi")
i, _ := strconv.Atoi(s)
s = fmt.Sprintf("%.2f", float64(i)/1024)
return s
}
if s != "0" {
// byte
i, _ := strconv.Atoi(s)
s = fmt.Sprintf("%.2f", float64(i)/1024/1024/1024)
return s
}
return s
}
该代码仅实现了查找内存占用超过限制的逻辑
首先, 我们通过命令, 将集群中所有的 ResourceQuota 资源导出到 resourcequotas.txt
文件中:
kubectl get resourcequota -A > resourcequotas.txt
# 文件内容如下:
NAMESPACE NAME AGE REQUEST LIMIT
namespace-001 not-best-effort 129d limits.cpu: 30500m/64, limits.memory: 59G/80G
namespace-002 not-best-effort 125d limits.cpu: 3300m/10, limits.memory: 3564M/10G
namespace-003 not-best-effort 4d5h limits.cpu: 1/4, limits.memory: 1G/8G
将生成的 txt 文件和代码放在一个目录中, 接下来执行代码, 会看到下面的输出:
$ go run main.go
...
namespace-102 not-best-effort 349d limits.cpu: 10100m/13, limits.memory: 20218M/26G(19.74/26)
namespace-103 not-best-effort 349d limits.cpu: 15600m/1, limits.memory: 26044M/1G(25.43/1) <== this line
namespace-104 not-best-effort 349d limits.cpu: 5200m/8, limits.memory: 5460M/16G(5.33/16)
...
可以看到 <== this line
, 名称空间 namespace-103 定义的内存限制是 1G, 但是实际使用了 25.43G, 很明显超过了资源限制. 我们的目的也就达到了.