Pod使用进阶
资源配置
实际上上面几个步骤就是影响一个 Pod 生命周期的大的部分,但是还有一些细节也会在 Pod 的启动过程进行设置,比如在容器启动之前还会为当前的容器设置分配的 CPU、内存等资源,我们知道我们可以通过 CGroup 来对容器的资源进行限制,同样的在 Pod 中我们也可以直接配置某个容器的使用的 CPU 或者内存的上限,那么 Pod 是如何来使用和控制这些资源的分配的呢?
首先对于 CPU,我们知道计算机里 CPU 的资源是按时间片的方式来进行分配的,系统里的每一个操作都需要 CPU 的处理,所以,哪个任务要是申请的 CPU 时间片越多,那么它得到的 CPU 资源就越多,这个很容易理解。
CPU资源值小数值也是可以使用的,一个请求 0.5 CPU 的容器会获得请求 1 个 CPU 的容器的 CPU 的一半。我们也可以使用后缀m 表示毫,例如 100m CPU、100 milliCPU 和 0.1 CPU 都相同,需要注意精度不能超过 1m。
CPU 请求只能使用绝对数量,而不是相对数量。0.1 在单核、双核或 48 核计算机上的 CPU 数量值是一样的。
Kubernetes 集群中的每一个节点可以通过操作系统的命令来确认本节点的 CPU 内核数量,然后将这个数量乘以 1000,
得到的就是节点总 CPU 总毫数。比如一个节点有四核,那么该节点的 CPU 总毫量为 4000m,如果你要使用一半的 CPU,则你要求的是 4000 * 0.5 = 2000m。在 Pod 里面我们可以通过下面的两个参数来限制和请求 CPU 资源:
spec.containers[].resources.limits.cpu
:CPU 上限值,可以短暂超过,容器也不会被停止。spec.containers[].resources.requests.cpu
:CPU 请求值,Kubernetes 调度算法里的依据值,可以超过。
这里需要明白的是,如果 resources.requests.cpu 设置的值大于集群里每个节点的最大可用 CPU 核心数,那么这个Pod 将无法调度,因为没有节点能满足它。
比如,现在我们定义一个 Pod,给容器的配置如下的资源:
# pod-resource-demo1.yaml
apiVersion: v1
kind: Pod
metadata:name: resource-demo1
spec:containers:- name: resource-demo1image: nginxports:- containerPort: 80resources:requests:memory: 50Micpu: 50mlimits:memory: 100Micpu: 100m
验证:
ubuntu@ubuntu:~/example$ kubectl get pod -A -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
default resource-demo1 1/1 Running 0 77s 10.244.1.19 node2 <none> <none>
# 查看详细信息(去宿主机)
ubuntu@ubuntu:~$ sudo crictl ps -a
[sudo] password for ubuntu:
WARN[0000] Config "/etc/crictl.yaml" does not exist, trying next: "/usr/bin/crictl.yaml"
WARN[0000] runtime connect using default endpoints: [unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]. As the default settings are now deprecated, you should set the endpoint instead.
WARN[0000] Image connect using default endpoints: [unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]. As the default settings are now deprecated, you should set the endpoint instead.
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE
4a0fd4488e6fb ad5708199ec7d 5 minutes ago Running resource-demo1 0 21f48c4b0073e resource-demo1 default
0073bc657e29d 5de71980e553f 6 minutes ago Running kube-flannel 5 582fc55cac83f kube-flannel-ds-w666x kube-flannel
84b2c4dccac5e 5de71980e553f 6 minutes ago Exited install-cni 0 582fc55cac83f kube-flannel-ds-w666x kube-flannel
b48e0c16fd661 cca2af40a4a9e 6 minutes ago Exited install-cni-plugin 5 582fc55cac83f kube-flannel-ds-w666x kube-flannel
d87f0f4a413c1 f1184a0bd7fe5 6 minutes ago Running kube-proxy 6 fb87471803b25 kube-proxy-hrqpb kube-system
3f0e8679ea54d 5de71980e553f 3 days ago Exited kube-flannel 4 8de4f6b9e6c48 kube-flannel-ds-w666x kube-flannel
baf5936de6e43 f1184a0bd7fe5 3 days ago Exited kube-proxy 5 5c101a10d5f96 kube-proxy-hrqpb kube-system
# 查看详细信息
ubuntu@ubuntu:~$ sudo crictl inspect 4a0fd4488e6fb
..."resources": {"linux": {"cpuPeriod": "100000","cpuQuota": "10000","cpuShares": "51","cpusetCpus": "","cpusetMems": "","hugepageLimits": [],"memoryLimitInBytes": "104857600","memorySwapLimitInBytes": "104857600","oomScoreAdj": "988","unified": {"memory.oom.group": "1","memory.swap.max": "0"}}},
...
超过容器限制的内存
当节点拥有足够的可用内存时,容器可以使用其请求的内存。但是,容器不允许使用超过其限制的内存。如果容器分配的内存超过其限制,该容器会成为被终止的候选容器,如果容器继续消耗超出其限制的内存,则终止容器。如果终止的容器可以被重启,则 kubelet 会重新启动它,就像其他任何类型的运行时失败一样。
如下所示我们创建一个 Pod,尝试分配超出其限制的内存,该容器的内存请求为 50 MiB,内存限制为 100 MiB:
# memory-request-limit-1.yaml
apiVersion: v1
kind: Pod
metadata:name: memory-demo-1
spec:containers:- name: memory-demo-1-ctrimage: dockerpull.pw/polinux/stressresources:requests:memory: "50Mi"limits:memory: "100Mi"command: ["stress"]args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]
验证:
ubuntu@ubuntu:~/example$ kubectl apply -f ./memory-request-limit-1.yaml
pod/memory-demo-1 created
ubuntu@ubuntu:~/example$ watch kubectl get pod memory-demo-1
# 可以看到不断杀死,重启
OOMKilled
超过整个节点容量的内存
内存请求和限制是与容器关联的,Pod 的内存请求是 Pod 中所有容器的内存请求之和。同理,Pod 的内存限制是 Pod 中所有容器的内存限制之和。
Pod 的调度是基于 requests 值的,只有当节点拥有足够满足 Pod 内存请求的内存时,才会将 Pod 调度至节点上运行。
如下所示我们创建一个 Pod,其内存请求超过了你集群中的任意一个节点所拥有的内存。在该 Pod 的资源清单文件中,拥有一个请求 1000 GiB 内存的容器,这应该超过了你集群中任何节点的容量。
# memory-request-limit-2.yaml
apiVersion: v1
kind: Pod
metadata:name: memory-demo-2
spec:containers:- name: memory-demo-2-ctrimage: dockerpull.pw/polinux/stressresources:requests:memory: "1000Gi"command: ["stress"]args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]
验证:
ubuntu@ubuntu:~/example$ kubectl apply -f ./memory-request-limit-2.yaml
pod/memory-demo-2 created
ubuntu@ubuntu:~/example$ kubectl get pod memory-demo-2
NAME READY STATUS RESTARTS AGE
memory-demo-2 0/1 Pending 0 36s
# 会发现一直在Pending 可以看一下事件
ubuntu@ubuntu:~/example$ kubectl describe pod memory-demo-2
Events:Type Reason Age From Message---- ------ ---- ---- -------Warning FailedScheduling 78s default-scheduler 0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 Insufficient memory. preemption: 0/3 nodes are available: 1 Preemption is not helpful for scheduling, 2 No preemption victims found for incoming pod.
给 Pod 分配扩展资源
除了我们经常使用的 CPU 和内存之外,其实我们也可以自己定制扩展资源,要请求扩展资源,需要在你的容器清单中包括resources.requests 字段。扩展资源可以使用任何完全限定名称,只是不能使用 *.kubernetes.io/,比如example.com/foo 就是有效的格式,其中 example.com 可以被替换为你组织的域名,而 foo 则是描述性的资源名称。
扩展资源类似于内存和 CPU 资源。一个节点拥有一定数量的内存和 CPU 资源,它们被节点上运行的所有组件共享,该节点也可以拥有一定数量的 foo 资源,这些资源同样被节点上运行的所有组件共享。此外我们也可以创建请求一定数量 foo资源的 Pod。
假设一个节点拥有一种特殊类型的磁盘存储,其容量为 800 GiB,那么我们就可以为该特殊存储创建一个名称,如example.com/special-storage,然后你就可以按照一定规格的块(如 100 GiB)对其进行发布。在这种情况下,你的节点将会通知它拥有八个 example.com/special-storage 类型的资源。
添加资源:
kubectl patch node node1 --type=merge --subresource=status -p '{"status": {"capacity": {"example.com/special-storage": "8"},"allocatable": {"example.com/special-storage": "8"}}
}'
移除资源:
kubectl patch node node1 --type=json --subresource=status -p='[{"op": "remove", "path": "/status/capacity/example.com~1special-storage"},{"op": "remove", "path": "/status/allocatable/example.com~1special-storage"}
]'
查看资源:
ubuntu@ubuntu:~/example$ kubectl describe node node1 | grep example.com/special-storageexample.com/special-storage: 8example.com/special-storage: 8example.com/special-storage 0 0
示例:
# resource-extended-demo.yaml
apiVersion: v1
kind: Pod
metadata:name: extended-resource-demo
spec:containers:- name: extended-resource-demo-ctrimage: nginxresources:requests:example.com/special-storage: 3limits:example.com/special-storage: 3
验证:
ubuntu@ubuntu:~/example$ kubectl apply -f ./resource-extended-demo.yaml
pod/extended-resource-demo created
ubuntu@ubuntu:~/example$ kubectl get pod extended-resource-demo -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
extended-resource-demo 1/1 Running 0 2m4s 10.244.2.44 node1 <none> <none>
可以看到该 Pod 可以正常运行,因为目前的扩展资源是满足调度条件的,所以可以正常调度。
同样的我们再创建一个类似的新的 Pod,资源清单文件如下所示:
# resource-extended-demo2.yaml
apiVersion: v1
kind: Pod
metadata:name: extended-resource-demo-2
spec:containers:- name: extended-resource-demo-2-ctrimage: nginxresources:requests:example.com/special-storage: 6limits:example.com/special-storage: 6
验证:
ubuntu@ubuntu:~/example$ kubectl apply -f ./resource-extended-demo2.yaml
pod/extended-resource-demo-2 created
ubuntu@ubuntu:~/example$ kubectl get pod extended-resource-demo-2 -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
extended-resource-demo-2 0/1 Pending 0 8s <none> <none> <none> <none>
# 这里删除 demo1 会发现
ubuntu@ubuntu:~/example$ kubectl delete -f ./resource-extended-demo.yaml
pod "extended-resource-demo" deleted
ubuntu@ubuntu:~/example$ kubectl get pod extended-resource-demo-2 -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
extended-resource-demo-2 1/1 Running 0 67s 10.244.2.45 node1 <none> <none>
静态Pod
概念
在 Kubernetes 集群中除了我们经常使用到的普通的 Pod 外,还有一种特殊的 Pod,叫做 Static Pod,也就是我们说的静态 Pod,静态 Pod 有什么特殊的地方呢?
静态 Pod 是 直接由 kubelet 管理 的 Pod,不经过 apiserver 和调度器。
它 不会出现在 etcd 中,所以你用 kubectl
无法直接 create
出来。
kubelet 会在本地指定的目录下(通常是 /etc/kubernetes/manifests/
)读取 Pod 的 YAML 文件,并自动创建容器。
用途
静态 Pod 一般用于部署 Kubernetes 自己的核心组件(在没有 kube-apiserver 的情况下还能运行):
- kube-apiserver
- kube-scheduler
- kube-controller-manager
- etcd
这些组件一旦启动,整个 Kubernetes 集群才算“活起来”。所以必须有一种方式让 kubelet 不依赖调度器就能运行它们,这就是 静态 Pod 的作用。
此外,有些人也会用静态 Pod 来运行一些和集群强绑定的工具,比如监控代理、日志收集器。
如何创建静态Pod
创建静态 Pod 有两种方式:配置文件和 HTTP 两种方式
配置文件(注意目录):
# /etc/kubernetes/manifests/static-nginx.yaml
apiVersion: v1
kind: Pod
metadata:name: static-nginxlabels:role: my-static-pod
spec:containers:- name: nginximage: nginx:1.25ports:- containerPort: 80
kubelet 会自动检测到文件,创建这个 Pod。
验证:
ubuntu@ubuntu:~/example$ vim /etc/kubernetes/manifests/static-nginx.yaml
ubuntu@ubuntu:~/example$ sudo vim /etc/kubernetes/manifests/static-nginx.yaml
[sudo] password for ubuntu:
ubuntu@ubuntu:~/example$ kubectl get pods -A | grep static-nginx
default static-nginx-master 0/1 Pending 0 5subuntu@ubuntu:~/example$ sudo rm -rf /etc/kubernetes/manifests/static-nginx.yaml
ubuntu@ubuntu:~/example$
API
kubelet 周期地从 –manifest-url= 参数指定的地址下载文件,并且把它翻译成 JSON/YAML 格式的 pod 定义。此后的操作方式与–pod-manifest-path= 相同,kubelet 会不时地重新下载该文件,当文件变化时对应地终止或启动静态 pod。
示例:
- 假设你有一个 Pod YAML 文件
static-web.yaml
,内容如下
apiVersion: v1
kind: Pod
metadata:name: static-weblabels:app: static-web
spec:containers:- name: nginximage: nginx:1.25ports:- containerPort: 80
把这个文件丢到一个可以访问的http的url
- 启动 kubelet 并指定
--manifest-url
编辑或启动 kubelet 时加上参数:
kubelet \--pod-manifest-path=/etc/kubernetes/manifests \--manifest-url=http://192.168.0.100/pods/static-web.yaml
- kubelet 会周期性下载这个 URL(默认 20 秒一次,可以通过
--manifest-url-header
、--file-check-frequency
调整)。 - 下载成功后,会把文件解析成 Pod 定义,直接在本节点创建静态 Pod
- 验证静态 Pod
kubectl get pods -A | grep static-web
Downward API
前面我们从 Pod 的原理到生命周期介绍了 Pod 的一些使用,作为 Kubernetes 中最核心的资源对象、最基本的调度单元,我们可以发现 Pod 中的属性还是非常繁多的,前面我们使用过一个 volumes 的属性,表示声明一个数据卷,我们可以通过命令kubectl explain pod.spec.volumes去查看该对象下面的属性非常多,前面我们只是简单使用了hostPath 和 emptyDir{} 这两种模式,其中还有一种模式叫做downwardAPI,这个模式和其他模式不一样的地方在于它不是为了存放容器的数据也不是用来进行容器和宿主机的数据交换的,而是让 Pod 里的容器能够直接获取到这个 Pod对象本身的一些信息。
目前 Downward API 提供了两种方式用于将 Pod 的信息注入到容器内部:
- 环境变量:用于单个变量,可以将 Pod 信息和容器信息直接注入容器内部
- Volume 挂载:将 Pod 信息生成为文件,直接挂载到容器内部中去
环境变量
# env-pod.yaml
apiVersion: v1
kind: Pod
metadata:name: env-podnamespace: kube-system
spec:containers:- name: env-podimage: busyboxcommand: ["/bin/sh", "-c", "env; sleep 3600"]env:- name: POD_FIXED_FIELDvalue: "fixed_field"- name: POD_NAMEvalueFrom:fieldRef:fieldPath: metadata.name- name: POD_NAMESPACEvalueFrom:fieldRef:fieldPath: metadata.namespace- name: POD_IPvalueFrom:fieldRef:fieldPath: status.podIP
验证
ubuntu@ubuntu:~/example/pod$ kubectl apply -f ./env-pod.yaml
pod/env-pod created
ubuntu@ubuntu:~/example/pod$ kubectl logs env-pod -n kube-system |grep POD
POD_IP=10.244.2.46
POD_FIXED_FIELD=fixed_field
POD_NAME=env-pod
POD_NAMESPACE=kube-system
ubuntu@ubuntu:~/example/pod$
上面打印 Pod 的环境变量可以看到有很多内置的变量,其中大部分是系统自动为我们添加的,Kubernetes 会把当前命名空间下面的 Service 信息通过环境变量的形式注入到 Pod 中去:
ubuntu@ubuntu:~/example/pod$ kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 5d20h
ubuntu@ubuntu:~/example/pod$
Volume 挂载
Downward API除了提供环境变量的方式外,还提供了通过 Volume 挂载的方式去获取 Pod 的基本信息。接下来我们通过Downward API将 Pod 的 Label、Annotation 等信息通过 Volume 挂载到容器的某个文件中去,然后在容器中打印出该文件的值来验证,对应的资源清单文件如下所示:
# volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:name: volume-podnamespace: kube-systemlabels:k8s-app: test-volumenode-env: testannotations: # Pod 的注解,存储非标识性元数据own: examplebuild: test
spec:volumes:- name: podinfo # 卷的名字downwardAPI: # Downward API 卷,可以让 Pod 访问自身的元数据items:- path: labelsfieldRef:fieldPath: metadata.labels # 挂载 Pod 的标签信息- path: annotations # 挂载到容器内的路径fieldRef:fieldPath: metadata.annotations # 挂载 Pod 的注解信息# 定义 Pod 内的容器containers:- name: volume-podimage: busyboxargs:- sleep- "3600" # 容器启动后休眠 3600 秒volumeMounts:- name: podinfo # 挂载的卷名称mountPath: /etc/podinfo # 挂载到容器内的路径
验证:
ubuntu@ubuntu:~/example/pod$ kubectl apply -f ./volume-pod.yaml
pod/volume-pod created
ubuntu@ubuntu:~/example/pod$ kubectl exec -it volume-pod -n kube-system -- sh
/ # ls /etc/podinfo/
annotations labels
/ # cat /etc/podinfo/labels
k8s-app="test-volume"
/ # cat /etc/podinfo/annotations
build="test"
kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{\"build\":\"test\",\"own\":\"example\"},\"labels\":{\"k8s-app\":\"test-volume\",\"node-env\":\"test\"},\"name\":\"volume-pod\",\"namespace\":\"kube-system\"},\"spec\":{\"containers\":[{\"args\":[\"sleep\",\"3600\"],\"image\":\"busybox\",\"name\":\"volume-pod\",\"volumeMounts\":[{\"mountPath\":\"/etc/podinfo\",\"name\":\"podinfo\"}]}],\"volumes\":[{\"downwardAPI\":{\"items\":[{\"fieldRef\":{\"fieldPath\":\"metadata.labels\"},\"path\":\"labels\"},{\"fieldRef\":{\"fieldPath\":\"metadata.annotations\"},\"path\":\"annotations\"}]},\"name\":\"podinfo\"}]}}\n"
kubernetes.io/config.seen="2025-09-09T06:39:27.929396547Z"
kubernetes.io/config.source="api"
/ #
在实际应用中,如果你的应用有获取 Pod 的基本信息的需求,一般我们就可以利用Downward API来获取基本信息,然后编写一个启动脚本或者利用 initContainer 将 Pod 的信息注入到我们容器中去,然后在我们自己的应用中就可以正常的处理相关逻辑了。
除了通过 Downward API 可以获取到 Pod 本身的信息之外,其实我们还可以通过映射其他资源对象来获取对应的信息,比如 Secret、ConfigMap 资源对象,同样我们可以通过环境变量和挂载 Volume 的方式来获取他们的信息,但是,通过环境变量获取这些信息的方式,不具备自动更新的能力。所以,一般情况下,都建议使用 Volume 文件的方式获取这些信息,因为通过 Volume 的方式挂载的文件在 Pod 中会进行热更新。