Pod 資源配置
![](https://www.isolves.com/d/file/p/2022/12-26/aa71ff168b3e6725d67c949d36a191d0.png)
實際上上面幾個步驟就是影響一個 Pod 生命周期的大的部分,但是還有一些細節(jié)也會在 Pod 的啟動過程進行設(shè)置,比如在容器啟動之前還會為當前的容器設(shè)置分配的 CPU、內(nèi)存等資源,我們知道我們可以通過 CGroup 來對容器的資源進行限制,同樣的,在 Pod 中我們也可以直接配置某個容器的使用的 CPU 或者內(nèi)存的上限。那么 Pod 是如何來使用和控制這些資源的分配的呢?
首先對于 CPU,我們知道計算機里 CPU 的資源是按“時間片”的方式來進行分配的,系統(tǒng)里的每一個操作都需要 CPU 的處理,所以,哪個任務(wù)要是申請的 CPU 時間片越多,那么它得到的 CPU 資源就越多,這個很容器理解。
然后還需要了解下 CGroup 里面對于 CPU 資源的單位換算:
1 CPU = 1000 millicpu(1 Core = 1000m)
0.5 CPU = 500 millicpu (0.5 Core = 500m)
這里的 m 就是毫、毫核的意思,Kube.NETes 集群中的每一個節(jié)點可以通過操作系統(tǒng)的命令來確認本節(jié)點的 CPU 內(nèi)核數(shù)量,然后將這個數(shù)量乘以1000,得到的就是節(jié)點總 CPU 總毫數(shù)。比如一個節(jié)點有四核,那么該節(jié)點的 CPU 總毫量為 4000m,如果你要使用0.5 core,則你要求的是 4000*0.5 = 2000m。在 Pod 里面我們可以通過下面的兩個參數(shù)來限制和請求 CPU 資源:
- spec.containers[].resources.limits.cpu:CPU 上限值,可以短暫超過,容器也不會被停止
- spec.containers[].resources.requests.cpu:CPU請求值,Kubernetes 調(diào)度算法里的依據(jù)值,可以超過
這里需要明白的是,如果 resources.requests.cpu 設(shè)置的值大于集群里每個節(jié)點的最大 CPU 核心數(shù),那么這個 Pod 將無法調(diào)度,因為沒有節(jié)點能滿足它。
到這里應(yīng)該明白了,requests 是用于集群調(diào)度使用的資源,而 limits 才是真正的用于資源限制的配置,如果你需要保證的你應(yīng)用優(yōu)先級很高,也就是資源吃緊的情況下最后再殺掉你的 Pod,那么你就把你的 requests 和 limits 的值設(shè)置成一致,在后面應(yīng)用的 Qos 中會具體講解。
比如,現(xiàn)在我們定義一個 Pod,給容器的配置如下的資源:
# pod-resource-demo1.yaml
apiVersion: v1
kind: Pod
metadata:
name: resource-demo1
spec:
containers:
- name: resource-demo1
image: Nginx
ports:
- containerPort: 80
resources:
requests:
memory: 50Mi
cpu: 50m
limits:
memory: 100Mi
cpu: 100m
這里,CPU 我們給的是 50m,也就是 0.05core,這 0.05core 也就是占了 1 CPU 里的 5% 的資源時間。而限制資源是給的是 100m,但是需要注意的是 CPU 資源是可壓縮資源,也就是容器達到了這個設(shè)定的上限后,容器性能會下降,但是不會終止或退出。比如我們直接創(chuàng)建上面這個 Pod:
? ~ kubectl Apply -f pod-resource-demo1.yaml
創(chuàng)建完成后,我們可以看到 Pod 被調(diào)度到 node1 這個節(jié)點上:
? ~ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
resource-demo1 1/1 Running 0 24s 10.244.1.27 node1 <none> <none>
然后我們到 node1 節(jié)點上去查看 Pod 里面啟動的 resource-demo1 這個容器:
? ~ crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
1e4ef680a5a88 87a94228f133e 41 seconds ago Running resource-demo1 0 a00af47f2a12e
......
我們可以去查看下主容器的信息:
? ~ crictl inspect 1e4ef680a5a88
{
"status": {
"id": "1e4ef680a5a88af7eae88a6901f12eb103dc3f8e1807f26337cd9bfb3704ca05",
"metadata": {
"attempt": 0,
"name": "resource-demo1"
},
......
"linux": {
"resources": {
"devices": [
{
"allow": false,
"access": "rwm"
}
],
"memory": {
"limit": 104857600
},
"cpu": {
"shares": 51,
"quota": 10000,
"period": 100000
}
},
"cgroupsPath": "kubepods-burstable-poda194c43a_9551_494b_bd72_ab898afdcc0c.slice:cri-containerd:1e4ef680a5a88af7eae88a6901f12eb103dc3f8e1807f26337cd9bfb3704ca05",
......
實際上我們就可以看到這個容器的一些資源情況,Pod 上的資源配置最終也還是通過底層的容器運行時去控制 CGroup 來實現(xiàn)的,我們可以進入如下目錄查看 CGroup 的配置,該目錄就是 CGroup 父級目錄,而 CGroup 是通過文件系統(tǒng)來進行資源限制的,所以我們上面限制容器的資源就可以在該目錄下面反映出來:
? ~ cd /sys/fs/cgroup/cpu/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda194c43a_9551_494b_bd72_ab898afdcc0c.slice
? ~ ls
cgroup.clone_children cpuacct.stat cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpuacct.usage cpu.cfs_quota_us cpu.shares tasks
cgroup.procs cpuacct.usage_percpu cpu.rt_period_us cpu.stat
? ~ cat cpu.cfs_quota_us
10000
其中 cpu.cfs_quota_us 就是 CPU 的限制值,如果要查看具體的容器的資源,我們也可以進入到容器目錄下面去查看即可。
最后我們了解下內(nèi)存這塊的資源控制,內(nèi)存的單位換算比較簡單:
1 MiB = 1024 KiB,內(nèi)存這塊在 Kubernetes 里一般用的是Mi單位,當然你也可以使用Ki、Gi甚至Pi,看具體的業(yè)務(wù)需求和資源容量。
單位換算
這里注意的是MiB ≠ MB,MB 是十進制單位,MiB 是二進制,平時我們以為 MB 等于 1024KB,其實1MB=1000KB,1MiB才等于1024KiB。中間帶字母 i 的是國際電工協(xié)會(IEC)定的,走1024乘積;KB、MB、GB 是國際單位制,走1000乘積。
這里要注意的是,內(nèi)存是不可壓縮性資源,如果容器使用內(nèi)存資源到達了上限,那么會OOM,造成內(nèi)存溢出,容器就會終止和退出。我們也可以通過上面的方式去通過查看 CGroup 文件的值來驗證資源限制。
靜態(tài) Pod¶
在 Kubernetes 集群中除了我們經(jīng)常使用到的普通的 Pod 外,還有一種特殊的 Pod,叫做Static Pod,也就是我們說的靜態(tài) Pod,靜態(tài) Pod 有什么特殊的地方呢?
靜態(tài) Pod 直接由節(jié)點上的 kubelet 進程來管理,不通過 master 節(jié)點上的 apiserver。無法與我們常用的控制器 Deployment 或者 DaemonSet 進行關(guān)聯(lián),它由 kubelet 進程自己來監(jiān)控,當 pod 崩潰時會重啟該 pod,kubelet 也無法對他們進行健康檢查。靜態(tài) pod 始終綁定在某一個 kubelet 上,并且始終運行在同一個節(jié)點上。kubelet 會自動為每一個靜態(tài) pod 在 Kubernetes 的 apiserver 上創(chuàng)建一個鏡像 Pod,因此我們可以在 apiserver 中查詢到該 pod,但是不能通過 apiserver 進行控制(例如不能刪除)。
創(chuàng)建靜態(tài) Pod 有兩種方式:配置文件和 HTTP 兩種方式
配置文件
配置文件就是放在特定目錄下的標準的 JSON 或 YAML 格式的 pod 定義文件。用 kubelet --pod-manifest-path=<the directory>來啟動 kubelet 進程,kubelet 定期的去掃描這個目錄,根據(jù)這個目錄下出現(xiàn)或消失的 YAML/JSON 文件來創(chuàng)建或刪除靜態(tài) pod。
比如我們在 node1 這個節(jié)點上用靜態(tài) pod 的方式來啟動一個 nginx 的服務(wù),配置文件路徑為:
? ~ cat /var/lib/kubelet/config.yaml
......
staticPodPath: /etc/kubernetes/manifests # 和命令行的 pod-manifest-path 參數(shù)一致
......
打開這個文件我們可以看到其中有一個屬性為 staticPodPath 的配置,其實和命令行的 --pod-manifest-path 配置是一致的,所以如果我們通過 kubeadm 的方式來安裝的集群環(huán)境,對應(yīng)的 kubelet 已經(jīng)配置了我們的靜態(tài) Pod 文件的路徑,默認地址為 /etc/kubernetes/manifests,所以我們只需要在該目錄下面創(chuàng)建一個標準的 Pod 的 JSON 或者 YAML 文件即可,如果你的 kubelet 啟動參數(shù)中沒有配置上面的--pod-manifest-path 參數(shù)的話,那么添加上這個參數(shù)然后重啟 kubelet 即可:
? ~ cat <<EOF >/etc/kubernetes/manifests/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
app: static
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
EOF
通過 HTTP 創(chuàng)建靜態(tài) Pods
kubelet 周期地從 –manifest-url= 參數(shù)指定的地址下載文件,并且把它翻譯成 JSON/YAML 格式的 pod 定義。此后的操作方式與–pod-manifest-path= 相同,kubelet 會不時地重新下載該文件,當文件變化時對應(yīng)地終止或啟動靜態(tài) pod。
kubelet 啟動時,由 --pod-manifest-path= 或 --manifest-url= 參數(shù)指定的目錄下定義的所有 pod 都會自動創(chuàng)建,例如,我們示例中的 static-web:
? ~ nerdctl -n k8s.io ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6add7aa53969 Docker.io/library/nginx:latest "/docker-entrypoint.…" 43 seconds ago Up
......
現(xiàn)在我們通過kubectl工具可以看到這里創(chuàng)建了一個新的鏡像 Pod:
? ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
static-web-node1 1/1 Running 0 109s
靜態(tài) pod 的標簽會傳遞給鏡像 Pod,可以用來過濾或篩選。 需要注意的是,我們不能通過 API 服務(wù)器來刪除靜態(tài) pod(例如,通過kubectl命令),kubelet 不會刪除它。
? ~ kubectl delete pod static-web-node1
pod "static-web-node1" deleted
? ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
static-web-node1 1/1 Running 0 4s
靜態(tài) Pod 的動態(tài)增加和刪除
運行中的 kubelet 周期掃描配置的目錄(我們這個例子中就是 /etc/kubernetes/manifests)下文件的變化,當這個目錄中有文件出現(xiàn)或消失時創(chuàng)建或刪除 pods:
? ~ mv /etc/kubernetes/manifests/static-web.yaml /tmp
# sleep 20
? ~ nerdctl -n k8s.io ps
// no nginx container is running
? ~ mv /tmp/static-web.yaml /etc/kubernetes/manifests
# sleep 20
? ~ nerdctl -n k8s.io ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
902be9190538 docker.io/library/nginx:latest "/docker-entrypoint.…" 14 seconds ago Up
......
其實我們用 kubeadm 安裝的集群,master 節(jié)點上面的幾個重要組件都是用靜態(tài) Pod 的方式運行的,我們登錄到 master 節(jié)點上查看/etc/kubernetes/manifests目錄:
? ~ ls /etc/kubernetes/manifests/
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
現(xiàn)在明白了吧,這種方式也為我們將集群的一些組件容器化提供了可能,因為這些 Pod 都不會受到 apiserver 的控制,不然我們這里kube-apiserver怎么自己去控制自己呢?萬一不小心把這個 Pod 刪掉了呢?所以只能有kubelet自己來進行控制,這就是我們所說的靜態(tài) Pod。
Downward API¶
前面我們從 Pod 的原理到生命周期介紹了 Pod 的一些使用,作為 Kubernetes 中最核心的資源對象、最基本的調(diào)度單元,我們可以發(fā)現(xiàn) Pod 中的屬性還是非常繁多的,前面我們使用過一個 volumes 的屬性,表示聲明一個數(shù)據(jù)卷,我們可以通過命令kubectl explain pod.spec.volumes去查看該對象下面的屬性非常多,前面我們只是簡單使用了 hostPath 和 emptyDir{} 這兩種模式,其中還有一種模式叫做downwardAPI,這個模式和其他模式不一樣的地方在于它不是為了存放容器的數(shù)據(jù)也不是用來進行容器和宿主機的數(shù)據(jù)交換的,而是讓 Pod 里的容器能夠直接獲取到這個 Pod 對象本身的一些信息。
目前 Downward API 提供了兩種方式用于將 Pod 的信息注入到容器內(nèi)部:
- 環(huán)境變量:用于單個變量,可以將 Pod 信息和容器信息直接注入容器內(nèi)部
- Volume 掛載:將 Pod 信息生成為文件,直接掛載到容器內(nèi)部中去
環(huán)境變量¶
我們通過 Downward API 來將 Pod 的 IP、名稱以及所對應(yīng)的 namespace 注入到容器的環(huán)境變量中去,然后在容器中打印全部的環(huán)境變量來進行驗證,對應(yīng)資源清單文件如下:
# env-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: env-pod
namespace: kube-system
spec:
containers:
- name: env-pod
image: busybox
command: ["/bin/sh", "-c", "env"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
我們可以看到上面我們使用了一種新的方式來設(shè)置 env 的值:valueFrom,由于 Pod 的 name 和 namespace 屬于元數(shù)據(jù),是在 Pod 創(chuàng)建之前就已經(jīng)定下來了的,所以我們可以使用 metata 就可以獲取到了,但是對于 Pod 的 IP 則不一樣,因為我們知道 Pod IP 是不固定的,Pod 重建了就變了,它屬于狀態(tài)數(shù)據(jù),所以我們使用 status 這個屬性去獲取。另外除了使用 fieldRef獲取 Pod 的基本信息外,還可以通過 resourceFieldRef 去獲取容器的資源請求和資源限制信息。
我們直接創(chuàng)建上面的 Pod:
? ~ kubectl apply -f env-pod.yaml
pod "env-pod" created
Pod 創(chuàng)建成功后,我們可以查看日志:
? ~ kubectl logs env-pod -n kube-system |grep POD
kubectl logs -f env-pod -n kube-system
POD_IP=10.244.1.121
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBE_DNS_SERVICE_PORT_DNS_TCP=53
HOSTNAME=env-pod
SHLVL=1
HOME=/root
KUBE_DNS_SERVICE_HOST=10.96.0.10
KUBE_DNS_PORT_9153_TCP_ADDR=10.96.0.10
KUBE_DNS_PORT_9153_TCP_PORT=9153
KUBE_DNS_PORT_9153_TCP_PROTO=tcp
KUBE_DNS_SERVICE_PORT=53
KUBE_DNS_PORT=udp://10.96.0.10:53
POD_NAME=env-pod
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBE_DNS_PORT_53_TCP_ADDR=10.96.0.10
KUBERNETES_PORT_443_TCP_PORT=443
KUBE_DNS_SERVICE_PORT_METRICS=9153
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBE_DNS_PORT_9153_TCP=tcp://10.96.0.10:9153
KUBE_DNS_PORT_53_UDP_ADDR=10.96.0.10
KUBE_DNS_PORT_53_TCP_PORT=53
KUBE_DNS_PORT_53_TCP_PROTO=tcp
KUBE_DNS_PORT_53_UDP_PORT=53
KUBE_DNS_SERVICE_PORT_DNS=53
KUBE_DNS_PORT_53_UDP_PROTO=udp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
POD_NAMESPACE=kube-system
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/
KUBE_DNS_PORT_53_TCP=tcp://10.96.0.10:53
KUBE_DNS_PORT_53_UDP=udp://10.96.0.10:53
我們可以看到 Pod 的 IP、NAME、NAMESPACE 都通過環(huán)境變量打印出來了。
環(huán)境變量
上面打印 Pod 的環(huán)境變量可以看到有很多內(nèi)置的變量,其中大部分是系統(tǒng)自動為我們添加的,Kubernetes 會把當前命名空間下面的 Service 信息通過環(huán)境變量的形式注入到 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 4d21h
Volume 掛載¶
Downward API除了提供環(huán)境變量的方式外,還提供了通過 Volume 掛載的方式去獲取 Pod 的基本信息。接下來我們通過Downward API將 Pod 的 Label、Annotation 等信息通過 Volume 掛載到容器的某個文件中去,然后在容器中打印出該文件的值來驗證,對應(yīng)的資源清單文件如下所示:
# volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
namespace: kube-system
labels:
k8s-app: test-volume
node-env: test
annotations:
own: youdianzhishi
build: test
spec:
volumes:
- name: podinfo
downwardAPI:
items:
- path: labels
fieldRef:
fieldPath: metadata.labels
- path: annotations
fieldRef:
fieldPath: metadata.annotations
containers:
- name: volume-pod
image: busybox
args:
- sleep
- "3600"
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
我們將元數(shù)據(jù) labels 和 annotaions 以文件的形式掛載到了 /etc/podinfo 目錄下,創(chuàng)建上面的 Pod:
? ~ kubectl create -f volume-pod.yaml
pod "volume-pod" created
創(chuàng)建成功后,我們可以進入到容器中查看元信息是不是已經(jīng)存入到文件中了:
? ~ kubectl exec -it volume-pod /bin/sh -n kube-system
/ # ls /etc/podinfo/
..2019_11_13_09_57_15.990445016/ annotations
..data/ 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":"youdianzhishi"},"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="2019-11-13T17:57:15.320894744+08:00"
kubernetes.io/config.source="api"
我們可以看到 Pod 的 Labels 和 Annotations 信息都被掛載到 /etc/podinfo 目錄下面的 lables 和 annotations 文件了。
目前,Downward API 支持的字段已經(jīng)非常豐富了,比如:
1. 使用 fieldRef 可以聲明使用:
spec.nodeName - 宿主機名字
status.hostIP - 宿主機IP
metadata.name - Pod的名字
metadata.namespace - Pod的Namespace
status.podIP - Pod的IP
spec.serviceAccountName - Pod的Service Account的名字
metadata.uid - Pod的UID
metadata.labels['<KEY>'] - 指定<KEY>的Label值
metadata.annotations['<KEY>'] - 指定<KEY>的Annotation值
metadata.labels - Pod的所有Label
metadata.annotations - Pod的所有Annotation
2. 使用 resourceFieldRef 可以聲明使用:
容器的 CPU limit
容器的 CPU request
容器的 memory limit
容器的 memory request
注意
需要注意的是,Downward API 能夠獲取到的信息,一定是 Pod 里的容器進程啟動之前就能夠確定下來的信息。而如果你想要獲取 Pod 容器運行后才會出現(xiàn)的信息,比如,容器進程的 PID,那就肯定不能使用 Downward API 了,而應(yīng)該考慮在 Pod 里定義一個 sidecar 容器來獲取了。
在實際應(yīng)用中,如果你的應(yīng)用有獲取 Pod 的基本信息的需求,一般我們就可以利用Downward API來獲取基本信息,然后編寫一個啟動腳本或者利用initContainer將 Pod 的信息注入到我們?nèi)萜髦腥ィ缓笤谖覀冏约旱膽?yīng)用中就可以正常的處理相關(guān)邏輯了。
除了通過 Downward API 可以獲取到 Pod 本身的信息之外,其實我們還可以通過映射其他資源對象來獲取對應(yīng)的信息,比如 Secret、ConfigMap 資源對象,同樣我們可以通過環(huán)境變量和掛載 Volume 的方式來獲取他們的信息,但是,通過環(huán)境變量獲取這些信息的方式,不具備自動更新的能力。所以,一般情況下,都建議使用 Volume 文件的方式獲取這些信息,因為通過 Volume 的方式掛載的文件在 Pod 中會進行熱更新。