1、什么是k8s?说出你的理解
K8s是kubernetes的简称,其本质是一个开源的容器编排系统,主要用于管理容器化的应用,其目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。
说简单点:k8s就是一个编排容器的系统,一个可以管理容器应用全生命周期的工具,从创建应用,应用的部署,应用提供服务,扩容缩容应用,应用更新,都非常的方便,而且还可以做到故障自愈,所以,k8s是一个非常强大的容器编排系统。
2、k8s的组件有哪些,作用分别是什么?
参考官网:https://kubernetes.io/zh-cn/docs/concepts/overview/components/
k8s主要由master节点和node节点构成。master节点负责管理集群,node节点是容器应用真正运行的地方。
master节点包含的组件有:kube-api-server、kube-controller-manager、kube-scheduler、etcd。
node节点包含的组件有:kubelet、kube-proxy、container-runtime。
kube-api-server:以下简称api-server,api-server是k8s最重要的核心组件之一,它是k8s集群管理的统一访问入口,提供了RESTful API接口, 实现了认证、授权和准入控制等安全功能;api-server还是其他组件之间的数据交互和通信的枢纽,其他组件彼此之间并不会直接通信,其他组件对资源对象的增、删、改、查和监听操作都是交由api-server处理后,api-server再提交给etcd数据库做持久化存储,只有api-server才能直接操作etcd数据库,其他组件都不能直接操作etcd数据库,其他组件都是通过api-server间接的读取,写入数据到etcd。
kube-controller-manager:以下简称controller-manager,controller-manager是k8s中各种控制器的的管理者,是k8s集群内部的管理控制中心,也是k8s自动化功能的核心;controller-manager内部包含deployment控制器、replicaSet控制器、statefulset控制器、daemonset控制器、job控制器、cronjob控制器、node控制器、endpoint控制器等等各种资源对象的控制器,每种控制器都负责一种特定资源的控制流程,而controller-manager正是这些controller的核心管理者。
kube-scheduler:以下简称scheduler,scheduler负责集群资源调度,其作用是将待调度的pod通过一系列复杂的调度算法计算出最合适的node节点,然后将pod绑定到目标节点上。shceduler会根据pod的信息,全部节点信息列表,过滤掉不符合要求的节点,过滤出一批候选节点,然后给候选节点打分,选分最高的就是最优节点,scheduler就会把目标pod安置到该节点。
etcd:etcd是一个分布式的键值对存储数据库,主要是用于保存k8s集群状态数据,比如,pod,service等资源对象的信息;etcd可以是单个也可以有多个,多个就是etcd数据库集群,etcd通常部署奇数个实例,在大规模集群中,etcd有5个或7个节点就足够了;另外说明一点,etcd本质上可以不与master节点部署在一起,只要master节点能通过网络连接etcd数据库即可。
kubelet:每个node节点上都有一个kubelet服务进程,kubelet作为连接master和各node之间的桥梁,负责维护pod和容器的生命周期,当监听到master下发到本节点的任务时,比如创建、更新、终止pod等任务,kubelet 即通过控制docker来创建、更新、销毁容器;kubelet还会定时执行pod中容器定义的探针,然后根据容器重启策略执行对应的操作。每个kubelet进程都会在api-server上注册本节点自身的信息,用于定期向master汇报本节点资源的使用情况。
kube-proxy:kube-proxy运行在node节点上,在Node节点上实现pod网络代理,维护网络规则和四层负载均衡工作,kube-proxy会监听api-server中从而获取service和endpoint的变化情况,创建并维护路由规则以提供服务ip和负载均衡功能。简单理解此进程是Service的透明代理兼负载均衡器,其核心功能是将到某个Service的访问请求转发到后端的多个pod实例上。
container-runtime:容器运行时环境,即运行容器所需要的一系列程序,目前k8s支持的容器运行时有很多,如docker、rkt、containerd或其他,比较受欢迎的是docker,但是新版的k8s已经宣布弃用docker
3、kubelet的功能、作用是什么?
答:kubelet部署在每个node节点上的,它主要有4个功能:
1、节点管理。kubelet启动时会向api-server进行注册,然后会定时的向api-server汇报本节点信息状态,资源使用状态等,这样master就能够知道node节点的资源剩余,节点是否失联等等相关的信息了。master知道了整个集群所有节点的资源情况,这对于 pod 的调度和正常运行至关重要。
2、pod管理。kubelet负责维护node节点上pod的生命周期,当kubelet监听到master的下发到自己节点的任务时,比如要创建、更新、删除一个pod,kubelet 就会通过CRI(容器运行时接口)插件来调用不同的容器运行时来创建、更新、删除容器;常见的容器运行时有docker、containerd、rkt等等这些容器运行时,我们最熟悉的就是docker了,但在新版本的k8s已经弃用docker了,k8s1.24版本中已经使用containerd作为容器运行时了。
3、容器健康检查。pod中可以定义启动探针、存活探针、就绪探针等3种,我们最常用的就是存活探针、就绪探针,kubelet 会定期调用容器中的探针来检测容器是否存活,是否就绪,如果是存活探针,则会根据探测结果对检查失败的容器进行相应的重启策略;
4、Metrics Server资源监控。在node节点上部署Metrics Server用于监控node节点、pod的CPU、内存、文件系统、网络使用等资源使用情况,而kubelet则通过Metrics Server获取所在节点及容器的上的数据。
4、pause容器作用是什么?
每个pod里运行着一个特殊的被称之为pause的容器,也称根容器,而其他容器则称为业务容器;
1、创建pause容器主要是为了为业务容器提供 Linux命名空间,共享基础:包括 pid、icp、net 等,这些业务容器共享pause容器的网络命名空间和volume挂载卷,当pod被创建时,pod首先会创建pause容器,从而把其他业务容器加入pause容器,从而让所有业务容器都在同一个网络命名空间中,这样就可以实现网络共享。这种网络命名空间共享设计确保了同一个pod内的容器可以直接通过localhost地址互相通信,从而实现高效的内部通信。
2、pod还可以共享存储,在pod级别引入数据卷volume,业务容器都可以挂载这个数据卷从而实现持久化存储。
3、pause容器还负责处理僵尸进程。在传统的Unix系统中,当一个子进程结束而其父进程尚未读取其退出状态时,子进程会成为僵尸进程,占用系统资源。pause容器通过持续监听并清理这些僵尸进程,优化了系统的资源管理。
4、由于pause容器始终保持运行状态,它还承担了维护pod ip地址的角色。pod的ip地址通常是动态分配的,但只要pause容器在运行,就可以维持这个ip地址不变,即便pod内的其他容器重新启动也不会影响ip地址。
5、pod的重启策略有哪些?
pod的重启策略(RestartPolicy)决定了当容器异常退出或健康检查失败时,kubelet将如何响应。(注意是kubelet重启容器,因为是kubelet负责容器的健康检测)
需要注意的是,虽然名为pod的重启策略(更规范的说法应该是pod中容器重启策略),但实际上是作用于pod内的所有容器。所有容器都将遵守这个策略,而不是单独的某个容器。
可以通过pod.spec.restartPolicy字段配置重启容器的策略,重启策略如下3种配置:
Always: 当容器终止退出后,总是重启容器,默认策略就是Always。
OnFailure: 当容器异常退出,退出状态码非0时,才重启容器。
Never: 当容器终止退出,不管退出状态码是什么,从不重启容器。
6、pod的镜像拉取策略有哪几种?
pod镜像拉取策略可以通过imagePullPolicy字段配置镜像拉取策略,主要有3种镜像拉取策略,如下:
Always: 默认值,总是重新拉取,即每次创建pod都会重新从镜像仓库拉取一次镜像。
IfNotPresent: 镜像在node节点宿主机上不存在时才拉取。
Never: 永远不会主动拉取镜像,仅使用本地镜像,需要你手动拉取镜像到node节点,如果本地节点不存在镜像则pod启动失败
7、pod的存活探针有哪几种?
kubernetes可以通过存活探针检查容器是否还在运行,可以为pod中的每个容器单独定义存活探针,kubelet将定期执行探针,如果探测失败,将杀死容器,并根据restartPolicy策略来决定是否重启容器,kubernetes提供了3种探测容器的存活探针,如下:
httpGet:通过容器的ip、端口、路径发送http 请求,返回200-400范围内的状态码表示成功。
exec:在容器内执行shell命令,根据命令退出状态码是否为0进行判断,0表示健康,非0表示不健康。
TCPSocket:与容器的ip、端口建立TCP Socket链接,能建立则说明探测成功,不能建立则说明探测失败。
8、pod的就绪探针有哪几种?
我们知道,当一个pod启动后,就会立即加入service的endpoint ip列表中,并开始接收到客户端的链接请求,假若此时pod中的容器的业务进程还没有初始化完毕,那么这些客户端链接请求就会失败,为了解决这个问题,kubernetes提供了就绪探针来解决这个问题的。
在pod中的容器定义一个就绪探针,就绪探针周期性检查容器,如果就绪探针检查失败了,说明该pod还未准备就绪,不能接受客户端链接,则该pod将从endpoint列表中移除,被剔除了service就不会把请求分发给该pod,然后就绪探针继续检查,如果随后容器就绪,则再重新把pod加回endpoint列表。k8s提供了3种就绪探针,如下:
httpGet:通过容器的ip、容器的端口以及路径来发送http get请求,返回200-400范围内的状态码表示请求成功。
exec:在容器内执行shell命令,它根据shell命令退出状态码是否为0进行判断,0表示健康,非0表示不健康。
TCPSocket:通过容器的ip、端口建立TCP Socket链接,能正常建立链接,则说明探针成功,不能正常建立链接,则探针失败。
9、简单讲一下 pod创建过程
情况一、如果面试官问的是使用kubectl run命令创建的pod,可以这样说:
#注意:kubectl run 在旧版本中创建的是deployment,但在新的版本中创建的是pod则其创建过程不涉及deployment
如果是单独的创建一个pod,则其创建过程是这样的:
1、首先,用户通过kubectl或其他api客户端工具提交需要创建的pod信息给api-server;
2、api-server验证客户端的用户权限信息,验证通过开始处理创建请求生成pod对象信息,并将信息存入etcd,然后返回确认信息给客户端;
3、api-server开始反馈etcd中pod对象的变化,其他组件使用watch机制跟踪api-server上的变动;
4、scheduler发现有新的pod对象要创建,开始调用内部算法机制为pod分配最佳的主机,并将结果信息更新至api-server;
5、node节点上的kubelet通过watch机制跟踪api-server发现有pod调度到本节点,通过CRI容器运行时接口调用底层的docker启动容器,如果pod定义了pv,此时还会调用CSI容器存储接口分配存储,然后还会调用CNI容器网络接口给pod 分配IP,这样等等一系列的创建pod过程,并将创建成功的结果反馈api-server,然后kubelet就会根据pod定义的探针持续的对容器进行健康检查探测。
6、api-server将收到的pod状态信息存入etcd中。 至此,整个pod创建完毕。
情况二、如果面试官说的是使用deployment来创建pod,则可以这样回答:
1、首先,用户使用kubectl create命令或者kubectl apply命令提交了要创建一个deployment资源请求;
2、api-server收到创建资源的请求后,会对客户端操作进行身份认证,在客户端的~/.kube文件夹下,已经设置好了相关的用户认证信息,这样api-server会知道是哪个用户请求,并对此用户进行鉴权,当api-server确定客户端的请求合法后,就会接受本次操作,并把相关的信息保存到etcd中,然后返回确认信息给客户端。(仅返回创建的信息并不是返回是否成功创建的结果)
3、api-server开始反馈etcd中过程创建的对象的变化,其他组件使用watch机制跟踪api-server上的变动。
4、controller-manager组件会监听api-server的信息,controller-manager是有多个类型的,比如Deployment Controller, 它的作用就是负责监听Deployment,此时Deployment Controller发现有新的deployment要创建,那么它就会去创建一个ReplicaSet,一个ReplicaSet的产生,又被另一个叫做ReplicaSet Controller监听到了,紧接着它就会去分析ReplicaSet的语义,它了解到是要依照ReplicaSet的template去创建pod, 它一看这个pod并不存在,那么就新建此pod,当pod刚被创建时,它的nodeName属性值为空,代表着此pod未被调度。
5、接着调度器Scheduler组件开始介入工作,Scheduler也是通过watch机制跟踪api-server上的变动,发现有未调度的pod,则根据内部算法、节点资源情况,pod定义的亲和性反亲和性等等,调度器会综合的选出一批候选节点,在候选节点中选择一个最优的节点,然后将pod绑定到该节点,将信息反馈给api-server。
6、kubelet组件布署于Node之上,它也是通过watch机制跟踪api-server上的变动,监听到有一个pod应该要被调度到自身所在Node上来,kubelet首先判断本地是否在此pod,如果不存在,则会进入创建pod流程,创建pod有分为几种情况,第一种是容器不需要挂载外部存储,则相当于直接docker run把容器启动,但不会直接挂载docker网络,而是通过CNI调用网络插件配置容器网络,比如分配pod IP等,如果需要挂载外部存储,则还要调用CSI来挂载存储。kubelet创建完pod,将信息反馈给api-server,api-servier将pod信息写入etcd。
7、pod建立成功后,ReplicaSet Controller会对其持续进行关注,如果pod因意外或被我们手动退出,ReplicaSet Controller会知道,并创建新的pod,以保持replicas数量期望值。
10、简单描述一下pod的终止过程
1、用户向api-server发送删除pod对象的命令;
2、api-server中的pod对象信息会随着时间的推移而更新,在宽限期内(默认30s),pod被视为dead;
3、将pod标记为terminating状态;
4、kubectl通过watch机制监听api-server,监控到pod对象为terminating状态了就会启动pod关闭过程;
5、kube-proxy 更新转发规则,endpoint控制器监控到pod对象的关闭行为时将其从所有匹配到此endpoint的server资源endpoint列表中删除;
6、如果当前pod对象定义了preStop钩子处理器,则在其被标记为terminating后会以同步的方式启动执行;
7、pod对象中的容器进程收到停止信息;
8、宽限期结束后,若pod中还存在运行的进程,那么pod对象会收到立即终止的信息;
9、kubelet请求api-server将此pod资源的宽限期设置为0从而完成删除操作,此时pod对用户已不可见。
11、pod的常见调度方式有哪几种?
Kubernetes中,Pod通常是容器的载体,主要有如下常见调度方式:
-
Deployment或RC:该调度策略主要功能就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维持用户指定的副本数量。
-
NodeSelector:定向调度,当需要手动指定将Pod调度到特定Node上,可以通过Node的标签(Label)和Pod的nodeSelector属性相匹配。
-
NodeAffinity亲和性调度:亲和性调度机制极大的扩展了Pod的调度能力,目前有两种节点亲和力表达:
-
requiredDuringSchedulingIgnoredDuringExecution:硬规则,必须满足指定的规则,调度器才可以调度Pod至Node上(类似nodeSelector,语法不同)。
-
preferredDuringSchedulingIgnoredDuringExecution:软规则,优先调度至满足的Node的节点,但不强求,多个优先级规则还可以设置权重值。
-
Taints和Tolerations(污点和容忍):
-
Taint:使Node拒绝特定Pod运行;
-
Toleration:为Pod的属性,表示Pod能容忍(运行)标注了Taint的Node。
12、pod的生命周期有哪几种?
pod生命周期有的5种状态(也称5种相位),如下:
Pending(挂起):API server已经创建pod,但是该pod还有一个或多个容器的镜像没有创建,包括正在下载镜像的过程;
Running(运行中):pod内所有的容器已经创建,且至少有一个容器处于运行状态、正在启动括正在重启状态;
Succeed(成功):pod内所有容器均已退出,且不会再重启;
Failed(失败):pod内所有容器均已退出,且至少有一个容器为退出失败状态
Unknown(未知):某于某种原因api-server无法获取该pod的状态,可能由于网络通行问题导致;
13、pod一直处于pending状态一般有哪些情况,怎么排查?
答:一个pod一开始创建的时候,它本身就是会处于pending状态,这时可能是正在拉取镜像,正在创建容器的过程。
如果等了一会发现pod还一直处于pending状态,那么我们可以使用kubectl describe命令查看一下pod的Events详细信息。一般可能会有这么几种情况导致pod一直处于pending状态:
1、调度器调度失败。Scheduer调度器无法为pod分配一个合适的node节点。而这又会有很多种情况,比如,node节点处在cpu、内存压力,导致无节点可
调度;pod定义了资源请求,没有node节点满足资源请求;node节点上有污点而pod没有定义容忍;pod中定义了亲和性或反亲和性而没有节点满足这些亲
和性或反亲和性;以上是调度器调度失败的几种情况。
2、pvc、pv无法动态创建。如果因为pvc或pv无法动态创建,那么pod也会一直处于pending状态,比如要使用StatefulSet创建redis集群,因为粗心大
意,定义的storageClassName名称写错了,那么会造成无法创建pvc,这种情况pod也会一直处于pending状态,或者,即
使pvc是正常创建了,但是由于某些异常原因导致动态供应存储无法正常创建pv,那么这种情况pod也会一直处于pending状态。
14、pod一直处于Waiting状态一般有哪些情况,怎么排查?
如果一个Pod卡在Waiting状态,则表示这个Pod已经调度到节点上,但是没有运行起来。可用通过Rancher UI查看Pod事件来查看相关信息。最常见的原因是拉取镜像失败。可以通过以下三种方式来检查:
1、使用的镜像名是否正确
2、镜像仓库中有没有这个镜像
3、用docker pull <image>命令在节点上手动拉取镜像进行测试验证
15、pod一直处于Crashing或者Unhealthy状态一般有哪些情况,怎么排查?
通常这种情况是因为容器应用异常退出导致,可以先看一下Pod的log
或者在Pod重新运行尚未故障时,进入到Pod中,执行命令查看对应的日志
16、node节点处于NotReady状态一般有哪些情况,怎么排查?
出现这种情况原因主要是对应的Worker节点上的kubelet与Master节点的kube-apiserver不能正常连接。
检查运行在节点上的 kubelet 是否正常。
docker logs kubelet //查看kubelet是否正常运行。
17、如何优雅的终止pod或者说如何保证pod不丢失流量
在一些重点领域,比如涉及金钱交易的系统,保证pod终止时不丢失流量是很重要的,这就涉及到如何保证pod优雅的退出的问题。
1、在开发层面,程序务必在业务代码里处理 SIGTERM 信号。主要逻辑是不接受新的流量进入,继续处理剩余流量,保证所有连接全部断开程序才退出。
2、在k8s层面,Pod里面可以设置preStop构子,preStop构子的作用是在容器终止之前立即被调用,主要用于优雅关闭应用程序、或者完成一些清理工作等等,这个钩子是同步的,即具有阻塞性的,也就是说它是会阻塞删除容器的操作。举个例子假设是容器运行的是nginx进程,则可以设置preStop钩子为nginx -s quit 让nginx进程优雅退出。
3、在一些程序终止时常较长的场景下,可以适当增加pod终止宽限期,即terminationGracePeriodSeconds参数,默认pod终止宽限期是30s,参数具体设置在deployment.spec.template.spec.terminationGracePeriodSeconds。当终止终止宽限期到达之后,无论Pod是否完成终止,也无论Pod是否正在被preStop阻塞,k8s都会发送强制退出信号给Pod让其终止。
18、service的类型有哪几种
service的类型一般有4种,分别是:
ClusterIP:表示service仅供集群内部使用,默认值就是ClusterIP类型
NodePort:表示service可以对外访问应用,会在每个节点上暴露一个端口,这样外部浏览器访问地址为:任意节点的ip:NodePort就能连上service了
LoadBalancer:表示service对外访问应用,这种类型的service是公有云环境下的service,此模式需要外部云厂商的支持,需要有一个公网ip地址
ExternalName:这种类型的service会把集群外部的服务引入集群内部,这样集群内直接访问service就可以间接的使用集群外部服务了
一般情况下,service都是ClusterIP类型的,通过ingress接入的外部流量。
19、deployment的更新升级策略有哪些?
答:deployment的升级策略主要有两种。
1、Recreate 重建更新:这种更新策略会杀掉所有正在运行的pod,然后再重新创建的pod;
2、rollingUpdate 滚动更新:这种更新策略,deployment会以滚动更新的方式来逐个更新pod,同时通过设置滚动更新的两个参数maxUnavailable、maxSurge来控制更新的过程。
20、deployment的滚动更新策略有两个特别主要的参数,解释一下它们是什么意思?
答:deployment的滚动更新策略,rollingUpdate 策略,主要有两个参数,maxUnavailable、maxSurge。
maxUnavailable:最大不可用数,maxUnavailable用于指定deployment在更新的过程中不可用状态的pod的最大数量,maxUnavailable的值可以是
一个整数值,也可以是pod期望副本的百分比,如25%,计算时向下取整。
maxSurge:最大激增数,maxSurge指定deployment在更新的过程中pod的总数量最大能超过pod副本数多少个,maxUnavailable的值可以是一个整数
值,也可以是pod期望副本的百分比,如25%,计算时向上取整。
21、简述一下deployment的更新过程
deployment是通过控制replicaset来实现,由replicaset真正创建pod副本,每更新一次deployment,都会创建新的replicaset,下面来举例deployment的更新过程:
假设要升级一个nginx-deployment的版本镜像为nginx:1.9,deployment的定义滚动更新参数如下:
replicas: 3
deployment.spec.strategy.type: RollingUpdate
maxUnavailable:25%
maxSurge:25%
通过计算我们得出,3*25%=0.75,maxUnavailable是向下取整,则maxUnavailable=0,maxSurge是向上取整,则maxSurge=1,所以我们得出在整个deployment升级镜像过程中,不管旧的pod和新的pod是如何创建消亡的,pod总数最大不能超过3+maxSurge=4个,最大pod不可用数3-maxUnavailable=3个。
现在具体讲一下deployment的更新升级过程:
使用`kubectl set image deployment/nginx nginx=nginx:1.9 --record` 命令来更新;
1、deployment创建一个新的replaceset,先新增1个新版本pod,此时pod总数为4个,不能再新增了,再新增就超过pod总数4个了;旧=3,新=1,总=4;
2、减少一个旧版本的pod,此时pod总数为3个,这时不能再减少了,再减少就不满足最大pod不可用数3个了;旧=2,新=1,总=3;
3、再新增一个新版本的pod,此时pod总数为4个,不能再新增了;旧=2,新=2,总=4;
4、减少一个旧版本的pod,此时pod总数为3个,这时不能再减少了;旧=1,新=2,总=3;
5、再新增一个新版本的pod,此时pod总数为4个,不能再新增了;旧=1,新=3,总=4;
6、减少一个旧版本的pod,此时pod总数为3个,更新完成,pod都是新版本了;旧=0,新=3,总=3;
1、用户通过kubectl set image命令或kubectl edit命令直接修改镜像将新的配置应用到集群中,api-server对当前用户操作进行鉴权,鉴权成功后
接收操作请求并返回信息给用户;
2、Deployment控制器检测到配置变化后,开始创建一个新的replicaset,replicaset开始创建新的pod,根据deployment定义的更新策略来执行更新
升级。默认情况下,它采用滚动更新(RollingUpdate)策略。
滚动更新策略有两个主要的参数,maxUnavailable和maxSurge 参数。maxUnavailable 参数定义可以有多少个pod不可用(未就绪),maxSurge则指定了可以比期望副本数多创建多少个pod,通过这两个参数,deployment会来逐步创建新版本pod,并同时删除旧版本pod。
3、在每个新pod被创建之后,kubelet会根据定义的livenessProbe和readinessProbe来检查新pod是否已经启动并准备好接收流量。当新pod通过存活探针并标记为"就绪"时,pod的ip和端口将会被endpoint检查并加入端点列表,此时Service会逐渐将流量从旧pod转移到新pod。
4、一旦所有旧pod都被新pod替换并且新版本的所有pod都已准备就绪,则升级过程结束
22、k8s生产中遇到什么特别影响深刻的问题吗,问题排查解决思路是怎么样的?
答:前端的lb负载均衡服务器上的keepalived出现过脑裂现象。
1、当时问题现象是这样的,vip同时出现在主服务器和备服务器上,但业务上又没受到影响;
2、这时首先去查看备服务器上的keepalived日志,发现有日志信息显示凌晨的时候备服务器出现了vrrp协议超时,所以才导致了备服务器接管了vip;查看主服务器上的keepalived日志,没有发现明显的报错信息,继续查看主服务器和备服务器上的keepalived进程状态,都是running状态的;查看主服务器上检测脚本所检测的进程,其进程也是正常的,也就是说主服务器根本没有成功执行检测脚本(成功执行检查脚本是会kill掉keepalived进程,脚本里面其实就是配置了检查nginx进程是否存活,如果检查到nginx不存活则kill掉keepalived,这样来实现备服务器接管vip);
3、排查服务器上的防火墙、selinux,防火墙状态和selinux状态都是关闭着的;
4、使用tcpdump工具在备服务器上进行抓取数据包分析,分析发现,现在确实是备接管的vip,也确实是备服务器也在对外发送vrrp心跳包,所以现在外部流量应该都是流入备服务器上的vip;
5、怀疑:主服务器上设置的vrrp心跳包时间间隔太长,以及检测脚本设置的检测时间设置不合理导致该问题;
6、修改vrrp协议的心跳包时间间隔,由原来的2秒改成1秒就发送一次心跳包;检测脚本的检测时间也修改短一点,同时还修改检测脚本的检测失败的次数,比如连续检测2次失败才认定为检测失败;
7、重启主备上的keepalived,现在keepalived是正常的,主服务器上有vip,备服务器上没有vip;
8、持续观察:第二天又发现keepalived出现过脑裂现象,vip又同时出现在主服务器和备服务器上,又是凌晨的时候备服务器显示vrrp心跳包超时,所以才导致备服务器接管了vip;
9、同样的时间,都是凌晨,vrrp协议超时;很奇怪,很有理由怀疑是网络问题,询问第三方厂家上层路由器是否禁止了vrrp协议,第三方厂家回复,没有禁止vrrp协议;
10、百度、看官方文档求解;
11、百度、看官网文档得知,keepalived有2种传播模式,一种是组播模式,一种是单播模式,keepalived默认在组播模式下工作,主服务器会往主播地址224.0.0.18发送心跳包,当局域网内有多个keepalived实例的时候,如果都用主播模式,会存在冲突干扰的情况,所以官方建议使用单播模式通信,单播模式就是点对点通行,即主向备服务器一对一的发送心跳包;
12、将keepalived模式改为单播模式,继续观察,无再发生脑裂现象。问题得以解决。
答:测试环境二进制搭建etcd集群,etcd集群出现2个leader的现象。
1、问题现象就是:刚搭建的k8s集群,是测试环境的,搭建完成之后发现,使用kubectl get nodes 显示没有资源,kubectl get namespace 一会能正常显示全部的命名空间,一会又显示不了命名空间,这种奇怪情况。
2、当时经验不是很足,第一点想到的是不是因为网络插件calico没装导致的,但是想想,即使没有安装网络插件,最多是node节点状态是notready,也不可能是没有资源发现呀;
3、然后想到etcd数据库,k8s的资源都是存储在etcd数据库中的;
4、查看etcd进程服务的启动状态,发现etcd服务状态是处于running状态,但是日志有大量的报错信息,日志大概报错信息就是集群节点的id不匹配,存在冲突等等报错信息;
5、使用etcdctl命令查看etcd集群的健康状态,发现集群是health状态,但是居然显示有2个leader,这很奇怪(当初安装etcd的时候其实也只是简单看到了集群是健康状态,然后没注意到有2个leader,也没太关注etcd服务进程的日志报错信息,以为etcd集群状态是health状态就可以了)
6、现在etcd出现了2个leader,肯定是存在问题的;
7、全部检测一遍etcd的各个节点的配置文件,确认配置文件里面各个参数配置都没有问题,重启etcd集群,报错信息仍未解决,仍然存在2个leader;
8、尝试把其中一个leader节点踢出集群,然后再重新添加它进入集群,仍然是报错,仍然显示有2个leader;
9、尝试重新生成etcd的证书,重新颁发etcd的证书,问题仍然存在,仍然显示有2个leader;日志仍是报错集群节点的id不匹配,存在冲突;
10、计算etcd命令的MD5值,确保各个节点的etcd命令是相同的,确保在scp传输的时候没有损耗等等,问题仍未解决;
11、无解,请求同事,架构师介入帮忙排查问题,仍未解决;
12、删除全部etcd相关的文件,重新部署etcd集群,etcd集群正常了,现在只有一个leader,使用命令kubectl get nodes 查看节点,也能正常显示了;
13、最终问题的原因也没有定位出来,只能怀疑是环境问题了,由于是刚部署的k8s测试环境,etcd里面没有数据,所以可以删除重新创建etcd集群,如果是线上环境的etcd集群出现这种问题,就不能随便删除etcd集群了,必须要先进行数据备份才能进行其他方法的处理。
23、pod内容器之间通信,同节点pod通信、不同节点的pod的通信是如何实现的,大概的流程是什么
假设k8s采用flannel插件,则同节点的pod通信、不同节点的pod通信是这样子的:
1、pod内容器之间通信
在创建一个pod时,首先会创建一个pause容器,也就是根容器,而根容器负责创建pod内所有容器共享的网络命名空间,而其他业务容器都会加入到这个网络命名空间中去,所以在一个pod里面,容器都是处于同一个命名空间,既然处于同一个命名空间,则容器进程彼此之间的访问直接通过localhost+端口即可。我们可以把pod想象成一个微型的虚拟机,虚拟机对外只有一个ip,而虚拟机内的应用彼此访问直接通过localhost+端口访问即可。
2、通节点pod的通信
在部署flannel插件的时候,k8s默认是采用daemonsets这种资源类型部署的,会在每个宿主机上有一个flanneld进程,这个进程就是用来管理k8s网络的。然后会在每个宿主机上生成一个网桥,这个网桥叫做cni0;
当一个pod被创建之后,cni插件会为pod创建一个虚拟以太网接口(veth pair),这个虚拟以太网接口(veth pair)它是一端接在pod里面,一端接在cni0网桥上,所以,当同一个节点上的pod相互通信,其实就是通过cni0进行通信的。
3、不同节点的pod的通信(待完善)
pod发送数据给到cni0网桥,cni0根据路由规则转发给flannel.1,flannel.1转发给宿主机上的eth0物理网卡,这样数据就发送到对端pod所在的物理机网卡上。
24、k8s升级过吗?简诉下升级流程及升级需要注意的事项
1. 查看当前集群版本
首先,检查集群和节点的当前版本:
kubectl version --short
2. 检查最新可用版本
在开始升级之前,确认你将升级到的目标版本。可以通过 Kubernetes 官方文档查看最新版本,或者使用以下命令检查可用版本:
kubectl cluster-info
或者通过 Kubernetes 发布页面查看最新版本:https://github.com/kubernetes/kubernetes/releases
3. 备份集群数据
备份 etcd 是 Kubernetes 升级过程中非常重要的步骤,特别是在升级控制平面时。可以使用以下命令来备份 etcd 数据:
ETCDCTL_API=3 etcdctl \
--endpoints=https://<etcd-endpoint>:2379 \
--cacert=<path-to-cafile> --cert=<path-to-certfile> --key=<path-to-keyfile> \
snapshot save /path/to/backup.db
4. 升级控制平面节点(Master Nodes)
Kubernetes 控制平面节点管理集群的状态,所有的组件(如 API Server、Controller Manager、Scheduler 等)都在这些节点上运行。升级这些组件是集群升级的关键步骤。
4.1 使用 kubeadm 升级
如果你使用 kubeadm 部署集群,升级步骤如下:
更新 kubeadm 工具:
在控制平面节点上,升级 kubeadm 工具到目标版本。
sudo apt-get update && sudo apt-get install -y kubeadm=1.24.0-00
查看升级计划:
查看可升级的版本和需要升级的组件。
kubeadm upgrade plan
执行控制平面升级:
执行升级操作,该命令会自动更新控制平面的所有组件。
sudo kubeadm upgrade apply v1.24.0
更新 kubelet 和 kubectl:
在控制平面节点上更新 kubelet 和 kubectl 工具到目标版本。
sudo apt-get install -y kubelet=1.24.0-00 kubectl=1.24.0-00
sudo systemctl daemon-reload
sudo systemctl restart kubelet
5. 升级工作节点(Worker Nodes)
在控制平面升级完成并确认集群正常工作后,可以开始升级工作节点。
5.1 将工作节点标记为不可调度
使用 cordon 命令将工作节点标记为不可调度,避免新的 Pod 被调度到该节点:
kubectl cordon <node-name>
5.2 驱逐节点上的 Pod
使用 drain 命令将节点上正在运行的 Pod 驱逐到其他节点:
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data
5.3 升级 kubeadm、kubelet 和 kubectl
在工作节点上,按照与控制平面相同的方式升级 kubeadm、kubelet 和 kubectl。
升级 kubeadm 工具:
sudo apt-get update && sudo apt-get install -y kubeadm=1.24.0-00
执行节点升级:
使用 kubeadm 升级该节点。
sudo kubeadm upgrade node
升级 kubelet 和 kubectl:
sudo apt-get install -y kubelet=1.24.0-00 kubectl=1.24.0-00
sudo systemctl daemon-reload
sudo systemctl restart kubelet
5.4 恢复节点调度
升级完成后,将节点重新标记为可调度:
kubectl uncordon <node-name>
5.5 确认节点状态
确保节点升级成功并处于 Ready 状态:
kubectl get nodes
6. 验证升级成功
升级完成后,验证集群是否正常工作,所有组件和节点是否处于健康状态:
kubectl get nodes
kubectl get pods -n kube-system
检查 kube-system 命名空间中的核心组件(如 kube-apiserver、kube-scheduler 等)的状态。
注意事项:
- 滚动升级:建议逐个节点进行升级,特别是在生产环境中,避免影响整个集群的可用性。
- 备份和恢复计划:在升级前做好备份,确保能够在必要时回滚到稳定的版本。
- 版本兼容性:Kubernetes 版本遵循严格的升级路径,不建议跨版本跳跃式升级,应按照每次升级一个次版本的方式进行。
25、你们的服务发布怎么做的?或者你们的cicd流程是怎么做的?
我们服务采用cicd流程发布的,具体的说,就是只要用到两个工具: jenkins和argocd。jenkins负责实现ci,argocd负责实现cd。主要是这么几个步骤:
1、开发写好代码将代码提交到gitlab仓库;
2、然后手动在jenkins页面上点击构建任务;
3、jenkins上定义了多个流水线项目,每个pipeline流水线项目都对应一个gitlab仓库的项目代码,流水线的配置都写在jenkinsfile文件里面,而jenkinsfile文件也是存放在gitlab仓库上进行托管的;
4、构建的流程只要是jenkinsfile文件定义的,jenkinsfile内容大致有这么几步:
第一步、先使用git命令克隆代码到工作目录并检出全部的分支然后写入到一个临时文件。
第二步、读取临时文件全部分支,提示用户选择要构建的分支和要发布的环境。
第三步、开始编译源代码,前端代码使用npm命令编译,后端代码使用maven编译。
第四步、代码编译完成就可以得到jar包了,这时开始构建镜像并推送镜像到harbor镜像仓库。
第五步、部署,这里的部署并不是真正意思上的部署,而是镜像的tag写回到gitlab仓库里去,并且使用kustomize命令修改yaml资源清单文件,这步主要是让argocd实现部署。
第六步、发送钉钉通知消息。
以上,第五步的更新gitlab仓库的时候,argocd会监听到gitlab仓库里面的k8s资源清单文件发生了改变,然后就会自动的应用部署,部署方式仍然是滚动更新deployment的镜像,这样就实现了自动化部署。
26、拿到一台新的服务器如何优化,如何进行加固安全
禁用SELinux
精简开机自启动服务
安装的Linux系统最小化,yum 安装软件也最小化,无用的包不安装。
更改ssh的默认22端口,改成其他端口,ssh禁用root远程登录,禁止空密码登录
配置sudoers文件,控制用户对系统命令的使用权限
设置linux时间同步,可以结合定时任务来同步时间服务器
调整系统文件描述符数量,在/etc/security/limits.conf文件里面调整
服务器内核参数优化
锁定系统关键的文件,使用chattr命令对文件锁定,锁定后所有用户都不能对文件进行修改删了,还可以将chattr命令重命名,防止被黑客识别。
服务器禁止被ping
开启防火墙
设置用户密码复杂度、过期策略,如最少密码长度、密码中必须包含的数字、大写字母、特殊字符等,以及密码的最大使用天数和到期警告天数。
修改PAM(Pluggable Authentication Modules)配置文件来设置账户锁定策略以对抗口令暴力破解
配置ssh登录超时策略。表示用户无操作多少秒超时自动退出 echo 'export TMOUT=300' >> /etc/profile
配置用户登录失败策略,在/etc/pam.d/system-auth中配置,比如密码错误锁定多少分钟
对多余帐户进行删除、锁定或禁止其登录,如:uucp、nuucp、lp、adm、sync、shutdown、halt、news、operator、gopher、shutdown等
限制保留的历史命令,HISTSIZE值,用于控制history命令保留历史记录数量;HISTFILESIZE值,控制.bash_history文件中存储历史记录数量;echo 'HISTSIZE=30' >>/etc/profile;echo 'HISTFILESIZE=30' >>/etc/profile
27、为什么linux文件删除后空间未释放?怎么处理?
在Linux系统中,有时会遇到文件被删除后,磁盘空间并未立即释放的情况。这通常是由于仍有进程在使用这些已被删除的文件。以下是对这一现象的基础概念解释、原因分析以及解决方案:
在Linux中,当一个文件被删除时,实际上只是删除了文件系统中的指向该文件的链接。如果此时有进程正在使用这个文件,那么该进程仍然可以通过其打开的文件描述符访问文件内容,直到它关闭这个文件描述符为止。
原因分析
- 进程仍在使用文件:即使文件已被删除,如果仍有进程持有该文件的打开状态,操作系统会保留文件的数据块,直到所有引用它的进程都关闭它。
- 文件系统延迟释放:某些文件系统可能会延迟释放空间,以优化性能。
解决方案
方法一:查找并终止使用文件的进程
- 使用
lsof
命令找出哪些进程正在使用已删除的文件。 - 使用
lsof
命令找出哪些进程正在使用已删除的文件。 - 这将列出所有打开了已删除文件的进程及其PID(进程ID)。
- 根据需要,可以选择性地终止这些进程。
- 根据需要,可以选择性地终止这些进程。
- 将
PID
替换为实际的进程ID。
方法二:强制释放空间
如果不想终止进程,可以使用 echo
命令将空字符串写入 /proc/PID/fd/FD
,其中 PID
是进程ID,FD
是文件描述符。
echo "" > /proc/PID/fd/FD
这会强制操作系统释放与该文件描述符关联的空间。
方法三:重启系统
作为最后的手段,重启系统可以清除所有打开的文件描述符,从而释放所有被占用的磁盘空间。