Spec中常见子属性:
- containers class 容器列表:定义容器得详细信息
- nodeName: 根据nodeName 将pod调度指定得Node上
- nodeSeletor map 根据Node中定义得信息选择将该调度到包含这些label的Node上
- hostNetwork 是否使用主机网络模式。默认False, 如果True使用宿主机网络
- volumns class 数据卷,定义在Pod上挂载存储信息
- restartPolicy 重启策略, 表示Pod在遇到故障的时候的处理策略
Pod结构1:container
既然要看container, 那我们就先用命令来看看可以选择的参数:
kubectl expalin pod.spec.containers
# 参数太多我就不详细放出来了
我们定义一个简单的多image pod文件:
apiVersion: v1
kind: Pod
metadata:
name: easy-pod
namespace: hellok8s
labels:
tag: try-many-containers
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
# 然后会发现这里Python容器会一直 warning Backoff.
# 为什么呢,因外除了nginx这样下载就在运行的容器, 比如我们的Python镜像属于黄静容器,启动后容器里面没有跑的进程然后
- name: python
image: python
imagePullPolicy: IfNotPresent
# 所以我们加一个commangd然他不断运行就好,下面这个属于是通用模板了
# 简单解释下, /bin/bash -c 执行sh命令 后面是一个不断循环获取日志的命令
command: ["/bin/bash","-ce","tail -f /dev/null"]
当然,我们也可以在container声明的地方来声明他的环境变量:(env参数)
# ...上面可以如出一辙
spec:
containers:
- name: helloenvs
image: python
imagePullPolicy: Never
# 在这里说明一下,这种写法不是很推荐,一般我们会将所有的env统一放到一个文件中使用
env:
- name: "username"
value: "laowang"
- name: "hobby"
value: "ACGN"
我们想确认自己的环境变量有没有放进去可以采用这样的方法:
# 这个exec有docker内味了,-c 指定使用的container ,剩下就是一个docker语句
kubectl exec env-try -n hellok8s -c helloenvs -it /bin/sh
# 这里会有一个新版本需要 [pod] -- [command]的提示,意思就是让你这么写:
kubectl exec env-try -n hellok8s -c helloenvs -it -- /bin/sh
# 下面是写入成功的验证:
echo $username
laowang
echo $hobby
ACGN
container还为我们提供了关于端口暴漏的很多选项。这个在上面的service和deploy都有了比较详细的应用了,不再赘述
FIELDS:
name <string> # 端口名称,如果指定,必须保证name在pod中是唯一的
containerPort<integer> # 容器要监听的端口(0<x<65536)
hostPort <integer> # 容器要在主机上公开的端口,如果设置,主机上只能运行容器的一个副本(一般省略)
hostIP <string> # 要将外部端口绑定到的主机IP(一般省略)
protocol <string> # 端口协议。必须是UDP、TCP或SCTP。默认为“TCP”。
最后是容器的配额。我们给容器分配在资源的时候一般都要加以限制。如果不做限制可能就会吃掉宿主机的大量资源。同时我们还要定义容器能使用的最小资源字段嘛,不然他怎么跑的起来啊。
容器使用resource字段做资源分配。其中resource又有两个子字段: limit:限制资源上限 requests: 限制资源下限。以下是一个简单的demo
apiVersion: v1
kind: Pod
metadata:
name: resource-try
namespace: hellok8s
spec:
containers:
- name: nginx-docker
image: nginx
resources:
limits:
cpu: 1
memory: 2Gi
requests:
cpu: 1
memory: 10M
至此,关于container的常用参数整合完毕
Pod结构2: 生命周期
我们一般将pod对象从创建到终止的一整个过程叫做Pod的生命周期, 一般包括:
- Pod创建的过程
- Pod运行初始化容器的过程 (init container)
- 运行主容器的过程 (main container):
- 容器启动后钩子(post start), 容器中之前钩子(pre stop)
- 容器的存活性探测 (liveness probe), 就绪性探测 (readiness probe)
- pod的终止过程
话说回来这张图不知道为啥少了Pod创建的最开始一小部分。不过问题不大
在生命周期中, Pod 会出现5中状态/相位 :
- Pending 挂起: Pod已被创建, 正在下载镜像来init container
- Running 运行: Pod 正在正常运行。所有的container都已被调度到镶银的节点上且正常工作
- Success 成功结束: Pod成功完成自己的使命,结束了运行且所有容器都被终止
- Failed 结束失败: 至少有一个容器终止失败, 或者说容器返回了非0值得状态
- Unknown 未知: 无法掌握Pod对象现状, 应该是网络配置的问题
Pod的创建:
创建其实都是系统在给我们做,我们不需要去挖pod创建的原理。pod创建的文字说明长篇说教如下:
-
用户通过kubectl 或其他api客户端提交要创建的pod信息给apiserver
-
apiServer生成Pod对象信息,并将信息存入etcd, 然后返回确认至客户端
-
apiServer反映etcd中的pod对象的变化, 其他组件使用watch机制来跟踪检查apiServer上的变动
-
scheduler发现有新pod创建, 开始为Pod分配主机并将结果更新至apiserver
-
node 节点上kubelet发现有pod调度过来,尝试调用docker启动容器并将结果送回apiServe
-
apiServer接受信息并存入pod当中
初识化容器:
初识化容器是Pod生命周期的第二阶段,做一些主容器的前置工作。它具有两大特征:
- 初始化容器必须运行完成直至结束,如果初识化失败, k8s会重启到成功为止
- 初识化容器严格按照定义顺序线性运行
初始化容器可以应用于:
- 提供主容器镜像中不具备的工具程序或自定义代码
- 初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足
虽然我感觉我用不上,不过你们可以尝试:https://gitee.com/yooome/golang/blob/main/k8s%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B/Kubernetes%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B.md#532-%E5%88%9D%E5%A7%8B%E5%8C%96%E5%AE%B9%E5%99%A8
钩子函数
钩子函数的概念和意义大家应该都已经很熟悉了,面向切面编程的思想应该已经深入人心了(这里不懂得同学可以看我前面Django关于中间件的教程)。
在K8s中钩子主要是在相应时刻到来的时候运行我们指定的代码。我们上面说过在K8s中有两个钩子:
- post start: 容器创建按之后指导性的钩子,用于判断容器是否成功运行,失败就会重启容器
- pre stop: 结束之前运行,讲容器终止并且运行期间禁止对容器操作
在我们定义钩子的时候支持如下三种定义方法:
lifecycle:
# 这个是启动后钩子,还可以改成结束前钩子
postStart:
# 方法1 exec, 在执行命令的时候就会先运行我们的command
exec:
command:
balabala
# 方法二,TCPsocket: 在当前容器尝试访问指定的socket(端口),讲真这个我查自恋的时候没见过太多人用过。
tcpSocket:
port: 5000
# 方法三, 当前容器向某些url发起请求。感觉可以在Restful资源调用这样的场景下用到
httpGET:
path: /
port: 443
scheme: HTTPS
当然直观这么看是比较抽象的,以exec举例
apiVersion: v1
kind: Pod
metadata:
name: pod-hook-try
namespace: hellok8s
# 这里要给Pod配置一个label否则expose的时候会报错没有Label无法selector
labels:
info: k8stry
spec:
containers:
- name: nginx-pod
image: nginx
ports:
- name: nginx-ports
containerPort: 80
protocol: TCP
lifecycle:
# 开始之后的钩子
postStart:
exec:
# 修改主页文件,至于为什么是这个路径可以自己去docker内部的/etc/nginx/conf.d/default.conf一目了然
command: ["/bin/bash","-c","cd /usr/share/nginx/html;rm index.html; echo This-is-a-exec-hook-try > index.html "]
# 结束之前的钩子
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
之后我们curl一下
# 配置一个 expose
kubectl expose pod pod-hook-try --port=80 --type=NodePort --name=hooktry -n hellok8s
# 查看暴漏端口
kubectl get svc -n hellok8s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hooktry NodePort 10.108.16.215 <none> 80:30359/TCP 3s
# curl一下,成功修改,外部也可以访问
curl 10.0.0.13:30359
This-is-a-exec-hook-try
容器探测
在上面我们说的过程中在Running态中还有一个容器探测的存在。容器探测是用于检测容器中的应用实例是否正常工作的**保证业务可用性的一种传统机制。**如果经过探测,实例的状态不符合预期,就会把出现问题的实例摘除,不承担业务流量 。k8s提供了两种探针来实现容器探测:
- liveness probes:存活性探针,用于检测应用实例当前是否处于正常运行状态。决定了k8s是否会重启容器
- readliness probes: 就绪性探针, 用于检测应用实例当前是否可以接受请求。决定了k8s是否会转发流量
探针的探测方式(感觉和钩子非常像, 就是把lifecycle删掉改成我们需要的探测):
# 活性探测指针, 失败会在describe 中报warning, 然后不停的reset
livenessProbe:
# 方法1 exec, 命令执行失效说明活性失效warning 重启
exec:
command:
balabala
# 方法二,TCPsocket: 访问端口失效说明活性失效 warning
tcpSocket:
port: 5000
# 方法三, HttpGet , 访问uri失效说明活性失效
httpGET:
path: /
port: 443
scheme: HTTPS
eg. 我们来做一个http get示例,毕竟Nginx示例最好就是看看主路由成功与否嘛。其他两个大同小异
apiVersion: v1
kind: Pod
metadata:
name: pod-probe-try
namespace: hellok8s
# 这里要给Pod配置一个label否则expose的时候会报错没有Label无法selector
labels:
info: probe-exec-try
spec:
containers:
- name: nginx-pod-can-run
image: nginx
ports:
- name: nginx-ports
containerPort: 80
protocol: TCP
# 一个可以成功活性检测
livenessProbe:
httpGET:
scheme: HTTP
port: 80
path: /
# 一个必然失败的就绪性检测
readinessProbe:
tcpScoket:
port: 5000
# 我们去看刚刚pod的状态,很神奇,是runningbut not ready的状态
kubectl get pod -n hellok8s.metadata.label
NAME READY STATUS RESTARTS AGE
pod-hook-try 1/1 Running 0 128m
pod-probe-try 0/1 Running 0 2m5s
# 我们来看看是如何解释他的
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
# 前面是一堆Normal, 我直接删了占空间。咱们的目的是warning. 这个warning是复合物我们预期的
Normal Started 3m50s kubelet, vm-0-2-ubuntu Started container nginx-pod-probe-try
Warning Unhealthy 24s (x21 over 3m44s) kubelet, vm-0-2-ubuntu Readiness probe failed: dial tcp 10.244.1.26:5000: connect: connection refused
关于如果livenessProbe就没通过的Pod的重启策略:Always :容器失效时,自动重启该容器,这也是默认值。 OnFailure : 容器终止运行且退出码不为0时重启Never : 不论状态为何,都不重启该容器
# 一个定义重启策略的小demo/伪代码
apiVersion:
kind:
metadata:
。。。
spec:
containers:
。。。
# 与container同级,是spec的下一级
restartPolicy: Never/Always/OnFailure
Pod的终止
- 用户向apiServer发送删除pod的指令
- apiServer的pod对象对时间的推移而更新,在宽限期(宽限期指将pod内的容器全都stop和rm的这样一个过程 default 30s) pod视为dead
- 将pod标记为terminating状态
- kubectl监控到pod对象关闭(将测到terminating状态)开始关闭pod
- 端点控制器监控到pod对象的关闭行为将service(涉及访问此pod的全部停掉, 用官方的话说,就是)资源的端点列表中移除
- 如果此前Pod有定义结束前的钩子,就会在标为terminating的同时运行钩子函数
- 如果宽限期结束后pod还有运行的进程, 就会收到立即终止的信号
- kubelet 请求apiServer彻底删除pod (设置宽限期 = 0),这里用户已经看不见pod了
Pod 调度器
先解释一下什么叫做调度。在集群中我们肯定会把pod分发到不同的Node上去运行的。调度就是指我们把Pod分发到Node的过程。在默认情况下, Pod的调度是由Scheduler组件根据响应分配的,不受人工控制。然鹅,实际情况下我们肯定有些时候要当家做主自己决定放服务到哪个节点去的嘛,K8s支持4类调度方式:
- 自动调度: 运行在哪个节点上完全由Scheduler计算得出
- 定向调度: 根据pod.yaml 中NodeName, NodeSelector的声明分配到指定的pod中
- 亲和性调度: (这个不好解释,就是字面意思声明亲和度,很谁亲近去谁那里。 NodeAffinity、PodAffinity、PodAntiAffinity
- 污点(容忍)调度: Taints, Toleration
定向调度
最简单的一种自定义调度方式:利用pod上声明的nodeName或nodeSelector ,强制调度到预期的node上。即使Node不存在也会强行调度,最后导致pod运行失败。
比如说这样的例子:
# 一个pod的简单的yaml
apiVersion: v1
kind: Pod
metadata:
name: direct-node-pod
namespace: hellok8s\
spec:
containers:
- name: python-container
image: python
# 别忘了python这种环境型docker要将个永恒运行命令
command: ["/bin/bash","-ce","tail -f /dev/null"]
# nodeName与nodeSelector不要户村
nodeName: vm-0-13-ubuntu
# 因为上面写了这里解注释掉了
# nodeSelector:
# info: master
# 我们给node打上标签
kubectl label node vm-0-13-ubuntu info=master
kubectl label node vm-0-2-ubuntu info=node1
# 查看一下确实有标签
kubectl get node --show-labels
# 查看状态
kubectl get pod -n hellok8s
NAME READY STATUS RESTARTS AGE
direct-node-pod 1/1 Running 0 19s
pod-hook-try 1/1 Running 0 172m
亲和性调度
亲和性调度我可能前面写的优点抽象。但是讲完了定向调度就好理解了。亲和性其实本身就相当于一个select 选择器嘛。它主要作用是解决了定向调度指定节点不存在的时候会强制调度出错的问题。通过亲和度,就行成了一种优先级调度模式。这样优先级高的节点如果不存在就会调度优先级次之的节点,不会出现上面的情况。
亲和度Affinity主要分为三类:
- nodeAffinity (node亲和性) : 以node为目标,解决pod可以调度到哪些node的问题
- podAffinty (pod亲和性) : 以pod为目标, 解决pod可以和那些已存在得pod部署在同一拓扑中的问题
- podAntiAffinity (pod反亲和性): 以pod为目标,解决pod不能和那些已经存在得pod部署在同一个拓扑区域当中
亲和度的文件写起来就要略微复杂了:
-
nodeAffinity: (参数列表)
pod.spec.affinity.nodeAffinity requiredDuringSchedulingIgnoredDuringExecution Node节点必须满足指定的所有规则才可以,相当于硬限制 nodeSelectorTerms 节点选择列表 matchFields 按节点字段列出的节点选择器要求列表 matchExpressions 按节点标签列出的节点选择器要求列表(推荐) key 键 values 值 operat or 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt preferredDuringSchedulingIgnoredDuringExecution 优先调度到满足指定的规则的Node,相当于软限制 (倾向) preference 一个节点选择器项,与相应的权重相关联 matchFields 按节点字段列出的节点选择器要求列表 matchExpressions 按节点标签列出的节点选择器要求列表(推荐) key 键 values 值 operator 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt weight 倾向权重,在范围1-100。
apiVersion: v1 kind: Pod metadata: name: direct-node-pod namespace: hellok8s spec: containers: - name: python-container image: python command: ["/bin/bash","-ce","tail -f /dev/null"] # 设置亲和性 affinity: # 设置node亲和性 nodeAffinity: # 软限制 preferredDuringSchedulingIgnoredDuringExecution: # 权重1 - weight: 2 preference: matchExpressions: - key: info values: ["master","hello1"] operator: In # 一个权重更大的假条件 - weight: 5 preference: matchExpressions: - key: info values: ["as1","s2"] operator: In
# 按照我们刚刚得设置,他会跑在master 也就是vm-0-13上面 kubectl get pod -owide -n hellok8s NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES direct-node-pod 1/1 Running 0 14s 10.244.0.10 vm-0-13-ubuntu <none> <none>
-
PodAffinity主要实现以运行的Pod为参照,实现让新创建的Pod跟参照pod在一个区域的功能。这个就不做演示了,原理大同小异。至于PodAntiAffinity 和这个配置完全一样,不过功能相反罢了
pod.spec.affinity.podAffinity requiredDuringSchedulingIgnoredDuringExecution 硬限制 namespaces 指定参照pod的namespace topologyKey 指定调度作用域 labelSelector 标签选择器 matchExpressions 按节点标签列出的节点选择器要求列表(推荐) key 键 values 值 operator 关系符 支持In, NotIn, Exists, DoesNotExist. matchLabels 指多个matchExpressions映射的内容 preferredDuringSchedulingIgnoredDuringExecution 软限制 podAffinityTerm 选项 namespaces topologyKey labelSelector matchExpressions key 键 values 值 operator matchLabels weight 倾向权重,在范围1-100
以上就是亲和性调度
-
污点与容忍
前两种调度方法都是在Pod的角度,或者说在Pod的文件中进行修正达到人为的进行Pod分发的过程。但是我们也可以站在Node的角度进行调整。
通过添加污点(Taints)的方法决定Pod是否允许调度到这个节点上,甚至把这个节点上已经调度的Pod驱逐出去。Node被设置上污点之后就和Pod之间存在了相斥的关系
污点的调度格式:
kubectl taint(关键字,代表污点的赋值) nodes XXX key=value:effect (key和value是污点的标签,effect描述污点的作用)
effect可选选项:
- PreferNoSchedule:kubernetes将尽量避免把Pod调度到具有该污点的Node上,除非没有其他节点可调度
- NoSchedule:kubernetes将不会把Pod调度到具有该污点的Node上,但不会影响当前Node上已存在的Pod
- NoExecute:kubernetes将不会把Pod调度到具有该污点的Node上,同时也会将Node上已存在的Pod驱离
# key 和 value随便起,但是得记住。我推荐就叫taints
# 设置第一级别污点
kubectl taint nodes vm-0-13-ubuntu taints=yes:PreferNoSchedule
node/vm-0-13-ubuntu tainted
# 清除污点,其他两种也大同小异
kubectl taint nodes vm-0-13-ubuntu taints:PreferNoSchedule-
node/vm-0-13-ubuntu untainted
当然,我们的Pod才是功能的核心。k8s的这些不过是个架构罢了,统统都得给Pod让路。所以Node你敢拒绝,我Pod就敢忽略。这种忽略的方式,就叫做容忍
# 容忍在pod中添加
# .....前面部分省略了,直接写spec
spec:
containers:
# ......
tolerations:
# 要容忍的key
- key: "taints"
# 操作符
operator: "Equal"
# 容忍的value
values: "yes"
# 添加容忍的规则,必须与标记规则相同
effect: "NoExecute"
OK, Pod调度器告一段落