1.pod的镜像拉取策略
1.1 镜像拉取说明
当你在创建容器时会针对指定的镜像来进行容器的创建,所以pod的创建是以镜像为基础。当你在拉取镜向不指定仓库的主机名,Kubernetes 认为你在使用 Docker 公共仓库。
在镜像名称之后,你可以添加一个标签(Tag)(与使用 docker 或 podman 等命令时的方式相同)。 使用标签能让你辨识同一镜像序列中的不同版本。
镜像标签可以包含小写字母、大写字母、数字、下划线(_)、句点(.)和连字符(-)。 关于在镜像标签中何处可以使用分隔字符(_、- 和 .)还有一些额外的规则。 如果你不指定标签,Kubernetes 认为你想使用标签latest
1.2 镜像拉取的策略
首先在资源式声明中存在着imagePullPolicy的字段,它的value决定着k8s创建容器时拉取镜像的方式策略。【此字段所在位置也说明了在声明式yaml中,imagePullPolicy是包含containers中】
kubectl explain pod.spec.containers.imagePullPolicy
如图所示,这三种便是k8s拉取镜像的三种策略:
IfNotPresent
只有当镜像在本地不存在时才会拉取。(先对本地进行排查,本地有该镜像直接使用,本地没有该镜像则选择在仓库中拉取)
Always
总是从仓库拉取镜像,无论本地是否存在镜像(即使本地中存在我们所指定的相关镜像,该策略也会先从仓库中拉取进行应用)
Never
Kubelet 不会尝试获取镜像。如果镜像已经以某种方式存在本地, kubelet 会尝试启动容器;否则,会启动失败。(如果本地不存在,并不会在仓库中拉取,直接报错)
注意:如果没有显式设定的话, Pod 中所有容器的默认镜像拉取策略是IfNotPresent。但是也存在着默认策略选择Always的情况。
此外:
在生产环境中部署容器时,你应该避免使用 :latest
标签,因为这使得正在运行的镜像的版本难以追踪,并且难以正确地回滚。(难以追溯版本,且latest一直会不断迭代更新,给版本维护照成困扰)
1.3 镜像拉取策略的设置操作
(1)Never策略的使用
kubectl run app-test --image=httpd --dry-run=client -o yaml > demo1.yaml
vim demo1.yaml
(2)IfNotPresent策略在本地无镜像的情况下使用
vim demo1.yaml
#查看详细的pod信息,其中也有日志的作用
kubectl describe pod app-test
(3)Always策略
2.pod的启动命令说明
在k8s的容器中也存在着和docker-compose类似的shell启动命令字段,用于pod容器启动后执行命令的操作。
该字段存在containers中:
kubectl explain pod.spec.containers
command,用于在 pod 中的容器初始化完毕之后运行一个命令
command: ["/bin/sh","-c","touch /tmp/hello.txt"]
- "/bin/sh","-c", 使用sh执行命令
- touch /tmp/hello.txt; 创建一个/tmp/hello.txt 文件
该字段还可以运用args进行编写(起到同样的效果):
args:
- /bin/bash
- touch /tmp/hello.txt
除了 command 参数外,还有一个 args 参数
command 已经可以完成启动命令和传递参数的功能,为什么这里还要提供一个 args 选项,用于传递参数呢?这其实跟 docker 有点关系,kubernetes 中的 command、args 两项其实是实现覆盖 Dockerfile 中 ENTRYPOINT 的功能。
1)如果 command 和 args 均没有写,那么用 Dockerfile 的配置。
2)如果 command 写了,但 args 没有写,那么 Dockerfile 默认的配置会被忽略,执行输入的 command
3)如果 command 没写,但 args 写了,那么 Dockerfile 中配置的 ENTRYPOINT 的命令会被执行,使用当前 args 的参数
4)如果 command 和 args 都写了,那么 Dockerfile 的配置被忽略,执行 command 并追加上 args 参数
3.Pod 容器的重启策略
k8s中重启策略适用于pod对象中的所有容器,首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长为10s,20s,40s,80s,160s,300s, 300s是最大延迟时长
kubectl explain pod.spec.restartPolicy
注意:yaml方式创建Deployment和StatefulSet类型时,restartPolicy只能是Always,kubectl run -个pod可以选择Always,OnFailure,Never三种策略
4. pod的状态说明
(1)Pod 一直处于Pending状态
Pending状态意味着Pod的YAML文件已经提交给Kubernetes,API对象已经被创建并保存在Etcd当中。但是,这个Pod里有些容器因为某种原因而不能被顺利创建。比如,调度不成功(可以通过kubectl describe pod命令查看到当前Pod的事件,进而判断为什么没有调度)。
可能原因:资源不足(集群内所有的Node都不满足该Pod请求的CPU、内存、GPU等资源); HostPort 已被占用(通常推荐使用Service对外开放服务端口)。
(2)Pod一直处于Waiting 或 ContainerCreating状态
首先还是通过 kubectl describe pod命令查看当前Pod的事件。可能的原因有:
1)镜像拉取失败,比如镜像地址配置错误、拉取不了国外镜像源(gcr.io)、私有镜像密钥配置错误、镜像太大导致拉取超时 (可以适当调整kubelet的-image-pull-progress-deadline和-runtime-request-timeout选项)等。
2)CNI网络错误,一般需要检查CNI网络插件的配置,比如:无法配置Pod 网络、无法分配IP地址。
3)容器无法启动,需要检查是否打包了正确的镜像或者是否配置了正确的容器参数
4)Failed create pod sandbox,查看kubelet日志,原因可能是磁盘坏道(input/output error)。
(3)Pod 一直处于ImagePullBackOff状态
通常是镜像名称配置错误或者私有镜像的密钥配置错误导致。
(4)Pod 一直处于CrashLoopBackOff状态
此状态说明容器曾经启动了,但又异常退出。这时可以先查看一下容器的日志。
通过命令kubectl logs 和kubectl logs --previous 可以发下一些容器退出的原因,比如:容器进程退出、健康检查失败退出;此时如果还未发现线索,还而已到容器内执行命令(kubectl exec cassandra - cat /var.log/cassandra/system.loq)来进一步查看退出原因;如果还是没有线索,那就需要SSH登录该Pod所在的Node上,查看Kubelet或者Docker的日志进一步排查。
(5) Pod处于Error状态
通常处于Error状态说明Pod启动过程中发生了错误。
常见的原因:依赖的ConfigMap、Secret或PV等不存在;请求的资源超过了管理员设置的限制,比如超过了LimitRange等;违反集群的安全策略,比如违反了PodSecurityPolicy.等;容器无法操作集群内的资源,比如开启RDAC后,需要为ServiceAccount配置角色绑定。
(6) Pod 处于Terminating或 Unknown状态
从v1.5开始,Kubernetes不会因为Node失联而删除其上正在运行的Pod,而是将其标记为Terminating 或 Unknown 状态。想要删除这些状态的Pod有三种方法:
1)从集群中删除Node。使用公有云时,kube-controller-manager会在VM删除后自动删除对应的Node。而在物理机部署的集群中,需要管理员手动删除Node(kubectl delete node)。
2)Node恢复正常。kubelet会重新跟kube-apiserver通信确认这些Pod的期待状态,进而再决定删除或者继续运行这些Pod。用户强制删除,用户可以执行(kubectl delete pods pod-name --grace-period=0 --force)强制删除Pod。除非明确知道Pod的确处于停止状态(比如Node所在VM或物理机已经关机),否则不建议使用该方法。特别是StatefulSet 管理的Pod,强制删除容易导致脑裂或数据丢失等问题。
3)Pod行为异常,这里所说的行为异常是指Pod没有按预期的行为执行,比如没有运行podSpec 里面设置的命令行参数。这一般是podSpec yaml文件内容有误,可以尝试使用 --validate 参数重建容器,比如(kubectl delete pod mypod 和 kubectl create --validate -f mypod.yaml);也可以查看创建后的podSpec是否是对的,比如(kubectl get pod mypod -o yaml);修改静态Pod的Manifest后未自动重建,kubelet 使用inotify 机制检测 /etc/kubernetes/manifests 目录(可通过 kubelet 的 -pod-manifest-path 选项指定)中静态Pod的变化,并在文件发生变化后重新创建相应的 Pod。但有时也会发现修改静态Pod的 Manifest后未自动创建新 Pod的情景,此时已过简单的修复方法是重启 Kubelet。
Unknown 这个异常状态意味着Pod的状态不能持续地被 kubelet汇报给 kube-apiserver,这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题。
(7)pod从创建到成功或失败的事件
PodScheduled
pod正处于调度中,刚开始调度的时候,hostip还没绑定上,持续调度之后,有合适的节点就会绑定hostip,然后更新etcd数据
Initialized
pod中的所有初始化容器已经初启动完毕
Ready
pod中的容器可以提供服务了
Unschedulable
不能调度,没有合适的节点
CrashLoopBackOff: 容器退出,kubelet正在将它重启
InvalidImageName: 无法解析镜像名称
ImageInspectError: 无法校验镜像
ErrImageNeverPull: 策略禁止拉取镜像
ImagePullBackOff: 正在重试拉取
RegistryUnavailable: 连接不到镜像中心
ErrImagePull: 通用的拉取镜像出错
CreateContainerConfigError: 不能创建kubelet使用的容器配置
CreateContainerError: 创建容器失败
m.internalLifecycle.PreStartContainer 执行hook报错
RunContainerError: 启动容器失败
PostStartHookError: 执行hook报错
ContainersNotInitialized: 容器没有初始化完毕
ContainersNotReady: 容器没有准备完毕
ContainerCreating: 容器创建中
PodInitializing:pod 初始化中
DockerDaemonNotReady: docker还没有完全启动
NetworkPluginNotReady: 网络插件还没有完全启动
Evicte: pod被驱赶
5. Pod 容器资源限制
5.1 资源限制的了解
在我前面Docker的Cgroup文章中,就提到过,为什么我们对容器进行资源限制。同理:首先K8s中pod使用宿主机的资源默认情况下是无节制的,但是当一个集群搭建成功后并投入生产环境中。如果其中的某一个pod因为不明原因出现了bug,疯狂占用宿主机资源,抢占其他pod的资源。势必会导致整个集群的瘫痪,所以pod资源的限制是非常有必要的
在资源控制器中我们也可以准确的找到相应的资源控制字段:
kubectl explain deployment.spec.template.spec.containers.resources
kubectl explain statefulset.spec.template.spec.containers.resources
在官方文档中(资源配额 | Kubernetes) 我们可以得知:pod控制的资源总共为三大类:
cpu,memory,hugepages(巨页)。其中我们运用最多的限制还是cpu和memory。
(1)cpu限制的参数了解
pod对于cpu限制的参数有两种表达形式:
第一种是指定个数的表达形式,例如:1 ,2, 0.5 ,0.2 ,0.3 指定cpu的个数(该个数可以为整数,也可以为小数点后一位的小数)
第二种是以毫核为单位的表达形式:100m 500m 1000m 2000m (这里1000m等价于一个cpu,该方式来自于cpu时间分片原理得来)
(2)memory的表达形式
pod对内存参数的要求十分严谨。对硬件有过研究的朋友,应该会了解到,我们常常提到的GB,MB,TB其实是于实际字节总数是有误差的(原因是GB是以10为底数计量,而真正的换算是以2为底数计量。导致了总量的误差。)而真正没有此误差的单位是Ki Mi Gi Ti
所以k8s中pod资源限制为了对pod的内存限制更为精准,采用的单位是 Ki Mi Gi Ti
5.2 实例运用
需求:创建一个deployment资源,镜像使用nginx:latest,创建3个pod副本,镜像拉取策略使用IfNotPresent,重启策略使用Always,容器资源预留cpu 0.25个,内存128MiB,上限最多1个cpu,1g内存
kubectl create deployement nginx-test --images=nginx:latest --dry-run=client -o yaml >test.yaml
vim test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:creationTimestamp: nulllabels:app: nginx-testname: nginx-test
spec:replicas: 3selector:matchLabels:app: nginx-testtemplate:metadata:labels:app: nginx-testspec:containers:- image: nginx:latestname: nginxresources:requests:memory: "128Mi"cpu: "250m"limits:memory: "1Gi"cpu: "1"imagePullPolicy: IfNotPresentrestartPolicy: Alwayskubectl apply -f test.yaml
#查看pod的资源使用情况
kubectl describe pod nginx-test
6. Pod 容器的探针
6.1 探针的概念及其作用
探针是由 kubelet 对容器执行的定期诊断(pod中探针又分为三类):
存活探针(livenessProbe)探测容器是否运行正常。如果探测失败则kubelet杀掉容器(不是Pod),容器会根据重启策略决定是否重启
就绪探针(readinessProbe)探测Pod是否能够进入READY状态,并做好接收请求的准备。如果探测失败Pod则会进入NOTREADY状态(READY为0/1)并且从所关联的service资源的端点(endpoints)中踢出,service将不会再把访问请求转发给这个Pod
启动探针(startupProbe)探测容器内的应用是否启动成功,在启动探针探测成功之前,其它类型的探针都会暂时处于禁用状态
注意:启动探针只是在容器启动后按照配置满足一次后就不再进行后续的探测了。存活探针和就绪探针会一直探测到Pod生命周期结束为止
kubectl explain pod.spec.containers
6.2 探针的探测方式
exec : 通过command字段设置在容器内执行的Linux命令来进行探测,如果命令返回码为0,则认为探测成功,返回码非0则探测失败
httpGet : 通过向容器的指定端口和uri路径发起HTTP GET请求,如果HTTP返回状态码为 >=200且<400的(2XX,3XX),则认为探测成功,返回状态码为4XX,5XX则探测失败
tcpSocket1 : 通过向容器的指定端口发送tcp三次握手连接,如果端口正确却tcp连接成功,则认为探测成功,tcp连接失败则探测失败
探针探测结果有以下值:
Success:表示通过检测。
Failure:表示未通过检测。
Unknown:表示检测没有正常进行。
6.3 探针字段
探针(Probe)有许多可选字段,可以用来更加精确的控制Liveness和Readiness两种探针的行为(Probe):
(1)initialDelaySeconds:容器启动后要等待多少秒后就探针开始工作,单位“秒”,默认是 0s,最小值是 0s;
(2)periodSeconds:执行探测的时间间隔(单位是秒),默认为 10s,最小值是 1s,
(3)timeoutSeconds:探针执行检测请求后,等待响应的超时时间,默认为 1s,最小值是 1s,
(4)successThreshold:探针检测失败后认为成功的最小连接成功次数,默认为 1,在 Liveness和startup探针中必须为 1,最小值为 1。
(5)failureThreshold:探测失败的重试次数,重试一定次数后将认为失败,默认为 3,最小值为 1
6.4 探针实验测试
(1)LivenessProbe 探针使用
1) 通过exec方式做健康探测
apiVersion: v1
kind: Pod
metadata:name: demo-livelabels:app: demo-live
spec:containers:- name: demo-liveimage: centos:7args: #创建测试探针探测的文件- /bin/sh- -c- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600livenessProbe:initialDelaySeconds: 10 #延迟检测时间periodSeconds: 5 #检测时间间隔exec: #使用命令检查command: #指令,类似于运行命令sh- cat #sh 后的第一个内容,直到需要输入空格,变成下一行- /tmp/healthy #由于不能输入空格,需要另外声明,结果为sh cat"空格"/tmp/healthy
容器在初始化后,执行(/bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600")首先创建一个 /tmp/healthy 文件,然后执行睡眠命令,睡眠 30 秒,到时间后执行删除 /tmp/healthy 文件命令。而设置的存活探针检检测方式为执行 shell 命令,用 cat 命令输出 healthy 文件的内容,如果能成功执行这条命令一次(默认successThreshold:1),存活探针就认为探测成功,由于没有配置(failureThreshold、timeoutSeconds),所以执行(cat /tmp/healthy)并只等待1s,如果1s内执行后返回失败,探测失败。在前 30 秒内,由于文件存在,所以存活探针探测时执行 cat /tmp/healthy 命令成功执行。30 秒后 healthy 文件被删除,所以执行命令失败,Kubernetes 会根据 Pod 设置的重启策略来判断,是否重启 Pod。
2)通过HTTP方式做健康探测
apiVersion: v1
kind: Pod
metadata:
name: liveness-http
labels:
test: liveness
spec:
containers:
- name: liveness
image: mydlqclub/springboot-helloworld:0.0.1
livenessProbe:
failureThreshold: 5 #检测失败5次表示未就绪
initialDelaySeconds: 20 #延迟加载时间
periodSeconds: 10 #重试时间间隔
timeoutSeconds: 5 #超时时间设置
successThreshold: 2 #检查成功为2次表示就绪
httpGet:
scheme: HTTP # 用于连接host的协议,默认为HTTP。
port: 8081 #容器上要访问端口号或名称。
path: /actuator/health #http服务器上的访问URI。
此外:
host:要连接的主机名,默认为Pod IP,可以在http request head中设置host头部。
httpHeaders:自定义HTTP请求headers,HTTP允许重复headers
#####过程了解##########################
Pod 中启动的容器是一个 SpringBoot 应用,其中引用了 Actuator 组件,提供了 /actuator/health 健康检查地址,在pod启动后,初始化等待20s后,livenessProbe开始工作,去请求HTTP://podIP:8081/actuator/health 接口,类似于curl -I HTTP://podIP:8081/actuator/health接口,考虑到请求会有延迟(curl -I后一直出现假死状态),所以给这次请求操作一直持续5s,如果5s内访问返回数值在>=200且<=400代表第一次检测success,如果是其他的数值,或者5s后还是假死状态,执行类似(ctrl+c)中断,并反回failure失败。等待10s后,再一次的去请求HTTP://podIP:8081/actuator/health接口。如果有连续的2次都是success,代表无问题。如果期间有连续的5次都是failure,代表有问题,直接重启pod,此操作会伴随pod的整个生命周期
3)通过TCP方式做健康探测
apiVersion: v1
kind: Pod
metadata:
name: liveness-tcp
labels:
app: liveness
spec:
containers:
- name: liveness
image: nginx
livenessProbe:
initialDelaySeconds: 15
periodSeconds: 20
tcpSocket:
port: 80
#########过程解析#####################
TCP 检查方式和 HTTP 检查方式非常相似,在容器启动 initialDelaySeconds 参数设定的时间后,kubelet 将发送第一个 livenessProbe 探针,尝试连接容器的 80 端口,类似于telnet 80端口,如果连接失败则将杀死 Pod 重启容器。
(2)ReadinessProbe 探针使用
livenessProbe+readinessProbe通过httpGet探测方法的实验过程:
本次过程我将nginx的网页目录中index.html修改为test.html。并写下了下面的资源yaml进行测试
apiVersion: v1
kind: Pod
metadata:
name: myapp-test4
spec:
containers:
- image: nginx:1.14
imagePullPolicy: IfNotPresent
name: myapp-test4
ports:
- containerPort: 80
name: http
livenessProbe:
httpGet:
port: http
path: /index.html
initialDelaySeconds: 5
periodSeconds: 4
failureThreshold: 2
readinessProbe:
httpGet:
port: 80
path: /test.html
initialDelaySeconds: 1
periodSeconds: 3
failureThreshold: 3
timeoutSeconds: 10
restartPolicy: Always
该过程:
(1)首先是就绪探针在容器重启1s后进行httpGet方式探测,寻找到了 test.html网页验证成功。将该Pod 标记为就绪状态,kubelet 将继续每隔 10 秒运行一次探测。
(2)除此之外我还设置了存活探针,存活探针在容器启动的5s后,进行httpGet探测,因为默认网页被修改的缘故,找不到index.html,随后继续了两次4s间隔的探测,依旧没有发现uri目标网页。根据重启策略重启容器(该过程实际上就是删除原有pod,根据镜像拉取新的pod)。
(3)由于容器被重启,所以一切恢复默认初始化,此时默认的网页目录只存在Index.html,不在存在test.html。所以本次中就绪探针的httpGet进行uri的探测失败,此后期间会做出3次间隔3s的重复探测,最终均为失败。该pod则会进入NOTREADY状态,并且从所关联的service资源的端点(endpoints)中踢出,该pod将彻底称为notready状态,后面的就绪探针也就不在进行探测。
(3)启动探针的使用
启动探针存在的必要:
有时候,会有一些现有的应用在启动时需要较长的初始化时间。 要这种情况下,若要不影响对死锁作出快速响应的探测,设置存活探测参数是要技巧的。 技巧就是使用相同的命令来设置启动探测,针对 HTTP 或 TCP 检测,可以通过将 failureThreshold * periodSeconds 参数设置为足够长的时间来应对糟糕情况下的启动时间。(因为启动探针在启动后,其他的两种探针就会进入短暂的禁用装态,于是给容器启动预留了充足的时间,保证容器中业务启动的顺利进行。而且启动探针只会启动一次,后续工作就完全交给其他两个探针,直到容器的生命周期结束)。
ports:
- name: liveness-port
containerPort: 8080
hostPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: liveness-port
failureThreshold: 1
periodSeconds: 10
startupProbe:
httpGet:
path: /healthz
port: liveness-port
failureThreshold: 30
periodSeconds: 10
在这里,幸亏有了启动探测,应用程序将会有最多 5 分钟(30 * 10 = 300s)的时间来完成其启动过程。 一旦启动探测成功一次,存活探测任务就会接管对容器的探测,对容器死锁作出快速响应。 如果启动探测一直没有成功,容器会在 300 秒后被杀死,并且根据 restartPolicy 来执行进一步处置。
7.Pod 容器的启动和退出动作
postStart 配置 exec.command 字段设置 Linux 命令,实现当应用容器启动时,会执行的额外操作
preStop 配置 exec.command 字段设置 Linux 命令,实现当应用容器退出时,会执行的最后一个操作
实例运用
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: soscscs/myapp:v1
lifecycle: #此为关键字段
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler >> /var/log/nginx/message"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo Hello from the prestop handler >> /var/log/nginx/message"]
volumeMounts:
- name: message-log
mountPath: /var/log/nginx/
readOnly: false
initContainers:
- name: init-myservice
image: soscscs/myapp:v1
command: ["/bin/sh", "-c", "echo 'Hello initContainers' >> /var/log/nginx/message"]
volumeMounts:
- name: message-log
mountPath: /var/log/nginx/
readOnly: false
volumes:
- name: message-log
hostPath:
path: /data/volumes/nginx/log/
type: DirectoryOrCreate