看见新力量NO.13|独家专访车麦&超电创始人孙泽锋
06-18
|简介 从本文开始,我们将详细介绍K8S的各个组件。学习一门技术,理论是第一位的。
只有充分理解底层原理,才能对以后的维护和调优有思路。 1.Pod1是什么。
Pod 是什么?我们在第二篇文章中有一个一般性的解释。你可以把它想象成一个“pod”,它包含一组相关的“bean”(容器)。
豆荚里的豆子一起吸收相同的营养,豆荚也是如此。内部的容器共享同一组资源。
K8S 官方文档将 Pod 描述为:Pod 是 Kubernetes 的基本构建块——您创建或部署的 Kubernetes 对象模型中最小、最简单的单元。 Pod 代表集群上运行的进程。
翻译成中文:Pod 是 K8S 的基本构建块,它是您可以在 K8S 集群中创建或部署的最小、最简单的单元。刚学K8S的同学普遍认为容器Docker是最小的单位。
事实上,事实并非如此。 Pod是K8S中最小的单元。
事实上,如果你把 Pod 看作虚拟机,把容器看作虚拟机中的应用程序,你就会明白很多。我们通过 K8S 与 OpenStack 的对比可以了解到: K8S OpenStackPodVMDockerapplication 应用程序和进程 OpenStack 管理的 VM 可以说是 OpenStack 中的最小单元。
我们知道虚拟机是隔离的。部署在其中的应用程序仅在虚拟机中运行,并且它们共享该虚拟机的CPU、Mem、网络和存储资源。
嗯,Pod 也是如此。 Pod 中的容器共享 Pod 中的 CPU、Mem、网络和存储资源。
那么 Pod 是如何做到的呢?看看下面的知识点你就明白了。 2、Pod的特点。
簇中的最小单元。注意,K8S集群的最小单位是Pod,而不是容器; K8S直接管理Pod,而不是容器。
一个 Pod 中可以运行一个或多个容器。如果一个 Pod 中只运行一个容器,则为“one-container-per-Pod”模式。
当然,你也可以将一组相关的容器放在一个Pod中。这些容器共享同一组网络命名空间和存储卷。
比如K8S官网文档给出了一个例子,将Web Server容器和File Puller容器放在一起(Web Server对外提供Web服务,File Puller负责内容存储和提供),然后就形成了一个 Pod。构成统一的服务单位。
另外,我们还可以考虑一些更复杂的东西,比如我们在传统运维中搭建的LAMP环境。如果我们将它们容器化部署在K8S集群中,会是一个什么样的模式呢? LAMP环境部署在IAAS层。
如果规模较小,可以使用一台虚拟机来部署apache+mysql+php环境。如果规模大的话,会使用更多的虚拟机并分布。
那么如果是PAAS层Pod容器部署呢?是不是apache+mysql+php分别跑一个docker,然后打包成一个Pod,然后如果规模大了,还要再建几个Pod? Pod 中的容器共享相同的网络和存储资源。在K8S中,每个Pod都会被分配一个唯一的IP地址,然后里面的容器会共享这个网络空间,其中包括IP地址和网络端口。
Pod 容器使用 localhost 进行内部通信。如果要与外部通信,则需要使用共享的IP和端口。
一个Pod可以指定一个共享存储Volume,然后该Pod中的所有容器都有权限访问这个Volume。 Volume用于持久化数据,Pod重启不会影响Volume中的数据。
Pod 中的容器共享相同的依赖关系。前面提到,具有相关关系的容器可以放在一个 Pod 中。
那么如何理解这里的关系呢?通常,我们在 Pod 中部署紧密耦合的服务,例如 LAMP 应用程序堆栈。这里的目的是实现联合调度、协调管理。
因此,最好不要将不相关的容器放入Pod中。如果没有规则地随意放置,你将无法体会到K8S的强大之处——编排。
二、Pod内部机制 1、Pod实现原理 我们在学习容器的时候了解到Linux提供了Namespace和Cgroup两种机制,这使得容器的出现成为可能。命名空间用于隔离进程,Cgroup用于控制进程资源的使用;命名空间由主机名、PID、文件系统、网络和 IPC 组成。
在K8S中,Pod的生成也是基于Namespace和Cgroup,所以我们可以用下图来说明Pod内部的架构综合: Pod架构。这些元素通过什么机制组合在一起?这是通过名为 Pause (gcr.io/google_containers/pause-amd64) 的容器来完成的。
K8S初始化Pod时,会先启动一个名为Pause的容器,然后再启动用户自定义的业务容器。我们将这个 Pause 容器视为“根容器”。
它有两个主要功能:扮演PID 1的角色,处理僵尸进程,并为Pod中的其他容器共享Linux命名空间的基础。首先我们来了解一下Linux系统下的PID 1。
过程、作用和意义。在Linux中,PID为1的进程称为超级进程,也称为根进程。
它是系统中的第一个进程,也是其他进程的父进程。所有进程都会挂在这个进程下。
如果子进程的父进程退出,子进程就会被挂起在PID 1下。其次,我们知道容器本身就是一个进程。
在Namespace中,Pause作为PID为1的进程存在于Pod中,其他业务容器挂载在这个Pause进程下。这样,一个Namespace下的进程就会以Pause为根,Pod下会存在一个树状结构。
最后,Pause还有一个函数,负责处理僵尸进程。僵尸进程:进程使用fork函数创建子进程。
如果子进程退出而父进程没有来得及调用wait或waitpid来获取其子进程的状态信息,那么子进程的描述符仍然保存在系统中,并且其进程号将一直存在并被占用(并且系统的进程数是有限的)。这种进程称为僵尸进程(以Z开头)。
Pause的容器代码是用C编写的(见下面的代码)。在Pause代码中,有一个for(;;)函数的无限循环。
函数中执行pause()函数。 Pause() 函数本身处于休眠状态,直到被信号中断。
因此,正是因为这种机制,Pause容器才会一直等待SIGCHLD信号。一旦有SIGCHLD信号(进程终止或停止时会发出该信号),Pause就会启动sigreap方法,sigreap方法会调用waitpid来获取其子进程的状态信息,这样僵尸进程就会不会在 Pod 中生成。
代码语言:c copy ## 暂停代码 static void sigdown(intsigno) { psignal(signo, "正在关闭,收到信号"); exit(0);}static void sigreap(int signo) { while (waitpid(-1 , NULL, WNOHANG) > 0);}int main() { if (getpid() != 1) /* 不是错误,因为暂停看到在基础设施容器之外使用。 */ fprintf(stderr, "警告:暂停应该是第一个进程\n"); if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) 返回 1; if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown) }, NULL) < 0) 返回 2; if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, .sa_flags = SA_NOCLDSTOP}, NULL) < 0) return 3;## 注意下面的 for 循环代码 for (;;)pause(); fprintf(stderr, "错误: 无限循环终止\n");返回 42;}2. Pod 的生命周期。
我们来分析一下Pod的生命周期。在K8S官方文档中,分别传递phase、Conditions、Container探针s、容器状态、Pod 就绪门、重启策略、Pod 生命周期等方面来描述 2.1 Pod 的状态值有哪些——phasePod 的状态由 PodStatus 对象中的 Phase 字段表示。
该阶段字段有以下值: 阶段名称 描述 PendingK8S 集群中已发起 Pod 创建请求,且其中的 Pod 还没有容器。该阶段通常发生在 Pod 被调度或 Pod 中的镜像被下载之前。
RunningPod已经被安排部署在一个Node中,并且里面的容器也已经创建好了。内部至少有一个容器正在运行或正在启动或重新启动。
SucceededPod 中的所有容器都成功运行,并且没有发生重启。 FailedPod 中的所有容器都会终止,并且至少有一个容器以失败方式终止。
换句话说,容器要么以非零状态退出,要么被系统终止。未知 由于某些原因,无法获取Pod的状态。
通常与 Pod 通信时会出现错误。 2.2 Pod Conditions 前面提到,PodStatus对象有一个phase字段,所以PodStatus也包含Pod Conditions。
这是一个数组,它包含的属性有: 字段 描述 lastProbeTime 上次检测到 Pod Condition 的时间戳。 lastTransitionTime 条件上次从一种状态转换到另一种状态的时间。
message 最后一次 Condition 状态转换的详细描述。 reasonCondition 上次转换的原因。
statusCondition 状态类型,可以是“True”、“False”、“Unknown”。 typeCondition 类型,包括以下几个方面: - PodScheduled(Pod 已被调度到其他节点) - Ready(Pod 可以提供服务请求,可以添加到所有匹配服务的负载均衡池中) - 已初始化(所有 init 容器已成功启动) - 不可调度(调度程序现在无法调度 Pod,例如由于缺乏资源或其他限制;) - ContainersReady( Pod中的所有容器都已经是就绪状态) 2.3 Containerprobesprobes中文意思是探针,所以containerprobes翻译成中文就是“容器探针”。
这是K8S诊断容器状态的一种机制。我们知道kubelet进程运行在Node中。
它的功能之一是收集容器的状态,然后报告给主节点。 “容器探测”机制是通过kubelet实现的。
那么kubelet是如何知道节点中容器的状态信息的呢?具体来说,kubelet 会调用容器提供的三个处理程序(钩子): ExecAction:在容器内执行指定的命令。如果命令退出且状态码为0,则认为诊断成功并且容器正常。
TCPSocketAction:通过容器的 IP 地址和端口号执行 TCP 检查。如果端口存在,则认为诊断成功,容器健康。
HTTPGetAction:通过容器的IP地址、端口号和路径调用HTTP GET方法。如果响应状态码大于等于且小于,则容器状态被认为是健康的。
每个Container探测都会得到三个结果: Success:容器通过诊断。失败:容器诊断失败。
未知:诊断失败,不应采取任何措施。另外,kubelet在运行的Container中可以有两种探测方法: livenessProbe:活性探测。
这是为了表明容器是否正在运行,服务是否正常。如果 LivenessProbe 检测到容器不健康,kubelet 会杀死该 Container,并根据 Container 的重启策略重新启动它。
如果Container没有提供livenessProbe,则默认状态为Success。 readinessProbe:就绪探针,这个是指示Container是否准备好提供服务(是否启动成功)。
如果 readinessProbe 检测失败,则 Container 的 Ready 将为 False,控制器会将该 Pod 的 Endpoint 从对应服务的 Endpoint 列表中移除,并且不再在该 Pod 上调度任何请求,直到下次检测成功。如果Container没有提供readinessProbe,则默认状态为Success。
之所以有这两种探测机制,主要是因为POD的生命周期会受到很多环境条件的影响,比如POD内部各个容器的状态,容器所依赖的上游或者外围服务的状态,因此,需要有一种机制来根据容器的不同状态来判断POD是否健康。因此,创建了活性和就绪检测来解决这个问题。
下面我们用两个动态图来介绍这两个Probe的具体工作方式(图片借用网络,入侵删除): 举个例子,如果一个Pod被LivenessProbe检测到,发现它无法再提供服务,那么LivenessProbe会根据容器的重启策略来决定。策略通过后是否重启并执行新的Pod替换操作。
Liveness探针机制工作动态图 有时有些应用程序需要一段时间来预热和启动。例如,后端项目的启动需要启动消息队列或数据库才能提供服务。
在这种情况下,使用就绪探针更为合适。准备就绪探测机制工作的动态图。
那么在具体的生产环境实践中,什么时候应该使用Liveness呢?何时使用准备状态?这里总结了一些经验,大家可以参考一下: (1)Liveness和Readiness应该直接检测程序,不要使用间接的检测方法。 (2) Liveness检测程序中不要做任何其他逻辑。
简单来说就是检测服务是否正常运行。如果主线程正常则直接返回,如果不正常则返回5xx。
如果存在其他逻辑,检测程序就会不准确。 (3)Readiness检测到的程序具有相关的处理逻辑。
Readiness主要检测并判断容器是否准备好对外提供服务。因此,实现一些逻辑来检查目标程序后端上所有依赖组件的可用性非常重要。
在实现就绪检测时,需要清楚地知道被检测的程序依赖于哪些函数,以及这些依赖函数何时就绪。例如,如果应用程序需要先建立与数据库的连接才能提供服务,那么在“Readiness”处理程序中,必须检查与数据库的连接是否已经建立,以最终确认程序是否准备就绪。
(4)Readiness不宜嵌套使用。也就是说,一个程序已经使用了Readiness来进行检测,所以不需要在外面再添加一层Readiness。
最后,Liveness和Readiness的YAML配置语法是相同的,也就是说,如果将Liveness设置为Readiness,则可以使用相同的YAML配置文件。 2.4 Container States Container States 一旦 Pod 落地 Node 创建完成,kubelet 就会在 Pod 中创建一个容器。
K8S 中容器有三种状态:等待、运行和终止。如果想要检查容器的状态,我们可以使用命令 kubectl describe pod [POD_NAME]。
该命令将显示 Pod 中每个容器的状态。另外,K8S创建资源对象时,可以利用生命周期来管理容器在运行和关闭之前的一些动作。
Lifecycle 有两个回调函数: PostStart:容器创建成功后,是预运行任务,用于资源部署、环境准备等 PreStop:容器终止前的任务,用于优雅关闭应用程序,通知其他等待:这是容器的默认状态。如果容器不处于“正在运行”或“已终止”状态,则它处于“等待”状态。
处于 Waiting 状态的容器仍然可以运行其所需的操作,例如拉取镜像、密钥等。在此状态下,Reason 字段会显示一些原因,指示其为何处于 Waiting 状态。
代码语言:javascript Copy... 状态:等待 原因:ErrImagePull...Running:表示容器正在运行。一旦容器进入运行状态,如果有postStart,就会执行。
另外,Started字段会显示容器启动时的具体时间代码。语言: javascript copy... 状态: 运行 开始: Wed, 30 Jan 16:46:38 0... 终止: 表示容器已终止。
当容器成功完成执行或由于某种原因失败时,就会出现此状态。显示容器终止的原因以及退出代码以及容器的开始和结束时间(如下例所示)。
另外,容器进入Termated之前,如果有preStop,就会被执行。代码语言:javascript 复制... 状态:已终止 原因:已完成 退出代码:0 开始:1 月 30 日星期三 11:45:26 0 完成:1 月 30 日星期三 11:45:26 0 ...2.5 Pod 生命周期控制方法 一般来说,Pod不会消失,除非控制器手动干预或删除它。
但是,例外情况是处于“成功”或“失败”状态的 Pod。如果处于该状态超过一定时间,比如termed-pod-gc-threshold的设置值,就会被垃圾回收机制清除。
注意:terminate-pod-gc-threshold用于master节点中设置gcTermination的阈值。默认值为 0 秒。
三类控制器控制 Pod 生命周期: Job:适合批量计算等一次性任务。任务完成后,Pod会被此类控制器清除。
Job的重启策略只能是“OnFailure”或“Never”。 ReplicationController、ReplicaSet 或 Deployment,此类控制器希望 Pod 保持运行,它们的重启策略只能是“始终”。
DaemonSet:每个节点一个 Pod。显然这类控制器的重启策略应该是“始终”。
3.Pod资源使用机制前面我们提到Pod就像一个虚拟机。我们可以给虚拟机分配固定的CPU、Mem、Disk、网络资源。
同理,Pod 也是如此。那么Pod如何使用和控制这些分配的资源呢?首先我们来了解一下CPU资源的分配方式:计算机中的CPU资源是以“时间片”的方式分配给请求的。
系统中的每一个操作都需要CPU处理。我们知道CPU的单位是Hz、GHz(1Hz=1/s,即单位时间内完成的振动次数,1GHz=1Hz=1次/s),频率越高,处理次数越多单位时间内完成的。
因此,任务申请的CPU时间片越多,获得的CPU资源就越多。其次我们来了解一下Cgroup中资源的一些换算单位: CPU换算单位 1 CPU = millicpu (1 Core = m) 0.5 CPU = millicpu (0.5 Core = m) 这里m表示millicore,millicore,在K8S集群中每个节点都可以确认通过操作系统命令获取该节点的CPU核心数,然后乘以该数字即可得到该节点的CPU核心总数。
例如,如果一个节点有四个核心,则该节点的CPU总数为m。如果要使用0.5核心,则需要*0.5 = m。
在K8S中,以下两个参数用于限制和请求CPU资源:spec.containers[].resources.limits.cpu 可以短暂超出CPU上限,容器不会被停止。可能会超出spec.containers[].resources.requests.cpu CPU请求值(K8S调度算法中的基础值)。
这里需要理解的是,如果resources.requests.cpu设置的值大于集群中每个Node的最大CPU核数,那么这个Pod就不会被调度(很容易理解就是没有Node)可以满足)。例如,我们在 YAML 中定义一个容器 CPU 资源如下: 代码语言:yaml copy resources: requests: memory: 50Mi cpu: 50m Limits: memory: Mi cpu: m 这里我们给出的 CPU 是 50m,即 0.05core 。
这0.05个核心占1个CPU资源时间的5%。另外,我们还需要知道K8S的CPU资源,这是一种可压缩的资源。
如果容器达到CPU设置值,就会开始限制,容器性能会下降,但不会终止并退出。最后我们来了解一下MEM的资源控制:单位换算:1 MiB = KiB。
请注意,MiB ≠ MB。 MB 是十进制单位,MiB 是二进制单位。
通常我们认为MB等于KB。事实上,1MB=KB,1MiB 等于 KiB。
中间带字母i的是国际电工委员会(IEC)根据产品制定的; KB、MB 和 GB 是国际单位制,基于产品。 K8S中的内存单元一般为Mi。
当然,你也可以使用Ki、Gi甚至Pi,这取决于具体的业务需求和资源容量。这里需要注意的是,内存不是可压缩的资源。
如果容器使用内存资源达到上限,就会发生OOM,导致内存溢出,容器会终止并退出。 3、Pod基本操作命令说明 具体命令创建 kubectl create -f xxx.yaml query kubectl get pod PodName / kubectl描述 pod PodName delete kubectl delete pod PodName update kubectl Replace /path/to/newPodName.yaml (当然也可以添加--force(强制替换))检查kubectllogsPodName命令的日志输出。
事实上,可能需要大量使用才能变得熟练。经过大量打字后,您将能够做到。
根据我个人的经验,计算机IT中的命令都离不开这些关键词:create、get、delete……当然,还有万能的--help。
版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。
标签:
相关文章
06-17
06-18
06-18
06-18
06-18
最新文章
【玩转GPU】ControlNet初学者生存指南
【实战】获取小程序中用户的城市信息(附源码)
包雪雪简单介绍Vue.js:开学
Go进阶:使用Gin框架简单实现服务端渲染
线程池介绍及实际案例分享
JMeter 注释 18 - JMeter 常用配置组件介绍
基于Sentry的大数据权限解决方案
【云+社区年度征文集】GPE监控介绍及使用