之前做了一个VPA项目的需求,就是需要不重启的方式修改容器的Cgroup的值已达到垂直扩缩容的目的,项目中核心的思路如下
- 上游下发要VPA的结果的值写入到容器的Annotation里面
- Kubelet 感知到这个 annoation 的变化
- 我们本地运行一个 Agent,里面运行一个类似的job监听kubelet的变化,当发现annotation的值改变的时候就去修改对应容器的Cgroup的数值即可
具体步骤如下
Cgroup 是什么
简而言之,对容器技术而言,其实现资源层面上的限制和隔离,依赖于 Linux 内核提供的 Cgroup 和 namespace 技术
cgroup 的主要作用:管理资源的分配和限制;主要限制的资源是CPU、内存、网络和磁盘IO
namespace 的主要作用:封装抽象、限制、隔离,使命名空间内的进程看起来拥有他们自己的全局资源
以一个最简单的例子来校验Cgroup的限制,我们使用docker来创建以下容器
docker run --rm -d --cpus=2 --memory=2g --name=2c2g redis:alpine
查看上述容器的ID
➜ dps | grep -i 2c2g
37ad8252f8de redis:alpine "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 6379/tcp 2c2g
找到 /sys/fs/cgroup/system.slice
目录对应的容器
可看到有很多容器的选项可供查看
查看CPU和内存的Max
➜ docker-37ad8252f8de50694e3321364c9ef5e94ae08f45f50e633e561feb291d1c27e9.scope cat cpu.max
200000 100000
➜ docker-37ad8252f8de50694e3321364c9ef5e94ae08f45f50e633e561feb291d1c27e9.scope cat memory.max
2147483648 # 这里的单位是Byte
我们查看容器的占用
可以看到这个2G就是我们设定的数值
那尝试直接修改这个cgroup的数值,是不是就能修改容器的内存和CPU的限制从而做到VPA的效果
我们尝试修改内存由原来的2G到现在的1G
echo "1073741824" >> memory.max
再次查看内存的占用比值,可以发现确实修改已经生效
Kubelet 的接口
首先明确一点,你在管控面 kubelet get 等信息基本都是从边缘端映射过来的,所以单机侧的 kubelet 也能获取到很多信息,比如
/configz 返回 kubelet 的相关配置信息
/metrics 暴露 kubelet 和容器的监控指标,比如 CPU 和内存的使用率,网络流量等
/pods 返回当前节点上的所有 Pod 的信息,包括 Pod 的状态以及容器信息等
/stats/summary 提供节点的资源使用统计信息,包括CPU、内存、网络、磁盘等
/exex/<pod_namespace>/<pod_name>/<container_name> 提供指定容器的日志,支持tail和follow参数
/logs 用于获取正在运行的pods和容器日志
配置服务账号访问kubelet
访问 kubelet 需要使用密钥,我们先配置服务账号访问,直接配置成最大的 clusterrole 权限
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: system:my-agent
subjects:
- kind: ServiceAccount
name: my-agent
namespace: default
roleRef:
kind: ClusterRole
name: system:my-agent
apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: system:my-agent
rules:
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- proxy
- apiGroups:
- ""
resources:
- nodes/log
- nodes/metrics
- nodes/proxy
- nodes/spec
- nodes/stats
verbs:
- '*'
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-agent
namespace: default
上面的服务账号创建完成之后就能看到下面的密钥了
kubectl get secret $(kubectl get serviceaccount my-agent -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 --decode
比如我这个机器上是
➜ ~ k get secret $(kubectl get serviceaccount my-agent -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 --decode
eyJhbGciOiJSUzI1NiIsImtpZCI6IlBQTDNXcGNKenVxNGZ3OHdpam5WQ0Jnd3d3NWpMbVZtSWJETTQ0WW5EZHMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Im15LWFnZW50LXRva2VuLXE0a2hsIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6Im15LWFnZW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiY2UyMzQzNGEtMTViYy00YzI1LWIwYTktZGQxM2E3MDllMGRlIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6bXktYWdlbnQifQ.UjNJxEbaLDm_19wVB4OPi-3u6fTG2noCQrLI7e7-PA1DoPc4SQJHzVXmPGwP6Ln4Odv-PrC0jkOpGF4DQQcQx589HsXN0zpmn6z-GJdzTsVxFFaJZpgZlfITGN99zQlr-AkRjgEm_9vUpijjjXOP0dWmPlHlmpxSYqpf3zSohIJsBySpXp1sQKboaeqTHbXU02xwsCjFDHeOyfESQcyVOtKQk9_w0Qvtiz5DcCClAZSCW8WLQPX9X7wie_vZCKlcNKiS5eKYCdymm6ZndXyidqb9Ga027_jhZnjvPO27rzBAL5wiTgonYAUau6tvB9KENfYAFF3pm2rTJZYujjUOfA
那用这个 token 是否可以访问到机器上的 pods 信息呢
我们试着对 /configz 接口发起访问,访问其 kubelet 的配置信息
可以看到访问成功
实操:部署pod并做VPA操作
手动修改
创建一个 nginx 的 deployment,设定资源限制为2core2G
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1 # 设置副本数
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest # 使用最新的 Nginx 镜像
resources:
requests:
memory: "2Gi" # 请求的内存
cpu: "2" # 请求的 CPU
limits:
memory: "2Gi" # 限制的内存
cpu: "2" # 限制的 CPU
ports:
- containerPort: 80 # Nginx 默认监听的端口
创建完成之后到目录 /sys/fs/cgroup/kubepods.slice
下面就能看到我们创建的pod
这个后缀就是我们pod的uid
进入到这个目录下面
可以发现这个pod里面实际运行了两个容器,一个 sandbox 容器用来隔离容器的网络,一个业务自身的容器
分别对应的pod的信息如下
我们进入到我们的业务pod里面,修改内存的取值如下
➜ docker-567f43cdcb3c5630ffc17735d92b2895912a65a3e03fd5575262a52709eba00b.scope cat memory.max
2147483648
修改为1G
然后使用 docker stats 查看容器的负载使用情况
可以看到容器的内存使用上限已经修改为我们期望的1GB,并且是不需要在重启容器的情况下进行的