2、k8s pod原理详解
Kubernetes Pod 介绍
Pod 直译是豆荚,可以把容器想像成豆荚里的豆子,把一个或多个关系紧密的豆子包在一起就是豆荚(一个 Pod)。在 k8s 中我们不会直接操作容器,而是把容器包装成 Pod 再进行管理。
Pod 介绍与原理
是 Kubernetes 项目中最小的 API 对象。如果换一个更专业的说法,我们可以这样描述:
Pod,是 Kubernetes 项目的原子调度单位。
Pod 是运行服务的基础.
基础容器是 pause.
每启动一个 Pod 都会附加启动这样一个容器,它的作用就只是简单的等待,设置 Pod 的网络。
一个 Pod 中的应用容器共享同一组资源:
(1)PID 命名空间:Pod 中的不同应用程序可以看见其他应用程序的进程 ID
(2)网络命名空间:Pod 中的多个容器能访问同一个 IP 和端口范围
(3)IPC 命名空间:Pod 中的多个容器能够使用 SystemV IPC 或 POSIX 消息队列进行通信。
(4)UTS 命名空间:Pod 中的多个容器共享一个主机名
(5)Volumes(共享存储卷):Pod 中的各个容器可以访问在 Pod 级别定义的 Volumes
每个 pod 中容器的镜像应该不同(不同的应用),避免端口重复
POD操作实战
POD的创建和删除
创建一个nginx pod
kubectl run ng --image=nginx:1.9 --port=80
状态检查
kubectl get pod name
kubectl describe pod name
kubectl logs name
通过yaml创建pod,通过–dry-run来生成一个yaml文件
kubectl run ng --image=nginx:1.9 --port=80 --dry-run=client -o yaml >ng.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
一个pod运行多个容器
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng01
name: multi-pod
spec:
containers:
- image: nginx:1.9
name: ng01
ports:
- containerPort: 80
resources: { }
- image: tomcat:8.0.41-jre8-alpine
name: tomcat8-1
ports:
- containerPort: 8080
resources: { }
dnsPolicy: ClusterFirst
restartPolicy: Always
status: { }
这个容器运行了两个容器nginx和tomcat,当执行了kubectl apply -f multi.yaml过后,查看pod如下:
删除pod
k delete pod ng
pod的生命周期管理
Pod 生命周期的变化,主要体现在 Pod API 对象的 Status 部分,这是它除了
Metadata 和 Spec 之外的第三个重要字段。其中, pod.status.phase,就是 Pod 的当前状
态,它有如下几种可能的情况:
Pending:这个状态意味着, Pod 的 YAML 文件已经提交给了 Kubernetes, API 对象已经被创建并保存在 Etcd 当中。但是,这个 Pod 里有些容器因为某种原因而不能被顺利创建。比如,调度不成功。
Running:这个状态下, Pod 已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。
Succeeded:这个状态意味着, Pod 里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。
Failed:这个状态下, Pod 里至少有一个容器以不正常的状态(非 0 的返回码)退出。这个状态的出现,意味着你得想办法 Debug 这个容器的应用,比如查看 Pod 的 Events 和日志。
Unknown:这是一个异常状态,意味着 Pod 的状态不能持续地被 kubelet 汇报给kube-apiserver,这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题。
更进一步地, Pod 对象的 Status 字段,还可以再细分出一组 Conditions。这些细分状
态的值包括: PodScheduled、 Ready、 Initialized,以及 Unschedulable。它们主要用于描述造成当前 Status 的具体原因是什么。
Restart policy
Restart policy for all containers within the pod. One of Always, OnFailure, Never.
Default to Always
如果是deployment的话,那么它的状态是Always,当pod消失挂掉以后,会自动重启
资源的配额和限制
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "200Mi"
cpu: "700m"
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
这个我们加了limits和requests,但是内存都是200Mi,cpu都是700m(cpu使用为1024,表示占700m)
当应用了这个yaml文件以后,我们通过describe看下:
QoS Class为:Guaranteed
当requests < limits的时候获取没有requests的时候如下:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "100Mi"
cpu: "600m"
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
QoS Class:Burstable
当都没有配置资源limits和requests的时候如下:
QoS Class:BestEffort
总结如下:
limits=requests -->Guaranteed
limits > requests -->Burstable
limitis ,requets 为空–>BestEffort
那这三个分别代表什么意思呢?就是说当k8s服务器上运行了很多的pod,那么这些pod都有Qos Class这个属性,当其中的某一个服务器资源不够用的时候,那么这个时候就要驱离pod了,也就是删除这个pod,在k8s的集群中找负载最小的服务器进行重启,所以QosClass这个参数就是用来判断那些先被驱离,通过上面的例子可以看出,当你limits和requests都没有配置的情况下是最先被驱离的,其次就是limits < requests的被驱离,而配置了limits和requests相等的pod是保留的。所以驱离的顺序:
BestEffort > Burstable > Guaranteed
静态pod
静态pod是什么呢?我们联想下java中的静态变量,静态变量在整个系统中是只存在一份的,所以静态变量是被所有的对象共享的;而k8s中的静态pod就表示在指定的目录中是存放静态pod的,当你将一个yaml文件放在静态pod所在目录,那么k8s会自动应用这个yaml文件,也就是说不需要你手动应用;
静态 Pod 是由 kubectl 进行管理的仅存于特定 Node 上的 Pod。他们不能通过 API Server 进行管理,无法与 ReplicationController、 Deployment 或者 DaemonSet 进行关联,并且 kubelet也无法对他们进行健康检查。静态 Pod 总是由 kubectl 进行创建,并且总是在 kubelet 所在的 Node 上运行。创建 Pod 有两种方式: 配置文件或 HTTP 方式,这里只说常用的配置文件方式:
配置文件方式
在目录 /etc/kubernetes/manifests 写入ng.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "100Mi"
cpu: "600m"
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
首先我们将pod监控起来,如下:
然后我们把ng放入 /etc/kubernetes/manifests过后再看观看这个pod的变化
可以看到马上放过去,这边监控就在开始创建了,所以这就是静态pod的作用
k8s中有很多的pod,这些pod都是kube-system命名空间的,如下:
其实比如调度器这些都是静态pod,都在指定目录创建的,我们看这个目录:
现在已经正常的
当我们从这个目录中将这个文件删除过后,这个pod也将被移除
Init Containers
初始化容器,顾名思义容器启动的时候,会先启动可一个或多个容器,如果有多个,那么这几个 Init Container 按照定义的顺序依次执行,只有所有的 Init Container 执行完后,主容器才会启动。由于一个 Pod 里的存储卷是共享的,所以 Init Container 里产生的数据可以被主容器使用到。
Init Container 可以在多种 K8S 资源里被使用到如 Deployment、Daemon Set, Pet Set, Job 等,但归根结底都是在 Pod 启动时,在主容器启动前执行,做初始化工作。
应用场景:
第一种场景:等待其它模块 Ready, 比如我们有一个应用里面有两个容器化的服务,一个是Web Server,另一个是数据库。其中 Web Server 需要访问数据库。但是当我们启动这个应用的时候,并不能保证数据库服务先启动起来,所以可能出现在一段时间内 Web Server 有数据库连接错误。为了解决这个问题,我们可以在运行 Web Server 服务的 Pod 里使用一个InitContainer,去检查数据库是否准备好,直到数据库可以连接, Init Container 才结束退出,然后 Web Server 容器被启动,发起正式的数据库连接请求。
第二种场景:初始化配置, 比如集群里检测所有已经存在的成员节点,为主容器准备好集群的配置信息,这样主容器起来后就能用这个配置信息加入集群。其它使用场景: 如将 pod 注册到一个中央数据库、下载应用依赖等
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: initcontainer
name: initcontainer
spec:
containers:
- image: nginx:1.9
name: nginx
volumeMounts:
- name: task-pv-storage
mountPath: "/usr/share/nginx/html"
resources: {}
initContainers:
- image: centos
name: centos
imagePullPolicy: IfNotPresent
command:
- bin/sh
- -c
- echo "this is one initContainers test">>/pod-data/index.html
volumeMounts:
- name: task-pv-storage
mountPath: "/pod-data"
resources: {}
volumes:
- name: task-pv-storage
emptyDir: {}
dnsPolicy: ClusterFirst
restartPolicy: Never
status: {}
k8s健康检查
探针是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 调用由容器实现的 Handler。有三种类型的探针: Exec探针:执行进程的地方,容器的状态由进程的退出状态代码确认; Http get探针:向容器发送HTTP GET请求,通过响应的HTTP状态代码判断容器是否准备好;如果响应的状态码大于等于200 且小于 400,则诊断被认为是成功的 Tcp socket探针:它打开一个TCP连接到容器的指定端口,如果连接己建立,则认为容器己准备就绪。
kubernetes会周期性地调用探针,并根据就绪探针的结果采取行动。如果某个pod报告它尚未准备就绪,则会从该服务中删除该pod。如果pod再次准备就绪,则重新添加pod; 每次探测都将获得以下三种结果之一:
成功:容器通过了诊断。
失败:容器未通过诊断。
未知:诊断失败,因此不会采取任何行动
livenessProbe:指示容器是否正在运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其 重启策略 的影响。如果容器不提供存活探针,则默认状态为 Success
readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为Failure。如果容器不提供就绪探针,则默认状态为 Success 只要pod的标签和服务的pod选择器匹配,pod就可以作为服务的后端,但是如果pod没有准备好,是不能处理请求的,这时候就需要就绪探针了,用来检查pod是否已经准备好了,如果检查成功就可以作为服务的后端处理消息了;
1、向pod添加检测探针 - 存活检测
#检测/tmp/live,每隔10秒就会被删除,liveness检测,如果被删除,就会返回失败,重启pod。陷入无限循环。
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
spec:
containers:
- name: liveness-exec-container
image: centos:7
imagePullPolicy: IfNotPresent
command: [ "/bin/sh","-c","touch /tmp/live ; sleep 10; rm -rf /tmp/live; sleep 10" ]
livenessProbe:
exec:
command: [ "test","-e","/tmp/live" ]
initialDelaySeconds: 1
periodSeconds: 3
#检测/tmp/live,每隔10秒就会被删除,liveness检测,如果被删除,就会返回失败,重启pod。陷入无限循环,如图:
基本上没10s就重启一次,所以这个探针是生效了的
2.向pod添加准备就绪探针
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources: { }
readinessProbe:
exec:
command:
- ls
- /var/ready
dnsPolicy: ClusterFirst
restartPolicy: Always
status: { }
当应用了这个yaml文件以后,在容器中会创建ng这个pod,但是这个pod的状态为running,但是ready状态返回的始终是0,因为在容器里面的/var/下没有ready这个文件,因为我写的是就绪探针是要在ls /var/ready成功,没有这个文件就不会成功
这个时候我们进入容器,在var下添加一个ready文件,那么pod的ready状态就会变成1
kubectl exec it ng -- sh or
kubectl exec -it ng /bin/bash
可以看到确实好了,这是一种就绪探针,但是一般这种探测我们使用http好一点,不可能每次进入容器去添加文件,所以修改下这个ng的yaml文件
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources: { }
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 3
dnsPolicy: ClusterFirst
restartPolicy: Always
status: { }
代表的意思就是说这个就绪探测是在容器启动过后延长3s执行,每隔3s执行一次,执行http请求,请求的路径是/,请求的端口是80,就是请求当前路径的nginx是否正常,如果请求正常,返回给k8s 的pod代表正常,就将ready修改为1,表示正常,我们看下监控的pod的状态变化:
可以看到是按照我们的预期执行的,pod从ready=0的状态变成了1的状态是根据配置的调度时间去执行的。
POD镜像升级
三种方式:
K edit pod name
K set image pod pod-name container-name=new image
K apply -f yaml file
Checking upgrading status
K describe pod pod-name # check image name
K get events
K get pod # check restart times.
POD 死掉后就不可以了。只能删除后重新创建。
pod 无法启动可能原因
a) 已有 pod 的名字,在相同命名空间。容器名字可以相同,但是在一个 POD 中的容
器名字不能相同。
b) 镜像名字是否正确,是否在本地存在
c) 是否有 1 号进程在运行。
d) 相应的 PVC, Configmap, secret 是否已经建立。
e) 是否有足够资源
f) 调度到相应的节点
g) 探针配置错误。
h) 镜像本身的错误。
POD调度过程
- 用户提交 pod, APIServer 记录到 etcd 中;
- scheduler 周期性查询 APIServer,以获取未绑定的 pod,尝试为 pod 分配节点;
- scheduler 调度:首先过滤不符合 pod 资源要求的主机。然后考虑整体优化策略对
主机打分,比如使用最低负载,使用分散主机等。最后选择最高分的主机存储绑
定信息到 etcd 中; - kubelet 周期查询绑定对象,获取需要在本机启动的 pod 并通过 docker 启动。
调度例子
nodeSelector
nodeSelector 是节点选择约束的最简单推荐形式。 nodeSelector 是 PodSpec 的一个字段。 它包含键值对的映射。为了使 pod 可以在某个节点上运行,该节点的标签中 必须包含这里的每个键值对(它也可以具有其他标签)。 最常见的用法的是一对键值对。
apiVersion: v1
kind: Pod
metadata:
name: nginx8
labels:
env: test
spec:
containers:
- name: nginx
image: nginx:1.7.9
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssy
这个时候应用这个pod以后,看下状态是:
是异常的,所以我们看下详细:
意思就是说3个节点中没有找到符合seelctor的可用节点,因为在pod中我们设置了nodeSelector中的disktype=ssy的,但是目前这三个节点都没有设置,现在我们设置一下:
kubectl label node k8s-node2 disktype=ssy
意思就是将node2节点设置一个label为disktype=ssy
这个时候这个nginx8就可以了,从pending变为running,而ready也变为1了,但是这个nginx8肯定是在k8s-node2这个节点上的,看下:
所以这就是制定节点部署,就不是k8s来给我自动计算到那个节点。
nodeName
nodeName 是节点选择约束的最简单方法,但是由于其自身限制,通常不使用它nodeName 是 PodSpec 的一个字段。 如果它不为空,调度器将忽略 Pod,并且给定点上运行的 kubelet 进程尝试执行该 Pod。 因此,如果 nodeName 在 PodSpec 中指了,则它优先于上面的节点选择方法。
使用 nodeName 来选择节点的一些限制: 如果指定的节点不存在, 如果指定的节点没有资源来容纳 Pod,Pod 将会调度失败并且其原因将显示为, 比如 OutOfmemory 或 OutOfcpu。 云环境中的节点名称并非总是可预测或稳定的。
nodeName的优先级是最高的,就算你在指定的nodeName上打上了污点,那么污点也会失效,也会在上面部署启动;
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
nodeName: k8s-node1
status: {}
我这里指定了ng这个pod部署到k8s-node1上
taint and Tolerance
节点亲和性 是 Pod 的一种属性,它使 Pod 被吸引到一类特定的节点 (这可能出于一种偏好,也可能是硬性要求)。 污点(Taint)则相反——它使节点能够排斥一类特定的 Pod。 容忍度(Toleration)是应用于 Pod 上的,允许(但并不要求)Pod 调度到带有与之匹配的污点的节点上。 污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod,是不会被该节点接受的。
简单来说就是如果节点上打了污点,那么如果你不是指定了nodeName的话是不会安装到这个节点上的,如果说节点上打了污点,但是你又指定了nodeName,那么也会在上面安装,而我们的k8s的master在使用kubadm安装的时候,是自动给打上污点了的,也就是如果没有指定nodename的话,那么默认的调度室不会调度到k8s的master上的,我们看下k8s的master上自动打的污点:
这样,我们把刚刚的那个节点指定nodeName放到k8s-master上看下
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
nodeName: k8s-master
status: {}
在执行看下:
所以当你指定了nodeName以后,就算该节点上有污点也会在上面部署,也就是nodeName的优先级非常高,我们看下节点的信息:
是成功的部署到了master上了
在node节点上打污点:
kubectl taint node k8s-node1 key1=value1:NoSchedule
现在k8s-node1就有了一个污点了,我们将刚刚的那个ng部署到上面看行不行
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
nodeSelector:
disktype: ssy
status: {}
我们将ng部署到label有disktype=ssy的节点上,这个时候我们在k8s-node1上创建一个label为disktype=ssy
创建label为:
kubectl label node k8s-node1 disktype=ssy
删除label为
kubectl label node k8s-node1 disktype-
node1上加了一个label为disktype=ssy的,而nodeSelector又选择的是disktype=ssy的节点,所以ng会部署到node1上,但是node1上打了污点,我们看下是否能成功?
状态是Pending,我们看下信息:
是不 成功的,信息如下:
default-scheduler 0/3 nodes are available: 1 node(s) didn’t match Pod’s node affinity/selector, 1 node(s) had taint {key1: value1}, that the pod didn’t tolerate, 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn’t tolerate.
翻译过来的就是说:3个node节点都不可用
1个节点不匹配pod,因为ng这个pod指定了selector;
1个节点有一个污点key1:value1,然后pod这个ng没有指定容忍tolerate配置,也不满足;
1个节点配置了污点role.kubernetes.io/master:(k8s-master)
大概就这么个意思,从上面可以知道打了污点要可以的话就需要在污点上打上一个容忍tolerate,所以我们修改下ng这个yaml文件
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ng
name: ng
spec:
containers:
- image: nginx:1.9
name: ng
ports:
- containerPort: 80
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
nodeSelector:
disktype: ssy
status: {}
加上了tolerations容忍度,也就是说当节点上有配置了容忍就是可以的,容忍的配置:
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
容忍的是污点,也就是说配置了容忍污点的信息,也就是可以的,对应的污点是:
- key:配置的是污点的key
opeator:配置的是污点的操作,Equal就是“=”,Exists代表存在,像k8smaster就是配置的Exists
value:就是“=”后面的value
errect:效果,就是“:”后面的值NoSchedule
配置了以后看下还行不行:
很显然是可以的,我们看下ng这个pod的信息:
表示nodeSelector是带有标签为disktype=ssy的节点和
配置了容忍的污点是key1=value1:NoSchedule
删除污点为:
kubectl taint nodes k8s-node1 key1=value1:NoSchedule-
标签常用命令
查看标签:
查看所有
kubectl get 资源(pod/node/rs/rc/deploy/svc --shwo-labels)
查看具体的资源:
kubectl get 资源(pod/node/rs/rc/deploy/svc 资源名称 --shwo-labels)
如
kubectl get pod --show-labels(pod的所有标签)
kubectl get pod ng --show-labels(pod为ng的标签)
设置标签
kubectl label 资源(pod/node/rs/deploy/svc 资源名称 标签key=标签值
如
kubectl label pod ng testlabel=1111
删除标签:
kubectl label 资源(pod/node/rs/deploy/svc 资源名称 标签key-
如
kubectl label pod ng testlabel-
根据label筛选资源:
kubectl get 资源(pod/node/rs/rc/deploy/svc -l key=value
比如筛选rs
kubectl get rs -l tier=nginx-rs