Kubernetes笔记(4) - Pod

Pod是Kubernetes系统的基础单元,是资源对象模型中可由用户建立或部署的最小组件,也是在Kubernetes系统上运行容器化应用的资源对象。html

容器与Pod的关系

Docker推荐采用单容器单进程的方式运行,但因为容器间的隔离机制,各容器进程间又没法实现IPC(Inter-Process Communication)通讯。这就致使功能相关的容器之间通讯困难,好比主容器与负责日志收集的容器之间的通讯。而Pod资源抽象正是用来解决此类问题的组件,Pod对象是一组容器的集合,这些容器共享Network、UTS(UNIX Time-sharing System)及IPC名称空间,所以具备相同的域名、主机名和网络接口,并可经过IPC直接通讯。
为一个Pod对象中的各容器提供网络名称空间等共享机制的是底层基础容器pause。
尽管Pod支持运行多个容器,但做为最佳实践,除非多个进程之间具备密切的关系,不然都应该将其构建到多个Pod中,这样多个Pod可被调度至多个不一样的主机运行,提升了资源利用率,也便于规模的伸缩。nginx

Sidecar pattern(边车模式)

多个进程之间具备密切的关系时,通常按照边车模型来组织多个容器,边车即为Pod的主应用容器提供协同的辅助应用容器,典型的应用场景是将主应用容器中的日志使用agent收集至日志服务器中时,能够将agent运行为辅助应用容器。redis

管理Pod对象的容器

Pod的配置清单举例:数据库

apiVersion: v1
kind: Pod
metadata:
 name: pod-example
spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2

其中spec字段下,containers为及其子字段name为必选项,image在手动场景Pod时必选,在但在被高级别管理资源如Deployment控制时可选,由于这个字段可能会被覆盖。后端

定义镜像的获取策略

Pod的核心功能是运行容器,而 经过image.imagePullPolicy能够自定义镜像的获取策略。api

  • Always:镜像标签为“latest”或镜像不存在时老是从指定的仓库中获取镜像
  • IfNotPresent:仅当本地镜像缺失时才从目标仓库下载镜像
  • Never:禁止从仓库下载镜像,即仅使用本地镜像
spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2
     imagePullPolicy: Always

对于标签为“latest”的镜像文件,其默认的镜像获取策略即为“Always”,而对于其余标签的镜像,其默认策略则为“IfNotPresent”。缓存

暴露端口

在Pod中暴露端口与为Docker容器暴露端口的意义不同:
在Docker的网络模型中,使用默认网络的容器化应用需经过NAT机制将其“暴露”(expose)到外部网络中才能被其余节点之上的容器客户端所访问;
而在K8S中,各Pod的IP地址已经处于同一网络平面,不管是否为容器暴露端口,都不会影响集群中其余节点之上的Pod客户端对其进行访问,因此暴露的端口只是信息性数据,并且显式指定容器端口也方便调用。安全

spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2
   ports:
   - name: http
     containerPort: 80
     protocol: TCP

这里的配置指定暴露容器上的TCP端口80,并将其命名为http。
Pod对象的IP地址仅在当前集群内可达,它们没法直接接收来自集群外部客户端的请求流量,尽管它们的服务可达性不受工做节点边界的约束,但依然受制于集群边界。如何让集群外部访问到Pod对象,将在后面学习。服务器

自定义运行的容器化应用

command字段可以指定不一样于镜像默认运行的应用程序,而且能够同时使用args字段进行参数传递,它们将覆盖镜像中的默认定义。不过,若是仅为容器定义了args字段,那么它将做为参数传递给镜像中默认指定运行的应用程序;若是仅为容器定义了command字段,那么它将覆盖镜像中定义的程序及参数,并以无参数方式运行应用程序。网络

spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2 
   imagePullPolicy: Never     
   command: ["/bin/sh"]
   args: ["-c", "while true; do sleep 30; done"]

环境变量

环境变量也是向容器化应用传递配置的一种方式,向Pod对象中的容器环境变量传递数据的方法有两种:env和envFrom,这里只介绍第一种方式,第二种方式将在介绍ConfigMap和Secret资源时进行说明。环境变量一般由name和value字段构成。

spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2 
   env:
   - name: REDIS_HOST
     value: do.macOS
   - name: LOG_LEVEL
     value: info

标签与标签选择器

标签的管理

标签选择器能够对附带标签的资源对象进行挑选,并进行所须要的操做。一个对象可拥有不止一个标签,而同一个标签也可被添加至多个资源之上。
能够为资源附加多个不一样纬度的标签以实现灵活的资源分组管理功能,例如,版本标签、环境标签、分层架构标签等,用于交叉标识同一个资源所属的不一样版本、环境及架构层级等。
定义标签示例:

apiVersion: v1
kind: Pod
metadata:
 name: pod-example
 labels:
   env: qa
   tier: frontend

资源建立后,在kubectl get pods命令中添加--show-labels选项就可显示lables信息。
-L <key1>, <key2>选项可增长对应的列信息。
直接管理活动对象的标签:

kubectl label pods/pod-example release=beta

为pod-example添加了release=beta,若是要修改已经存在的减值对,须要添加--overwrite选项。

标签选择器

标签选择器用于表达标签的查询条件或选择标准,Kubernetes API目前支持两个选择器:基于

  • equality-based,可用操做符有“=”“==”和“! =”三种,前两种等价
  • set-based,支持in、notin和exists三种操做符,此外还有能够只指定KEY来筛选全部存在此键名标签的资源,!KEY则筛选全部不存在此键名标签的资源
    使用标签选择器时遵循如下逻辑:
  • 同时指定的多个选择器之间的逻辑关系为“与”操做
  • 使用空值的标签选择器意味着每一个资源对象都将被选中
  • 空的标签选择器将没法选出任何资源。

Kubernetes的诸多资源对象必须以标签选择器的方式关联到Pod资源对象,例如Service、Deployment和ReplicaSet类型的资源等,能够在spec字段经过嵌套的“selector”字段来指定选择器,有两种方式:

  • matchLabels:经过直接给定键值对来指定标签选择器
  • matchExpressions:基于表达式指定的标签选择器列表,每一个选择器都形如“{key:KEY_NAME, operator: OPERATOR,values: [VALUE1, VALUE2, …]}”,选择器列表间为“逻辑与”关系;使用In或NotIn操做符时,其values不强制要求为非空的字符串列表,而使用Exists或DostNotExist时,其values必须为空。
    格式举例:
selector:
 matchLabels:
   component: redis
 matchExpressions:
   - {key: tier, operator: In, values: [cache]}
   - {key: environment, operator: Exists, values:}

资源注解

标签以外,Pod与其余各类资源还能使用资源注解(annotation),也是键值类型的数据,不过它不能用于标签及挑选Kubernetes对象,仅可用于为资源提供“元数据”信息。另外,注解中的元数据不受字符数量的限制,能够为结构化或非结构化形式,并且对字符类型也没有限制。
Annotation中放置构建、发行或镜像相关的信息,指向日志、监控、分析或审计仓库的地址,或者由客户端库或工具程序生成的用于调试目的的信息:如名称、版本、构建信息等信息。

查看资源注解
使用kubectl get -o yamlkubectl describe命令均能显示资源的注解信息。

kubectl describe pods pod-example | grep "Annotations"

管理资源注解
在配置清单中定义annotations:

apiVersion: v1
kind: Pod
metadata:
 name: pod-example
 annotations:
   created-by: "cluster admin"

追加annotations:

kubectl annotate pods pod-example created-by2="admin"

Pod对象的生命周期

Pod的生命周期如图:

Phase

Pod对象老是应该处于其生命进程中如下几个Phase(阶段)之一:

  • Pending:API Server建立了Pod资源对象并已存入etcd中,但它还没有被调度完成,或者仍处于从仓库下载镜像的过程当中。‰
  • Running:Pod已经被调度至某节点,而且全部容器都已经被kubelet建立完成。‰
  • Succeeded:Pod中的全部容器都已经成功终止而且不会被重启。‰
  • Failed:全部容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态或已经被系统终止。‰
  • Unknown:API Server没法正常获取到Pod对象的状态信息,一般是因为其没法与所在工做节点的kubelet通讯所致。

Pod的建立过程

Pod的建立过程是指Pod自身及其主容器及其辅助容器建立的过程。

  1. 用户经过kubectl或其余API客户端提交PodSpec给API Server。
  2. API Server尝试着将Pod对象的相关信息存入etcd中,待写入操做执行完成,API Server即会返回确认信息至客户端。
  3. API Server开始反映etcd中的状态变化。
  4. 全部的Kubernetes组件均使用“watch”机制来跟踪检查API Server上的相关的变更。
  5. kube-scheduler(调度器)经过其“watcher”觉察到API Server建立了新的Pod对象但还没有绑定至任何工做节点。
  6. kube-scheduler为Pod对象挑选一个工做节点并将结果信息更新至API Server。
  7. 调度结果信息由API Server更新至etcd存储系统,并且API Server也开始反映此Pod对象的调度结果。
  8. Pod被调度到的目标工做节点上的kubelet尝试在当前节点上调用Docker启动容器,并将容器的结果状态回送至API Server。
  9. API Server将Pod状态信息存入etcd系统中。
  10. 在etcd确认写入操做成功完成后,API Server将确认信息发送至相关的kubelet,事件将经过它被接受。

Pod生命周期中的重要行为

除了建立应用容器(主容器及其辅助容器)以外,用户还能够为Pod对象定义其生命周期中的多种行为,如用于初始化的容器、存活性探测及就绪性探测等

用于初始化的容器

用于初始化的容器(init container)是应用程序的主容器启动以前要运行的容器,经常使用于为主容器执行一些预置操做,典型的应用如:

  • 用于运行特定的工具程序,出于安全等方面的缘由,这些程序不适于包含在主容器镜像中。
  • 提供主容器镜像中不具有的工具程序或自定义代码。
  • 为容器镜像的构建和部署人员提供了分离、独立工做的途径,使得他们没必要协同起来制做单个镜像文件。
  • 初始化容器和主容器处于不一样的文件系统视图中,所以能够分别安全地使用敏感数据,例如Secrets资源。
  • 初始化容器要先于应用容器串行启动并运行完成,所以可用于延后应用容器的启动直至其依赖的条件获得知足。

在资源清单中经过initContainers字段定义:

spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2 
 initContainers:
 - name: init-something
   image: busybox
   command: ['sh', '-c', 'sleep 10']
生命周期钩子函数

Kubernetes为容器提供了两种生命周期钩子:

  • postStart:在容器建立完成以后当即运行,可是Kubernetes没法确保它必定会在容器中的ENTRYPOINT以前运行。
  • preStop:在容器终止操做以前当即运行,它以同步的方式调用,所以在其完成以前会阻塞删除容器的操做。

钩子函数的实现方式有“Exec”和“HTTP”两种,前一种在钩子事件触发时直接在当前容器中运行由用户定义的命令,后一种则是在当前容器中向某URL发起HTTP请求。钩子函数定义在容器的spec.lifecycle字段。

容器的重启策略

容器程序发生崩溃或容器申请超出限制的资源等缘由均可能会致使Pod对象的终止,此时是否应该重建该Pod对象则取决于其重启策略(restartPolicy)属性的定义。

  • Always:只要Pod对象终止就将其重启,此为默认设定。
  • OnFailure:仅在Pod对象出现错误时方才将其重启。
  • Never:从不重启。

容器在重启失败后,以后的重启将有一段时间的延迟,且延迟时间愈来愈长,依次为10秒、20秒、40秒、80秒、160秒、300秒。

Pod的终止过程

  1. 用户发送删除Pod对象的命令。
  2. API服务器中的Pod对象会随着时间的推移而更新,在宽限期内(默认为30秒),Pod被视为“dead”。
  3. 将Pod标记为“Terminating”状态。
  4. (与第3步同时运行)kubelet在监控到Pod对象转为“Terminating”状态的同时启动Pod关闭过程。
  5. (与第3步同时运行)端点控制器监控到Pod对象的关闭行为时将其从全部匹配到此端点的Service资源的端点列表中移除。
  6. 若是当前Pod对象定义了preStop钩子处理器,则在其标记为“terminating”后即会以同步的方式启动执行;如若宽限期结束后,preStop仍未执行结束,则第2步会被从新执行并额外获取一个时长为2秒的小宽限期。
  7. Pod对象中的容器进程收到TERM信号。
  8. 宽限期结束后,若存在任何一个仍在运行的进程,那么Pod对象即会收到SIGKILL信号。
  9. Kubelet请求API Server将此Pod资源的宽限期设置为0从而完成删除操做,它变得对用户再也不可见。

若是在等待进程终止的过程当中,kubelet或容器管理器发生了重启,那么终止操做会从新得到一个满额的删除宽限期并从新执行删除操做。

Pod存活性探测

kubelet可基于存活性探测断定什么时候须要重启一个容器。可经过spec.containers.livenessProbe定义,支持三种探测方法:

  • exec
  • httpGet
  • tcpSocket

exec

exec类型的探针经过在目标容器中执行由用户自定义的命令来断定容器的健康状态,若命令状态返回值为0则表示“成功”经过检测,其它值均为“失败”状态。它只有一个可用属性“command”,用于指定要执行的命令,示例:

apiVersion: v1
kind: Pod
metadata:
 name: liveness-exec-demo
 labels:
   test: liveness-exec-demo
spec:
 containers:
 - name: liveness-exec-demo
   image: busybox 
   args: ["/bin/sh", "-c", " touch /tmp/healthy;sleep 60; rm -rf /tmp/healthy;sleep 600"]
   livenessProbe:
     exec:
       command: ["test", "-e", "/tmp/healthy"]

这段配置清单基于busybox镜像启动一个容器,并执行args定义的命令,此命令在容器启动时建立/tmp/healthy文件,并于60秒以后将其删除。存活性探针运行“test -e/tmp/healthy”命令检查/tmp/healthy文件的存在性,若文件存在则返回状态码0,表示成功经过测试。
因此60秒后使用describe命令能够看到容器被重启的event。

httpGet

httpGet方式是向目标容器发起一个HTTP GET请求,根据其响应码进行结果断定,2xx或3xx时表示检测经过。
可配置字段有:

  • host,请求的主机地址,默认为Pod IP,也能够在httpHeaders中使用“Host:”来定义。
  • port,请求的端口,必选字段。
  • httpHeaders,自定义的请求报文头。
  • path,请求的HTTP资源路径。
  • scheme:创建链接使用的协议,仅可为HTTP或HTTPS,默认为HTTP。

示例

apiVersion: v1
kind: Pod
metadata:
 name: liveness-http-demo
 labels:
   test: liveness-http-demo
spec:
 containers:
 - name: liveness-http-demo
   image: nginx:1.12-alpine
   ports:
   - name: http
     containerPort: 80
   lifecycle:
     postStart:
       exec:
         command: ["/bin/sh", "-c", " echo Healthy > /usr/share/nginx/html/healthz"]
   livenessProbe:
     httpGet:
       path: /healthz
       port: http
       scheme: HTTP

这个配置清单经过postStart hook建立了一个专用于httpGet测试的页面文件healthz。而为httpGet探测指定的路径为“/healthz”,地址默认为Pod IP,端口使用了容器中定义的端口名称http。
启动容器后健康检查是正常的,但执行以下命令删除healthz页面后,可在event中看到Container liveness-http-demo failed liveness probe, will be restarted

kubectl exec liveness-http-demo rm /usr/share/nginx/html/healthz

通常应为HTTP探测操做定义专用的URL路径,此URL路径对应的Web资源应该以轻量化的方式在内部对应用程序的各关键组件进行全面检测以确保它们可正常向客户端提供完整的服务。

tcpSocket

基于TCP的存活性探测用于向容器的特定端口发起TCP请求并尝试创建链接,链接创建成功即为经过检测。相比较来讲,它比基于HTTP的探测要更高效、更节约资源,但精准度较低。
可配置字段有:

  • host,请求链接的目标IP地址,默认为Pod IP。
  • port,请求链接的目标端口,必选字段。

举例:

spec:
 containers:
 - name: liveness-tcp-demo
   image: nginx:1.12-alpine 
   livenessProbe:
     tcpSocket:
       port: 80

存活性探测行为属性

对于配置了liveness的pod,经过describe命令能够看到相似这样的信息,有delay、timeout等配置,因为以前没有指定因此都为默认值:

Liveness:       tcp-socket :80 delay=0s timeout=1s period=10s #success=1 #failure=3
  • initialDelaySeconds,存活性探测延迟时长,即容器启动多久以后再开始第一次探测操做,显示为delay属性,默认为0秒,整型
  • timeoutSeconds,存活性探测的超时时长,显示为timeout属性,默认为1s,整型,最小1s
  • periodSeconds,存活性探测的频度,显示为period属性,整型,默认为10s,最小值为1s;太高的频率会对Pod对象带来较大的额外开销,而太低的频率又会使得对错误的反应不及时
  • successThreshold,处于失败状态时,探测操做至少连续多少次的成功才被认为是经过检测,显示为#success属性,默认值为1,最小值也为1,整型
  • failureThreshold:处于成功状态时,探测操做至少连续多少次的失败才被视为是检测不经过,显示为#failure属性,默认值为3,最小值为1,整型。

另外,liveness检测仅对当前服务有效,好比但后端服务(如数据库或缓存服务)致使故障时,重启当前服务并不能解决问题,但它却会被一次次重启,直到后端服务恢复正常为止。

Pod就绪性探测

Pod对象启动后,容器应用一般须要一段时间才能完成其初始化过程,例如加载配置或数据,甚至有些程序还须要预热的过程。所以应该避免在Pod对象启动后当即让其处理客户端请求,而是等待容器初始化工做执行完成并转为Ready状态,尤为是存在其余提供相同服务的Pod对象的场景更是如此。
就绪性探测是用来判断容器就绪与否的周期性操做,探测操做返回“success”状态时,就认为容器已经就绪。
与liveness探测相似,它也支持三种方式,但定义时使用的属性名为readinessProbe。
举例:

apiVersion: v1
kind: Pod
metadata:
 name: readiness-tcp-demo
 labels:
   test: readiness-tcp-demo
spec:
 containers:
 - name: readiness-tcp-demo
   image: nginx:1.12-alpine 
   readinessProbe:
     tcpSocket:
       port: 80

未定义就绪性探测的Pod对象在Pod进入“Running”状态后将当即就绪。生产实践中,必须为须要时间进行初始化容器以及关键性Pod资源中的容器定义就绪性探测。

资源需求及资源限制

K8S中可由容器或Pod请求或消费的“计算资源”是指CPU和内存,其中CPU属于可压缩(compressible)型资源,可按需收缩,而内存则是不可压缩型资源,对其执行收缩操做可能会致使没法预知的问题。
目前资源隔离属于容器级别,因此CPU和内存资源的配置须要在Pod中的容器上进行,支持两种属性:

  • requests,定义了其请求的确保可用值,即容器运行可能用不到这些额度的资源,但用到时必需要确保有如此多的资源可用;
  • limits,限制资源可用的最大值

在K8S中,1个单位的CPU至关于虚拟机上的1颗虚拟CPU(vCPU)或物理机上的一个超线程(Hyperthread,或称为一个逻辑CPU),它支持分数计量方式,一个核心(1 core)至关于1000个微核心(millicores),所以500m至关因而0.5个核心。内存的计量方式与平常使用方式相同,默认单位是字节,也可使用E(Ei)、P(Pi)、T(Ti)、G(Gi)、M(Mi)和K(Ki)做为单位后缀。

资源需求

apiVersion: v1
kind: Pod
metadata:
 name: stress-demo
spec:
 containers:
 - name: stress-demo
   image: ikubernetes/stress-ng
   command: ["/usr/bin/stress-ng", "-m 1", "-c 1", "--metrics-brief"]
   resources:
     requests:
       memory: "128Mi"
       cpu: "200m"

以上的配置清单定义了容器的资源需求为128M内存、200m(0.2)个CPU核心。它运行stress-ng(一个多功能系统压力测工具)镜像启动一个进程(-m 1)进行内存性能压力测试,再启动一个专用的CPU压力测试进程(-c 1)。
而后使用kubectl exec stress-demo -- top命令来查看资源的使用状况,在个人电脑(6核,内存16G)上显示的内存占用为262m,CPU占用2*17%(约等于2/6,由于两个测试线程分布于两个CPU核心以满载的方式运行),都远高于requests中定义的值,这是由于当前资源充裕,一旦
资源紧张时,节点仅保证容器有五分之一个CPU核心可用,对于有着6个核心的节点来讲,它的占用率约为3.33%,多占用的资源会被压缩。内存为非可压缩型资源,因此此Pod在内存资源紧张时可能会因OOM被杀死(killed)。
若是没有定义requests,那么在CPU资源紧张时,可能会被其它Pod压缩至极低的水平,甚至会达到Pod不可以被调度运行的境地,而不可压缩型的内存资源,则可能因OOM致使进程被杀死。所以在Kubernetes系统上运行关键型业务相关的Pod时必须使用requests属性为容器定义资源的确保可用量。

集群中的每一个节点拥有的CPU和内存资源是固定的,Kubernetes的调度器在调度Pod时,会根据容器的requests属性来断定哪些节点可接收运行当前的Pod资源,而对于一个节点的资源来讲,每运行一个Pod对象,其requests中定义的请求量都要被预留,直到给全部Pod对象分配完为止。

资源限制

经过定义资源需求能够保证容器的最少资源量,若是要限制容器使用资源的上限,则须要定义资源限制。
若是定义了资源限制,则容器进程没法得到超出其CPU配额的可用时间,而进程申请分配超出其limits定义的内存资源时,它将被OOM killer杀死。

Pod的服务质量类别

Kubernetes容许节点资源对limits的过载使用,这意味着节点没法同时知足其上的全部Pod对象以资源满载的方式运行。因而就须要肯定Pod对象的优先级,在内存资源紧缺时,先终止低优先级的Pod对象。
Pod对象的优先级是根据requests和limits属性肯定的,分为三个级别或QoS(Quality of Service):

  • Guaranteed,Pod中全部容器对全部资源类型都定义了Limits和Requests,并且Limits值等于Requests值且不为0,Requests值未定义时默认等于Limits,优先级最高。
  • BestEffort,没有为任何一个容器设置requests或limits属性的Pod资源属于这一类,优先级最低。
  • Burstable,不为Guaranteed和BestEffort时,优先级中等。

以上只适用于内存资源紧缺时,CPU资源没法获得知足时,Pod仅仅是暂时获取不到相应的资源而已。

学习资料

《Kubernetes实战进阶》 马永亮著

相关文章
相关标签/搜索