图片

 

1. 问题说明

  • 针对K8S 1.13 后的版本,提供 K8S 通用的优化要点。常见的使用经验、问题处理分享。

  • 针对 K8S 组件,一些典型参数以及常见的一些问题和处理方法。

2. 问题处理

先看一下Kubernetes主要架构组件图:

 

2.1  etcd

Kubernetes 中的大部分概念如 Pod、ReplicaSet、Deployment、Service、Node 等都可以被 看作一种资源对象,几乎所有资源对象都可以通过 Kubernetes 提供的 kubectl 工具(或者 API 编程调用)执行增、删、改、查等操作并将其保存在 etcd 中持久化存储。从这个角度 来看,Kubernetes 其实是一个高度自动化的资源控制系统,它通过跟踪对比 etcd 库里保存 的“资源期望状态”与当前环境中的“实际资源状态”的差异来实现自动控制和自动纠错 的高级功能。

  • etcd 保存整个集群的状态信息,类比相当于k8s的数据库。

  • 高可用方面,etcd 需要以集群的方式进行部署,以实现 etcd 数据存储的冗余、备份与高可用。etcd 集群需要奇数形式,至少由三台组成,考虑到同步的开销,一般不推荐超过7个etcd节点。

  • 存储,由于etcd 的特性,应考虑使用高性能的存储设备,如 SSD 磁盘,在Kubernetes 集群较大时,必须使用PICe SSD 来提升IO性能减少读写延迟。

  • etcd 性能有两个关键因素:延迟(latency):延迟是完成操作的时间;吞吐量(throughput):吞吐量是在某个时间期间之内完成操作的总数量。

 

压缩空间

# 获取当前版本号
$ rev=$(ETCDCTL_API=3 etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9]*')
# 压缩所有旧版本
$ ETCDCTL_API=3 etcdctl compact $rev
# 去碎片化
$ ETCDCTL_API=3 etcdctl defrag

时间参数

etcd 的心跳间隔默认是 100 毫秒。Etcd 的选举超时时间默认是 1000 毫秒(即从节点等待多久没收到主节点的心跳就尝试去竞选领导者)。在网络环境吞吐量大情况下,如同步吞吐在100MB左右。可适当调整:

--heartbeat-interval=300

--election-timeout=5000

 

问题一:

etcd集群状态:不同节点上有时候结果还不一样。有时候member1 不健康,有时候 member3 不健康 —— unhealthy。

原因:

etcd 对系统时间很敏感,当集群节点出现时间不一致时则出现问题。

解决:

统一 NTP 服务器校准。集群各节点使用如:

  1. timedatectl set-timezone Asia/Shanghai 确保时区。

  2. ntpdate -u $NTP-SERVER 进行时间同步。

  3. 使用 chronyd 进行自动时间同步。

问题二:

  1. 集群加入主机后,主机列表没有新增。

  2. 接入主机命令执行之后,一般会报错 Node “xxx" not found。(原因是添加被etcd 拒绝)

  3. 主机状态 未知。

  4. Kubelet 状态有 database space exceeded报错。

原因:

  1. 默认情况下,ETCD使用的空间上限是 2 GB。一般支持最大 8 GB。

  2. 默认 etcd 的 snap 频率是 10万次提交 进行一次 Snapshot。Kubernetes 数据存储在 etcd,当 k8s load 较高时,Snap 频率会提高。

--snapshot-count '100000'number of committed transactions to trigger a snapshot to disk.
--max-snapshotsmaximum number of snapshot files to retain (0 is unlimited).

解决:

# ETCDCTL_API=3 etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt --write-out=table endpoint status

 

查看 节点数据存储量,通常数据量应该很小。几百K到几十M不等。出问题一般会超过2G。

  1. 获取 revision,然后压缩compact

# ETCDCTL_API=3 etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt endpoint status --write-out="json"
# ETCDCTL_API=3 etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt compact 4974364

 

 2.defrag 整理空间

# ETCDCTL_API=3 etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt defrag

 

   3.修改默认空间

   etcd 的硬盘存储上限(默认是 2GB),当 etcd 数据量超过默认 quota 值后便不再接受写请求,可以通过设置 --quota-backend-bytes 参数来增加存储大小。

2.2  APIServer

APISERVER 是整个系统的对外接口,提供一套 RESTful 的 Kubernetes API,供客户端和其它组件调用。

  • 提供对k8s资源操作的唯一入口,并提供认证授权,访问控制,API注册与发现等机制。

  • 只有 apiserver 才能操作 etcd,其他组件通过 APISERVER 进行数据的查询和集群状态更新。

  • Kubernetes 所有的数据变动都会经过 APISERVER。

组件参数功能描述使用建议
–enable-garbage-collector“–enable-garbage-collector"设置了是否开启garbage   collector,该选项必须和kube-controller-manager的”–enable-garbage-collector"参数设置一致,该参数使用true作为默认值。Kubernetes的garbage   collector是用来删除曾经有owner,但是现在没有owner的对象的。举个例子,一个ReplicaSet是一组Pod的owner,当这个ReplicaSet被删除后,Kubernetes的garbage   collector负责处理掉这个ReplicaSet控制下的Pod。如果这些Pod在业务中需要被保留,则"–enable-garbage-collector"应被设置为false,其他情况则应设置为true。
–enable-logs-handler"–enable-logs-handler"设置了是否为apiserver安装logs   handler,该参数使用true作为默认值。如果不处理apiserver的log是有必要的,则将该参数设置为false。其他情况下,设置为true或者使用默认值。
–max-mutating-requests-inflight“–max-mutating-requests-inflight"设置了apiserver在一定时间内并行处理mutating   request的最大数量,当数量超过这个数值时,超过部分的request会被拒绝处理。当设置为0时,表示apiserver没有单位时间内请求数量的限制,该参数使用200作为默认值。该参数旨在限制单位时间内处理mutating requests的最大数量。因为mutating   request通常需要比non-mutating request处理更多的业务逻辑,所以mutating   request需要消耗更多内存资源,”–max-mutating-requests-inflight"的值也要比"–max-requests-inflight"的值更低。性能测试在决定特定业务场景中"–max-requests-inflight"和"–max-mutating-requests-inflight"两个值的比例时是有必要的。
–max-requests-inflight"–max-requests-inflight"设置了apiserver在一定时间内并行处理non-mutating   request的最大数量,当数量超过这个数值时,超过部分的request会被拒绝处理。当设置为0时,表示apiserver没有单位时间内请求数量的限制,该参数使用400作为默认值。该参数可以很好地控制kube-apiserver的内存消耗,而API   server在处理大量request时对CPU并没有很高要求。当该参数值过低时,系统会发生大量request-limit-exceed错误。当该参数值过高时,kube-apiserver会因试图并行处理过多request而内存不够(OOM)发生故障。总体而言,25~30个Pod并行处理15个请求是足够的。
–target-ram-mb"–target-ram-mb"设置了apiserver的内存限制。实践中,32C120G可以运行2000个Node和60000个Pod,相当于60M/Node和30Pod/Node。通常而言,每20~30个Pod使用60M是比较合理的。
  • 上述参数根据实际生产环境调整。

  • 在 v1.10 以前的版本中可能出现 kubelet 连接 apiserver 超时之后不会主动 reset 掉连接进行重试,除非主动重启 kubelet 或者等待十多分钟后其进行重试。v1.14 及以上版本修复此问题。

  • Kube-APIServer模块的代码审核也是最严格的,一般不会有什么较大的改动,一旦有改动,都是需要长久的讨论才能决定怎么改,毕竟API-Server的性能,直接决定了Kubernetes整体的性能。

问题一:

Kubernetes 的各个组件与 Master 之间可以通过 kube-apiserver 的非安全端口 http://<kube-apiserver-ip>:8080 进行访问。但如果 API Server 需要对外提供服务,或者集群中的某些容器也需要访问 API Server 以获取集群中的某些信息,存在安全隐患。

解决方案:

安全的做法是启用 HTTPS 安全机制。设置 kube-apiserver 的启动参数“--token-auth-file”,或启用RBAC认证,使用文件提供安全认证:

--secure-port=6443

--insecure-port=0

--basic-auth-file=/etc/kubernetes/basic_auth_file

 

8080 端口在后续的版本中将被弃用。

 

问题二:

大量 POD 重建导致 events 增多,api server 压力较大,集群访问受到影响。

解决方案 1:

 kubectl delete events --all -n tenant1  

  • 删除该租户下的所有 events。

解决方案 2:

 kubectl get pod -o wide --all-namespaces | awk '$5>0'

  • 查看租户下不断重启的 容器组,并处理。

 

2.3 kube-scheduler

kube-scheduler,负责资源调度(Pod 调度)的进程,相当 于公交公司的“调度室”。实现 Pod 的调度, 整个调度过程通过执行一系列复杂的算法,最终为每个 Pod 都计算出一个最佳的目标节点。

  • 通过 watch 到api-server 事件变化,获取需要创建并且未进行调度的 pod 进行调度,如果调度成功会把信息通过 api-server记录到etcd中。

  • daemonset 不经过 scheduler 调用,直接在 node 启动。

  • 对某些 Node 定义特定的 Label,并且在 Pod 定义文件中使用 NodeSelector 这 种标签调度策略,kube-scheduler 进程可以实现 Pod 定向调度的特性。

  • Pod/Node Affinity & Anti-affinity

  • Taint & Toleration

  • Priority & Preemption

  • Pod Disruption Budget

 

systemd 启动参数:

  • --kubeconfig:设置与 API Server 连接的相关配置,可以与 kube-controller-manager 使用的 kubeconfig 文件相同。

  • --logtostderr:设置为 false 表示将日志写入文件,不写入 stderr。

  • --log-dir:日志目录。

  • --v:日志级别。

问题一:

scheduler 默认监听 0.0.0.0,存在安全隐患。

解决:

启用 HTTPS,指定监听地址为内网或本机127.0.0.1。

--bind-address ip ,在 HTTPS 安全端口提供服务时监听的 IP 地址,默认值为 0.0.0.0。

--secure-port int,设置 HTTPS 安全模式的监听端口号,设置为 0 表示不启用 HTTPS,默认值为 10257。

 

2.4 kube-controller-manager

kube-manager,是 Kubernetes 中各种操作系统的管理者,是集群内部的管理控制中心,也是 Kubernetes 自动化功能的核心。作为集群内部的管理控制中心,负责集群内资源的管理。目的在于及时发现故障并自动化修复,确保集群始终处于预期的工作状况。

  • 负责集群内的Node、Pod 副本、服务端点(Endpoint)、命名空间(Namespace)、服务账号(ServiceAccount)、资源定额(ResourceQuota)等的管理。

  • 当某个Node意外挂掉时,kube-controller-manager 会及时发现此故障并执行自动化修复流程,确保集群始终处于预期的工作状态。

 

通过设置 kube-controller-manager 组件的启动参数--leader-elect=true,即可实现 kube-controller-manager 组件的高可用性。

设置参数说明
–feature-gates=ExperimentalCriticalPodAnnotation=truefalse关于ExperimentalCriticalPodAnnotation=true这个参数,kube-controller-manager中并没有在使用。如果确实想用CriticalPod,为保险起见,最好还是设置上。
–enable-garbage-collectortrue设置为true表示启动垃圾回收器,必须与kube-apiserver的该参数设置为相同的值。
–cluster-cidr" "表示Pod的IP范围,用于有云提供商的公有云环境下。它的用途是桥接来自集群外的流量。
–service-cluster-ip-range" "表示Service的IP范围,用于有云提供商的公有云环境下。目前kube-manager-controller正在重构过程中,将其与云提供商有关部分进行解耦,被解耦的部分重组成一个新的组件,组件名称为cloud-controller-manager,整个重组过程预计在kubernetes-release-1.9版本完成。
–terminated-pod-gc-threshold12500设置可保存的终止Pod的数量,如果超过该数量,垃圾回收器开始删除操作。设置为不大于0的值表示不启用该功能。

问题一:

节点宕机,保障业务不受影响。

解决:

  1. Pod 应该多实例,分布不同主机。避免某个节点宕机带来的单点影响。

  2. 当计算节点出现问题时,kube-controller 默认需要5分钟后才会驱逐计算节点上的容器并重建。这个时间不建议减少太多,避免因为网络抖动带来 pod 频繁调度和误杀问题。

  3. kube-controller-manager,其中「--unhealthy-zone-threshold」参数可以设置在一个 zone 中有多少比例的 Node 失效时将被判断为 unhealthy 不进行节点驱逐,默认值为 0.55。

  4. 如果对时间敏感度有强烈需求,以下脚本供参考。

#!/bin/sh

KUBECTL="/usr/bin/kubectl"

# Get only nodes which are not drained yet
NOT_READY_NODES=$($KUBECTL get nodes | grep -P 'NotReady(?!,SchedulingDisabled)' | awk '{print $1}' | xargs echo)
# Get only nodes which are still drained
READY_NODES=$($KUBECTL get nodes | grep '\sReady,SchedulingDisabled' | awk '{print $1}' | xargs echo)

echo "Unready nodes that are undrained: $NOT_READY_NODES"
echo "Ready nodes: $READY_NODES"


for node in $NOT_READY_NODES; do
echo "Node $node not drained yet, draining..."
$KUBECTL drain --delete-local-data --ignore-daemonsets --force $node
echo "Done"
done;

for node in $READY_NODES; do
echo "Node $node still drained, uncordoning..."
$KUBECTL uncordon $node
echo "Done"
done;
  • 将上方脚本加入crontab中,设置为1分钟执行一次。

 

问题二:

Kubernetes 的关键组件除了 API Server 、 scheduler、controller-manager 是运行在 master 节点上的,其余都在普通节点上。当集群内普通节点资源被消耗完,关键组件可能没有足够的资源来运行(如网络组件),导致整个集群无法正常工作。

解决:

将普通 Pod (非关键组件)从节点上驱逐来释放占有的资源。但是这样做,普通 Pod 与关键组件 Pod 会一起进入调度器,普通 Pod 可能会先一步被调度到机器上,重新占有释放的资源。这样事情就会陷入一个死循环。可以将被选中的节点(用于释放资源),标记一个暂时的污点(taint “CriticalAddonsOnly” ),这样不能容忍污点的 Pod 将不能调度到有污点的节点上。

  1. 组件必须运行在 kube-system 命名空间下;

  2. 1.14 前版本,可以 scheduler.alpha.kubernetes.io/critical-pod 的注解(annotation )设置为空字符串;

  3. PodSpec’s 的容忍(tolerations) 设置为:[{"key":"CriticalAddonsOnly","operator":"Exists"}];

  4. 需要将 priorityClassName 设置为 system-cluster-critical 或 system-node-critical ,后者是整个群集的最高级别。

 

 

2.5 kubelet

kubelet 负责 Pod 对应的容器的创建、启停等任务,同时与 Master 密切协作,实现集群管理的基本功能。

  • kubelet 负责容器声明周期。

  • kubelet 服务依赖于 Docker 服务。如果 docker 进程异常或启动失败,则 kubelet 服务也随之出错。

  • 建议 kubelet 以主机服务进程启动运行,如 systemd。

  • kubelet 默认采用向 Master 自动注册本 Node 的机制。定时向 Master 汇报自身的节点情况,包括操作系统、Docker 版本、机器的 CPU 和内存情况,以 及当前有哪些 Pod 在运行等。

组件参数默认值说明
--experimental-allocatable-ignore-evictionfalse(1)设置为true时,当计算节点可分配资源总量时,–eviction-hard参数将被忽略。此时,节点可分配资源总量 [Allocatable] = [Node   Capacity] - [Kube-Reserved] - [System-Reserved]。(2)设置为false时,当计算节点可分配资源总量时,在原先的基础上还要减去–eviction-hard参数所设置的值。此时,节点可分配资源总量   [Allocatable] = [Node Capacity] - [Kube-Reserved] - [System-Reserved] -   [Hard-Eviction-Threshold]。(&)[Node Capacity] 表示节点的实际资源量;[Kube-Reserved]   表示为Kubernetes系统组件预留的资源量,可通过–kube-reserved参数设置;[System-Reserved]   表示为系统预留的资源量,可通过–system-reserved参数设置。
--cgroup-root’ ’ , 表示将使用容器运行时的默认值为pods设置的root cgroup。
--experimental-mounter-path’ ’,空(1)设置为空时,表示使用默认的挂载命令。(2)设置为绝对路径时,表示使用指定路径上的挂载命令。同时,其会将--expermental-check-node-capabilities-before-mount参数重置为false,因此,当执行挂载操作时,并不会去检查节点是否具备挂载此种类型数据卷的必要组件,如二进制可执行文件。
–experimental-check-node-capabilities-before-mountfalse(1) 设置为false时,在执行挂载操作前,不会去检查本节点是否具有挂载某种类型数据卷的必要组件,如二进制可执行文件等等。当挂载操作失败之后,会重新再执行此挂载操作。(2)设置为true时,在执行挂载操作前,会根据要挂载的数据卷类型,确定数据卷插件,并检查本节点具有此数据卷插件的必要的依赖性组件。当检查结果是不具备时,此挂载操作立即失败,对于此挂载操作,不会进行重试。(&)--expermental-mounter-path参数取值,会影响本参数的最终取值。
–enable-debugging-handlerstrue设置为true表示提供远程访问本节点容器的日志、进入容器执行命令等相关的REST服务。在安全性方面,还需后序调研。
–eviction-hardmemory.available<100Mi,nodefs.available<10%,nodes.inodesFree<5%表示触发Pod Eviction操作的一组硬门限设置。当节点资源达至这个下限时,出于节点稳定性的考虑,kubelet会立刻执行Pod Eviction操作,释放资源,维护节点的稳定性。
–feature-gates (1)ExperimentalCriticalPodAnnotation   默认值为false;设置为true时,表示当CriticalPod被调度器调度到本节点后,如果因为节点资源不足而无法运行时,kubelet会启用重调度机制,按qos等级删除一部分pod,释放资源来使CriticalPod可以在本节点上运行。另外,CriticalPod不会被kubelet的Pod   Eviction机制驱逐出去。

问题一:

kubelet 容器启动出错。

解决:

kubelet Pod status,获悉容器相关状态:

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:网络插件还没有完全启动

 

问题二:

kubelet 故障引起的常见结果。

解决:

kubelet 故障对应用影响:

问题描述应用
kubelet 出故障无法在该节点启动新的容器组    crashing kubelet cannot start new   pods on the node [不会]不影响
kubelet 出故障kubelet might delete the pods or not影响
kubelet 出故障节点被标记为不健康   node marked unhealthy不影响
kubelet 出故障可能导致 RC 在其他节点启动新的容器组(Reschedule)   replication controllers start new pods elsewhere不一定:  对可扩展的应用来说,没影响;  可能会影响到 无法扩展的一些应用

问题三:

重启kubelet之后,会导致节点上面的一些Pod也发生重启,并且会留下一些状态为MatchNodeSelector的僵尸Pod。

原因:

正常情况下,如果我们在某一节点上重启了kubelet,那么上面的Pod(容器)是不会重启的。这是kubelet 现存 BUG,但触发需要一定条件。

  1. 某些Pod是通过NodeSelector的label给调度到某些专有label的节点上的。

  2. 当kubelet重启并且与apiServer的连接非常不稳定时,会有几率出现这些Pod被重启。

解决:

当重要业务节点需要重启 kubelet 时,为保证上面运行的 pod 业务。可以修改 kubelet 配置的 --node-labels 参数

 

  • 添加--node-labels 后,再执行 kubelet 重启的操作。

2.6 kube-proxy

kube-proxy,实现 Kubernetes Service 的通信与负载均衡机制的重要组件。

  • 负责把对 Service 的请求转发到后端的某个 Pod 实例上,自动建立每个 Service 到对应 Pod 的请求转发路由表,从而实现 Service 的负载均衡机制。

  • kube-proxy 服务依赖于 network 服务。

  • 生成 service 相关网络访问规则,支持 iptables 和 ipvs 规则(最新版默认使用这个)创建。

组件参数默认值说明
–iptables-min-sync-period0表示iptables规则的最小同步周期。(1)设置为非0时,kube-proxy使用令牌桶算法实现流控控制,避免频繁刷新。例如,当--iptables-min-sync-period=10s,   kube-proxy以每秒0.1个令牌的速度,向令牌桶中放入令牌,桶的容量为2。(2)设置为0时,kube-proxy则不启用流控控制。
–iptables-sync-period30s表示iptables规则的最大同步周期。
—feature-gates=   ExperimentalCriticalPodAnnotation=true 关于ExperimentalCriticalPodAnnotation=true这个参数,kube-proxy中并没有在使用。如果确实想用CriticalPod,为保险起见,最好还是设置上。
  • --iptables-sync-period 刷新 iptables 规则的最大时间间隔(例如 5s,1m,2h22m)必须大于 0。(默认 30 秒)

  • --iptables-min-sync-period 当端点和 Service 改变时(例如 5s,1m,2h22m),iptables 规则刷新的最小间隔可以被刷新。(默认10秒)

问题一:

节点故障时,上面运行的 pod 需要较长时间才恢复正常。

原因:

Kube-proxy 默认 30 秒就刷新一次 iptables 规则。如果主机故障或删除 iptables 规则,那就需要 30 秒后 kube-proxy 才能实现并恢复。

解决:

  1. 按实际调整 iptables 刷新、同步规则时间。

  2. pod 设置 readiness 健康检查,探测 pod 异常时可以自动去除前端 service 访问。

  3. 调整 kubelet 的 --node-monitor-grace-period 参数,减少这个时间让节点从调度中摘除。

  4. 上层添加硬负载,如 F5;软负载如Haproxy 可在 retries 配置中解决、Nginx在 max_fails 配置中解决。

 

问题二:

kube-proxy 不工作,导致访问某个节点的 pod 业务,服务异常。

原因:

iptables 规则没生成或规则没及时同步。

解决:

  1. 删除 kube-proxy 重建。

  2. Service 需要的 ClusterIP 段可能有冲突,查找内网是否存在与默认10.96.0.0/12网段。

  3. 大规模,如 service 超过5000个。使用 ipvs 模式替代 iptables。

 

问题三:

存在 keepalived 的节点,监控脚本就不停重启keepalived

原因:

kube-proxy 使用 ipvs 模式,重启会导致 ipvs 的所有规则清除。keepalived节点依靠ipvs来转发, 但是只要restart该节点的kube-proxy, ipvsadm -L -n规则就全没了。

解决:

调整 kube-proxy 的参数:--cleanup-ipvs=false 。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐