![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/bed059552ed86580939aa18fbdf1553e.jpg)
在生产中使用 Kubernetes 的这些年里,我们积累了许多有趣的故事,说明各种系统组件中的错误如何导致令人不快和/或难以理解的后果,从而影响容器和 Pod 的运行。 在本文中,我们选择了一些最常见或最有趣的内容。 即使你从来没有足够幸运遇到这样的情况,阅读这样的短篇侦探故事 - 尤其是“第一手” - 总是很有趣,不是吗?
故事 1. Supercronic 和 Docker 挂起
在其中一个集群上,我们定期收到冻结的 Docker,这干扰了集群的正常运行。 同时,在Docker日志中观察到以下内容:
level=error msg="containerd: start init process" error="exit status 2: "runtime/cgo: pthread_create failed: No space left on device
SIGABRT: abort
PC=0x7f31b811a428 m=0
goroutine 0 [idle]:
goroutine 1 [running]:
runtime.systemstack_switch() /usr/local/go/src/runtime/asm_amd64.s:252 fp=0xc420026768 sp=0xc420026760
runtime.main() /usr/local/go/src/runtime/proc.go:127 +0x6c fp=0xc4200267c0 sp=0xc420026768
runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1 fp=0xc4200267c8 sp=0xc4200267c0
goroutine 17 [syscall, locked to thread]:
runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1
… 我们对该错误最感兴趣的是以下消息: pthread_create failed: No space left on device。 快速学习 解释说 Docker 无法分叉进程,这就是它定期冻结的原因。
在监控中,对应的情况如下图:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/bd778052c87b338493bae54b26830ef3.jpg)
在其他节点上也观察到类似的情况:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/ef512532a95ca982e4342071115dbe9f.jpg)
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/43c32ebca78755dde348ed5e7ac75c79.jpg)
在相同的节点上我们看到:
root@kube-node-1 ~ # ps auxfww | grep curl -c
19782
root@kube-node-1 ~ # ps auxfww | grep curl | head
root 16688 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 17398 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 16852 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 9473 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 4664 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 30571 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 24113 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 16475 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 7176 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 1090 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>事实证明,这种行为是 pod 与 (我们用来在 pod 中运行 cron 作业的 Go 实用程序):
_ docker-containerd-shim 833b60bb9ff4c669bb413b898a5fd142a57a21695e5dc42684235df907825567 /var/run/docker/libcontainerd/833b60bb9ff4c669bb413b898a5fd142a57a21695e5dc42684235df907825567 docker-runc
| _ /usr/local/bin/supercronic -json /crontabs/cron
| _ /usr/bin/newrelic-daemon --agent --pidfile /var/run/newrelic-daemon.pid --logfile /dev/stderr --port /run/newrelic.sock --tls --define utilization.detect_aws=true --define utilization.detect_azure=true --define utilization.detect_gcp=true --define utilization.detect_pcf=true --define utilization.detect_docker=true
| | _ /usr/bin/newrelic-daemon --agent --pidfile /var/run/newrelic-daemon.pid --logfile /dev/stderr --port /run/newrelic.sock --tls --define utilization.detect_aws=true --define utilization.detect_azure=true --define utilization.detect_gcp=true --define utilization.detect_pcf=true --define utilization.detect_docker=true -no-pidfile
| _ [newrelic-daemon] <defunct>
| _ [curl] <defunct>
| _ [curl] <defunct>
| _ [curl] <defunct>
…问题是这样的:当一个任务在 supercronic 中运行时,它产生的进程 无法正确终止, 转变为 .
注意:更准确地说,进程是由 cron 任务生成的,但 supercronic 不是 init 系统,不能“采用”其子进程生成的进程。 当发出 SIGHUP 或 SIGTERM 信号时,它们不会传递给子进程,导致子进程不会终止并保持僵尸状态。 您可以阅读有关这一切的更多信息,例如, .
有几种方法可以解决问题:
- 作为临时解决方法 - 在单个时间点增加系统中 PID 的数量:
/proc/sys/kernel/pid_max (since Linux 2.5.34) This file specifies the value at which PIDs wrap around (i.e., the value in this file is one greater than the maximum PID). PIDs greater than this value are not allo‐ cated; thus, the value in this file also acts as a system-wide limit on the total number of processes and threads. The default value for this file, 32768, results in the same range of PIDs as on earlier kernels - 或者不直接在 supercronic 中启动任务,而是使用相同的 ,它能够正确终止进程并且不会产生僵尸。
故事 2. 删除 cgroup 时出现“僵尸”
Kubelet 开始消耗大量 CPU:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/6140058330faaa3785b089dcba857056.jpg)
没有人会喜欢这样,所以我们武装自己 并开始处理这个问题。 调查结果如下:
- Kubelet 花费了超过三分之一的 CPU 时间从所有 cgroup 中提取内存数据:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20241'%3E%3C/svg%3E)
- 在内核开发者的邮件列表中你可以找到 。 简而言之,要点可以归结为: 各种 tmpfs 文件和其他类似的东西没有从系统中完全删除 当删除一个cgroup时,所谓的 僵尸。 它们迟早会从页面缓存中删除,但服务器上有大量内存,内核认为没有必要浪费时间删除它们。 这就是为什么它们不断堆积。 为什么会发生这种情况? 这是一个带有 cron 作业的服务器,它不断创建新作业以及新的 Pod。 因此,会为其中的容器创建新的 cgroup,但很快就会被删除。
- 为什么 kubelet 中的 cAdvisor 浪费这么多时间? 通过最简单的执行就很容易看出这一点
time cat /sys/fs/cgroup/memory/memory.stat。 如果在健康的机器上该操作需要 0,01 秒,那么在有问题的 cron02 上则需要 1,2 秒。 问题是 cAdvisor 从 sysfs 读取数据的速度非常慢,它试图考虑僵尸 cgroup 中使用的内存。 - 为了强制删除僵尸,我们尝试按照 LKML 中的建议清除缓存:
sync; echo 3 > /proc/sys/vm/drop_caches, - 但结果发现内核更复杂,导致车子崩溃。
该怎么办? 问题正在修复(,有关说明,请参阅 内核更新 Linux 最高版本 4.16。
历史 3. Systemd 及其挂载
同样,kubelet 在某些节点上消耗了太多资源,但这一次它消耗了太多内存:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/044c4e23a772c61a6206b9b20aa67c1d.jpg)
原来是 systemd 使用了有问题。 Ubuntu 16.04,当管理为连接创建的挂载点时会发生这种情况。 subPath 来自 ConfigMap 或机密。 Pod 完成其工作后 systemd 服务及其服务挂载仍然存在 在系统中。 随着时间的推移,它们会积累大量。 甚至还有关于这个主题的问题:
- ;
- .
...最后一个指的是 systemd 中的 PR: (systemd 中的问题 - ).
问题已不复存在。 Ubuntu 18.04,但如果您想继续使用 Ubuntu 16.04,您可能会发现我们针对此问题提供的解决方法很有用。
所以我们做了如下的DaemonSet:
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
labels:
app: systemd-slices-cleaner
name: systemd-slices-cleaner
namespace: kube-system
spec:
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app: systemd-slices-cleaner
template:
metadata:
labels:
app: systemd-slices-cleaner
spec:
containers:
- command:
- /usr/local/bin/supercronic
- -json
- /app/crontab
Image: private-registry.org/systemd-slices-cleaner/systemd-slices-cleaner:v0.1.0
imagePullPolicy: Always
name: systemd-slices-cleaner
resources: {}
securityContext:
privileged: true
volumeMounts:
- name: systemd
mountPath: /run/systemd/private
- name: docker
mountPath: /run/docker.sock
- name: systemd-etc
mountPath: /etc/systemd
- name: systemd-run
mountPath: /run/systemd/system/
- name: lsb-release
mountPath: /etc/lsb-release-host
imagePullSecrets:
- name: antiopa-registry
priorityClassName: cluster-low
tolerations:
- operator: Exists
volumes:
- name: systemd
hostPath:
path: /run/systemd/private
- name: docker
hostPath:
path: /run/docker.sock
- name: systemd-etc
hostPath:
path: /etc/systemd
- name: systemd-run
hostPath:
path: /run/systemd/system/
- name: lsb-release
hostPath:
path: /etc/lsb-release...它使用以下脚本:
#!/bin/bash
# we will work only on xenial
hostrelease="/etc/lsb-release-host"
test -f ${hostrelease} && grep xenial ${hostrelease} > /dev/null || exit 0
# sleeping max 30 minutes to dispense load on kube-nodes
sleep $((RANDOM % 1800))
stoppedCount=0
# counting actual subpath units in systemd
countBefore=$(systemctl list-units | grep subpath | grep "run-" | wc -l)
# let's go check each unit
for unit in $(systemctl list-units | grep subpath | grep "run-" | awk '{print $1}'); do
# finding description file for unit (to find out docker container, who born this unit)
DropFile=$(systemctl status ${unit} | grep Drop | awk -F': ' '{print $2}')
# reading uuid for docker container from description file
DockerContainerId=$(cat ${DropFile}/50-Description.conf | awk '{print $5}' | cut -d/ -f6)
# checking container status (running or not)
checkFlag=$(docker ps | grep -c ${DockerContainerId})
# if container not running, we will stop unit
if [[ ${checkFlag} -eq 0 ]]; then
echo "Stopping unit ${unit}"
# stoping unit in action
systemctl stop $unit
# just counter for logs
((stoppedCount++))
# logging current progress
echo "Stopped ${stoppedCount} systemd units out of ${countBefore}"
fi
done...并且它使用前面提到的 supercronic 每 5 分钟运行一次。 它的 Dockerfile 如下所示:
FROM ubuntu:16.04
COPY rootfs /
WORKDIR /app
RUN apt-get update &&
apt-get upgrade -y &&
apt-get install -y gnupg curl apt-transport-https software-properties-common wget
RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable" &&
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - &&
apt-get update &&
apt-get install -y docker-ce=17.03.0*
RUN wget https://github.com/aptible/supercronic/releases/download/v0.1.6/supercronic-linux-amd64 -O
/usr/local/bin/supercronic && chmod +x /usr/local/bin/supercronic
ENTRYPOINT ["/bin/bash", "-c", "/usr/local/bin/supercronic -json /app/crontab"]故事 4. 调度 Pod 时的竞争力
人们注意到:如果我们将一个 pod 放置在一个节点上,并且它的镜像被抽出很长时间,那么“击中”同一节点的另一个 pod 就会简单地 不开始拉取新 Pod 的镜像。 相反,它会等待直到拉取前一个 pod 的映像。 结果,一个已经被调度的 pod,其镜像在一分钟内就可以下载完毕,最终会处于以下状态: containerCreating.
事件看起来像这样:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"事实证明, 来自慢速注册表的单个映像可能会阻止部署 每个节点。
不幸的是,摆脱这种情况的方法并不多:
- 尝试直接在集群中或直接与集群一起使用您的 DockerRegistry(例如,GitLabRegistry、Nexus 等);
- 使用诸如 .
故事 5. 节点因内存不足而挂起
在各种应用程序的运行过程中,我们也遇到过节点完全无法访问的情况:SSH没有响应,所有监控守护进程都掉了,然后日志中没有任何(或几乎没有)异常。
我将以 MongoDB 运行的一个节点为例,通过图片告诉您。
这就是上面的样子 对 事故:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/5de916d270a862cbcbb5ed23c31f698e.jpg)
就像这样—— 后 事故:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/0f32bf1113204cf19f4639a297e40348.jpg)
在监控中,也有一个急剧的跳跃,此时节点不再可用:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/31e770cac5be32bb7f95cfbbc6b9f1ae.jpg)
因此,从截图中可以清楚地看出:
- 机器上的RAM已接近尾声;
- RAM 消耗急剧增加,然后突然禁用对整个计算机的访问;
- 一个大任务到达 Mongo,这迫使 DBMS 进程使用更多内存并主动从磁盘读取。
结果表明,如果在 Linux 可用内存耗尽(出现内存压力)且没有交换空间时, 对 当 OOM 杀手到来时,将页面放入页面缓存和将其写回磁盘之间可能会出现平衡行为。 这是由 kswapd 完成的,它勇敢地释放尽可能多的内存页以供后续分配。
不幸的是,由于 I/O 负载较大且可用内存较少, kswapd成为整个系统的瓶颈,因为他们与它联系在一起 所有 系统中内存页面的分配(页面错误)。 如果进程不想再使用内存,而是固定在 OOM 杀手深渊的边缘,这种情况可能会持续很长时间。
自然的问题是:为什么 OOM 杀手来得这么晚? 在当前的迭代中,OOM 杀手非常愚蠢:只有当尝试分配内存页面失败时(即,它才会终止进程)。 如果页面错误失败。 这种情况在很长一段时间内都不会发生,因为 kswapd 勇敢地释放内存页面,将页面缓存(实际上是系统中的整个磁盘 I/O)转储回磁盘。 更详细地,您可以阅读消除内核中此类问题所需步骤的描述 .
这种行为 带芯 Linux 4.6 +。
故事 6.Pod 陷入 Pending 状态
在某些集群中,确实有很多 pod 正在运行,我们开始注意到大多数 pod 都处于“挂起”状态很长时间 Pending,尽管 Docker 容器本身已经在节点上运行并且可以手动使用。
有了这个 describe 没有任何错误:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Successfully assigned sphinx-0 to ss-dev-kub07
Normal SuccessfulAttachVolume 1m attachdetach-controller AttachVolume.Attach succeeded for volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal SuccessfulMountVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "sphinx-config"
Normal SuccessfulMountVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "default-token-fzcsf"
Normal SuccessfulMountVolume 49s (x2 over 51s) kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal Pulled 43s kubelet, ss-dev-kub07 Container image "registry.example.com/infra/sphinx-exporter/sphinx-indexer:v1" already present on machine
Normal Created 43s kubelet, ss-dev-kub07 Created container
Normal Started 43s kubelet, ss-dev-kub07 Started container
Normal Pulled 43s kubelet, ss-dev-kub07 Container image "registry.example.com/infra/sphinx/sphinx:v1" already present on machine
Normal Created 42s kubelet, ss-dev-kub07 Created container
Normal Started 42s kubelet, ss-dev-kub07 Started container经过一番挖掘,我们假设 kubelet 根本没有时间将有关 pod 状态和活跃/就绪测试的所有信息发送到 API 服务器。
并且研究了help后,我们发现了以下参数:
--kube-api-qps - QPS to use while talking with kubernetes apiserver (default 5)
--kube-api-burst - Burst to use while talking with kubernetes apiserver (default 10)
--event-qps - If > 0, limit event creations per second to this value. If 0, unlimited. (default 5)
--event-burst - Maximum size of a bursty event records, temporarily allows event records to burst to this number, while still not exceeding event-qps. Only used if --event-qps > 0 (default 10)
--registry-qps - If > 0, limit registry pull QPS to this value.
--registry-burst - Maximum size of bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry-qps. Only used if --registry-qps > 0 (default 10)如你所见 默认值相当小,并且 90% 的情况满足了所有需求...但是,在我们的案例中这还不够。 因此,我们设置以下值:
--event-qps=30 --event-burst=40 --kube-api-burst=40 --kube-api-qps=30 --registry-qps=30 --registry-burst=40...并重新启动 kubelet,之后我们在 API 服务器的调用图中看到了下图:
![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/b2ae099729e55a686f6bec3012b96195.jpg)
...是的,一切都开始飞翔!
PS
感谢我们公司的众多工程师,特别是我们研发团队的同事 Andrey Klimentyev(Andrey Klimentyev)在收集 bug 和准备本文方面提供的帮助。).
聚苯硫醚
另请阅读我们的博客:
- «“。
- Kubernetes 提示和技巧循环:
- «“;
- «“;
- «“;
- «“。
来源: habr.com

![Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案]](/wp-content/uploads/2019/03/0d15d1de17cd6838fc1cad19615af218.jpg)