监控数据的可视化分析神器 Grafana 的告警实践
672
2022-10-28
详解 Kubernetes 中的 Pod
1. 引言
前面的文章中,我们相信介绍了 Kubernetes 的组成和架构,并且搭建出了一个基础的 Kubernetes 集群。
但我们对于 Kubernetes 最基础的 Pod 的了解仍然十分有限,本文我们就来详细介绍和讲解一下 Kubernetes 最核心的抽象 -- Pod。
2. 什么是 Pod
在操作系统中,程序往往并非是单兵作战的,如果我们执行 pstree 命令,就可以看到进程是以成组的方式运行的,这才是最常见的状态。
想想我们的线上服务,各个服务之间也有着复杂的种种关系,即便是在单机上,也不乏这样需要成组调度的进程,这些进程间错综复杂的“关系”,对于一个进程即一个镜像的 Docker 抽象来说,是很难去处理的,这就需要在此之上进一步的抽象,来解决成组调度的问题。于是 Pod 应运而生。
事实上,Pod 只是 Kubernetes 中的一层逻辑概念,Kubernetes 调度的仍然是基础的容器,只是经过我们的配置,Kubernetes 将一些容器看作一个 Pod,从而能够统一调度,进而让他们处于同一个 Linux Namespace 中。
3. Pod 实现共享的手段 -- Infra 容器
3.1 传统部署面临的挑战
传统通过 docker 镜像部署的方法是很难处理 Linux Namespace 的共享问题的。
假设有 A、B 两个容器,我们想让他们共享网络和 Volume Namespace,那么就需要县启动其中的一个:
$ docker run B
然后再在另一个启动时加入参数:
$ docker run --net=B --volumes-from=B --name=A image-A
这样一来,docker B 必须先启动,docker A 后启动,这两个本是一组的两个容器却不得不有了启动的先后,让他们的关系从平等关系变成了拓扑关系,如果规模扩大,这样的问题就会变得无比复杂而难以处理。
3.2 Kubernetes 的解决方案 -- Infra 容器
Kubernetes 解决上述问题靠的是引入 Infra 容器:
Pod 中各容器可以直接使用 localhost 进行通信,因为他们共享了 Infra 容器的 Net Namespace。各容器看到的网络设备与 Infra 容器看到的完全一致。Pod 的生命周期只与 Infra 容器一致,而与容器 A 及容器 B 无关。
例如下面的 Pod 配置:
apiVersion: v1kind: Podmetadata: name: two-containersspec: restartPolicy: Never volumes: - name: shared-data hostPath: path: /data containers: - name: nginx-container image: nginx volumeMounts: - name: shared-data mountPath: /usr/share/nginx/html - name: debian-container image: debian volumeMounts: - name: shared-data mountPath: /pod-data command: ["/bin/sh"] args: ["-c", "echo Hello from the debian container > pod-data/index.html"]
如果在 Pod 中配置和开启了 PID Namespace 的共享,就可以在容器中看到一个名为“/pause”的进程,它就是 Infra 的容器进程。
3.3 实例
考虑一个 java 应用应该如何被部署到云服务器上的呢?在传统的 Docker 部署模式下,我们可以看到 java 程序是以 tomcat 进程的方式运行起来的。而打包有 java 代码的 war 包则仅仅是 tomcat webapps 目录下的一部分。
于是,基于 Docker 的部署方案有两种选择:
每次上线前,将打包好的 war 包放到 tomcat 基础镜像中的 webapps 目录下,然后再打一个新的镜像,这个新镜像用来在线上部署;所有的宿主机都外挂一个分布式存储系统,然后映射到磁盘上的一个指定路径,并且在 Tomcat 镜像启动时挂载到 Tomcat 中。
方案 1 看起来很方便,但一来我们会感觉到显然每次上线前都要进行的镜像打包工作是不必要的,二来,在遇到问题需要回滚前,首先需要将要回滚到的 war 包放到 Tomcat 镜像的 webapps 目录然后重新打镜像,之后再部署,这对于线上问题的处理也太过繁琐。
而方案 2 虽然免去了反复打包的困扰,但我们不得不维护一个分布式存储系统,看起来也毫无必要。
但在 Kubernetes 看来,问题却很容易解决:
apiVersion: v1kind: Podmetadata: name: java-webspec: volumes: - name: app-volume emptyDir: {} initContainers: - image: techlog/smaple:v0.0.1 name: war command: ["cp", "/sample.war", "/app"] volumeMounts: - mountPath: /app name: app-volume containers: - image: techlog/tomcat:7.0 name: tomcat command: ["sh", "-c", "/root/apache-tomcat-7.0.42-v2/bin/start.sh"] volumeMounts: - name: app-volume ports: - containerPort: 8080 hostPort: 8001
在 Pod 的 spec 定义中,initContainers 是先于 containers 中定义的镜像先行启动的镜像列表。同时,不同于 containers 中不保证启动顺序的启动,initContainers 中定义的容器是保证按照列表顺序顺次启动的。
基于上述定义,我们的 war 包只需要每次放到宿主机的固定位置然后被复制到容器的指定路径即可,再也不用反复执行打镜像的操作了,世界是不是都清爽了呢?
这个例子中的 sample 镜像就充当了一个 sidecar 的角色,所谓的 sidecar,就是一种将应用程序的功能划分为单独进程的设计模式。类似的,我们也可以将日志收集、上报等功能划分出来作为一个 sidecar 容器单独启动起来,从而让整个系统更为清晰。
4. Pod 的配置
Pod 是 Kubernetes 的最小调度单位,而 Container 是 Pod 的最小组成单位。我们知道,容器其实就是单个启动的进程,而 Pod 则是要统一部署和调度的一组进程,因此,配置 Pod 时,需要考虑什么属性是进程组维度的,什么属性是进程维度的。
以下字段在 Pod 配置中是非常重要的:
NodeSelector
供用户将 Pod 与 Node 绑定的字段:
apiVersion: v1kind: pod...spec: nodeSelector: disktype: ssd
这样一来,containers 中只有打了这个指定的键值对 “disktype: ssd” 的容器才会在节点上运行。
HostAliases
用来在 Pod 的 hosts 文件(/etc/hosts)中添加的内容:
apiVersion: v1kind: pod...spec: hostAliases: - ip: "10.0.0.1" hostnames: - "foo.remote" - "bar.remote" - ip: "127.0.0.1" hostnames: - "foo.local" - "bar.local"
在 pod 启动后,/etc/hosts 文件中便会出现:
10.0.0.1 foo.remote bar.remote127.0.0.1 foo.local bar.local
Linux Namespace 相关配置
想要在 Pod 中开启共享哪种 Linux Namespace 资源,只要在 spec 中配置相应的开关即可,例如:
shareProcessNamespace: true -- 开启 PID NameSpace 共享。也可以通过配置 hostPID 实现;hostIPC: truehostNetwork: true
ImagePullPolicy
镜像拉取策略,可以选择:
Always -- 每次启动前都拉取一次;Never -- 从不拉取镜像;IfNotPresent -- 只有不存在的时候拉取。
Lifecycle
Pod 允许用户定义容器状态变化时触发的钩子,例如:
apiVersion: v1kind: Podmetadata: name: lifecycle-demospec: 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: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]
它定义了容器启动前与结束前要做的事。
5. Pod 的生命周期
一个 Pod 的生命周期也就是这个 API 对象的 status,有以下五种:
Pending -- API 对象已经成功创建,并且保存在 etcd 中,但 Pod 中的某些容器创建、调度不成功。Running -- Pod 调度成功,且所有包含的容器都已经创建成功,至少有一个容器正在运行。Secceeded -- 所有容器都已经正常运行完毕并退出。Failed -- 至少有一个容器以非 0 返回码的错误状态退出。Unknown -- Pod 状态异常,可能是与 Kubernetes 的 Master 节点通信出现了问题。
当需要进一步排查引起相应状态的原因时,我们需要关注 Pod 的 Events 以及细分的状态字段 Conditions,它包括:
PodScheduled -- Pod 已经被调度;Ready -- Pod 已就绪;Initialized -- Pod 已完成初始化;Unschedulable -- Pod 无法被调度。
发表评论
暂时没有评论,来抢沙发吧~