K8s 之 Pod 高级用法(Advanced Usage of Pods in Kubernetes)

news/2025/1/18 0:39:31/

K8s 中之Pod 的高级用法

作为 Kubernetes 的核心编排对象之一,Pod 承载了丰富的信息。其中,资源定义(如 CPU 和内存)和调度相关的字段将在我们讨论调度器时详细介绍。本文中,我们将首先探讨一种特殊的 Volume 类型,以帮助你理解 Pod 对象中各种重要字段的意义。

这种特殊的 Volume 类型称为 Projected Volume。它是什么意思呢?

在 Kubernetes 中,有几种特殊的 Volume 并不是用于存储容器数据或促进容器与主机之间的数据交换。相反,这些特殊 Volume 的目的是为容器提供预定义的数据。从容器角度来看,这些 Volume 中的信息本质上是由 Kubernetes “投影”到容器中的。这就是 Projected Volume 的本质。

目前,Kubernetes 支持四种 Projected Volume 类型:

  1. Secret

  2. ConfigMap

  3. Downward API

  4. ServiceAccountToken

在本文中,我将首先讨论Secret。Secret 的目的是存储 Pod 需要访问的加密数据,并保存在 Etcd 中。然后,你可以将此 Volume 挂载到 Pod 的容器中,以访问存储在这些 Secret 中的信息。

Secret 的一个典型用例是存储数据库凭证。例如:

apiVersion: v1
kind: Pod
metadata:name: test-projected-volume
spec:containers:- name: test-secret-volumeimage: busyboxargs:- sleep- "86400"volumeMounts:- name: mysql-credmountPath: "/projected-volume"readOnly: truevolumes:- name: mysql-credprojected:sources:- secret:name: user- secret:name: pass

在这个 Pod 中,我定义了一个简单的容器。它声明挂载的 Volume 是projected 类型,而不是更常见的emptyDir 或hostPath 类型。此 Volume 的数据源是名为user 和pass 的Secret 对象,分别对应数据库用户名和密码。

数据库用户名和密码作为Secret 对象存储在 Kubernetes 中。创建这些Secret 对象的命令如下:

$ cat ./username.txt
admin
$ cat ./password.txt
c1oudc0w!$ kubectl create secret generic user --from-file=./username.txt
$ kubectl create secret generic pass --from-file=./password.txt

在此设置中,username.txt 和password.txt 文件分别包含用户名和密码。user 和pass 是分配给Secret 对象的名称。要查看这些Secret 对象,可以使用以下kubectl 命令:

$ kubectl get secrets
NAME           TYPE                                DATA      AGE
user          Opaque                                1         51s
pass          Opaque                                1         51s

当然,除了使用kubectl create secret 命令外,我还可以通过编写 YAML 文件直接创建Secret 对象,例如:

apiVersion: v1
kind: Secret
metadata:name: mysecret
type: Opaque
data:user: YWRtaW4=pass: MWYyZDFlMmU2N2Rm

可以看到,通过 YAML 文件创建的Secret 对象只包含一个Secret 对象。但其data 字段以 Key-Value 格式存储了两条Secret 数据。键 "user" 对应第一条数据,键 "pass" 对应第二条。

需要注意的是,Secret 对象要求这些数据必须进行 Base64 编码,以防止明文密码带来的安全风险。这种编码操作也非常简单,例如:

$ echo -n 'admin' | base64
YWRtaW4=
$ echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm

需要注意的是,通过这种方式创建的Secret 对象内容仅进行了 Base64 编码,并未加密。在实际生产环境中,应启用 Kubernetes 中的 Secret 加密以增强数据安全性。我将在后续更深入讨论 Secret 时提供更多关于启用 Secret 加密的详细信息。

接下来,让我们尝试创建这个 Pod:

$ kubectl create -f test-projected-volume.yaml

Pod 进入 Running 状态后,让我们验证这些 Secret 对象是否在容器中可用。

$ kubectl exec -it test-projected-volume -- /bin/sh
$ ls /projected-volume/
user
pass
$ cat /projected-volume/user
root
$ cat /projected-volume/pass
1f2d1e2e67df

从结果中可以看到,存储在 Etcd 中的用户名和密码信息已作为文件出现在容器的 Volume 目录中。文件名对应于kubectl create secret 命令中指定的键或Secret 对象data 字段中定义的键。

重要的是,当 Secret 挂载到容器中时,如果 Etcd 中的数据更新,Volume 中的文件也会更新。这是由 kubelet 组件管理的,它会定期维护这些 Volume。

但需要注意的是,这些更新可能会有一些延迟。因此,在应用程序代码中建立数据库连接时,最好实现重试和超时逻辑。

与 Secret 类似,ConfigMap 用于存储不需要加密的配置数据。ConfigMap 的使用几乎与 Secret 相同:你可以使用kubectl create configmap 从文件或目录创建 ConfigMap,或者直接编写 ConfigMap 对象的 YAML 文件。

例如,Java 应用程序所需的配置文件(如.properties 文件)可以存储在 ConfigMap 中,如下所示:

# .properties 文件内容
$ cat example/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice# 从 .properties 文件创建 ConfigMap
$ kubectl create configmap ui-config --from-file=example/ui.properties# 查看 ConfigMap 中保存的信息(data)
$ kubectl get configmaps ui-config -o yaml
apiVersion: v1
data:ui.properties: |color.good=purplecolor.bad=yellowallow.textmode=truehow.nice.to.look=fairlyNice
kind: ConfigMap
metadata:name: ui-config...

接下来,我们将讨论 Downward API,它允许 Pod 中的容器直接访问有关 Pod API 对象本身的信息。

例如:

apiVersion: v1
kind: Pod
metadata:name: test-downwardapi-volumelabels:zone: us-est-coastcluster: test-cluster1rack: rack-22
spec:containers:- name: client-containerimage: k8s.gcr.io/busyboxcommand: ["sh", "-c"]args:- while true; doif [[ -e /etc/podinfo/labels ]]; thenecho -en '\n\n'; cat /etc/podinfo/labels; fi;sleep 5;done;volumeMounts:- name: podinfomountPath: /etc/podinforeadOnly: falsevolumes:- name: podinfoprojected:sources:- downwardAPI:items:- path: "labels"fieldRef:fieldPath: metadata.labels

在这个 Pod 的 YAML 文件中,我定义了一个简单的容器并声明了一个 projected 类型的 Volume。这次,Volume 的数据源是 Downward API。此 Downward API Volume 配置为将 Pod 的metadata.labels 信息暴露给容器

通过此配置,当前 Pod 的 Labels 字段值将由 Kubernetes 自动挂载到容器中,作为/etc/podinfo/labels 文件。

容器的启动命令会不断打印/etc/podinfo/labels 的内容。因此,创建此 Pod 后,可以使用kubectl logs 命令查看打印的 Labels 字段,如下所示:

$ kubectl create -f dapi-volume.yaml
$ kubectl logs test-downwardapi-volume
cluster="test-cluster1"
rack="rack-22"
zone="us-est-coast"

目前,Downward API 支持丰富的字段,包括:

  1. 使用fieldRef,可以声明访问:

  • spec.nodeName - 节点的主机名

  • status.hostIP - 主机的 IP 地址

  • metadata.name - Pod 的名称

  • metadata.namespace - Pod 的命名空间

  • status.podIP - Pod 的 IP 地址

  • spec.serviceAccountName - Pod 的服务账户名称

  • metadata.uid - Pod 的 UID

  • metadata.labels['<KEY>'] - 指定<KEY> 标签的值

  • metadata.annotations['<KEY>'] - 指定<KEY> 注解的值

  • metadata.labels - Pod 的所有标签

  • metadata.annotations - Pod 的所有注解

2. 使用resourceFieldRef,可以声明访问:

随着 Kubernetes 的发展,Downward API 支持的字段列表可能会扩展。因此,此处列出的信息仅供参考,使用 Downward API 时应查阅官方文档以获取最新信息。

但需要注意的是,Downward API 只能访问在容器进程启动前可以确定的信息。如果需要容器运行后才可获得的信息,如容器的 PID,应考虑使用 sidecar 容器

在实践中,Secret、ConfigMap 和 Downward API 提供的信息也可以通过环境变量访问。但环境变量不支持自动更新。因此,我通常建议使用 Volume 文件来访问这些信息。

了解了 Secret 后,让我们转向与它们密切相关的概念:Service Account。

你可能会想,是否可以在 Pod 中安装 Kubernetes 客户端,以便直接从容器内部访问和操作 Kubernetes API。

是的,这是可能的。但首先需要解决 API Server 的授权问题。

Service Account 对象是 Kubernetes 内置的“服务账户”,用于权限分配。例如,Service Account A 可能仅限于对 Kubernetes API 的 GET 操作,而 Service Account B 可能拥有对所有 Kubernetes API 操作的完全访问权限。

此类 Service Account 的授权信息和凭证存储在与它们关联的特殊 Secret 对象中,称为 ServiceAccountToken。任何在 Kubernetes 集群中运行的应用程序都必须使用存储在此 ServiceAccountToken 中的授权信息(即 Token)合法访问 API Server。

因此,Kubernetes 中的 Projected Volume 本质上由三种类型组成:Secret、ConfigMap 和 Downward API。第四种类型 ServiceAccountToken 本质上是一种特殊的 Secret。

此外,Kubernetes 提供了一个默认的“服务账户”(default Service Account)以方便使用。任何在 Kubernetes 中运行的 Pod 都可以使用此默认 Service Account,而无需显式声明。

这是如何实现的?

通过 Projected Volume 机制实现。如果你检查 Kubernetes 集群中运行的任何 Pod,你会发现每个 Pod 自动声明了一个名为default-token-xxxx 的 Secret 类型 Volume,并将其挂载到每个容器的固定目录中。例如:

$ kubectl describe pod nginx-deployment-5c678cfb6d-lg9lw
Containers:
...Mounts:/var/run/secrets/kubernetes.io/serviceaccount from default-token-s8rbq (ro)
Volumes:default-token-s8rbq:Type:       Secret (a volume populated by a Secret)SecretName:  default-token-s8rbqOptional:    false

上述 Secret 类型 Volume 实际上是默认 Service Account 的 ServiceAccountToken。Kubernetes 自动为每个创建的 Pod 的spec.volumes 部分添加默认 ServiceAccountToken 的定义,并自动为每个容器包含相应的volumeMounts 字段。此过程对用户完全透明。

Pod 创建后,容器内的应用程序可以直接从挂载目录访问默认 ServiceAccountToken 的授权信息和文件。此路径在 Kubernetes 中是固定的:/var/run/secrets/kubernetes.io/serviceaccount。此 Secret 类型 Volume 的内容如下:

$ ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace  token

因此,你的应用程序可以直接加载这些授权文件以访问和操作 Kubernetes API。此外,如果你使用官方 Kubernetes 客户端库(k8s.io/client-go),它可以自动从该目录加载文件,你无需进行任何配置或编码。

在集群内运行 Kubernetes 客户端并使用默认 Service Account 进行自动授权的方法称为“InClusterConfig”,这是处理 Kubernetes API 编程授权的最推荐方式。

当然,考虑到自动挂载默认 ServiceAccountToken 的潜在风险,Kubernetes 允许你配置默认行为以不自动挂载此 Volume 到 Pod 中的容器

除了默认 Service Account 外,我们通常需要创建自定义 Service Account 以处理不同的权限设置。这样,Pod 中的容器可以使用这些自定义 Service Account 的 ServiceAccountTokens 进行授权。我们将在讨论 Kubernetes 插件开发时实践此操作。

接下来,让我们看看 Pod 的另一个重要配置:容器健康检查和恢复机制。

在 Kubernetes 中,你可以为 Pod 中的容器定义健康检查“探针”。这样,kubelet 将根据探针的返回值确定容器的状态,而不是仅依赖容器是否运行(来自 Docker 的信息)。此机制是确保生产环境中应用程序健康和正常运行的关键方法。

让我们看看 Kubernetes 文档中的一个示例。

apiVersion: v1
kind: Pod
metadata:labels:test: livenessname: test-liveness-exec
spec:containers:- name: livenessimage: busyboxargs:- /bin/sh- -c- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600livenessProbe:exec:command:- cat- /tmp/healthyinitialDelaySeconds: 5periodSeconds: 5

在这个 Pod 中,我们定义了一个有趣的容器。它启动后首先在/tmp 目录中创建一个名为healthy 的文件,表示其正常运行。30 秒后,它将删除此文件。

同时,我们定义了一个类型为exec 的 livenessProbe(健康检查)。这意味着容器启动后,将在容器内执行指定的命令,例如:cat /tmp/healthy。如果此文件存在,该命令的返回值为 0,Pod 将认为此容器不仅在运行,而且健康。此健康检查在容器启动后 5 秒开始执行(initialDelaySeconds: 5),并每 5 秒执行一次(periodSeconds: 5)。

现在,让我们将此过程付诸实践。

首先,创建此 Pod:

$ kubectl create -f test-liveness-exec.yaml

然后,检查 Pod 的状态:

$ kubectl get pod
NAME                READY     STATUS    RESTARTS   AGE
test-liveness-exec   1/1       Running   0          10s

可以看到,由于通过了健康检查,Pod 进入了 Running 状态。

30 秒后,让我们再次检查 Pod 的 Events:

$ kubectl describe pod test-liveness-exec

你会注意到 Pod 在 Events 中报告了一个异常:

FirstSeen LastSeen    Count   From            SubobjectPath           Type        Reason      Message
--------- --------    -----   ----            -------------           --------    ------      -------
2s        2s      1   {kubelet worker0}   spec.containers{liveness}   Warning     Unhealthy   Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory

显然,健康检查探针检测到/tmp/healthy 不再存在,因此报告容器不健康。接下来会发生什么?

让我们再次检查此 Pod 的状态:

$ kubectl get pod test-liveness-exec
NAME           READY     STATUS    RESTARTS   AGE
liveness-exec   1/1       Running   1          1m

你可能会注意到,Pod 并未进入 Failed 状态,而是保持在 Running 状态。为什么呢?

实际上,如果你观察到 RESTARTS 字段从 0 变为 1,你就会明白原因:出现异常的容器已被 Kubernetes 重启。在此过程中,Pod 保持 Running 状态。

需要注意的是,Kubernetes 没有与 Docker 相同的 Stop 语义。因此,尽管是重启,实际上意味着容器被重新创建。

此功能称为 Kubernetes 中的 Pod 恢复机制,也称为restartPolicy。它是 Pod 的 spec(pod.spec.restartPolicy)中的一个标准字段,默认值为 Always,表示:任何时候容器出现问题,都会被重新创建。

但需要强调的是,Pod 恢复过程始终发生在当前节点上,不会移动到其他节点。事实上,一旦 Pod 绑定到某个节点,它将一直留在该节点上,除非绑定发生变化(即pod.spec.node 字段被修改)。这意味着如果主机发生故障,Pod 不会自动迁移到其他节点。

如果你希望 Pod 出现在其他可用节点上,你需要使用 Deployment 等控制器来管理 Pod,即使你只需要一个副本。

你还可以调整restartPolicy 以更改 Pod 的恢复策略。除了 Always 外,还有两个选项:

  • Always:容器不运行时自动重启。

  • OnFailure:仅在容器失败时自动重启。

  • Never:永不重启容器

在实践中,你需要根据应用程序的特性设置这些恢复策略。

例如,如果一个 Pod 只是计算 1+1=2,退出并变为 Succeeded,使用restartPolicy=Always 强制重启此 Pod 的容器没有实际意义。

如果你需要在容器退出后保留上下文信息,如日志、文件和目录,应将restartPolicy 设置为 Never。否则,这些内容可能会在容器自动重新创建时丢失(被垃圾回收)。

值得一提的是,Kubernetes 官方文档总结了restartPolicy 与容器和 Pod 状态之间的复杂关系。实际上,你不需要记住所有这些细节。只需记住以下两个基本设计原则:

  • 只要restartPolicy 允许在失败时重启容器(如 Always),Pod 将保持在 Running 状态并尝试重启容器。否则,Pod 将进入 Failed 状态。

  • 对于包含多个容器的 Pod,只有当所有容器都处于异常状态时,Pod 才会进入 Failed 状态。在此之前,Pod 保持 Running 状态。此时,Pod 的 READY 字段将显示正常容器的数量。

$ kubectl get pod test-liveness-exec
NAME           READY     STATUS    RESTARTS   AGE
liveness-exec   0/1       Running   1          1m

因此,如果一个 Pod 只包含一个容器且该容器异常退出,Pod 只有在restartPolicy=Never 时才会进入 Failed 状态。在其他情况下,由于 Kubernetes 可以重启容器,Pod 的状态保持 Running。

如果 Pod 有多个容器且只有一个容器异常退出,Pod 仍将保持 Running 状态,即使restartPolicy=Never。只有当所有容器都异常退出时,Pod 才会进入 Failed 状态。

其他情况可以类似推导。

现在,让我们回到之前提到的 livenessProbe。

除了在容器内执行命令外,livenessProbe 还可以定义为发起 HTTP 或 TCP 请求。格式如下:

...
livenessProbe:httpGet:path: /healthzport: 8080httpHeaders:- name: X-Custom-Headervalue: AwesomeinitialDelaySeconds: 3periodSeconds: 3
...livenessProbe:tcpSocket:port: 8080initialDelaySeconds: 15periodSeconds: 20

因此,你的 Pod 可以暴露一个健康检查 URL(如/healthz)或配置健康检查以监控应用程序的监听端口。这两种方法在 Web 服务应用程序中非常常见。

在 Kubernetes Pod 中,还有一个名为readinessProbe 的字段。虽然其用法与livenessProbe 类似,但其目的却大不相同。readinessProbe 的成功或失败决定了 Pod 是否可以通过 Service 访问,但它不影响 Pod 的生命周期。这部分将在讨论 Services 时详细介绍。

讨论了这么多字段后,你现在应该对 Pod 的语义和描述能力有了初步的了解。

此时,你可能会想:Pod 中有这么多字段,而且不可能记住所有字段,Kubernetes 能否自动填充其中一些字段?

这个需求实际上非常实用。例如,开发者只需提交一个非常简单的 Pod YAML,Kubernetes 就可以自动为相应的 Pod 对象添加其他必要信息,如标签、注解、volumes 等。这些信息可以由运维团队预定义。

此功能称为 PodPreset,在 Kubernetes 版本 v1.11 中引入。

例如,假设开发者编写了以下pod.yaml 文件:

apiVersion: v1
kind: Pod
metadata:name: websitelabels:app: websiterole: frontend
spec:containers:- name: websiteimage: nginxports:- containerPort: 80

作为一个 Kubernetes 初学者,你可能会觉得这很有趣:这不就是你最习惯编写的最简单的 Pod YAML 文件吗?确实,你可能闭着眼睛都能写出这个 YAML 文件中的字段。

然而,如果运维人员看到这个 Pod,他们可能会摇头:这个 Pod 根本不适合生产环境!

在这种情况下,运维人员可以定义一个 PodPreset 对象。在此对象中,他们可以预定义任何希望添加到开发者编写的 Pod 中的字段。例如,考虑这个preset.yaml

apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:name: allow-database
spec:selector:matchLabels:role: frontendenv:- name: DB_PORTvalue: "6379"volumeMounts:- mountPath: /cachename: cache-volumevolumes:- name: cache-volumeemptyDir: {}

在这个PodPreset 定义中,第一部分是一个selector。这意味着后续定义将仅适用于具有selector 定义的标签 "role: frontend" 的 Pod,以防止“附带损害”。

然后,定义了一组 Pod 的 Spec 中的标准字段及其对应值。例如,env 字段定义了DB_PORT 环境变量,volumeMounts 指定了容器的 Volume 挂载路径,volumes 定义了一个emptyDir Volume。

接下来,假设运维人员首先创建了这个PodPreset,然后开发者创建了 Pod:

$ kubectl create -f preset.yaml
$ kubectl create -f pod.yaml

Pod 启动运行后,让我们检查此 Pod 的 API 对象:

$ kubectl get pod website -o yaml
apiVersion: v1
kind: Pod
metadata:name: websitelabels:app: websiterole: frontendannotations:podpreset.admission.kubernetes.io/podpreset-allow-database: "resource version"
spec:containers:- name: websiteimage: nginxvolumeMounts:- mountPath: /cachename: cache-volumeports:- containerPort: 80env:- name: DB_PORTvalue: "6379"volumes:- name: cache-volumeemptyDir: {}

此时,你可以清楚地看到 Pod 已添加了额外的标签、环境变量(env)、volumes 和 volume mounts,这些都与PodPreset 中指定的配置一致。此外,Pod 已自动注解,表明它已被PodPreset 修改。

需要注意的是,PodPreset 中定义的内容仅在 Pod 对象创建之前附加到 Pod API 对象中,并且不会影响任何 Pod 控制器的定义。

例如,如果你提交了一个nginx-deployment,Deployment 对象本身永远不会被PodPreset 修改;只有此 Deployment 创建的 Pod 会被修改。这个区别至关重要。

现在,让我们回答一个问题:如果为单个 Pod 定义了多个PodPreset 对象,会发生什么?

在实践中,Kubernetes 将合并来自不同PodPreset 对象的修改。但是,如果PodPreset 对象中指定的修改存在冲突,这些冲突字段将不会被更改。


http://www.ppmy.cn/news/1564013.html

相关文章

python发送get请求与post请求

一.首先要用到requests库 import requests requests库的不定长参数有13个 其中params,json,headers,data最常用 二.get案例一: (查看对应首页信息) url"http://baidu.com" pub_params {"application":"app",{"application_client_ty…

如何保证光谱相机的稳定性和可靠性

光学系统设计与制造 高质量光学元件&#xff1a;采用高精度研磨和镀膜的透镜、棱镜、光栅等光学元件。优质的透镜可以减少像差和色差&#xff0c;确保光线准确聚焦&#xff1b;高质量的镀膜能够提高光学元件的透光率&#xff0c;降低反射损失&#xff0c;并且增强对不同波段光…

leetcode 面试经典 150 题:汇总区间

链接汇总区间题序号228题型数组解法一次遍历法难度简单熟练度✅✅✅ 题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属…

云手机技术怎么实现的?

前言 随着亚矩阵云手机在跨境电商、海外社媒矩阵搭建、出海运营、海外广告投放、国内新媒体矩阵运营、品牌应用矩阵运营等领域内的普及和使用&#xff0c;云手机的理念已经被越来越多人所接受和认同。今天我们就一起来浅析一下&#xff0c;到底云手机的技术是怎么实现的&#…

2025华数杯国际赛A题完整论文讲解(含每一问python代码+数据+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2025“华数杯”国际大学生数学建模竞赛A题Can He Swim Faster的完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文…

idea 如何安装 github copilot

idea 如何安装 github copilot 要在 IntelliJ IDEA 中安装 GitHub Copilot&#xff0c;可以按照以下步骤操作&#xff1a; 打开 IntelliJ IDEA: 启动 IntelliJ IDEA。 打开插件管理器: 点击菜单栏中的 File。 选择 Settings&#xff08;Windows/Linux&#xff09;或 Prefere…

python创建pdf水印,希望根据文本长度调整水印字体大小,避免超出页面

为了根据文本长度动态调整水印字体大小&#xff0c;可以先测量文本长度&#xff0c;然后根据页面宽度和高度动态计算合适的字体大小。以下是修改后的代码&#xff1a; from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter from reportlab.pdfbas…

归子莫的科技周刊#2:白天搬砖,夜里读诗

归子莫的科技周刊#2&#xff1a;白天搬砖&#xff0c;夜里读诗 本周刊开源&#xff0c;欢迎投稿。 刊期&#xff1a;2025.1.5 - 2025.1.11。原文地址。 封面图 下班在深圳看到的夕阳&#xff0c;能遇到是一种偶然的机会&#xff0c;能拍下更是一种幸运。 白天搬砖&#xff0c;…