Kubernetes 核心概念之1 pod

官方文档:https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

Pod 概述

Pod 是 Kubernetes 系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型,也是在 Kubernetes 上运行容器化应用的资源对象,其它的资源对象都是用来支撑或者扩展 Pod 对象功能的,比如控制器对象 RC 是用来管控 Pod 对象的,Service 或者 Ingress 资源对象是用来暴露 Pod 引用对象的,PersistentVolume 资源对象是用来为 Pod 提供存储等等,K8S 不会直接处理容器,而是 Pod,Pod 是由一个或多个 container 组成。

Pod 是 Kubernetes 的最重要概念,每一个 Pod 都有一个特殊的被称为“根容器”的 Pause 容器。Pause 容器对应的镜像属于 Kubernetes 平台的一部分,除了 Pause 容器,每个 Pod 还包含一个或多个紧密相关的用户业务容器。

Pod 基本概念

  • 最小部署的单元。
  • Pod 里面是由一个或多个容器组成【一组容器的集合】,每个 Pod 包含一个或多个紧密相关的用户业务容器。
  • 一个 Pod 中的容器是共享网络命名空间。
  • Pod 是短暂的。

Pod 存在的意义

  • 创建容器使用 Docker,Docker 的最佳实践是:一个容器运行一个应用程序,使用一个进程。

  • Pod 是多进程设计,运用多个应用程序,也就是需要一个 Pod 里面有多个容器,且一个容器里面运行一个应用程序。

  • Pod 的存在是为了亲密性应用,比如:

    • 两个应用之间进行交互
    • 网络之间的调用【通过 127.0.0.1 或 socket】
    • 两个应用之间需要频繁调用

Pod 的设计理念是支持多个容器在一个 Pod 中共享网络地址和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。

同时 Pod 对多容器的支持是 K8S 中最基础的设计理念。在生产环境中,通常是由不同的团队各自开发构建自己的容器镜像,在部署的时候组合成一个微服务对外提供服务。

Pod 是 K8S 集群中所有业务类型的基础,可以把 Pod 看作运行在 K8S 集群上的小机器人,不同类型的业务就需要不同类型的小机器人去执行。目前 K8S 的业务主要可以分为以下几种

  • 长期伺服型:long-running
  • 批处理型:batch
  • 节点后台支撑型:node-daemon
  • 有状态应用型:stateful application

上述的几种类型,分别对应的小机器人控制器为:Deployment、Job、DaemonSet 和 StatefulSet (后面将介绍控制器)

Pod 实现机制

Pod 主要有以下两大机制

  • 共享网络
  • 共享存储

共享网络

容器本身之间相互隔离的,一般是通过 NameSpaceCgroups 进行隔离,那么 Pod 里面的容器如何实现通信?

  • 首先需要满足前提条件,也就是容器都在同一个 namespace 之间。

Pod 实现原理:首先会在 Pod 会创建一个根容器 pause 容器,然后我们在创建业务容器 nginx,redis 等,在我们创建业务容器的时候,会把它添加到 pause 容器 中,而在 pause 容器 中会独立出 ip地址,mac地址,port 等信息,然后让所有容器共享。

共享存储

Pod 持久化数据,专门存储到某个地方中

使用 Volumn 数据卷进行共享存储,案例如下所示

Pod 的定义

在一个 yaml 文件中,Pod 是这样定义的(完整):

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
apiVersion: v1                //版本
kind: pod //类型,此处为 Pod 类型
metadata: //元数据
name: string //元数据,Pod 的名称
namespace: string //元数据,Pod 的命名空间,如果不填写默认是default
labels: //元数据,Pod 的标签列表,可写多个label
- name: string //元数据,标签的名称
annotations: //元数据,自定义注解列表
- name: string //元数据,自定义注解名称
spec: //Pod 中容器的详细定义
containers: //Pod 中容器的列表
- name: string //容器的名称
image: string //容器中的镜像
imagePullPolicy: Always | Never | IfNotPresent //获取镜像的策略: 默认为Always(拉取镜像库镜像,即每次都尝试重新下载镜像)、Never(仅使用本地镜像)、IfNotPresent(本地有镜像就不拉取镜像库镜像,如果没有就拉取镜像库镜像)
command: [string] //容器的启动命令列表(不配置则使用镜像内部的命令)
args: [string] //启动命令参数列表
workingDir: string //容器的工作目录
volumeMounts: //挂载到容器内部的存储卷配置
- name: string //引用pod定义的共享存储卷的名称,需使用volumes[]部分定义的共享存储卷名称
mountPath: string //存储卷在容器内部Mount的绝对路径
readOnly: boolean //是否为只读模式,默认为读写模式
ports: //容器需要暴露的端口号列表
- name: string //端口的名称
containerPort: int //容器要暴露的端口
hostPort: int //容器所在主机监听的端口(容器暴露端口映射到宿主机的端口,设置host port时同一台宿主机将不能启动该容器的第二份版本)
protocol: string //端口协议,支持TCP和UDP,默认值为TCP
env: //容器运行前设置的环境变量列表
- name: string //环境变量的名称
value: string //环境变量的值
resources: //资源限制和资源请求设置
limits: //资源限制,容器的最大可用资源数量
cpu: string //CPU限制,单位为core数
memory: string //内存限制,单位可以为MiB、GiB等
requests: //资源请求设置
cpu: string //CPU请求,单位为core数,容器启动的初始可用数量
memory: string //内存请求,单位可以为MiB、GiB等,容器启动的初始可用数量
livenessProbe: //pod内各容器健康检查的设置
exec: //对pod内各容器健康检查的设置,exec方式
command: [string] //exec方式需要指定的命令或者脚本
httpGet: //对pod内各容器健康检查的设置,使用httpGet方式,需指定path、port
path: string //指定的路径
port: number //指定的端口
host: string //指定的主机
scheme: string
httpHeaders:
- name: string
value: string
tcpSocket: //对pod内各容器健康检查的设置,tcpSocket方式
port: number
initialDelaySeconds: 0 //容器启动完成后首次探测的时间,单位为s
timeoutSeconds: 0 //对容器健康检查的探测等待响应的超时时间设置,单位为s,默认值为1s。若超过该超时时间设置,则将认为该容器不健康,会重启该容器
periodSeconds: 0 //对容器健康检查的定期探测时间设置,单位为s,默认10s探测一次
successThreshold: 0
failureThreshold: 0
securityContest:
privileged: false
restartPolicy: Always | Never | OnFailure //重启策略,默认为always
nodeSelector: object //节点选择,表示将该 Pod 调度到包含这些 label 的 node 上,以 key:value 的方式指定
imagePullSecrets: //pull镜像时使用的secret名称,以name:secretkey格式指定
- name: string
hostNetwork: false //是否使用主机网络模式,弃用docker网桥,默认为否

volumes: //在该pod上定义的共享存储卷列表
- name: string //共享存储卷的名称,在一个pod中每个存储卷定义一个名称
emptyDir: {} //类型为emptyDir的存储卷,表示与pod同生命周期的一个临时目录,其值为一个空对象:emptyDir: {}
hostPath: //类型为hostPath的存储卷,表示pod容器挂载的宿主机目录,通过volumes[].hostPath.path指定
path: string //pod容器挂载的宿主机目录
secret: //类型为secret的存储卷,表示挂载集群预定义的secret对象到容器内部
secretName: string
items:
- key: string
path: string
configMap: //类型为configMap的存储卷,表示挂载集群预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string

Pod 分类

Pod 分为 普通Pod静态Pod 两类。

普通 Pod 一旦被创建,就会放入 etcd 中存储,随后会被 k8s Master 调度到某个具体的 node 上并进行绑定,随后该 Pod 所对应的 Node 上的 kubelet 进程实例化成一组相关的 Docker 容器并启动。在默认情况下,当 Pod 里某个容器停止时,K8s 会自动检测到这个问题并且重新启动这个 Pod 里的所有容器,如果 Pod 所在的 node 宕机,则会将这个 node 上的所有 Pod 重新调度到其它节点。

静态 Pod 是由 kubelet 进行管理的仅存在于特定 node 上的 Pod,它们不能通过 API Server 进行管理,无法与 ReplicationController、Deployment 或 DaemonSet 进行关联,并且 kubelet 也无法对它们进行健康检查。

Pod 生命周期和重启策略

Pod 的几个状态(生命周期)

Pod 生命周期包含 PendingRunningCompeletedFailedUnknown 五个状态。

状态 说明
Pending API Server 已经创建了该 Pod,但 Pod 中的一个或多个容器的镜像还没有创建,包括镜像下载过程
Running Pod 内所有容器已创建,且至少一个容器处于运行、正在启动或正在重启状态
Compeleted Pod 内所有容器均成功执行退出,且不会再重启
Failed Pod 内所有容器均已退出,但至少—个容器退出失败
Unknown 由于某种原因无法获取 Pod 状态,例如网络通信不畅等

Pod 镜像拉取策略 imagePullPolicy

拉取策略主要分为了以下几种

  • IfNotPresent:默认值,镜像在宿主机上不存在才拉取。
  • Always:每次创建 Pod 都会重新拉取一次镜像。
  • Never:Pod 永远不会主动拉取这个镜像。

示例

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: Always # 拉取策略

Pod 重启机制 restartPolicy

因为 Pod 中包含了很多个容器,假设某个容器出现问题了,那么就会触发 Pod 重启机制。

重启策略主要分为以下三种

  • Always:当容器终止退出后,总是重启容器,默认策略 【nginx等,需要不断提供服务】
  • OnFailure:当容器异常退出(退出状态码非0)时,才重启容器。
  • Never:当容器终止退出,从不重启容器 【批量任务】

示例

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: myservice
spec:
containers:
- name: busybox-container
image: busybox
command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]
restartPolicy: Never # 重启策略

Pod 健康检查 livenessProbe

通过容器检查,原来我们使用下面的命令来检查

1
kubectl get pod

但是有的时候,程序可能出现了 Java 堆内存溢出,程序还在运行,但是不能对外提供服务了,这个时候就不能通过容器检查来判断服务是否可用了。

这个时候就可以使用应用层面的检查

  1. livenessProbe 存活检查。如果检查失败,将杀死容器,根据 Pod 的 restartPolicy【重启策略】来操作
  2. readinessProbe 就绪检查。如果检查失败,Kubernetes 会把 Pod 从 Service endpoints 中剔除

Probe 支持以下三种检查方式:

  • http Get:发送 HTTP 请求,返回 200-400 范围状态码为成功。
  • exec:执行 Shell 命令返回状态码是 0 为成功。
  • tcpSocket:发起 TCP Socket 建立成功。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: myservice
spec:
containers:
- name: liveness-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
initialDelaySeconds: 15
timeoutSeconds: 1

Pod 资源限制 resources

也就是我们 Pod 在进行调度的时候,可以对调度的资源进行限制,例如我们限制 Pod 调度是使用的资源是 2C4G,那么在调度对应的 node 节点时,只会占用对应的资源,对于不满足资源的节点,将不会进行调度。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master ~]# cat test.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-command
labels:
env: dev
namespace: default
spec:
containers:
- image: busybox
name: busybox-container
command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]
resources: # 资源配额
limits: # 限制最大资源,上限
cpu: 2 # CPU限制,单位是code数
memory: 2G # 内存最大限制
requests: # 请求资源(最小,下限)
cpu: 1 # CPU请求,单位是code数
memory: 500G # 内存最小请求

Pod 节点选择器 nodeSelector

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: pod-command
labels:
env: dev
namespace: default
spec:
nodeSelector: # 节点选择器
env_role: dev
containers:
- image: busybox

节点选择器选择过程

我们可以通过以下命令,给我们的节点新增标签,然后节点选择器就会进行调度了

1
kubectl label node node1 env_role=prod

节点亲和性 nodeAffinity

节点亲和性 nodeAffinity 和之前 nodeSelector 基本一样的,根据节点上标签约束来决定 Pod 调度到哪些节点上

  • 硬亲和性:约束条件必须满足
  • 软亲和性:尝试满足,不保证

支持常用操作符:in、NotIn、Exists、Gt、Lt、DoesNotExists

反亲和性:就是和亲和性刚刚相反,如 NotIn、DoesNotExists等

示例

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy
labels:
app: web
spec:
replicas: 6
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx-deploy
image: nginx:latest
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性:约束条件必须满足
nodeSelectorTerms:
- matchExpressions:
- key: app # 约束条件
operator: In
values:
- server
- web

preferredDuringSchedulingIgnoredDuringExecution: # 软亲和性:尝试满足,不保证
- weight: 60 # 设置app=web的权重为60
preference:
matchExpressions:
- key: app
operator: In
values:
- web

- weight: 40 # 设置app=server的权重为40
preference:
matchExpressions:
- key: app
operator: In
values:
- server

创建 Pod 流程

  • 首先创建一个 pod,然后创建一个 API Server 和 etcd【把创建出来的信息存储在 etcd 中】。
  • 然后创建 Scheduler,监控 API Server 是否有新的 Pod,如果有的话,会通过调度算法,把 pod 调度某个 node 上。
  • 在 node 节点,会通过 kubelet --apiserver 读取 etcd 拿到分配在当前 node 节点上的 pod,然后通过 docker 创建容器。

污点和污点容忍 Tainttolerations

污点 Taint

nodeSelector 和 NodeAffinity 都是 Pod 想要调度到某些节点上,属于 Pod 的属性,是在调度的时候实现的。

Taint 污点:节点不做普通分配调度,是节点属性,是 node 觉得是否将 Pod 调度到本节点。

场景

  • 专用节点【限制ip】
  • 配置特定硬件的节点【固态硬盘】
  • 基于 Taint 驱逐【在 node1 不放,在 node2 放】

查看污点情况

1
kubectl describe node k8smaster | grep Taint

污点值有三个

  • NoSchedule:一定不被调度
  • PreferNoSchedule:尽量不被调度【也有被调度的几率】
  • NoExecute:不会调度,并且还会驱逐 Node 已有 Pod

节点添加污点语法

1
kubectl taint node [node] key=value:污点的三个值

举例

1
kubectl taint node k8snode1 env_role=yes:NoSchedule

删除污点

1
kubectl taint node k8snode1 env_role:NoSchedule-

演示

我们现在创建多个Pod,查看最后分配到Node上的情况

  1. 首先我们创建一个 nginx 的pod

    1
    kubectl create deployment web --image=nginx
  2. 然后使用命令查看

    1
    kubectl get pods -o wide

    我们可以非常明显的看到,这个 Pod 已经被分配到 k8snode1 节点上了

  3. 下面我们把 pod 复制5份,在查看情况pod情况

    1
    kubectl scale deployment web --replicas=5

    我们可以发现,因为 master 节点存在污点的情况,所以节点都被分配到了 node1 和 node2 节点上

  4. 我们可以使用下面命令,把刚刚我们创建的 pod 都删除

    1
    kubectl delete deployment web
  5. 现在给了更好的演示污点的用法,我们现在给 node1 节点打上污点

    1
    kubectl taint node k8snode1 env_role=yes:NoSchedule
  6. 然后我们查看污点是否成功添加

    1
    kubectl describe node k8snode1 | grep Taint

  7. 然后我们在创建一个 pod

    1
    2
    3
    4
    # 创建nginx pod
    kubectl create deployment web --image=nginx
    # 复制五次
    kubectl scale deployment web --replicas=5
  8. 然后我们在进行查看

    1
    kubectl get pods -o wide

    我们能够看到现在所有的 pod 都被分配到了 k8snode2上,因为刚刚我们给 node1 节点设置了污点

  9. 最后我们可以删除刚刚添加的污点

    1
    kubectl taint node k8snode1 env_role:NoSchedule-

污点容忍 tolerations

污点容忍就是某个节点可能被调度,也可能不被调度


Kubernetes 核心概念之1 pod
https://flepeng.github.io/042-云原生-02-kubernetes-31-核心概念-Kubernetes-核心概念之1-pod/
作者
Lepeng
发布于
2023年3月1日
许可协议