Skip to content

什么是k8s?说出你的理解

K8s是kubernetes的简称,其本质是一个开源的容器编排系统,主要用于管理容器化的应用,其目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。 说简单点:k8s就是一个编排容器的系统,一个可以管理容器应用全生命周期的工具,从创建应用,应用的部署,应用提供服务,扩容缩容应用,应用更新,都非常的方便,而且还可以做到故障自愈,所以,k8s是一个非常强大的容器编排系统。

k8s的组件有哪些,作用分别是什么?

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实例上。

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获取所在节点及容器的上的数据。

kube-api-server的端口是多少?各个pod是如何访问kube-api-server的?

kube-api-server的端口是8080和6443,前者是http的端口,后者是https的端口,(注意:有些8080是k8s低版本的才有的端口,高版本中不开放此端口了)以我本机使用kubeadm安装的k8s为例: 在命名空间的kube-system命名空间里,有一个名称为kube-api-master的pod,这个pod就是运行着kube-api-server进程,它绑定了master主机的ip地址和6443端口,但是在default命名空间下,存在一个叫kubernetes的服务,该服务对外暴露端口为443,目标端口6443,这个服务的ip地址是ClusterIP地址池里面的第一个地址,同时这个服务的yaml定义里面并没有指定标签选择器,也就是说这个kubernetes服务所对应的endpoint是手动创建的,该endpoint也是名称叫做kubernetes,该endpoint的yaml定义里面代理到master节点的6443端口,也就是kube-api-server的ip和端口。这样一来,其他pod访问kube-api-server的整个流程就是:pod创建后嵌入了环境变量,pod获取到了kubernetes这个服务的ip和443端口,请求到kubernetes这个服务其实就是转发到了master节点上的6443端口的kube-api-server这个pod里面。

k8s中命名空间的作用是什么?

namespace是kubernetes系统中的一种非常重要的资源,namespace的主要作用是用来实现多套环境的资源隔离,或者说是多租户的资源隔离。 k8s通过将集群内部的资源分配到不同的namespace中,可以形成逻辑上的隔离,以方便不同的资源进行隔离使用和管理。不同的命名空间可以存在同名的资源,命名空间为资源提供了一个作用域。 可以通过k8s的授权机制,将不同的namespace交给不同的租户进行管理,这样就实现了多租户的资源隔离,还可以结合k8s的资源配额机制,限定不同的租户能占用的资源,例如CPU使用量、内存使用量等等来实现租户可用资源的管理。

k8s提供了大量的REST接口,其中有一个是Kubernetes Proxy API接口,简述一下这个Proxy接口的作用,已经怎么使用。

好的。kubernetes proxy api接口,从名称中可以得知,proxy是代理的意思,其作用就是代理rest请求;Kubernets API server 将接收到的rest请求转发到某个node上的kubelet守护进程的rest接口,由该kubelet进程负责响应。我们可以使用这种Proxy接口来直接访问某个pod,这对于逐一排查pod异常问题很有帮助。 下面是一些简单的例子:

pod是什么?

在kubernetes的世界中,k8s并不直接处理容器,而是使用多个容器共存的理念,这组容器就叫做pod。pod是k8s中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型,其他的资源对象都是用来支撑pod对象功能的,比如,pod控制器就是用来管理pod对象的,service或者imgress资源对象是用来暴露pod引用对象的,persistentvolume资源是用来为pod提供存储等等,简而言之,k8s不会直接处理容器,而是pod,pod才是k8s中可以创建和管理的最小单元,也是基本单元。

pod的原理是什么?

在微服务的概念里,一般的,一个容器会被设计为运行一个进程,除非进程本身产生子进程,这样,由于不能将多个进程聚集在同一个单独的容器中,所以需要一种更高级的结构将容器绑定在一起,并将它们作为一个单元进行管理,这就是k8s中pod的背后原理。

pod有什么特点?

Pod 作为 Kubernetes 的最小调度单元,其生命周期管理是集群运维的核心知识:

  1. 生命周期短暂:Pod 是临时的,可以被创建、销毁和重新调度
  2. 原子性调度:Pod 中的所有容器总是被调度到同一个节点上
  3. 共享网络:Pod 内的容器通过 localhost 互相通信
  4. 共享存储:Pod 可以定义一组共享的存储卷

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地址。

pod的重启策略有哪些?

pod的重启策略(RestartPolicy)决定了当容器异常退出或健康检查失败时,kubelet将如何响应。(注意是kubelet重启容器,因为是kubelet负责容器的健康检测) 需要注意的是,虽然名为pod的重启策略(更规范的说法应该是pod中容器重启策略),但实际上是作用于pod内的所有容器。所有容器都将遵守这个策略,而不是单独的某个容器。 可以通过pod.spec.restartPolicy字段配置重启容器的策略,重启策略如下3种配置:

pod的镜像拉取策略有哪几种?

pod镜像拉取策略可以通过imagePullPolicy字段配置镜像拉取策略,主要有3种镜像拉取策略,如下:

  • Always(始终拉取):每次创建或重启 Pod 时,强制从镜像仓库拉取最新镜像,即使本地节点已存在同名镜像
  • IfNotPresent(本地不存在拉取)优先使用本地节点已缓存的镜像;仅当本地不存在时,才从仓库拉取
  • Never(永不拉取)仅使用本地节点已存在的镜像,绝不尝试从仓库拉取;若本地缺失,则 Pod 启动失败

pod的存活探针有哪几种?

kubernetes可以通过存活探针检查容器是否还在运行,可以为pod中的每个容器单独定义存活探针,kubelet将定期执行探针,如果探测失败,将杀死容器,并根据restartPolicy策略来决定是否重启容器,kubernetes提供了3种探测容器的存活探针,如下:

  • 启动探针 Startup Probe :用于判断容器内应用程序是否启动,如果配置了startupProbe,就会先禁止其他的探针,直到它成功为止,成功后将不再进行探测。(1.16版本后加入,针对容器内应用服务是否已经启动监控)
  • 就绪探针 Readiness Probe :判断容器是否已经就绪,若未就绪,容器将会处于未就绪,未就绪的容器,不会进行流量的调度。 Kubernetes 会把 Pod 从 service endpoints 中剔除。
  • 存活探针 Liveness Probe :判断容器内的应用程序是否正常,若不正常,根据 Pod 的restartPolicy 重启策略操作,如果没有配置该探针,默认就是success

存活探针的属性参数有哪几个?

存活探针的附加属性参数有以下几个:

  • initialDelaySeconds:表示在容器启动后延时多久秒才开始探测;
  • periodSeconds:表示执行探测的频率,即间隔多少秒探测一次,默认间隔周期是10秒,最小1秒;
  • timeoutSeconds:表示探测超时时间,默认1秒,最小1秒,表示容器必须在超时时间范围内做出响应,否则视为本次探测失败;
  • successThreshold:表示最少连续探测成功多少次才被认定为成功,默认是1,对于liveness必须是1,最小值是1;
  • failureThreshold:表示连续探测失败多少次才被认定为失败,默认是3,连续3次失败,k8s 将根据pod重启策略对容器做出决定;

注意:定义存活探针时,一定要设置initialDelaySeconds属性,该属性为初始延时,如果不设置,默认容器启动时探针就开始探测了,这样可能会存在 应用程序还未启动就绪,就会导致探针检测失败,k8s就会根据pod重启策略杀掉容器然后再重新创建容器的莫名其妙的问题。 在生产环境中,一定要定义一个存活探针。

pod的就绪探针有哪几种?

我们知道,当一个pod启动后,就会立即加入service的endpoint ip列表中,并开始接收到客户端的链接请求,假若此时pod中的容器的业务进程还没有初始化完毕,那么这些客户端链接请求就会失败,为了解决这个问题,kubernetes提供了就绪探针来解决这个问题的。 在pod中的容器定义一个就绪探针,就绪探针周期性检查容器,如果就绪探针检查失败了,说明该pod还未准备就绪,不能接受客户端链接,则该pod将从endpoint列表中移除,被剔除了service就不会把请求分发给该pod,然后就绪探针继续检查,如果随后容器就绪,则再重新把pod加回endpoint列表。k8s提供了3种就绪探针,如下:

  • exec:通过在容器内执行指定命令,来判断命令退出时返回的状态码,返回状态码是0表示正常。
  • httpGet:通过对容器的 IP 地址、端口和 URL 路径来发送 GET 请求;如果响应的状态码在 200 ~399 间,表示正常。
  • tcpSocket:通过对容器的 IP 地址和指定端口,进行 TCP 检查,如果端口打开,发起 TCPSocket 建立成功,表示正常。

就绪探针的属性参数有哪些

就绪探针的附加属性参数有以下几个:

  • initialDelaySeconds:延时秒数,即容器启动多少秒后才开始探测,不写默认容器启动就探测;
  • periodSeconds :执行探测的频率(秒),默认为10秒,最低值为1;
  • timeoutSeconds :超时时间,表示探测时在超时时间内必须得到响应,负责视为本次探测失败,默认为1秒,最小值为1;
  • failureThreshold :连续探测失败的次数,视为本次探测失败,默认为3次,最小值为1次;
  • successThreshold :连续探测成功的次数,视为本次探测成功,默认为1次,最小值为1次;

就绪探针与存活探针区别是什么?

两者作用不一样。 存活探针,是检测容器是否存活,如果检测失败,kubelet将调用容器运行时(如docker)将检查失败的容器杀死,创建新的启动容器来保持pod正常工作; 就绪探针,是检测容器是否可以正常接收流量,当就绪探针检查失败,并不重启容器,而是将pod移出endpoint列表,就绪探针确保了service中的pod都是可用的,确保客户端只与正常的pod交互并且客户端永远不会知道系统存在问题。

简单讲一下 pod创建过程

情况一:直接通过kubectl run创建Pod

  1. 用户通过kubectl或其他api客户端工具提交需要创建的pod信息给api-server
  2. api-server验证kubectl客户端的用户权限信息,验证通过后:
    • 生成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启动容器
  6. api-server将收到的pod状态信息存入etcd中

通过Deployment创建Pod

  1. 用户使用kubectl create命令或kubectl apply命令提交创建deployment资源请求
  2. api-server:
    • 收到请求
    • 对客户端操作进行身份认证(客户端的~/.kube文件夹已设置认证信息)
  3. api-server开始反馈etcd中创建的对象变化,其他组件使用watch机制跟踪api-server变动
  4. controller-manager组件(如Deployment Controller):
    • 监听api-server的信息
  5. 调度器Scheduler组件:
    • 通过watch机制跟踪api-server变动
    • 发现有未调度的pod
    • 根据内部算法分配节点
  6. kubelet组件(布置于Node上):
    • 通过watch机制跟踪api-server
    • 监听到pod应调度到自身所在Node
  7. Pod建立成功后:
    • ReplicaSet Controller持续关注pod
    • 若pod因意外或被手动退出,ReplicaSet Controller会知悉

简单描述一下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对用户已不可见。

描述一下pod的终止流程

以下为容器在 Kubernetes 环境中的pod终止流程:

  1. Pod 被删除,此时 Pod 里有 DeletionTimestamp,且状态置为 Terminating。此时调整 CLB 到该 Pod 的权重为 0。
  2. kube-proxy 更新转发规则,将 Pod 从 service 的 endpoint 列表中摘除掉,新的流量不再转发到该 Pod。
  3. 如果 Pod 配置了 preStop Hook ,将会执行。
  4. kubelet 将对 Pod 中各个 container 发送 SIGTERM 信号,以通知容器进程开始优雅停止。
  5. 等待容器进程完全停止,如果在 terminationGracePeriodSeconds 内 (默认30s) 还未完全停止,将发送 SIGKILL 信号强制停止进程。
  6. 所有容器进程终止,清理 Pod 资源。

pod的生命周期有哪几种?

pod生命周期有的5种状态(也称5种相位),如下:

  • Pending(挂起):API server已经创建pod,但是该pod还有一个或多个容器的镜像没有创建,包括正在下载镜像的过程;
  • Running(运行中):pod内所有的容器已经创建,且至少有一个容器处于运行状态、正在启动括正在重启状态;
  • Succeed(成功):pod内所有容器均已退出,且不会再重启;
  • Failed(失败):pod内所有容器均已退出,且至少有一个容器为退出失败状态
  • Unknown(未知):某于某种原因api-server无法获取该pod的状态,可能由于网络通行问题导致;

pod状态一般有哪些?

pod的状态一般会有以下这些:

  • ContainerCreating(容器正在创建):容器正在创建中
  • Pending(挂起):API server已经创建pod,但是该pod还有一个或多个容器的镜像没有创建,包括正在下载镜像的过程;
  • Running(运行中):pod内所有的容器已经创建,且至少有一个容器处于运行状态、正在启动括正在重启状态;
  • MatchNodeSelector (匹配节点选择器):pod正在等待被调度到匹配其nodeSelector的节点上,当一个pod定义有节点选择器但没有任何节点存在指定的标签时,pod将处于“MatchNodeSelector”状态。
  • ErrImagePull(镜像拉取异常): 这个错误表示Kubernetes无法从指定的镜像仓库拉取镜像。可能的原因有很多,比如网络问题、镜像名称或标签错误、或者没有权限访问这个镜像仓库等。
  • ImagePullBackOff(镜像拉取异常): 这个错误表示Kubernetes尝试拉取镜像,但是失败了,然后它回滚了之前的操作。这通常是因为镜像仓库的问题,比如网络问题、镜像不存在、或者没有权限访问这个镜像仓库等。
  • Error(pod异常):可能是容器运行时异常
  • CrashLoopBackOff(崩溃重启) :pod正在经历一个无限循环的崩溃和重启过程。
  • Succeed(成功):pod内所有容器均已退出,且不会再重启;
  • Failed(失败):pod内所有容器均已退出,且至少有一个容器为退出失败状态
  • Unknown(未知):某于某种原因api-server无法获取该pod的状态,可能由于网络通行问题导致;

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状态。

pod的钩子函数有哪几种,作用是什么?

pod的钩子函数有PostStart和PreStop两种,它们在容器的生命周期中特定时刻被调用并执行指定的操作。

  1. postStart构子,作用是在容器创建后立即执行,但并不能保证该钩子会在容器的ENTRYPOINT之前运行。它主要用于资源部署、环境准备等。如果该钩子执行失败或花费太长时间,容器将无法达到“Running”状态。一个典型的PostStart应用是在容器启动时执行一些配置或准备工作,比如修改配置文件、更新本地资源等。例如,可以在此钩子中修改Nginx的默认首页。
  2. preStop构子,作用是在容器终止之前立即被调用,是阻塞的,即同步的。它主要用于优雅关闭应用程序、通知其他系统以及完成清理工作。如果钩子执行 期间挂起,pod的状态将停留在“Running”状态并且不会达到“Failed”状态。在其完成之前会阻塞删除容器的操作。 应用场景:一个常见的PreStop应用是在容器关闭前优雅地停止一个服务,如Nginx或MySQL服务。

注意: postStart构子是异步的,并不能保证该钩子会在容器的ENTRYPOINT之前运行; preStop构子是同步的,它会阻塞当前容器的结束流程,直到Hook定义操作完成之后才允许容器被结束。 不管是postStart构子还是preStop构子,都可以使用 exec、httpGet、tcpSocket等3种方法来定义。

如何优雅的终止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让其终止。

腾讯云的这篇文章讲的不错:https://www.tencentcloud.com/zh/document/product/457/42070

pod的初始化容器是干什么的?

init container,初始化容器用于在启动应用容器之前完成应用容器所需要的前置条件,初始化容器本质上和应用容器是一样的,但是初始化容器是仅运行一次就结束的任务,初始化容器具有两大特征:

  1. 初始化容器必须运行完成直至成功结束,若某初始化容器运行失败,那么kubernetes需要重启它直到成功完成;

  2. 初始化容器必须按照定义的顺序执行,当且仅当前一个初始化容器成功之后,后面的一个初始化容器才能运行;

举个例子,我们最常见的es容器里面就有一个初始化容器,这个初始化容器的执行命令就是配置内核参数,因为es对某些内核参数要求设置比较大,所以直接通过初始化容器修改了内核参数。(容器与宿主机共享内核,所以修改的就是宿主内核)

pod的资源请求、限制如何定义?

pod的资源请求、资源限制可以直接在pod中定义,主要包括两块内容,limits,限制pod中容器能使用的最大cpu和内存,requests,pod中容器启动时申请的cpu和内存。

yaml
 resources:						#资源配额
      limits:					#限制最大资源,上限
        cpu: 2					#CPU限制,单位是code数
        memory: 2G				#内存最大限制
      requests:					#请求资源(最小,下限)
        cpu: 1					#CPU请求,单位是code数
        memory: 500G			#内存最小请求

pod的定义中有个command和args参数,这两个参数不会和docker镜像的entrypointc冲突吗?

不会。 在pod中定义的command参数用于指定容器的启动命令列表,如果不指定,则默认使用Dockerfile打包时的启动命令,args参数用于容器的启动命令需要的参数列表; 特别说明: kubernetes中的command、args其实是实现覆盖dockerfile中的ENTRYPOINT的功能的。当:

  1. 如果command和args均没有写,那么使用Dockerfile的配置;
  2. 如果command写了但args没写,那么Dockerfile默认的配置会被忽略,执行pod容器指定的command;
  3. 如果command没写但args写了,那么Dockerfile中的ENTRYPOINT的会被执行,使用当前args的参数;
  4. 如果command和args都写了,那么Dockerfile会被忽略,执行pod当前定义的command和args。

标签及标签选择器是什么,如何使用?

标签是键值对类型,标签可以附加到任何资源对象上,主要用于管理对象,查询和筛选。标签常被用于标签选择器的匹配度检查,从而完成资源筛选;一个资源可以定义一个或多个标签在其上面。

标签选择器,标签要与标签选择器结合在一起,标签选择器允许我们选择标记有特定标签的资源对象子集,如pod,并对这些特定标签的pod进行查询,删除等操作。 标签和标签选择器最重要的使用之一在于,在deployment中,在pod模板中定义pod的标签,然后在deployment定义标签选择器,这样就通过标签选择器来选择哪些pod是受其控制的,service也是通过标签选择器来关联哪些pod最后其服务后端pod。

service是如何与pod关联的?

答案:通过标签选择器。每一个由deployment创建的pod都带有标签,这样,service就可以定义标签选择器来关联哪些pod是作为其后端了,就是这样,service就与pod关联在一起了。

service的域名解析格式、pod的域名解析格式

service的DNS域名表示格式为<servicename>.<namespace>.svc.<clusterdomain>,servicename是service的名称,namespace是service所处的命名空间,clusterdomain是k8s集群设置的域名后缀,一般默认为 cluster.local,一般的,我们不会去改k8s集群设置的域名后缀,同时,当pod要链接的svc处于pod当前命名空间时,可以省略<namespace>以及后面的.svc不写,这样,就可以有下面三种方式来表示svc的域名:

bash
#查看k8s集群设置的域名后缀
grep -i clusterDomain /opt/kubernetes/config/kubelet-config.yml	#二进制安装的k8s集群,可以这样查看
grep -i clusterDomain /etc/kubernetes/kubelet.conf 	#kubeadm安装的k8s集群,各个节点的kubelet.conf文件中的字段clusterDomain
grep -i clusterDomain /var/lib/kubelet/config.yaml	#kubeadm安装的k8s集群,各个节点的config.yaml文件中的字段clusterDomain
kubectl  -n kube-system get cm coredns -oyaml		#coredns cm里面也可以看到
kubectl  exec -it busybox --  cat /etc/resolv.conf	#直接看pod里面的resolv.conf文件亦可

svc-nginx.default.svc.cluster.local	#完整的写法		
svc-nginx.default					#带命名空间写法,省略了后面的.svc.<clusterdomain>
svc-nginx							#如果pod与svc在同一个命名空间,可以将命名空间省略不写,换句话说链接的是当前命名空间的svc

#于是,svc域名+svc的端口,我们就可以在pod里面访问svc对应的应用了,如下
wget http://svc-deployment-nginx.default.svc.cluster.local:80		#完整的写法
wget http://svc-deployment-nginx.default:80							#带命名空间写法
wget http://svc-deployment-nginx:80									#如果pod与svc在同一个命名空间,可以将命名空间省略不写

pod的DNS域名格式为:<pod-ip>.<namespace>.pod.<clusterdomain> ,其中,pod-ip需要使用-将ip之间的点替换掉,namespace为pod所在的命名空间,clusterdomain是k8s集群设置的域名后缀,一般默认为 cluster.local ,如果没有改变k8s集群默认的域名后缀,则可以省略该后缀不写。除此之外,其他的均不可省略,这一点与svc域名有所不同。 演示如下:

对于deployment、daemonsets等创建的无状态的pod,还还可以通过<pod-ip>.<deployment-name>.<namespace>.svc.<clusterdomain> 这样的域名访问。(这点存疑,一直测试失败,不指定是书中写错了还是什么)

对于StatefulSet创建的pod,statefulset.spec.serviceName字段解释如下:

也就是说StatefulSet创建的pod,其pod的域名为:pod-specific-string.serviceName.default.svc.cluster.local,而pod-specific-string就是pod的名称。 例如:redis-sts-0.redis-svc.default.svc.cluster.local:6379,redis-sts-1.redis-svc.default.svc.cluster.local:6379,redis-sts-2.redis-svc.default.svc.cluster.local:6379,redis-sts-3.redis-svc.default.svc.cluster.local:6379,redis-sts-4.redis-svc.default.svc.cluster.local:6379,redis-sts-5.redis-svc.default.svc.cluster.local:6379,pod里面的后端应用程序就可以拿这串字符串去连接Redis集群了。

service的类型有哪几种

service的类型一般有4种,分别是:

  • ClusterIP:表示service仅供集群内部使用,默认值就是ClusterIP类型
  • NodePort:表示service可以对外访问应用,会在每个节点上暴露一个端口,这样外部浏览器访问地址为:任意节点的ip:NodePort就能连上service了
  • LoadBalancer:表示service对外访问应用,这种类型的service是公有云环境下的service,此模式需要外部云厂商的支持,需要有一个公网ip地址
  • ExternalName:这种类型的service会把集群外部的服务引入集群内部,这样集群内直接访问service就可以间接的使用集群外部服务了

一般情况下,service都是ClusterIP类型的,通过ingress接入的外部流量。

一个应用pod是如何发现service的,或者说,pod里面的容器用于是如何连接service的?

答:有两种方式,一种是通过环境变量,另一种是通过service的dns域名方式。 1、环境变量:当pod被创建之后,k8s系统会自动为容器注入集群内有效的service名称和端口号等信息为环境变量的形式,这样容器应用直接通过取环境变量值就能访问service了,如,每个pod都会自动注入了api-server的svc:curl http://${KUBERNETES_SERVICE_HOST}:{KUBERNETES_SERVICE_PORT} 2、DNS方式:使用dns域名解析的前提是k8s集群内有DNS域名解析服务器,默认k8s中会有一个CoreDNS作为k8s集群的默认DNS服务器提供域名解析服务器;service的DNS域名表示格式为<servicename>.<namespace>.svc.<clusterdomain>,servicename是service的名称,namespace是service所处的命名空间,clusterdomain是k8s集群设置的域名后缀,一般默认为 cluster.local ,这样容器应用直接通过service域名就能访问service了,如wget http://nginx-svc.default.svc.cluster.local:80,另外,service的port端口如果定义了名称,那么port也可以通过DNS进行解析,格式为:_<portname>._<protocol>.<servicename>.<namespace>.svc.<clusterdomain>

如何创建一个service代理外部的服务,或者换句话来说,在k8s集群内的应用如何访问外部的服务,如数据库服务,缓存服务等?

答:可以通过创建一个没有标签选择器的service来代理集群外部的服务。 1、创建service时不指定selector标签选择器,但需要指定service的port端口、端口的name、端口协议等,这样创建出来的service因为没有指定标签选择器就不会自动创建endpoint; 2、手动创建一个与service同名的endpoint,endpoint中定义外部服务的ip和端口,endpoint的名称一定要与service的名称一样,端口协议也要一样,端口的name也要与service的端口的name一样,不然endpoint不能与service进行关联。 完成以上两步,k8s会自动将service和同名的endpoint进行关联,这样,k8s集群内的应用服务直接访问这个service就可以相当于访问外部的服务了。

service、endpoint、kube-proxy三种的关系是什么?

*service*:在kubernetes中,service是一种为一组功能相同的pod提供单一不变的接入点的资源。当service被建立时,service的ip和端口不会改变,这样外部的客户端(也可以是集群内部的客户端)通过service的ip和端口来建立链接,这些链接会被路由到提供该服务的任意一个pod上。通过这样的方式,客户端不需要知道每个单独提供服务的pod地址,这样pod就可以在集群中随时被创建或销毁。 *endpoint*:service维护一个叫endpoint的资源列表,endpoint资源对象保存着service关联的pod的ip和端口。从表面上看,当pod消失,service会在endpoint列表中剔除pod,当有新的pod加入,service就会将pod ip加入endpoint列表;但是正在底层的逻辑是,endpoint的这种自动剔除、添加、更新pod的地址其实底层是由endpoint controller控制的,endpoint controller负责监听service和对应的pod副本的变化,如果监听到service被删除,则删除和该service同名的endpoint对象,如果监听到新的service被创建或者修改,则根据该service信息获取得相关pod列表,然后创建或更新service对应的endpoint对象,如果监听到pod事件,则更新它所对应的service的endpoint对象。 *kube-proxy*:kube-proxy运行在node节点上,在Node节点上实现pod网络代理,维护网络规则和四层负载均衡工作,kube-proxy会监听api-server中从而获取service和endpoint的变化情况,创建并维护路由规则以提供服务ip和负载均衡功能。简单理解此进程是Service的透明代理兼负载均衡器,其核心功能是将到某个Service的访问请求转发到后端的多个pod实例上。

kube-proxy有哪几种模式?

kube-proxy有3种模式,分别是userspace(用户空间)、iptables、ipvs,其中user namespace已经被废弃了这种就不在讲解了。 目前用的最多的就是iptables和ipvs这两种模式。

你们生产环境kube-proxy使用哪种模式,为什么?

生产环境中使用ipvs。因为ipvs性能比iptables高。 iptables本质上是Linux的一个高效的防火墙,提供数据包处理和过滤方面的能力,kube-proxy使用iptables这种模式的时候,其连接处理算法复杂度是O(n),其中的n随集群规模同步增长,换句话说,当k8s集群中的service数量很多,比如集群有1000个service,每个service后端又有多个Pod副本,那每个节点上的iptable规则将非常多,kube-proxy需要动态维护这些庞大的规则将带来严重的节点性能问题。

而ipvs是Linux内核功能中专门用于处理高性能负载均衡的功能,它使用更高效的数据结构,如hash表并支持索引,IPVS有一套优化过的 API,使用优化的查找算法,而不是简单的从列表中查找规则,ipvs还支持多种调度算法,如rr、wrr、lc、wlc,而iptables就只有一种随机平等的选择算法。其次,kube-proxy在IPVS模式下,连接处理算法复杂度是O(1)而不是O(n)。换句话说,多数情况下,连接处理效率是和集群规模大小无关。 iptables和ipvs ipvs与iptables相比较,其优势为: (1)ipvs为大型集群提供了更好的可扩展性和性能 (2)ipvs支持比iptables更复杂的负载均衡算法,如rr、wrr、lc、wlc,而iptables 就只有一种随机平等的选择算法。 (3)ipvs支持服务健康检查和链接重试等功能 (4)ipvs可以动态修改ipset集合

无头service和普通的service有什么区别,无头service使用场景是什么?

答:无头service没有cluster ip,在定义service时将 service.spec.ClusterIP:None,就表示创建的是无头service。 普通的service是用于为一组后端pod提供请求连接的负载均衡,让客户端能通过固定的service ip地址来访问pod,这类的pod是没有状态的,同时service还具有负载均衡和服务发现的功能。普通service跟我们平时使用的nginx反向代理很相识。 但是,试想这样一种情况,有6个redis pod ,它们相互之间要通信并要组成一个redis集群,不在需要所谓的service负载均衡,这时无头service就是派上用场了,无头service由于没有cluster ip,kube-proxy就不会处理它也就不会对它生成规则负载均衡,无头service直接绑定的是pod 的ip。无头service仍会有标签选择器,有标签选择器就会有endpoint资源。 使用场景:无头service一般用于有状态的应用场景,如Kaka集群、Redis集群等,这类pod之间需要相互通信相互组成集群,不在需要所谓的service负载均衡。

deployment怎么扩容或缩容?

答:直接修改pod副本数即可,可以通过下面的方式来修改pod副本数: 1、直接修改yaml文件的replicas字段数值,然后kubectl apply -f xxx.yaml来实现更新; 2、使用kubectl edit deployment xxx 修改replicas来实现在线更新; 3、使用kubectl scale --replicas=5 deployment/deployment-nginx命令来扩容缩容。

deployment的更新升级策略有哪些?

答:deployment的升级策略主要有两种。 1、Recreate 重建更新:这种更新策略会杀掉所有正在运行的pod,然后再重新创建的pod; 2、rollingUpdate 滚动更新:这种更新策略,deployment会以滚动更新的方式来逐个更新pod,同时通过设置滚动更新的两个参数maxUnavailable、maxSurge来控制更新的过程。

deployment的滚动更新策略有两个特别主要的参数,解释一下它们是什么意思?

答:deployment的滚动更新策略,rollingUpdate 策略,主要有两个参数,maxUnavailable、maxSurge。

  1. maxUnavailable:最大不可用数,maxUnavailable用于指定deployment在更新的过程中不可用状态的pod的最大数量,maxUnavailable的值可以是一个整数值,也可以是pod期望副本的百分比,如25%,计算时向下取整。
  2. maxSurge:最大激增数,maxSurge指定deployment在更新的过程中pod的总数量最大能超过pod副本数多少个,maxUnavailable的值可以是一个整数值,也可以是pod期望副本的百分比,如25%,计算时向上取整。

deployment更新的命令有哪些?

答:可以通过三种方式来实现更新deployment。

  1. 直接修改yaml文件的镜像版本,然后kubectl apply -f xxx.yaml来实现更新;
  2. 使用kubectl edit deployment xxx 实现在线更新;
  3. 使用kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1 命令来更新。

简述一下deployment的更新过程

deployment是通过控制replicaset来实现,由replicaset真正创建pod副本,每更新一次deployment,都会创建新的replicaset,下面来举例deployment的更新过程: 假设要升级一个nginx-deployment的版本镜像为nginx:1.9,deployment的定义滚动更新参数如下:

yaml
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;

deployment的回滚使用什么命令

在升级deployment时kubectl set image 命令加上 --record 参数可以记录具体的升级历史信息,使用kubectl rollout history deployment/deployment-nginx 命令来查看指定的deployment升级历史记录,如果需要回滚到某个指定的版本,可以使用kubectl rollout undo deployment/deployment-nginx --to-revision=2 命令来实现。

讲一下都有哪些存储卷,作用分别是什么?

作用常用场景
emptyDir用于存储临时数据的简单空目录一个pod中的多个容器需要共享彼此的数据,emptyDir的数据随着容器的消亡也会销毁
hostPath用于将目录从工作节点的文件系统挂载到pod中不常用,缺点是pod的调度不是固定的,当pod消失后deployment重新创建的pod可能无法访问之前节点的数据
configMap用于将非敏感的数据保存到键值对中,可作为环境变量、命令行参数或存储卷被pods挂载使用将应用程序的不敏感配置文件创建为configmap卷,在pod中挂载configmap卷,可实现热更新
secret主要用于存储和管理敏感数据,通过Volume挂载或环境变量方式访问将应用程序的账号密码等敏感信息通过secret卷的形式挂载到pod中使用,pod会自动解密Secret信息
downwardApi用于暴露pod元数据(如pod的名字)pod中的应用程序需要指定pod的name等元数据时,通过downwardApi卷的形式挂载给pod使用
projected特殊卷,用于将多种卷类型一次性挂载给pod使用将configMap、secret等卷一次性的挂载给pod使用
pvc存储卷声明通常会创建pvc表示对存储的申请,然后在pod中使用pvc
网络存储卷pod挂载网络存储卷,将数据持久化到后端存储常见的网络存储卷有nfs存储、glusterfs卷、ceph rbd存储卷

pv的访问模式有哪几种

pv的访问模式有3种,如下:

  • ReadWriteOnce:简写:RWO 表示,只仅允许单个节点以读写方式挂载;
  • ReadOnlyMany:简写:ROX 表示,可以被许多节点以只读方式挂载;
  • ReadWriteMany:简写:RWX 表示,可以被多个节点以读写方式挂载;

pv的回收策略有哪几种

主要有2中回收策略:retain 保留、delete 删除。

  • Retain:保留,该策略允许手动回收资源,当删除PVC时,PV仍然存在,PV被视为已释放,管理员可以手动回收卷。 Delete:删除,如果Volume插件支持,删除PVC时会同时删除PV,动态卷默认为Delete,目前支持Delete的存储后端包括AWS EBS,GCE PD,Azure Disk,OpenStack Cinder等。
  • Recycle:回收,如果Volume插件支持,Recycle策略会对卷执行rm -rf清理该PV,并使其可用于下一个新的PVC,但是本策略将来会被弃用,目前只有NFS和HostPath支持该策略。(这种策略已经被废弃,不用记)

在pv的生命周期中,一般有几种状态

pv一共有4中状态,分别是: 创建pv后,pv的的状态有以下4种:Available(可用)、Bound(已绑定)、Released(已释放)、Failed(失败)

  • Available:表示pv已经创建正常,处于可用状态;
  • Bound:表示pv已经被某个pvc绑定,注意,一个pv一旦被某个pvc绑定,那么该pvc就独占该pv,其他pvc不能再与该pv绑定;
  • Released:表示pvc被删除了,pv状态就会变成已释放;
  • Failed:表示pv的自动回收失败;

存储类的资源回收策略:

主要有2中回收策略,delete 删除,默认就是delete策略、retain 保留。

  • Retain:保留,该策略允许手动回收资源,当删除PVC时,PV仍然存在,PV被视为已释放,管理员可以手动回收卷。
  • Delete:删除,如果Volume插件支持,删除PVC时会同时删除PV,动态卷默认为Delete,目前支持Delete的存储后端包括AWS EBS,GCE PD,Azure Disk,OpenStack Cinder等。

注意:使用存储类动态创建的pv默认继承存储类的回收策略,当然当pv创建后你也可以手动修改pv的回收策略。

怎么使一个node脱离集群调度,比如要停机维护单又不能影响业务应用

要使一个节点脱离集群调度可以使用kubectl cordon <node-name> 命令使节点不可调度,该命令其实背后原理就是给节点打上node-status.kubernets.io/unschedulable污点,这样新的pod如果没有容忍将不会调度到该节点,但是已经存在于该节点的pod仍然可以继续在该节点上运行不受影响,除非pod消忙了被重新调度了。如果需要恢复节点重新调度,可以使用kubectl uncordon <node-name> 命令恢复节点可调度。 如果节点是要停机维护,则可以对节点上的pod 进行驱逐:使用kubectl drain <node-name>命令用于将节点上的pod驱逐出去,以便对节点进行维护。 kubectl drain 命令的语法如下:

bash
kubectl drain <node-name>

--ignore-daemonsets 参数用于忽略由DaemonSet控制器管理的pods,不加该参数会报错; --delete-local-data 参数用于在节点上删除所有本地数据,包括PersistentVolume和PersistentVolumeClaim等资源; --force 参数强制删除pod,默认删除的是ReplicationController, ReplicaSet, Job, DaemonSet 或者StatefulSet创建的pod,如果有静态pod,则需要设置强制执行删除的参数--force。 这个命令会将节点上所有的pods驱逐出去,包括由DaemonSet控制器管理的pods。但是需要注意的是, kubectl drain 命令会将节点上所有的pods驱逐出去,包括由DaemonSet控制器管理的pods,由于ds创建的pod会具有容忍,所以又会马上在正在清空的节点上启动新的pod,我们可以使用 --ignore-daemonsets 参数来忽略由DaemonSet控制器管理的pods。

kubectl drain 命令背后原理其实还是首先将指定的节点标记为不可调,从而阻止新 pod 分配到节点上(实质上是 kubectl cordon),然后删除pod。

综上所述,要停机维护:

bash
1、kubectl cordon node01								#设置节点不可调度
2、kubectl drain node01 --ignore-daemonsets  --force	#驱逐pod
3、kubectl uncordon node01  							#恢复节点调度

pv存储空间不足怎么扩容?

一般的,我们会使用动态分配存储资源,在创建storageclass时指定参数 allowVolumeExpansion:true,表示允许用户通过修改pvc申请的存储空间自动完成pv的扩容,当增大pvc的存储空间时,不会重新创建一个pv,而是扩容其绑定的后端pv。这样就能完成扩容了。但是allowVolumeExpansion这个特性只支持扩容空间不支持减少空间。

k8s生产中遇到什么特别影响深刻的问题吗,问题排查解决思路是怎么样的?

(此问题被问到的概率高达90%,所以可以自己准备几个自己在生产环境中遇到的问题进行讲解)

前端的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集群了,必须要先进行数据备份才能进行其他方法的处理。

etcd集群节点可以设置为偶数个吗,为什么要设置为奇数个呢?

不能,也不建议这么设置。 etcd采用了Raft一致性算法来确保数据的一致性和高可用性。根据Raft算法的要求,为了确保算法的正确性和容错性,集群一般包含2n+1个节点,所以进行Leader选举和数据复制时,节点数必须是奇数个。 奇数个节点与配对的偶数个节点(如3个节点和4个节点)相比,容错能力相同,但可以少一个节点;其次,偶数个节点的集群在选举过程中由于等额选票的存在,有较大概率触发下一轮选举,从而增加了不可用的风险。因此,综合考虑性能和容错能力,etcd官方文档推荐的etcd集群大小是3, 5, 7。同时需要注意的是,虽然增加节点可以提高读的吞吐和提高集群的可用性,但节点数越多可能会导致写操作的吞吐降低。

etcd官方推荐3、5、7个节点,虽然raft算法也是半数以上投票才能有 leader,但奇数只是推荐,其实偶数也是可以的。如 2、4、8个节点。下面分情况说明:

  • 1 个节点:就是单实例,没有集群概念,不做讨论
  • 2 个节点:是集群,但没人会这么配,这里说点废话:双节点的etcd能启动,启动时也能有主,可以正常提供服务,但是一台挂掉之后,就选不出主了,因为他只能拿到1票,剩下的那台也无法提供服务,也就是双节点无容错能力,不要使用。
  • 3 节点:标准的3 节点etcd 集群只能容忍1台机器宕机,挂掉 1 台此时等于2个节点的情况,如果再挂 1 台,就和 2节点的情形一致了,一直选,一直增加任期,但就是选不出来,服务也就不可用了
  • 4 节点:最大容忍1台服务器宕机
  • 5 节点:最大容忍2台服务器宕机
  • 6 节点:最大容忍2台服务器宕机
  • 7和8个节点,最大容忍3台服务器宕机
  • 以此类推,9和10个节点,最大容忍4台服务器宕机

总结以上可以得出结论:偶数节点虽然多了一台机器,但是容错能力是一样的,也就是说,虽然可以设置偶数节点,但没增加什么容错能力,还浪费了一台机器。同时etcd 是通过复制数据给所有节点来达到一致性,因此偶数集群多出一台机器既增加不了性能,反而还会拉低写入速度。

你们生产环境etcd节点一般是几个节点?

我们使用的3节点的etcd集群,3节点etcd集群允许存在1台机器宕机,如果此时两台etcd节点宕机,那此时剩余的1台节点由于无法进行选举,所以整个etcd集群服务就不可用了,同理5个节点则可以最大容忍2个节点不可用,7节点可以容忍3个节点不可用。 目前etcd官方推荐etcd集群节点为3节点、5节点、7个节点,3节点可以支撑小规模的k8s集群,5到7节点可以支持中大型规模的k8s集群。

etcd节点是越多越好吗?

不是,etcd 集群是一个 Raft Group,没有 shared。所以它的极限有两部分,一是单机的容量限制,内存和磁盘;二是网络开销,每次 Raft 操作需要所有节点参与,每一次写操作需要集群中大多数节点将日志落盘成功后,Leader 节点才能修改内部状态机,并将结果返回给客户端。因此节点越多性能越低,并且出错的概率会直线上升,并且是呈现线性的性能下降,所以扩展很多 etcd 节点是没有意义的,其次,如果etcd集群超过7个达到十几个几十个,那么,对运维来说也是一个不小的压力了,并且集群的配置什么的也会更加的复杂,而不是简单易用了。因此,etcd集群的数量一般是 3、5、7, 3 个是最低标准,7个已经是最高了。

etcd集群节点之间是怎么同步数据的?

Raft 算法数据同步机制

  1. 领导者选举(Leader Election)
    • 集群启动时,节点随机超时后发起投票竞选 Leader
    • 获得半数以上节点投票的节点成为 Leader(如 3 节点集群需 2 票)
    • Leader 独占写入权限,负责同步数据到其他节点
  2. 日志复制(Log Replication)
    • 步骤 1:客户端向 Leader 写入数据(如 key=foo, value=bar
    • 步骤 2:Leader 将操作包装为 Log Entry,持久化到本地日志
    • 步骤 3:Leader 发送 AppendEntries RPC请求给所有 Follower
    • 步骤 4:Follower 校验一致性后,持久化日志并返回成功
    • 步骤 5:Leader 收到半数以上节点成功响应后提交日志
    • 步骤 6:Leader 更新 CommitIndex,通知 Follower 提交日志
  3. 状态机应用(State Machine Apply)
    • 所有节点按日志顺序将已提交的 Log Entry 应用到状态机(即 etcd 的键值存储)
    • 保障强一致性:所有节点最终应用相同日志序列 → 数据完全一致

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所在的物理机网卡上。

一个用户请求流量是如何进入k8s集群内部的?

以一个内网的k8s集群为例: 1、用户访问域名,域名必要对应一个服务器ip地址,此时电脑开始解析域名,解析的流程可能是先检查浏览器是否缓存有这个域名的解析记录、检查window的host文件、最后是请求windows网络配置的dns服务器; 2、域名被解析到内网LB负载均衡服务器,LB负载均衡服务器上使用nginx定义了一组后端负载服务器ip端口,而这组后端服务器其实就是k8s的节点; 3、这组k8s节点是ingress-nginx-controller的暴露的端口,所以此时流量交给ingress-nginx-controller处理; 4、k8s上定义了一组ingress规则,规则里定义了对应的后端service,当用户流量来到ingress后会进行规则匹配并将交给后端service处理; 5、service会将流量分发给对应的endpoint,而endpoint对应这一组pod ip端口,所以此时,用户流量已经交给了pod中的容器处理了。 上面就是用户请求流量进入k8s集群内部的大致流程。

用户访问我们的k8s集群里面的应用网站,出现500报错,你是如何排查这种问题的?

1、按F12 打开开发者模式,切换到网络栏,按F5刷新一下页面,看哪个请求地址是红色的,红色就是访问有问题的,看地址,响应值、接口url等信息; 2、得到了接口,那么应该很容易知道是哪个后端服务出现问题,此时直接去查看日志即可。如果有elk,直接登录Kibana查看日志即可,如果没有,则直接去服务器上查看应用的日志即可。

k8s后端存储使用的是什么?

后端存储方面,我们不同的项目使用的方案不一样,这可能是早期系统架构决定的,现在也是一直沿用,主要就是包含3种存储方案:

  1. 第一种存储方案是,hostPath和local-path-provisioner,先说hostPath:pod中直接使用hostPath卷来挂载数据,然后pod中要节点选择器固定调度到指定的节点,我们一般采用这种方式来实现pod直接挂载服务器数据或者pod日志落盘到服务器磁盘;但是hostPath卷属于静态卷,所以我们还使用了local-path-provisioner来动态供给localPath,local-path-provisioner能够让Kubernetes的本地存储支持动态pv,当使用local-path-provisioner的pod被调度时,scheduler调度器和pv控制器会同时进行控制,然后在pod所在节点上创建对应的本地存储目录,当pod被重新调度后,因为pod所对应的pv存在节点选择器,所以pod仍然能够调度到之前的节点上,从而继续使用或读取之前的数据。
  2. 第二中存储方案是glusterfs:我们使用glusterfs作为后端存储,先在服务器上大磁盘单独划分数据分区,创建目录,然后创建一个glusterfs文件系统(glusterfs没有选举一说,可以任意数量扩展),然后在glusterfs文件系统上创建卷,然后在k8s创建存储类,这样就实现了静态供给pv,如果需要动态供给pv,还需要使用heketi软件。有时候我们也会把glusterfs中的卷直接挂载到服务器上使用。
  3. 最后一种存储方案是cephfs:ceph官网上推荐在k8s集群中使用rook-ceph,我们使用的就是rook-ceph,rook-ceph我们使用的helm安装的,创建完成ceph集群之后再创建cephfs,然后创建存储类进行动态pv供给。

你们的服务发布怎么做的?或者你们的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的镜像,这样就实现了自动化部署。

拿到一台新的服务器如何优化,如何进行加固安全

  • 禁用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

如何进行k8s的安全加固

  1. API Server 加固

    • 禁用匿名访问:确保 --anonymous-auth=false
    • 启用 TLS 双向认证:强制客户端证书验证,禁用 HTTP 端口(--insecure-port=0)。
    • 集成企业身份源:通过 OIDC/LDAP/AD 统一管理用户认证,避免静态 kubeconfig 分发。
  2. RBAC 最小权限原则

    • 限制 ClusterRoleBinding:定期审计并清理绑定了 cluster-admin 的账户。
    • 命名空间隔离:为不同团队创建独立命名空间,通过 RoleBinding 而非 ClusterRoleBinding 授权。
    • 服务账户权限控制
      • 为每个应用创建专属 ServiceAccount,设置 automountServiceAccountToken: false 避免默认挂载。
      • 示例:仅允许服务账户读取 ConfigMap:
        yaml
        apiVersion: rbac.authorization.k8s.io/v1  
        kind: Role  
        rules:  
        - apiGroups: [""]  
          resources: ["configmaps"]  
          verbs: ["get"]
  3. 零信任网络模型

    • 默认拒绝所有流量:在每个命名空间部署 default-deny-all NetworkPolicy。
    • 精细化白名单:仅开放必要通信(如前端 Pod → 数据库的 5432 端口):
      yaml
      apiVersion: networking.k8s.io/v1  
      kind: NetworkPolicy  
      spec:  
        podSelector: {matchLabels: {role: db}}  
        ingress:  
        - from:  
          - podSelector: {matchLabels: {app: web}}  
          ports: [{protocol: TCP, port: 5432}]
    • 依赖 CNI 插件:确保集群使用 Calico/Cilium 等支持 NetworkPolicy 的 CNI。
  4. 节点级防火墙

    • 控制平面仅开放 6443(API Server)、2379-2380(etcd)端口。
    • Worker 节点限制 kubelet API(10250/TCP)仅内网访问。
  5. Pod 安全策略

    • 启用 Pod Security Admission (PSA):替代已废弃的 PSP,通过命名空间标签强制执行策略:
      bash
      kubectl label ns prod pod-security.kubernetes.io/enforce=restricted
    • 安全上下文配置
      yaml
      securityContext:  
        runAsNonRoot: true  
        readOnlyRootFilesystem: true  
        capabilities:  
          drop: ["ALL"]
  6. 内核级防护

    • 启用 AppArmor/seccomp 限制容器系统调用。
    • 使用 gVisor/Kata 等沙箱容器运行时隔离高危工作负载。
  7. 镜像漏洞扫描

    • CI/CD 集成 Trivy/Clair,阻断含高危 CVE 的镜像入集群。
  8. 镜像签名验证

    • 使用 cosign 签名镜像,部署时校验完整性:
      bash
      cosign verify --key public-key.pem your-registry/image:tag
  9. 私有仓库+白名单:限制仅从受信仓库拉取镜像。

  10. etcd 静态加密

  • 启用 AES-CBC 加密 Secrets:
    yaml
    apiVersion: apiserver.config.k8s.io/v1  
    kind: EncryptionConfiguration  
    resources:  
      - resources: ["secrets"]  
        providers: [{aescbc: {keys: [{name: key1, secret: <BASE64_KEY>}]}}]
  1. 动态密钥管理
  • 使用 Vault+CSI 驱动注入敏感数据,避免硬编码。
  1. 审计日志全覆盖
  • 记录所有 API 请求,关联用户身份和操作时间:
    yaml
    apiVersion: audit.k8s.io/v1  
    kind: Policy  
    rules: [{level: Metadata, verbs: ["*"]}]
  • 日志集成 SIEM 系统(如 ELK/Splunk)实时告警。
  1. 运行时入侵检测
  • 部署 Falco 监控异常行为(如宿主机文件访问或特权提升)。