Kubernetes-Pod的启停和正确处理客户端请求

Pod的创建流程

pod是k8s中的基本单元,每个pod都包含了一个特殊的根容器Pause和一或多个紧密相关的业务容器。
Pause容器解决了Pod中一个容器死亡是整体死亡还是部分死亡的问题,即以pause容器的状态代表了整个pod的状态。另外pause还简化了pod中容器之间的通信和文件共享的问题。
在CRI(container runtime interface)中,pod中容器所共有的环境和定义的资源约束被称为PodSandBox,PodSandBox在作为Docker运行时就是一个Linux的命名空间,kubelet将创建一个cgroup给容器运行时,并运行所有进程来保障对Pod的资源约束。
我们都知道kubelet是启动、停止和删除容器的组件,其流程如下:
1、kubectl 向 Api server 发出创建pod的请求;
2、Api server接收请求,生成包含创建信息的yaml写入etcd;
3、scheduler 查看Api server,判断该Pod是否是新建的,如需创建,则进行调度计算,分配节点,并将信息写入etcd;
4、kubelet 监测etcd,发现创建pod信息,将该信息中的node编号与自己比较,如果相同,则调用Docker的api创建container;
5、在启动pod前,kubelet会调用RuntimeService.RunPodSandbox创建基础环境(网络资源等),基础环境就绪后,就可以创建、启停和删除不同的容器。

生命周期钩子

lifecycle hooks:
Post-start hooks:启动后的钩子
Pre-stop hooks:停止前的钩子
这两种生命周期的钩子是针对容器来指定的,和初始化init容器不同,init容器是针对整个pod。

Post-start hooks:
该类型的钩子是在容器的主进程启动时被执行的,它与主进程是并行执行的,它可以在我们的应用启动时做一些其他工作,可以让用户在不改动应用程序的情况下运行一些需要的命令。
Pre-stop hooks:
该类型的钩子是在容器被关闭之前立即执行的,在一个配置了Pre-stop hooks的容器中,其关闭之前会执行该钩子,执行后才会向容器发出sigterm的信号。
与Post-start hooks不同,无论Pre-stop hooks是否执行成功,容器最终都会被终止。如果该钩子失败,你会看到FailedPreStopHook的警告,但是因为Pod被关闭终止,你可能很难看到这个警告。
因此这里需要注意的是,如果你的Pre-stop hooks定义了很重要的内容,你需要确认的是该钩子是否成功执行。

优雅的关闭Pod

在kubelet终止pod时,会给每个容器一定的时间来优雅的停止,这个时间叫做终止宽限期(termination grace period)。
该值可在 Pod 的的.spec.terminationGracePeriodSeconds 字段中定义,默认为30s,也可在kubectl delete 时通过 --grace-period 参数指定一个时间(如Pod中已定义,则覆盖 Pod 中的配置)。当该值超过后,会使用SIGKILL 强制干掉 Pod。

在一个设置了Pre-stop hooks的Pod关闭流程:
1、Api server接收delete请求,修改etcd中的相关状态
2、Api server给pod设置deletion timestamp,终止进程开始工作
这里api server还会将删除时间通知给kubelet和controller manager中的endpoint控制器,这两个并行事件在这里用A(kubelet)和B(endpoint controller)表示:
A.1、执行Pre-stop hooks,等待执行完毕
A.2、向容器主进程发送sigterm信号
A.3、等待容器优雅的关闭或者宽限期超时
A.4、如果主进程没有优雅的关闭则使用SIGKILL 强制终止进程

B.1、向api server发送修改endpoint api对象的请求,从endpoint中移除pod 的ip地址
B.2、api server通知所有kube-proxy服务更新iptables,kube-proxy从iptables中移除对应pod

这里我们关注kubelet该事件的流程:

对于一些需要严格准守优雅关闭的服务,比如数据库、分布式的数据存储来说,如果不能确保你的服务在宽限期之内被正常的关闭,这里推荐使用一个专门用来处理关闭流程的Pod(批处理或者定时任务),来持续或者周期性的监控那些没有被正常处理或者迁移的数据。

Pod启停时的应用

Pod启动与服务就绪

ReadinessProbe探针是用来判断容器服务是否达到可用的状态,达到可用状态的Pod才可以接收请求。
对于被Service管理的Pod来说,Service与Pod后端ip的映射也是基于pod是否就绪来设置的,如果pod的状态从ready变为flase,那么系统会将Service对应的后端ip隔离出去,知道Pod的状态恢复到ready,这样就保证了客户端请求不会被转发到服务不可用的Pod上。

如果我们在一个Pod中没有指定就绪探针,那么pod默认总是ready状态,这个被认为总是就绪的pod会立即开始提供服务,尽管这个时候服务是不可用的,这时客户端就会收到connection refused等报错信息。
妥善处理Pod启动的第一步就是添加一个正常获取服务url请求的就绪探针。

Pod关闭和客户端请求

结合在上述的Pod关闭流程里对于两个并行事件的描述,我们不难推测在kube-proxy更新iptables比容器主进程收到SIGNTERM信号花费时间长的情况,在这种情况下客户端请求依旧会通过没有完成更新的iptables到达已经停止接收请求的服务,这时客户端就会收到连接被拒绝的错误。
在这里插入图片描述

那么怎么去解决客户端连接被拒绝的错误,我们知道如果在Pod接收到SIGNTERM信号后立即停止服务,那么所有尝试连接该pod的客户端都会收到连接被拒绝的错误。,因此这里Pod必须在接收到SIGNTERM信号后仍然提供服务直到所有的iptables更新完毕。
但是我们无法保证所有工作节点上的kube-proxy或者其他负载均衡分发的组件对网络状态更新完毕的时间,目前我们能做的只有用Pre-stop hooks来给kube-proxy预留一定的完成时间,来尽可能提高用户的体验。
需要注意的是preStop的时间不能太长,长时间的延迟会导致容器无法正常关闭的异常情况,并且会导致delete后pod还会显示在pod列表中,这会给删除pod的用户带来困扰。

lifecycle:
  preStop:
     exec:
         command:
         - sh
         - -c
         - "sleep 5"
要使用kubernetes-client java获取pod容器内存与CPU使用率,可以按照以下步骤进行操作。 首先,你需要在Java项目中添加kubernetes-client的依赖,例如在pom.xml文件中添加以下代码: ```xml <dependencies> <dependency> <groupId>io.kubernetes</groupId> <artifactId>client-java</artifactId> <version>9.0.0</version> </dependency> </dependencies> ``` 接下来,你可以使用以下代码获取pod的相关信息: ```java import io.kubernetes.client.apis.CoreV1Api; import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.models.V1Container; import io.kubernetes.client.models.V1ContainerStatus; import io.kubernetes.client.models.V1NamespaceList; import io.kubernetes.client.models.V1Pod; import io.kubernetes.client.models.V1PodList; import io.kubernetes.client.models.V1PodMetrics; import io.kubernetes.client.models.V1PodMetricsList; import io.kubernetes.client.util.Config; import java.util.Map; public class KubernetesClientExample { public static void main(String[] args) throws Exception { // 创建Kubernetes客户端 io.kubernetes.client.Configuration config = Config.defaultClientConfig(); io.kubernetes.client.apis.CoreV1Api api = new CoreV1Api(); // 获取pod列表 V1PodList podList = api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null); for (V1Pod pod : podList.getItems()) { // 获取pod所属的命名空间和名称 String namespace = pod.getMetadata().getNamespace(); String name = pod.getMetadata().getName(); // 获取pod的容器列表和相关状态信息 for (V1Container container : pod.getSpec().getContainers()) { String containerName = container.getName(); V1ContainerStatus containerStatus = pod.getStatus().getContainerStatuses().stream() .filter(status -> status.getName().equals(containerName)) .findFirst().orElse(null); if (containerStatus != null) { // 获取容器的CPU和内存使用率 Map<String, Quantity> usage = containerStatus.getUsage(); Quantity cpuUsage = usage.get("cpu"); Quantity memoryUsage = usage.get("memory"); System.out.println("Namespace: " + namespace + ", Pod: " + name + ", Container: " + containerName + ", CPU Usage: " + cpuUsage + ", Memory Usage: " + memoryUsage); } } } } } ``` 上述代码中,我们首先创建了Kubernetes客户端,然后通过CoreV1Api实例来获取pod列表。对于每个pod,我们遍历其容器列表,并获取容器的名称以及相关状态信息。在状态信息中,我们可以找到容器的CPU和内存使用率,以及其他相关指标。 需要注意的是,这里获取的是当时刻的使用率数据,如果你想要定时获取容器的使用率信息,可以使用定时任务等方法来实现。 这是一个简单的示例,你可以根据实际需求和项目结构进行相应的扩展和修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值