K8S系列深入解析Pod对象
Posted 颜淡慕潇
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了K8S系列深入解析Pod对象相关的知识,希望对你有一定的参考价值。
目录
序言
任何一件事情,只要坚持六个月以上,你都可以看到质的飞跃。
在上一篇文章中,【K8S系列】深入解析Pod对象(一)
我们深入解析了 Pod 的 API 对象,讲解了 Pod 和 Container 的关系。
作为 Kubernetes 项目里最核心的编排对象,Pod 携带的信息非常丰富。
今天,从一种特殊的 Volume ——Projected Volume 开始,主要讲解:
- Secret
- Downward API
从上面这两种类型展开,希望能帮助你更加深入地理解 Pod 对象各个重要字段的含义。
文章标记颜色说明:
- 黄色:重要标题
- 红色:用来标记结论
- 绿色:用来标记一级论点
- 蓝色:用来标记二级论点
1.Volume 简单介绍
Kubernetes(k8s)中的 Volume 是一种抽象概念,用于表示容器中的存储设备。
Volume 可以包含多个容器,以便它们可以在容器之间共享数据。Volume 在容器内部进行挂载,从而使容器中的应用程序可以像使用本地存储设备一样使用它们。
Kubernetes 中的 Volume 具有以下特点:
生命周期:Volume 可以独立于容器而存在,因此可以在容器之间共享和重用数据。
类型:Kubernetes 中支持多种类型的 Volume,例如 persistentVolumeClaim(PVC)emptyDir、hostPath、configMap、secret 、downwardAPI、projected、CSI等等。每种 Volume 类型都有其特定的用途和限制。
挂载方式:Volume 可以通过不同的挂载方式挂载到容器中,例如作为容器的文件夹、作为容器的环境变量等等。每种挂载方式也都有其特定的用途和限制。
存储介质:Volume 可以与不同的存储介质一起使用,例如本地磁盘、网络存储、云存储等等。
Kubernetes 中的 Volume 提供了一种灵活、可扩展的方式来管理容器中的数据存储。通过使用 Volume,可以轻松地实现容器之间的数据共享和数据持久化,从而使 Kubernetes 应用程序更加可靠和可维护。
2 Projected Volume 介绍
在 Kubernetes 中,有几种特殊的 Volume,它们存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。
这些特殊 Volume 的作用,是为容器提供预先定义好的数据。
所以,从容器的角度来看,这些 Volume 里的信息就是仿佛是被 Kubernetes“投射”(Project)进入容器当中的。这正是 Projected Volume 的含义
详细介绍一下:
Kubernetes(k8s)中的 projected Volume 是一种特殊的 Volume 类型,可以把它翻译为“投射数据卷”。
简单说,它可以将多个 Volume 类型投影到一个 Volume 中。
它可以将多个 Volume 挂载到容器中,从而实现更加灵活和高效的容器化部署。
projected Volume 支持以下四种 Volume 类型的投影:
Secret:将 Kubernetes 中的 Secret 对象挂载到容器中,从而使容器可以访问加密的敏感信息,如密码、证书等。
ConfigMap:将 Kubernetes 中的 ConfigMap 对象挂载到容器中,从而使容器可以访问 ConfigMap 中的配置信息。
DownwardAPI:将容器的元数据(如 Pod 名称、命名空间、标签等)挂载到容器中,从而使容器可以访问自身的元数据。
ServiceAccountToken:将 ServiceAccount 的令牌挂载到容器中,从而使容器可以通过 API 服务器与 Kubernetes API 进行通信。
使用 projected Volume 可以将多个 Volume 类型投影到一个 Volume 中,从而简化 Volume 的管理和配置。
例如,可以将 ConfigMap 和 Secret 投影到一个 Volume 中,并将其挂载到容器中,以便容器可以访问配置信息和加密的敏感信息。
使用 projected Volume 可以提高容器应用程序的灵活性和可维护性,从而更加高效地进行容器化部署。
2.1 Secret
2.1.1 yaml讲解
我们先来看一下Secret,它的作用,是帮你把 Pod 想要访问的加密数据,存放到 Etcd 中。然后,就可以通过在 Pod 的容器里挂载 Volume 的方式,访问到这些 Secret 里保存的信息了。
Secret 最典型的使用场景,是存放数据库的 Credential 信息,比如下面这个例子:
文件名:test-projected-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-projected-volume
spec:
containers:
- name: test-secret-volume
image: busybox
args:
- sleep
- "86400"
volumeMounts:
- name: mysql-cred
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: mysql-cred
projected:
sources:
- secret:
name: user
- secret:
name: passward
小插曲:
在这个例子中,我们用到了BusyBox镜像,简单介绍下这个镜像:
BusyBox是一个开源项目,提供了一个类似于UNIX操作系统的命令行界面,它包含了许多常用的工具程序,如文件操作、文本处理、网络配置、系统监测等。它被设计成在嵌入式系统中使用,因为它占用的空间很小,可以在资源受限的设备上运行。
在容器技术中,BusyBox通常被用作一个基础镜像,因为它非常轻量级,可以帮助构建小型容器,这也是它被称为"微型容器"的原因。
另外,很多容器镜像都会以BusyBox为基础,通过添加其他应用程序和库来构建完整的应用程序容器。
言归正传,讲下yaml示例:
在这个 Pod 中,定义了一个简单的容器。它声明挂载的 Volume,是 projected 类型。这个 Volume 的数据来源(sources),则是名为 user 和 pass 的 Secret 对象,分别对应的是数据库的用户名和密码。
这里用到的数据库的用户名、密码,就是以 Secret 对象的方式交给 Kubernetes 保存的。完成这个操作的指令,如下所示:
$ kubectl create secret generic user --from-file=./username.txt
$ kubectl create secret generic passward --from-file=./password.txt
如果要查看这些 Secret 对象的话,只要执行一条 kubectl get 命令就可以了:
kubectl get secrets
结果如下:
NAME TYPE DATA AGE
user Opaque 1 30s
passward Opaque 1 30s
又来一个小插曲:
除了使用 kubectl create secret 指令外,我们也可以直接通过编写 YAML 文件的方式来创建这个 Secret 对象,如:
apiVersion: v1
kind: Secret
metadata:
name: mytestsecret
type: Opaque
data:
user: YWRtaW4=
passward: YWRtaW4xMjM0
注意:通过编写 YAML 文件创建出来的 Secret 对象只有一个。
它的 data 字段,是以 Key-Value 的格式保存了两份 Secret 数据。其中,“user”就是第一份数据的 Key,“passward”是第二份数据的 Key。
需要注意的是,Secret 对象要求这些数据必须是经过 Base64 转码的,以免出现明文密码的安全隐患。这个转码操作也很简单,比如:
➜ ~ echo -n 'admin' | base64
YWRtaW4=
➜ ~ echo -n 'admin1234' | base64
YWRtaW4xMjM0
Tips:
需要注意的是,像这样创建的 Secret 对象,它里面的内容仅仅是经过了转码,而并没有被加密。在真正的生产环境中,需要在 Kubernetes 中开启 Secret 的加密插件,增强数据的安全性
2.1.2 创建Pod
言归正传:
我们来创建一下这个 Pod,文件名为:test-projected-volume.yaml
kubectl create -f test-projected-volume.yaml
查看结果:
$ kubectl exec -it test-projected-volume -- /bin/sh
$ ls /projected-volume/
user
passward
$ cat /projected-volume/user
admin
$ cat /projected-volume/passward
admin1234
从上面结果中,可以看到,保存在 Etcd 里的用户名和密码信息,已经以文件的形式出现在了容器的 Volume 目录里。
而这个文件的名字,就是 kubectl create secret 指定的 Key,或者说是 Secret 对象的 data 字段指定的 Key。
更重要的是,像这样通过挂载方式进入到容器里的 Secret,一旦其对应的 Etcd 里的数据被更新,这些 Volume 里的文件内容,同样也会被更新。其实,这是 kubelet 组件在定时维护这些 Volume。
2.2 Downward API
2.2.1 yaml示例
Downward API:是让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息。
举例:
apiVersion: v1
kind: Pod
metadata:
name: test-downwardapi-volume
labels:
zone: us-est-coast
cluster: test-cluster1
rack: rack-22
spec:
containers:
- name: client-container
image: k8s.gcr.io/busybox
command: ["sh", "-c"]
args:
- while true; do
if [[ -e /etc/podinfo/labels ]]; then
echo -en '\\n\\n'; cat /etc/podinfo/labels; fi;
sleep 5;
done;
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
readOnly: false
volumes:
- name: podinfo
projected:
sources:
- downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
在这个 Pod 的 YAML 文件中,定义了一个简单的容器,声明了一个 projected 类型的 Volume。
只不过这次 Volume 的数据来源,变成了 Downward API。而这个 Downward API Volume,则声明了要暴露 Pod 的 metadata.labels 信息给容器。
2.2.2 Downward API 支持字段
Kubernetes Downward API 允许容器在运行时通过环境变量或卷挂载的方式获取 Pod 和容器的元数据信息。支持的字段包括:
- metadata.name: Pod 的名称
- metadata.namespace: Pod 的命名空间
- metadata.labels: Pod 的标签
- metadata.annotations: Pod 的注释
- spec.nodeName: Pod 调度到的节点名称
- spec.serviceAccountName: Pod 使用的服务账户名称
- spec.containers[*].name: 容器名称
- spec.containers[*].image: 容器使用的镜像名称
- spec.containers[*].imagePullPolicy: 容器拉取镜像使用的策略
- spec.containers[].ports[].containerPort: 容器暴露的端口号
- spec.containers[].env[].name: 容器中定义的环境变量名称
- spec.containers[].env[].value: 容器中定义的环境变量值
- status.hostIP: Pod 调度到的节点的 IP 地址
- status.podIP: Pod 的 IP 地址
- status.podIPs[*].ip: Pod 的 IP 地址(多网卡情况下)
- status.startTime: Pod 启动时间
需要注意的是,这些字段可能会因 Kubernetes 版本而有所不同,具体的支持情况需要参考相应版本的文档
不过,需要注意的是,Downward API 能够获取到的信息,一定是 Pod 里的容器进程启动之前就能够确定下来的信息。
而你如果想要获取 Pod 容器运行后才会出现的信息,比如,容器进程的 PID,那就肯定不能使用 Downward API 了,而应该考虑在 Pod 里定义一个 sidecar 容器。
其实,Secret、ConfigMap,以及 Downward API 这三种 Projected Volume 定义的信息,大多还可以通过环境变量的方式出现在容器里。
但是,通过环境变量获取这些信息的方式,不具备自动更新的能力。所以,一般情况下,都建议你使用 Volume 文件的方式获取这些信息。
3 投票
K8S系列深入解析Pod对象
目录
序言
任何一件事情,只要坚持六个月以上,你都可以看到质的飞跃。
在上一篇文章中:【K8S系列】Pod详解
详细介绍了 Pod 这个 Kubernetes 项目中最重要的概念。
而在今天这篇文章中,我会和你分享 Pod 对象的更多细节。希望这篇文章,能帮助你对K8s-pod有个进一步的了解
文章标记颜色说明:
- 黄色:重要标题
- 红色:用来标记结论
- 绿色:用来标记一级论点
- 蓝色:用来标记二级论点
1.问题引入
1.1 问题描述
通过上一篇的学习,我们知道Pod,不是容器,而是 Kubernetes 项目中的最小编排单位。
将这个设计落实到 API 对象上,容器(Container)就成了 Pod 属性里的一个普通的字段。
那么,我们思考一个问题:
到底哪些属性属于 Pod 对象,而又有哪些属性属于 Container 呢?
2 问题解答
需要明确一个概念,也是上一篇文章提到的:Pod 扮演的是传统部署环境里“虚拟机”的角色。
这样的设计,是为了使用户从传统环境(虚拟机环境)向 Kubernetes(容器环境)的迁移,更加平滑。
所以,我们可以把 Pod 看成传统环境里的“机器”、把容器看作是运行在这个“机器”里的“用户程序”,那么很多关于 Pod 对象的设计是不是就容易理解了?
比如,凡是调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的。
这些属性的共同特征是,它们描述的是“机器”这个整体,而不是里面运行的“程序”。
比如:
- Pod 的网络定义:即-配置这个“机器”的网卡
- Pod 的存储定义:即-配置这个“机器”的磁盘
- Pod 的安全定义:即-配置这个“机器”的防火墙
- Pod 的资源调度:即-这台“机器”运行在哪个服务器之上
下面先介绍 Pod 中几个重要字段的含义和用法
2.1 pod 属性
2.1.1 NodeSelector
NodeSelector:是一个供用户将 Pod 与 Node 进行绑定的字段,
用法如下所示:
apiVersion: v1 kind: Pod …… spec: nodeSelector: disktype: ssd
这个配置,意味着这个 Pod 永远只能运行在携带了“disktype: ssd”标签(Label)的节点上;否则,它将调度失败。
2.1.2 HostAliases
HostAliases:定义了 Pod 的 hosts 文件(比如 /etc/hosts)里的内容,用法如下:
apiVersion: v1 kind: Pod ... spec: hostAliases: - ip: "10.1.2.4" hostnames: - "test1.remote" - "test2.remote" ...
在这个 Pod 的 YAML 文件中,设置了一组 IP 和 hostname 的数据。这样,这个 Pod 启动后,/etc/hosts 文件的内容将如下所示:
cat /etc/hosts # Kubernetes-managed hosts file. 127.0.0.1 localhost ... 10.244.135.10 hostaliases-pod 10.1.2.4 test1.remote 10.1.2.4 test2.remote
其中,最下面两行记录,是通过 HostAliases 字段为 Pod 设置的。
需要指出的是,在 Kubernetes 项目中,如果要设置 hosts 文件里的内容,一定要通过这种方法。
否则,如果直接修改了 hosts 文件的话,在 Pod 被删除重建之后,kubelet 会自动覆盖掉被修改的内容。
2.1.3 shareProcessNamespace
凡是跟容器的 Linux Namespace 相关的属性,也一定是 Pod 级别的。
因为,Pod 的设计,是要让它里面的容器尽可能多地共享 Linux Namespace,仅保留必要的隔离和限制能力。
这样,Pod 模拟出的效果,就跟虚拟机里程序间的关系非常类似了。
举个例子,在下面这个 Pod 的 YAML 文件中,定义了 shareProcessNamespace=true:
apiVersion: v1 kind: Pod metadata: name: nginx spec: shareProcessNamespace: true containers: - name: nginx image: nginx - name: shell image: busybox stdin: true tty: true
这就意味着这个 Pod 里的容器要共享 PID Namespace。
在这个 YAML 文件中,定义了两个容器:
- 一个是 nginx 容器,
- 一个是开启了 tty 和 stdin 的 shell 容器。
什么是tty 和 stdin,可以直接认为 tty 就是 Linux 给用户提供的一个常驻小程序,用于接收用户的标准输入,返回操作系统的标准输出。
当然,为了能够在 tty 中输入信息,还需要同时开启 stdin(标准输入流)。
这个 Pod 被创建后,就可以使用 shell 容器的 tty 跟这个容器进行交互了。我们一起实践一下:
1. 创建pod:
kubectl create -f nginx.yaml
2. 进入pod:使用 kubectl attach 命令,连接到 shell 容器的 tty 上
kubectl attach -it nginx -c shell
3. 查看进程:shell 容器里执行 ps 指令,查看所有正在运行的进程:
/ # ps ax PID USER TIME COMMAND 1 root 0:00 /pause 8 root 0:00 nginx: master process nginx -g daemon off; 14 101 0:00 nginx: worker process 15 root 0:00 sh 21 root 0:00 ps ax
可以看到,在这个容器里,不仅看到它本身的 ps ax 指令,还可以看到 nginx 容器的进程,以及 Infra 容器的 /pause 进程。
这就意味着,整个 Pod 里的每个容器的进程,对于所有容器来说都是可见的:它们共享了同一个 PID Namespace。
2.1.4 NodeName
NodeName:当Pod 的这个字段被赋值,Kubernetes 项目就会被认为这个 Pod 已经经过了调度,调度的结果就是赋值的节点名字。
所以,这个字段一般由调度器负责设置,但用户也可以设置它来“骗过”调度器,当然这个做法一般是在测试或者调试的时候才会用到。
2.1.5 其他pod属性
凡是 Pod 中的容器要共享宿主机的 Namespace,也一定是 Pod 级别的定义,
比如:
- Network
- IPC
- PID
- Namespace
apiVersion: v1 kind: Pod metadata: name: nginx spec: hostNetwork: true hostIPC: true hostPID: true containers: - name: nginx image: nginx - name: shell image: busybox stdin: true tty: true
在这个 Pod 中,定义了共享宿主机的 Network、IPC 和 PID Namespace。
这就意味着,这个 Pod 里的所有容器,会直接使用宿主机的网络、直接与宿主机进行 IPC 通信、看到宿主机里正在运行的所有进程。
2.2 容器属性
Kubernetes 项目中对 Container 的定义,和 Docker 相比并没有什么太大区别。
- Image(镜像)
- Command(启动命令)
- workingDir(容器的工作目录)
- Ports(容器要开发的端口)
- volumeMounts(容器要挂载的 Volume)
这些都是构成 Kubernetes 项目中 Container 的主要字段。不过除了这些,还有这么几个属性值得我们额外关注。
2.2.1 ImagePullPolicy
ImagePullPolicy 字段。它定义了镜像拉取的策略。注意事项:
- Always:即每次创建 Pod 都重新拉取一次镜像
- Never / IfNotPresent:则意味着 Pod 永远不会主动拉取这个镜像,或者只在宿主机上不存在这个镜像时才拉取。
- ImagePullPolicy 的值默认是Always 。
- 当容器的镜像是类似于 nginx 或者 nginx:latest 这样的名字时,ImagePullPolicy 也会被认为 Always。
2.2.2 Lifecycle
Lifecycle 字段。它定义的是 Container Lifecycle Hooks。
顾名思义,Container Lifecycle Hooks 的作用,是在容器状态发生变化时触发一系列“钩子”。我们来看这样一个例子:
apiVersion: v1 kind: Pod metadata: name: lifecycle-demo spec: containers: - name: lifecycle-demo-container image: nginx lifecycle: postStart: exec: command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"] preStop: exec: command: ["/usr/sbin/nginx","-s","quit"]
postStart :指在容器启动后,立刻执行一个指定的操作。
另外,postStart 定义的操作,虽然是在 Docker 容器 ENTRYPOINT 执行之后,但它并不严格保证顺序。也就是说,在 postStart 启动时,ENTRYPOINT 有可能还没有结束。
当然,如果 postStart 执行超时或者错误,Kubernetes 会在该 Pod 的 Events 中报出该容器启动失败的错误信息,导致 Pod 也处于失败的状态。
preStop: 发生的时机,则是容器被杀死之前(比如,收到了 SIGKILL 信号)。
而需要明确的是,preStop 操作的执行,是同步的。
所以,它会阻塞当前的容器杀死流程,直到这个 Hook 定义操作完成之后,才允许容器被杀死,这跟 postStart 不一样。
所以,在上面例子中,它在容器成功启动之后,在 /usr/share/message 里写入了一句“Hello from the postStart handler”(即 postStart 定义的操作)。
而在这个容器被删除之前,则先调用了 nginx 的退出指令(即 preStop 定义的操作),从而实现了容器的“优雅退出”。
3 总结
在这篇文章中,详细讲解了 Pod API 对象,介绍了 Pod 的核心使用方法,并分析了 Pod 和 Container 在字段上的异同。
希望这些讲解能够帮你更好地理解和记忆 Pod YAML 中的核心字段,以及这些字段的准确含义。
最后,Pod API 对象是整个 Kubernetes 体系中最核心的一个概念,后面讲解各种控制器时都会反复提到。
4. 投票
以上是关于K8S系列深入解析Pod对象的主要内容,如果未能解决你的问题,请参考以下文章