在k8s上Pod是运行容器应用及调度的最小单元,同一个Pod可以有一到多个容器,这些容器共享UTS、IPC和Network名称空间,并且能够访问同一组存储卷。封装了应用容器的Pod代表运行在k8s系统上的进程或进程组,它可由单个容器或少量具有强耦合关系的容器组成,用于抽象、组织和管理集群之上的应用程序。

Pod创建和删除过程

k8s的podip和clusterip区别 k8s中的pod_html


Pod的创建过程如上图所示:

  1. 用户通过kubectl或其它客户端发送创建Pod的请求给API Server
  2. API Server将Pod的相关信息写入etcd,等待写入成功后,API Server就会返回确认信息给客户端
  3. Scheduler监测到有新的Pod创建,就会为其选择一个合适的运行节点,并将调度结果更新至API Server
  4. 调度结果信息由API Server更新至etcd,并确认信息同步给Scheduler
  5. 相应工作节点上的kubelet监测到有新的Pod绑定到本节点后会读取Pod配置信息,调用本地容器运行时创建相应的容器启动Pod对象,然后将结果更新至API Server
  6. API Server将kubelet发来的Pod状态信息存至etcd,并将确认信息发至相应的kebelet

k8s的podip和clusterip区别 k8s中的pod_kubernetes_02


Pod删除过程如上图所示:

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

Pod常见状态

Pod对象被创建后可能存在很多状态,下面列出一些常见状态及含义:

  • Unschedulable:Pod不能被调度,kube-scheduler没有匹配到合适的node节点
  • PodScheduled:Pod正处于调度中
  • Pending:正在创建Pod,但Pod中的容器还没有全部创建完成,如果Pod一直处于此状态应该检查Pod依赖的存储资源是否具备
  • Failed:Pod中有容器启动失而导致Pod工作异常
  • Unknow:由于某种原因无法获取Pod的当前状态,通常是由于与Pod所在的节点通信失败导致
  • Initialized:Pod中的所有初始化容器已经完成
  • ImagePullBackOff:Pod所在的节点下载镜像失败
  • Running:Pod内部的容器已被创建并且正常运行
  • Ready:表示Pod中的容器可以正常提供服务
  • Error:Pod启动过程中发生错误
  • NodeLost:Pod所在节点失联
  • Waiting:Pod等待启动
  • Terminating:Pod正在被删除
  • CrashLoopBackOff:Pod运行出现错误,但kubelet正在将它重启
  • InvalidImageName:node节点无法解析镜像名称导致镜像无法下载
  • ImageInspectError:无法校验镜像,镜像不完整导致
  • ErrImageNeverPull:策略禁止拉取镜像,镜像中心权限是私有等
  • RegistryUnavailable:镜像服务器不可用,网络原因或镜像服务器故障
  • ErrImagePull:镜像拉取出错,超时或下载被强制终止
  • CreateContainerConfigError:不能创建kubelet使用的容器配置
  • CreateContainerError:创建容器失败
  • RunContainerError:Pod运行失败,容器中没有初始化PID为1的守护进程
  • ContainersNotInitialized:Pod没有初始化完毕
  • ContainersNotReady:Pod没有准备完毕
  • ContainerCreating:Pod正在创建中
  • PodInitialzing:Pod正在初始化中
  • DockerDaemonNotReady:节点上的docker服务没有启动
  • NetworkPluginNotReady:网络插件没有启动

pause容器

每一个Pod都有一个特殊的Pause容器,又叫Infra容器,是Pod的基础容器,镜像体积只有几百k左右,主要的功能是为Pod中的多个容器提供网络通信。
Pause容器被创建后会初始化Network Namespace,之后Pod内的其它容器就可以加入到Pause容器中共享Pause容器的Network Namespace了,因此如果一个容器中有两个容器A和B,那么关系如下:

  1. A和B可以直接使用localhost通信
  2. A和B可以看到相同的网卡、IP与端口监听信息
  3. Pod只有一个IP地址,也就是该Pod的Network Namespace对应的IP地址,被Pod内的容器共享使用
  4. Pod删除后Pause容器也会被删除,其IP被回收

Pause容器共享的Namespace有:

  • Network Namespace:Pod中的多个容器共享同一个网络名称空间,即使用相同的IP和端口信息
  • IPC Namespace:Pod中的多个容器可以使用System V IPC或POSIX消息队列进行通信
  • UTS Namespace:Pod中的多个容器共享一个主机名

init容器

init容器官方文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/init-containers/ Pod中的init容器主要可以用来为业务容器做一些初始化设置,例如下面这些场景:

  1. 可以为业务容器提前准备好需要的运行环境,,比如将业务容器需要的配置文件提前准备好并放在指定位置、检查数据权限或完整性、软件版本等基础运行环境
  2. 可以在运行业务容器之前准备好需要的业务数据,比如从OSS下载或者从其他位置copy
  3. 检查依赖的服务是否能够访问

init容器的特性:

  1. 一个Pod可以有多个init容器,但是每个init容器和业务容器的运行环境是隔离的
  2. init容器会比业务容器先启动
  3. 所有init容器运行成功之后才能启动业务容器,如果init容器运行失败会导致Pod被重启(Pod重启策略为Never时除外)
  4. 如果Pod定义有多个init容器,会以init容器定义顺序串行运行,直到所有init容器成功运行结束
  5. init容器不支持探针检测,因为执行完成退出后就不再运行了

init容器定义在Pod资源的.spec.initContainers字段中,其可用字段和.spec.containers字段类似,具体可以参考官方文档。下面是一个示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-with-initcontainers
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-with-initcontainers
  template:
    metadata:
      labels:
        app: nginx-with-initcontainers
    spec:
      initContainers:
      - name: init-web-data	#初始化容器1,负责为nginx生成html页面
        image: centos:7.8.2003
        command: ["/bin/bash", "-c", "for i in {1..10};do echo '<h1>'$i web page at $(date +%Y%m%d%H%M%S)'<h1/>' >>/data/nginx/html/myserver/index.html;sleep 2;done"]
        volumeMounts:
        - name: html-data
          mountPath: /data/nginx/html/myserver
      - name: change-data-owner		#初始容器2,负责修改html文件的权限
        image: busybox:1.28.3
        command: ["/bin/sh", "-c", "/bin/chmod -R 644 /data/nginx/html/myserver/*"]
        volumeMounts:
        - name: html-data
          mountPath: /data/nginx/html/myserver
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: html-data
          mountPath: /usr/share/nginx/html/myserver
      volumes:
      - name: html-data
        hostPath:
          path: /tmp/nginx/html

访问Pod测试

k8s的podip和clusterip区别 k8s中的pod_nginx_03


查看Pod中文件权限

k8s的podip和clusterip区别 k8s中的pod_nginx_04

Pod重启策略

Pod对象因应用容器崩溃、启动状态检测失败、存活状态检测失败或者使用资源超出限制都会被终止,此时重启与否取决于Pod的restartPolicy字段的定义,该字段支持以下值:

  • Always:无论因何原因、因何种方式终止,kubelet都将重启该Pod,此为默认设置
  • OnFailure,仅在Pod中进程以非0方式退出时将其重启
  • Never:不论容器运行状态如何都不会重启该Pod

容器探针检测

探针介绍

容器探针官方文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#container-probes 探针检测是由kubelet定期对容器进行的诊断,以确保Pod的运行状态符合预期,通过探针检测可以减少运维问题并提高服务质量。

Pod内容器的探针检测主要分为3类:

  1. startupProbe:启动状态检测,判断容器内的进程是否已经启动完成,用来判断容器内的进程是否已经启动成功,如果配置了startupProbe会先禁用所有其它检测,直到startupProbe检测成功为止;如果startupProbe检测失败,kubelet会杀死容器,并按照重启策略进行下一步操作;如果容器没有定义startupProbe,则默认为Success
  2. LivenessProbe:存活状态检测,检测容器是否正在运行,如果存活检测失败,kubelet会杀死容器,并按照重启策略执行下一步操作;如果容器没有定义存活状态检测,则默认为Success
  3. ReadinessProbe:就绪状态检测,检测容器是否就绪可以对外提供服务,如果就绪检测失败,端点控制器将从与此Pod匹配的所有Service对象的端点中删除该Pod的IP地址;如果容器没有定义就绪状态检测,则默认为Success

使用探针检查容器主要有三种不同的方式实现,每个探针检测都必须定义为这三种方式中的一种:

  1. ExecAction:在容器内执行一条命令,如果命令返回码为0则表示成功
  2. TCPSocketAction:通过与容器上的TCP端口建立连接进行诊断,如果建立连接成功则表示成功
  3. HTTPGetAction:向容器的ip:port/path发送HTTP Get请求进行诊断,如果响应码为2xx或3xx则表示成功

每次探测都将获得以下3种结果之一:

  • Success:成功,容器通过检测
  • Failed:失败,容器检测失败
  • Unknow:未知,诊断失败,因此不会采取任何行动
存活状态检测

容器探针有很多配置字段,具体可以参考https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ Pod配置中,.spec.containers.livenessProbe用于定义存活状态检测,其可嵌套使用的字段如下:

spec:
    containers:
    - name: ...
      image: ...
      livenessProbe:
        exec:	#命令式探针配置
          command: <[]string>	#要执行的命令
        httpGet: #http探针配置
          host: <string>	#连接使用的主机名,默认是Pod IP,也可以在HTTP Header中设置HOST替代
          httpHeaders: <[]Object>	#请求中自定义的 HTTP 头。HTTP 头字段允许重复
          path: <string>	#http url路径
          port: <int>	#访问容器的端口号
          scheme: <string>	#请求协议,默认http,也可以选择https
        tcpSocket: #tcp探针配置
          host: <string>	#连接的主机名,默认是Pod IP
          port: <string>	#连接的端口
        initialDelaySeconds: <int>	#容器成功启动后等待多少秒开始执行存活检测
        periodSeconds: <int>	#每隔多少秒执行一次存活检测
        successThreshold: <int>	#成功阈值,默认为1,对于存活检测来说只能为1
        failureThreshold: <int>	#失败阈值,默认为3
        timeoutSeconds: <int>	#检测超时时间
httpGet探针

下面是基于httpGet的存活状态检测探针示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: liveness-http-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: liveness-http-demo
  template:
    metadata:
      labels:
        app: liveness-http-demo
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80
        livenessProbe:
          httpGet:	#向nginx的80端口发起http请求以验证存活状态
            path: index.html
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          failureThreshold: 3
          timeoutSeconds: 1

查看pod状态,因为存活状态检测正常,不会被重启

k8s的podip和clusterip区别 k8s中的pod_nginx_05


然后修改请求路径为错误的路径重新重建Deployment,来验证存活状态检测失败的效果

k8s的podip和clusterip区别 k8s中的pod_html_06


重建之后查看Pod状态,通过kubectl describe可以看到Pod的存活检测失败

k8s的podip和clusterip区别 k8s中的pod_html_07


3次检测失败后,Pod会被重启,大约启动后20s左右

k8s的podip和clusterip区别 k8s中的pod_kubernetes_08


Pod多次重启之后存活检测都未成功,会被至于CrushLoopBackOff状态

k8s的podip和clusterip区别 k8s中的pod_Pod_09

tcpSocket探针

下面是基于tcpSocket的存活状态检测探针示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: liveness-tcpsocket-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: liveness-tcpsocket-demo
  template:
    metadata:
      labels:
        app: liveness-tcpsocket-demo
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80
        livenessProbe:
          tcpSocket:	#通过与容器的80端口建立TCP连接来进行存活状态检测
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          failureThreshold: 3
          timeoutSeconds: 2
exec探针

下面是基于exec的存活状态检测探针示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: liveness-exec-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: liveness-exec-demo
  template:
    metadata:
      labels:
        app: liveness-exec-demo
    spec:
      containers:
      - name: redis
        image: redis
        imagePullPolicy: IfNotPresent
        ports:
        - name: redis
          containerPort: 6379
        livenessProbe:
          exec:
            command:
            - /usr/local/bin/redis-cli
            - quit
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          failureThreshold: 3
          timeoutSeconds: 2
就绪状态检测

容器的就绪状态检测定义在Pod的.spec.containers.readinessProbe字段中,同样支持httpGet、tcpSocket和exec3种方式,且它们各自的定义方式和存活探针检测一致。因此将容器的livenessProbe改为readinessProbe,并略作适应性修改就可以使用。下面以httpGet为例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: readiness-httpget-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: readiness-httpget-demo
  template:
    metadata:
      labels:
        app: readiness-httpget-demo
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - name: http
          containerPort: 80
        readinessProbe:
          httpGet:
            port: 80
            path: index.html
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          failureThreshold: 3
          timeoutSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
  name: readinessprobe-svc
spec:
  type: NodePort
  selector:
    app: readiness-httpget-demo
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP
    nodePort: 30180

创建后查看Pod状态,正常运行,也可以通过Service对象访问Pod

k8s的podip和clusterip区别 k8s中的pod_kubernetes_10


k8s的podip和clusterip区别 k8s中的pod_nginx_11


然后进入Pod将index.html移除,来模拟就绪状态检测失败的情况

k8s的podip和clusterip区别 k8s中的pod_html_12


通过kubectl desccribe命令查看,可以看就绪状态探测失败的信息

k8s的podip和clusterip区别 k8s中的pod_Pod_13


3次检测失败后,Pod中Ready的容器数量会变为0;同时Service会把Pod IP从对应的Endpoint中移除,此时就不能通过Service访问Pod了

k8s的podip和clusterip区别 k8s中的pod_Pod_14


k8s的podip和clusterip区别 k8s中的pod_kubernetes_15


k8s的podip和clusterip区别 k8s中的pod_kubernetes_16

启动状态检测

容器的启动状态检测定义在Pod的.spec.containers.startupProbe字段中,定义方式和其余两种探针一致。下面还是以httpGet为例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: startup-httpget-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: startup-httpget-demo
  template:
    metadata:
      labels:
        app: startup-httpget-demo
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - name: http
          containerPort: 80
        startupProbe:
          httpGet:
            port: 80
            path: index.html
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          failureThreshold: 3
          timeoutSeconds: 2

创建后查看Pod状态,正常运行。如果启动状态检测失败,kubelet也会杀死容器,并按照重启策略进行处理,和存活状态检测处理方式一致。

k8s的podip和clusterip区别 k8s中的pod_nginx_17

综合示例

postStart和preStop钩子函数

postStart和preStop官方文档:https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/ k8s为容器提供了postStart和preStop钩子,它们的作用如下:

  • postStart:在容器创建完成后立即执行的钩子,该钩子定义的的操作完成后容器才能真正的完成启动过程,如果执行失败不会继续创建Pod;k8s不能保证postStart钩子在容器的主应用程序之前运行
  • preStop:在容器终止操作运行之前立即执行的钩子,它以同步方式调用,因此在它完成之前会阻塞删除容器的操作,这意味者preStop钩子的事件成功执行并退出,容器终止操作才能真正完成

钩子函数的实现方式类似于容器探针,也支持exec、tcpSocket和httpGet三种实现方式,配置格式和工作逻辑与容器探针都一致。
postStart和preStop定义在Pod内容器的lifecycle字段,下面是一个示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: lifecycle-demo
spec:
  replicas: 1
  selector:
    matchExpressions:
    - {key: app, operator: In, values: ["lifecycle-demo"]}
  template:
    metadata:
      labels:
        app: lifecycle-demo
    spec:
      containers:
      - name: image
        image: nginx
        lifecycle:
          postStart:	#postStart配置
            exec:	#使用exec方式执行命令,生成默认页面
              command: ["/bin/sh", "-c", "echo poststart web page >/usr/share/nginx/html/index.html"]
          preStop:	#preStop配置
            exec:	#使用exec方式执行命令,优雅终止nginx
              command: ["/bin/sh", "-c", "nginx -s quit; while killall -0 nginx; do sleep 1; done"]

访问容器测试,可以看到index.html以被替换

k8s的podip和clusterip区别 k8s中的pod_nginx_18

Pod生命周期

k8s的podip和clusterip区别 k8s中的pod_html_19

如上图所示,如果一个Pod配置了上述所有行为定义,则一个Pod的生命周期运行步骤如下:

  1. 先创建pause容器,它初始化Pod基础环境,并为后续加入的容器提供共享名称空间
  2. 按顺序以串行方式运行各个初始化容器,任何一个初始化容器运行失败都会导致Pod创建失败,并按照重启策略处理,默认是重启
  3. 所有初始化容器运行完成后,启动应用程序容器。多应用容器Pod环境中,会并行启动所有应用容器,它们各自按其定义展开生命周期。应用容器创建之后会立即执行postStart钩子函数,该步骤失败会导致相关容器被重启
  4. 运行容器启动状态检测,判断容器是否启动成功,如果检测失败,同样按照重启策略进行处理;未定义时默认状态为Success
  5. 容器成功启动后,定期运行存活状态检测和就绪状态检测。存活状态检测失败会导致容器被重启;就绪状态检测失败会导致Pod从其所属的Service对象的可用端点列表中移除。
  6. 终止Pod对象时会先运行preStop钩子函数,并在宽限期结束后终止容器