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 服务器校准。集群各节点使用如:
timedatectl set-timezone Asia/Shanghai 确保时区。
ntpdate -u $NTP-SERVER 进行时间同步。
使用 chronyd 进行自动时间同步。
问题二:
集群加入主机后,主机列表没有新增。
接入主机命令执行之后,一般会报错 Node “xxx" not found。(原因是添加被etcd 拒绝)
主机状态 未知。
Kubelet 状态有 database space exceeded报错。
原因:
默认情况下,ETCD使用的空间上限是 2 GB。一般支持最大 8 GB。
默认 etcd 的 snap 频率是 10万次提交 进行一次 Snapshot。Kubernetes 数据存储在 etcd,当 k8s load 较高时,Snap 频率会提高。
--snapshot-count '100000' | number of committed transactions to trigger a snapshot to disk. |
---|---|
--max-snapshots | maximum 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。
获取 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=true | false | 关于ExperimentalCriticalPodAnnotation=true这个参数,kube-controller-manager中并没有在使用。如果确实想用CriticalPod,为保险起见,最好还是设置上。 |
–enable-garbage-collector | true | 设置为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-threshold | 12500 | 设置可保存的终止Pod的数量,如果超过该数量,垃圾回收器开始删除操作。设置为不大于0的值表示不启用该功能。 |
问题一:
节点宕机,保障业务不受影响。
解决:
Pod 应该多实例,分布不同主机。避免某个节点宕机带来的单点影响。
当计算节点出现问题时,kube-controller 默认需要5分钟后才会驱逐计算节点上的容器并重建。这个时间不建议减少太多,避免因为网络抖动带来 pod 频繁调度和误杀问题。
kube-controller-manager,其中「--unhealthy-zone-threshold」参数可以设置在一个 zone 中有多少比例的 Node 失效时将被判断为 unhealthy 不进行节点驱逐,默认值为 0.55。
如果对时间敏感度有强烈需求,以下脚本供参考。
#!/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 将不能调度到有污点的节点上。
组件必须运行在 kube-system 命名空间下;
1.14 前版本,可以 scheduler.alpha.kubernetes.io/critical-pod 的注解(annotation )设置为空字符串;
PodSpec’s 的容忍(tolerations) 设置为:[{"key":"CriticalAddonsOnly","operator":"Exists"}];
需要将 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-eviction | false | (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-mount | false | (1) 设置为false时,在执行挂载操作前,不会去检查本节点是否具有挂载某种类型数据卷的必要组件,如二进制可执行文件等等。当挂载操作失败之后,会重新再执行此挂载操作。(2)设置为true时,在执行挂载操作前,会根据要挂载的数据卷类型,确定数据卷插件,并检查本节点具有此数据卷插件的必要的依赖性组件。当检查结果是不具备时,此挂载操作立即失败,对于此挂载操作,不会进行重试。(&)--expermental-mounter-path参数取值,会影响本参数的最终取值。 |
–enable-debugging-handlers | true | 设置为true表示提供远程访问本节点容器的日志、进入容器执行命令等相关的REST服务。在安全性方面,还需后序调研。 |
–eviction-hard | memory.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,但触发需要一定条件。
某些Pod是通过NodeSelector的label给调度到某些专有label的节点上的。
当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-period | 0 | 表示iptables规则的最小同步周期。(1)设置为非0时,kube-proxy使用令牌桶算法实现流控控制,避免频繁刷新。例如,当--iptables-min-sync-period=10s, kube-proxy以每秒0.1个令牌的速度,向令牌桶中放入令牌,桶的容量为2。(2)设置为0时,kube-proxy则不启用流控控制。 |
–iptables-sync-period | 30s | 表示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 才能实现并恢复。
解决:
按实际调整 iptables 刷新、同步规则时间。
pod 设置 readiness 健康检查,探测 pod 异常时可以自动去除前端 service 访问。
调整 kubelet 的 --node-monitor-grace-period 参数,减少这个时间让节点从调度中摘除。
上层添加硬负载,如 F5;软负载如Haproxy 可在 retries 配置中解决、Nginx在 max_fails 配置中解决。
问题二:
kube-proxy 不工作,导致访问某个节点的 pod 业务,服务异常。
原因:
iptables 规则没生成或规则没及时同步。
解决:
删除 kube-proxy 重建。
Service 需要的 ClusterIP 段可能有冲突,查找内网是否存在与默认10.96.0.0/12网段。
大规模,如 service 超过5000个。使用 ipvs 模式替代 iptables。
问题三:
存在 keepalived 的节点,监控脚本就不停重启keepalived
原因:
kube-proxy 使用 ipvs 模式,重启会导致 ipvs 的所有规则清除。keepalived节点依靠ipvs来转发, 但是只要restart该节点的kube-proxy, ipvsadm -L -n规则就全没了。
解决:
调整 kube-proxy 的参数:--cleanup-ipvs=false 。