首页
  • 监控

    • grafana
    • prometheus
  • 学习笔记

    • 《核心系统命令实战》
    • 《MySQL 是怎样运行的:从根儿上理解 MySQL》
    • 《Ansible权威指南》
  • 博客搭建
  • git
  • python
  • 友情链接
  • 文档编写规范
  • 我用过的电脑
  • 喷涂相关
  • 每日一溜
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

小刘说

砥砺前行
首页
  • 监控

    • grafana
    • prometheus
  • 学习笔记

    • 《核心系统命令实战》
    • 《MySQL 是怎样运行的:从根儿上理解 MySQL》
    • 《Ansible权威指南》
  • 博客搭建
  • git
  • python
  • 友情链接
  • 文档编写规范
  • 我用过的电脑
  • 喷涂相关
  • 每日一溜
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • zabbix

  • docker

  • kubernetes

    • 二进制安装高可用k8s集群(v1.25.0)
    • 二进制安装高可用k8s集群(v1.27.2)
    • kubernetes架构解析
    • kubernetes基础概念-Pod
      • 1. Pod概念
      • 2. 为什么需要Pod
      • 3. Pod使用
        • 3.1. 创建Pod
        • 3.2. Pod的状态
        • 3.2.1. Phase
        • 3.2.3. Conditions
        • 3.3. Pod的探针(健康检查)
        • 3.3.1. 探测类型
        • 3.3.2. 检查机制
        • 3.3.3. 探测结果
        • 3.4. 镜像拉取策略和Pod恢复策略
        • 3.4.1. 镜像拉取策略
        • 3.4.2. Pod恢复策略
        • 3.5. 容器生命周期回调
    • kubernetes调度基础
    • kubernetes控制器-Deployment
    • kubernetes控制器-Service
    • Kubernetes(k8s) YAML文件详解
  • harbor

  • mysql

  • nexus

  • jenkins

  • elasticsearch

  • 学习笔记

  • apache2.2升级2.4
  • heredoc(cat EOF)
  • Rocky linux 9 初始化
  • 运维
  • kubernetes
小刘
2023-07-25
目录

kubernetes基础概念-Pod

# kubernetes基础概念-Pod

# 1. Pod概念

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。是一组(一个或多个) 容器 (opens new window); 这些容器共享存储、网络、以及怎样运行这些容器的声明。

  简单来说,Pod就是Kubernetes里的“应用“;而一个应用可以由多个容器组成。而每一个Pod可以共享同一个命名空间,如:

  • Mount namespaces(挂载命名空间)

  • Network namespaces(网络命名空间)

  • UTS namespaces(主机名命名空间)

  • IPC namespaces(进程间通信命名空间)

  • PID namespaces(进程ID命名空间)

  • User namespaces(用户命名空间)

  通过共享不同的命名空间,让每个Pod总是一起运行在同一个工作节点上。而每个Pod就像一个独立的逻辑机器,拥有自己的IP、主机名、进程等,并包含一个或多个容器,每个容器都运行一个应用进程。

# 2. 为什么需要Pod

  首先需要了解一个概念,容器是一个“单进程”模型。一个正在运行的Docker容器,其实就是一个启用了多个Linux Namespace的应用进程,而这个进程能够使用的资源量,则受Cgroups配置的限制。

  由于一个容器的本质就是一个进程,用户的应用进程实际上就是容器里PID=1的进程,也是其他后续创建的所有进程的父进程。这就意味着,在一个容器中,你没办法同时运行两个不同的应用,除非你能事先找到一个公共的PID=1的程序来充当两个不同应用的父进程。

  容器本身设计之初就是希望容器和应用能够同生命周期,否则,一旦出现类似于“容器是正常运行的,但是里面的应用早已经挂了”的情况就很麻烦了。

  而在真正的操作系统中,进程并不是单打独斗的,而是以进程组(线程组)的方式存在,可以登录到一台Linux服务器执行如下命令:

pstree -g
1

  这条命令的作用,是展示当前系统中正在运行的进程的树状结构,返回结果如下:

systemd(1)-+-accounts-daemon(1984)-+-{gdbus}(1984)
           | `-{gmain}(1984)
           |-acpid(2044)
          ...      
           |-lxcfs(1936)-+-{lxcfs}(1936)
           | `-{lxcfs}(1936)
           |-mdadm(2135)
           |-ntpd(2358)
           |-polkitd(2128)-+-{gdbus}(2128)
           | `-{gmain}(2128)
           |-rsyslogd(1632)-+-{in:imklog}(1632)
           |  |-{in:imuxsock) S 1(1632)
           | `-{rs:main Q:Reg}(1632)
           |-snapd(1942)-+-{snapd}(1942)
           |  |-{snapd}(1942)
           |  |-{snapd}(1942)
           |  |-{snapd}(1942)
           |  |-{snapd}(1942)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

  比如,这里有一个叫作rsyslogd的程序,它负责的是Linux操作系统里的日志处理。可以看到,rsyslogd的主程序main,和它要用到的内核日志模块imklog,imuxsock等,同属于1632进程组。这些进程相互协作,共同完成rsyslogd程序的职责。这三个进程一定要运行在同一台机器上,否则,它们之间基于Socket的通信和文件交换,都会出现问题。

  所以,Google工程师提出了Pod的概念,通过以Pod为原子调度单位解决了两个问题:

  1. 进程组通信问题:进程互相之间会发生直接的文件交换、使用localhost或者Socket文件进行本地通信、会发生非常频繁的远程调用、需要共享某些Linux Namespace等等。
  2. 成组资源调度问题:如果以容器的“单进程”模型来看,为了保证进程的真实可用性,这三个进程就需要分为三个容器,同时三个容器必须要工作到同一个节点才可以正常使用。假设每个容器需要的资源是1GB,node01剩余内存有3GB,node02剩余内存有2.5GB,而最开始的时候有两个容器同时调度到了node02节点,但是进行imuxsock容器调度的时候发现,node02可用资源只有0.5 GB了,并不足以运行imuxsock容器,但是imuxsock容器又必须调度到node02节点上,这就是一个成组调度没有被妥善处理的例子。

  而Pod内为了使用同一个namespace,就必须有一个中间容器起到“Hold”住namespace的作用,否则Pod中的多个容器就不是对等关系,而是拓扑关系了。

  所以,在Kubernetes项目里,Pod的实现需要使用一个中间容器,这个容器叫作Infra容器。在这个Pod中,Infra容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过Join Network Namespace的方式,与Infra容器关联在一起。

在Kubernetes项目里,Infra容器一定要占用极少的资源,所以它使用的是一个非常特殊的镜像,叫作:registry.k8s.io/pause。这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有100~200 KB左右。

在Infra容器“Hold住”Namespace后,用户容器就可以加入到Infra容器的Namespace当中了。

# 3. Pod使用

# 3.1. 创建Pod

在生产环境中,很少单独运行一个Pod,因为单独创建的Pod并不能实现一些高级的发布策略,所以在实际使用中经常会用Deployment、DaemonSet、StatefulSet等高级控制器调度并管理Pod。我们有时候也会单独启动一个Pod用于测试业务等,新建 nginx.yml 如下:

apiVersion: v1                             # 必选,API的版本号
kind: Pod	                               # 必选,类型Pod
metadata:	                               # 必选,元数据
  name: nginx	                           # 必选,符合RFC 1035规范的Pod名称
  namespace: web-testing                   # 可选,不指定默认为default,Pod所在的命名空间,命令行中可以使用 -n 指定命名空间
  labels:	                               # 可选,标签选择器,一般用于Selector
    app: nginx               
    role: frontend                       # 可选,标签,可以写多个
  annotations:	                         # 可选,注释列表
    app: nginx               
spec:	                                   # 必选,用于定义容器的详细信息
  restartPolicy: Always	                 # 可选,容器重启策略,默认为Always
  os: 
    name: linux                          # 可选,希望 Pod 运行在哪个操作系统之上
  containers:	                           # 必选,容器列表
  - name: nginx	                         # 必选,符合RFC 1035规范的容器名称
    image: nginx:latest	                 # 必选,容器所用的镜像的地址
    imagePullPolicy: Always	             # 可选,镜像拉取策略
    command:           
      - nginx	                           # 可选,容器启动执行的命令
      - -g 
      - "daemon off;" 
    resources:	                         # 可选,资源限制和资源请求限制
      limits:	                           # 最大限制设置
        cpu: 500m                        # 一个CPU等于1个物理CPU核或者1个虚拟核,0.1等价于100m
        memory: 512Mi                    # 可以使用以数量为后缀,如G、M、k,也可使用2的幂数,如Gi、Mi、Ki
      requests:	                         # 可选,启动所需的资源
        cpu: 100m  
        memory: 128Mi  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

使用kubectl apply命令从YAML文件创建Pod:

kubectl apply -f nginx.yml
1

查看pod的运行状态:

kubectl get pod nginx
1

# 3.2. Pod的状态

# 3.2.1. Phase

和裸容器部署一样,Pod在运行时也会有不同的状态,Pod的状态信息保存在PodStatus对象中,在PodStatus中有一个Phase字段,用于描述Pod在其生命周期中的不同状态。

通常情况下,在 Pod 的生命周期中,每个 Pod 会处于 5 个不同的 phase:pending,running,succeed,failed,unknown。

状态 说明
Pending(挂起) Pod 已被 Kubernetes 系统接收,但仍有一个或多个容器未被创建,可以通过 kubectl describe 查看处于 Pending 状态的原因
Running(运行中) Pod 已经被绑定到一个节点上,并且所有的容器都已经被创建,而且至少有一个是运行状态,或者是正在启动或者重启,可以通过 kubectl logs 查看 Pod 的日志
Succeeded(成功) 所有容器执行成功并终止,并且不会再次重启,可以通过 kubectl logs 查看 Pod日志
Failed(失败) 所有容器都已终止,并且至少有一个容器以失败的方式终止,也就是说这个容器要么以非零状态退出,要么被系统终止,可以通过 logs 和 describe 查看 Pod 日志和状态
Unknown(未知) 通常是由于通信问题造成的无法获得 Pod 的状态

可以通过命令查看:

kubectl get pods nginx -o yaml | grep phase
1

以下是一些常见的导致pod处于上述状态的原因:

状态 说明
ImagePullBackOff ErrImagePull 镜像拉取失败,一般是由于镜像不存在、网络不通或者需要登录认证引起的,可以使用 kubectl describe 命令查看具体原因
CrashLoopBackOff 容器启动失败,可以通过 logs 命令查看具体原因,一般为启动命令不正确,健康检查不通过等
OOMKilled 容器内存溢出,一般是容器的内存 Limit 设置的过小,或者程序本身有内存溢出,可以通过 kubectl logs 查看程序启动日志
Terminating Pod 正在被删除,可以通过 kubectl describe 查看状态
SysctlForbidden Pod 自定义了内核配置,但 kubelet 没有添加内核配置或配置的内核参数不支持,可以通过 kubectl describe 查看具体原因
Completed 容器内部主进程退出,一般计划任务执行结束会显示该状态,此时可以通过 kubectl logs 查看容器日志
ContainerCreating Pod 正在创建,一般为正在下载镜像,或者有配置不当的地方,可以通过 kubectl describe 查看具体原因

# 3.2.3. Conditions

conditions用于描述Pod当前处于哪个phase,以及处于该phase的原因。及作为一个辅助手段,详细的展示 pod 的状态信息,用于问题排查分析时提供更多依据。在同一时间内,1个Pod可能处于多个conditions。

状态 说明
Initialized 所有的 init containers 都已成功完成
Ready 只有 pod 中的所有 container 就绪,且 pod 的 readiness probe 也完成了,意味着 pod 可以对外提供服务了
ContainersReady Pod 中所有容器都已就绪,但这并不意味着 Pod 也 ready
PodScheduled Pod 已经被调度到某节点

可以通过命令查看:

kubectl describe pod nginx | grep Conditions: -A 5
1

# 3.3. Pod的探针(健康检查)

在生产环境下,进程正常启动并不代表应用能正常处理请求,所以合理地设计应用的健康检查尤为重要。在使用裸机或者裸容器部署时,一般很难对应用做很完善的健康检查,而Pod提供的探针可以很方便地用来检测容器内的应用是否正常。目前探针有3种检测方式,可以根据不同的场景选择合适的健康检查方式。

# 3.3.1. 探测类型

目前支持的探测器类型有3种,可以选择性地对容器进行检测。

种类 说明
startupProbe Kubernetes1.16 新加的探测方式,用于判断容器内的应用程序是否已经启动。如果配置了 startupProbe,就会先禁用其他探测,直到它成功为止。如果探测失败,Kubelet会杀死容器,之后根据重启策略进行处理,如果探测成功,或没有配置 startupProbe,则状态为成功,之后就不再探测。
livenessProbe 用于探测容器是否在运行,如果探测失败,kubelet 会“杀死”容器并根据重启策略进行相应的处理。如果未指定该探针,将默认为 Success
readinessProbe 一般用于探测容器内的程序是否健康,即判断容器是否为就绪(Ready)状态。如果是,则可以处理请求,反之Endpoints Controller 将从所有的 Service 的Endpoints中删除此容器所在 Pod 的IP 地址。如果未指定,将默认为 Success

startupProbe

    startupProbe:
      tcpSocket:	                       # 端口检查方式
        port: 80
      initialDelaySeconds: 60	           # 可选,容器启动后要等待多少秒后才启动探针检测
      periodSeconds: 5	                 # 可选,执行探测的时间间隔,默认是 10 秒。最小值是 1
      timeoutSeconds: 2	                 # 可选,探针超时后等待多少秒
      successThreshold: 1                # 可选,检查成功为1次表示就绪
      failureThreshold: 1                # 可选,检测失败1次表示未就绪
1
2
3
4
5
6
7
8

livenessProbe

    livenessProbe:	                     
      exec:	                             
        command: 
        - cat
        - /etc/hosts
      initialDelaySeconds: 60	           # 可选,容器启动后要等待多少秒后才启动探针检测
      periodSeconds: 5	                 # 可选,执行探测的时间间隔,默认是 10 秒。最小值是 1
      timeoutSeconds: 2	                 # 可选,探针超时后等待多少秒
      successThreshold: 1                # 可选,检查成功为1次表示就绪
      failureThreshold: 1                # 可选,检测失败1次表示未就绪
1
2
3
4
5
6
7
8
9
10

readinessProbe

    readinessProbe:	                     
      httpGet:	                         # 
        scheme: HTTP                     # 设置连接主机的协议,默认是HTTP
        path: /_health
        port: 80
        httpHeaders:                     # 可选,自定义请求中的HTTP头
          - name: end-user
            value: jason
      initialDelaySeconds: 60	           # 可选,容器启动后要等待多少秒后才启动探针检测
      periodSeconds: 5	                 # 可选,执行探测的时间间隔,默认是 10 秒。最小值是 1
      timeoutSeconds: 2	                 # 可选,探针超时后等待多少秒
      successThreshold: 1                # 可选,检查成功为1次表示就绪
      failureThreshold: 1                # 可选,检测失败1次表示未就绪
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3.3.2. 检查机制

实现方式 说明
exec 在容器内执行一个指定的命令,如果命令返回值为 0,则认为容器健康
tcpSocket 通过 TCP 连接检查容器指定的端口,如果端口开放,则认为容器健康
httpGet 对指定的URL 进行 Get 请求,如果状态码在 200~400 之间,则认为容器健康
grpc 使用 gRPC (opens new window) 执行一个远程过程调用。如果响应的状态是 "SERVING",则认为容器健康

需要注意的是,同一种探测类型中只能实现一种检查机制。

前三种已经在探测类型中演示,这里不再赘述。

grpc

    livenessProbe:	                     
      grpc:	                             # grpc检查方式
        port: 2379
      initialDelaySeconds: 60	           # 可选,容器启动后要等待多少秒后才启动探针检测
      periodSeconds: 5	                 # 可选,执行探测的时间间隔,默认是 10 秒。最小值是 1
      timeoutSeconds: 2	                 # 可选,探针超时后等待多少秒
      successThreshold: 1                # 可选,检查成功为1次表示就绪
      failureThreshold: 1                # 可选,检测失败1次表示未就绪
1
2
3
4
5
6
7
8

# 3.3.3. 探测结果

状态 说明
Success(成功) 容器通过检测
Failure(失败) 容器检测失败
Unknown(未知) 诊断失败,不采取任何措施

# 3.4. 镜像拉取策略和Pod恢复策略

# 3.4.1. 镜像拉取策略

可以使用 .spec.containers[].imagePullPolicy 指定镜像的拉取策略

操作方式 说明
Always 总是拉取,当tag为latest时,默认为Always
Never 无论是否存在都不拉取
IfNotPresent 镜像不存在时拉取

# 3.4.2. Pod恢复策略

可以使用 .spec.restartPolicy 指定Pod的恢复策略

操作方式 说明
Always 默认策略。容器失效时,自动重启该容器
OnFailure 容器以不为 0 的状态码终止,自动重启该容器
Never 无论何种状态,都不会重启

这里我们新创建一个Pod进行演示:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: test-liveness-exec
spec:
  containers:
  - name: liveness
    image: nginx
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在这个Pod中,它在启动之后做的第一件事,就是在/tmp目录下创建了一个healthy文件,以此作为自己已经正常运行的标志。而30s过后,它会把这个文件删除掉。

与此同时,我们定义了一个这样的livenessProbe(健康检查)。它的类型是exec,这意味着,它会在容器启动后,在容器里面执行一条我们指定的命令,比如:cat /tmp/healthy。这时,如果这个文件存在,这条命令的返回值就是0,Pod就会认为这个容器不仅已经启动,而且是健康的。这个健康检查,在容器启动5s后开始执行(initialDelaySeconds: 5),每5s执行一次(periodSeconds: 5)。

创建这个Pod,并查看Pod的状态:

kubectl create -f test-liveness-exec.yaml
sleep 20
kubectl get pod
1
2
3

等待30s后,再次查看Pod的状态:

30s后,我们删除了/tmp/healthy,健康检查探查到/tmp/healthy已经不存在了,但是Pod还是保持着Running的状态,而在这个过程中,Pod为什么保持Running状态不变呢?

我们看到RESTARTS字段变为了1,表示异常的容器已经被kubernetes重启了,而这个过程中,Pod保持Running状态不变。

而针对这种情况,只需要记住两个基本的设计原理:

  1. 只要Pod的restartPolicy指定的策略允许重启异常的容器(比如:Always),那么这个Pod就会保持Running状态,并进行容器重启。否则,Pod就会进入Failed状态 。
  2. 对于包含多个容器的Pod,只有它里面所有的容器都进入异常状态后,Pod才会进入Failed状态。在此之前,Pod都是Running状态。此时,Pod的READY字段会显示正常容器的个数。

# 3.5. 容器生命周期回调

容器生命周期回调,指的是在容器发生状态变化时,需要触发的一系列 "动作"。

参考 Attach Handlers to Container Lifecycle Events (opens new window)

种类 说明
postStart 容器创建后立即执行,注意由于是异步执行,它无法保证一定在 ENTRYPOINT 之前运行。如果失败,容器会被杀死,并根据 RestartPolicy 决定是 否重启
preStop 容器终止前执行,常用于资源清理。执行完成之后容器将成功终止,如果失败,容器同样也会被杀死。在其完成之前 会阻塞删除容器的操作
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  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"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

官方给出的文档中,使用了postStart和preStop命令,postStart在容器启动时输出了 echo Hello from the postStart handler 到message文件,而preStop实现了nginx的优雅退出。

需要注意的是,preStop会阻塞容器的结束流程,直到 "动作" 顺利完成后,才会允许容器结束。

参考文章及书籍:

  • DOCKER基础技术:LINUX NAMESPACE(上) (opens new window)
  • DOCKER基础技术:LINUX NAMESPACE(下) (opens new window)
  • 深入剖析Kubernetes (opens new window)
  • 同一个POD中默认共享哪些名称空间 (opens new window)
上次更新: 2024/05/11, 03:55:33

← kubernetes架构解析 kubernetes调度基础→

最近更新
01
kubernetes控制器-Service
08-18
02
kubernetes控制器-Deployment
08-08
03
kubernetes调度基础
07-27
更多文章>
Theme by Vdoing | Copyright © 2023-2024 本站支持IPv6访问 本站支持SSL安全访问
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式