惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
爱范儿
爱范儿
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
Jina AI
Jina AI
Project Zero
Project Zero
宝玉的分享
宝玉的分享
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
Simon Willison's Weblog
Simon Willison's Weblog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tenable Blog
F
Fortinet All Blogs
大猫的无限游戏
大猫的无限游戏
Last Week in AI
Last Week in AI
月光博客
月光博客
雷峰网
雷峰网
G
Google Developers Blog
V
V2EX
T
Tor Project blog
罗磊的独立博客
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
W
WeLiveSecurity
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Privacy International News Feed
S
Securelist
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Proofpoint News Feed
Blog — PlanetScale
Blog — PlanetScale
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
小众软件
小众软件
Scott Helme
Scott Helme
I
Intezer
T
Threat Research - Cisco Blogs
The GitHub Blog
The GitHub Blog
N
Netflix TechBlog - Medium
C
CERT Recently Published Vulnerability Notes
Security Archives - TechRepublic
Security Archives - TechRepublic
酷 壳 – CoolShell
酷 壳 – CoolShell
L
LINUX DO - 最新话题
N
News | PayPal Newsroom
L
Lohrmann on Cybersecurity
T
Troy Hunt's Blog
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
人人都是产品经理
人人都是产品经理
Latest news
Latest news
AWS News Blog
AWS News Blog
Apple Machine Learning Research
Apple Machine Learning Research

陈少文的网站

巨变与机遇的未来十年 Kubernetes 平台管理软件压力测试方案 使用镜像部署 Hexo 静态页面 终于等到你 - GitHub 镜像仓库服务(ghcr.io) 一起来学 Go --(6)Interface 一起来学 Go --(5)Goroutine 和 Channel 什么是函数式编程 如何在 Kubernetes 集群集成 Kata 柯里化与偏函数 使用 PyGithub 自动创建 Label 软件产品是团队能力的输出 Helm 2 、Helm 3 比较 IoT 变现 Kubernetes 中的 DNS 服务 国内的 Helm 镜像源 Harbor 使用自签证书支持 Https 访问 DevOps 工具链之 Prow 如何使用 kfctl 安装 Kubeflow VS Code 无法下载 Go 插件的工具包 工程师更应具有服务精神 你不知道的 Docker 使用技巧 使用 Docker 运行 Tensorflow 论中国 什么是左移 如何清空 Git 仓库全部历史记录 一禅小和尚 有风吹过厨房 时间的玫瑰 如何在 CentOS 安装 GPU 驱动 开发 Tips(19) 使用 Velero 备份 Kubernetes 集群 Kubernetes Cheat Sheet 开发 Tips(18) 如何构建一个 Java 工程 开发 Tips(17) KubeSpray 安装 Kubernetes 报错 ip in ansible_all_ipv4_addresses 基于 Kubernetes 和 Jenkins 搭建自动化测试系统 在 Kubernetes 上动态创建 Jenkins Slave 使用 Jenkins 进行服务拨测 开发 Tips(16) Kubernetes 签发 Ingress 证书及日常故障运维 Kubernetes 中 Deployment 的基本操作 Kubernetes 中的证书 如何使用 KubeBuilder 开发一个 Operator Kubernetes 1.6.0 安装问题汇总 镜像管理工具 -- Harbor 开发 Tips(15) Docker 如何拉取镜像 开发 Tips(14) 使用 Helm 安装 harbor 开发 Tips(13) 使用 S2I 构建云原生应用 在 Kubernetes 中使用 emptyDir、hostPath、localVolume 开发 Tips(12) 开发 Tips(11) 代码质量分析工具 SonarQube 使用 Kubeadm 安装 Kubernetes 集群 一起来学 Go --(4)常用函数 Kubernetes 中的 Ceph Kubernetes 之 Volumes Kubernetes 之 Labels、Selectors 开发 Tips(10) 开源正在重构商业模式 Kubernetes 之网络 Kubernetes 之 API 使用 Helm 和 Operator 快速部署 Prometheus Kubernetes 复杂有状态应用管理框架 -- Operator Kubernetes 的包管理器 -- Helm 一起来学 Go --(3)Go Modules 如何一步一步地优化博客方案 kubectl 实用指南 Kubernetes 中的基本概念 搭建远程 Kubernetes 开发环境 大公司和小公司的 ToB 思路 开发 Tips(9) Go 入门指南 一起来学 Go --(2)数据与逻辑结构 如何预防 Web 富文本中的 XSS 攻击 django-xss-cleaner 云工作时代 一起来学 Go --(1)背景与特点 SaaS 开发团队的不同阶段 你不知道的 Git 使用技巧 输出既服务 微服务设计 继续奔跑 开发 Tips(8) 从账户安全到二次验证 Django 性能之数据库查询优化 Django 性能之分库分表 敏捷开发之研发流程 打造一致性的团队 开发 Tips(7) Pytest 进阶学习之 Mock PaaS 部署之 buildpack Go 开发配置 领域输出才是 PaaS 的核心竞争力 Pytest 入门学习 开发 Tips(6) 如何使用 Jenkins、Docker、GitLab 搭建 Django 自动化部署流程
从 CPU 到网络记录一次排查应用慢的过程
微信公众号 · 2023-11-08 · via 陈少文的网站

1. 现象

业务反馈应用 app-a 的接口慢,查看日志发现是某一个 Pod 慢,删除该 Pod 让其更换节点就好。

从监控指标可以看到,Pod 的 CPU 使用率确实有剧增。

但该 Pod 没有达到 Limit 的限制,没有被限流 CPU。

接着看节点的 CPU 监控,发现节点的 CPU 使用率也有剧增。

并且增加的部分是 System CPU,也就是内核态的 CPU。

进一步,确定增加的部分是 System CPU,也就是内核态的 CPU。

使用内核态 CPU 的主要场景有网络、磁盘 IO 等。

2. 排查问题

2.1 排查磁盘 IO

iowait 高并不意味着磁盘 IO 高,iowait 高只是说明 CPU 等待 IO 的时间长,但是并不一定是因为磁盘 IO 高导致的,也可能是网络 IO 高所致。但 iowait 低则能判断出磁盘 IO 压力小。

从上面的监控可以看到 CPU 的 iowait 低,说明磁盘没有瓶颈。

另外,从监控指标可以看到,磁盘 IO 速度、IOPS 也都没有异常波动。

也可以结合磁盘设备平均 IO 队列数、读延迟、写延迟等指标继续确认磁盘 IO 是否有异常。

至此,基本排除了磁盘 IO 的瓶颈。

2.2 排查网络 IO

网络 IO 先看节点 Node 的进出流量,再找异常流量的 Pod。

从监控可以看到,节点的网络 IO 有异常波动,从 500 Mbps 到 800 Mbps 。

接着找到这个节点上流量异常的 Pod。

2.3 找到问题

很快,我就从监控系统中找出了这个 Pod。

时间点上很符合,大量的网络 IO、CPU 使用剧增。

原来是应用 app-b,在 HPA 扩容调度时,恰好调度到了这个节点上,导致这个节点的网络 IO、CPU 使用剧增,影响到了延时敏感的应用 app-a,导致应用 app-a 的接口慢。

2.4 解决问题

解决办法就是利用 Pod 的反亲和性,将应用 app-a 和应用 app-b 从调度层隔离开来,不让其同时存在于一个节点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
kind: Deployment
name: app-a
spec:
  spec:
    restartPolicy: Always
    affinity:
      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - app-b
            topologyKey: kubernetes.io/hostname

至此,应用 app-a 慢的情况比之前改善了很多,但还是会偶尔出现慢的情况。于是,我继续花了点时间排查。

3. 为什么 Pod CPU 没有被限流

我一度怀疑应用 app-a 的 Pod CPU 有被限流,监控指标没有反应出来而已。但排查了一段时间后,我并没有发现相关的证据。这个猜测可能是错误的。

3.1 K8s 中的 CFS 限流机制

CFS 是 Linux 内核中用来实现 CPU 带宽控制的一种机制,它可以设定一个进程组可使用的 CPU 时间的上限。

通常,CPU 使用的的计量周期为 100ms。CPU Limit 决定了每个计量周期,也就是 100ms 时间内,可以使用的 CPU 时间上限。

1 CPU 代表着 100ms 内,可以使用 1 个核心的 CPU 时间;2 CPU 代表着 100ms 内,可以使用 2 个核心的 CPU 时间。但如果是 0.5 CPU,代表着 100ms 内,就不是 0.5 个核心的 CPU 时间,而是 50ms 的 CPU 时间,因为 CPU 的核心是整数,不能分割,能分割的是占用时间。

这里其实并不会涉及使用率的问题,无论应用将 CPU 用来计算、还是闲置,都会被计算在内。

如果 CPU 使用超过了 CPU Limit 规定的时间,就会被限制使用。这里其实还有一个细节就是,多久调度一次 CPU 的使用。这也是我非常感兴趣的一个问题,在后面的文章中,我会继续分析。

3.2 kubelet 对 Pod CPU 指标的采集

  1. Prometheus 中的服务发现配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
job_name: kubernetes-nodes-cadvisor
    kubernetes_sd_configs:
    - role: node
    relabel_configs:
    - action: labelmap
      regex: __meta_kubernetes_node_label_(.+)
    - replacement: kubernetes.default.svc:443
      target_label: __address__
    - regex: (.+)
      replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
      source_labels:
      - __meta_kubernetes_node_name
      target_label: __metrics_path__

数据采集的链路是 cAdvisor -> kubelet -> kube-apiserver -> Prometheus。

  1. cAdvisor 对 CPU 指标的采集

CPU 限流的指标主要是 container_cpu_cfs_throttled_seconds_total 这是 cAdvisor 源码中的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  name:      "container_cpu_cfs_throttled_seconds_total",
  help:      "Total time duration the container has been throttled.",
  valueType: prometheus.CounterValue,
  condition: func(s info.ContainerSpec) bool { return s.Cpu.Quota != 0 },
  getValues: func(s *info.ContainerStats) metricValues {
    return metricValues{
      {
        value:     float64(s.Cpu.CFS.ThrottledTime) / float64(time.Second),
        timestamp: s.Timestamp,
      }}
}

cAdvisor 获取 CPU 指标的方式:

1
2
3
spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares")
spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us")
quota := readString(cpuRoot, "cpu.cfs_quota_us")

内核会动态修改这些容器文件,cAdvisor watch 了这些文件变化,当有文件创建时,说明有新的容器创建;当有文件变更时,说明指标发生了变化,这些都会触发指标的采集。

在容器中,可以看到这些文件:

1
2
3
4
5
ls /sys/fs/cgroup/cpu

cgroup.clone_children  cpu.cfs_quota_us   cpu.shares    cpuacct.usage         cpuacct.usage_percpu_sys   cpuacct.usage_user
cgroup.procs           cpu.rt_period_us   cpu.stat      cpuacct.usage_all     cpuacct.usage_percpu_user  notify_on_release
cpu.cfs_period_us      cpu.rt_runtime_us  cpuacct.stat  cpuacct.usage_percpu  cpuacct.usage_sys          tasks

其中与 CPU 限流相关的文件是

  • cpu.cfs_period_us
  • cpu.cfs_quota_us
1
2
3
cat /sys/fs/cgroup/cpu/cpu.cfs_period_us

100000

这里的 100000 微秒,也就是 100 ms,即一个 CPU 上 cfs 控制程序使用的周期。

1
2
3
cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us

200000

这里的 200000 微秒,也就是 200 ms,cfs_quota_us 表示总的时间周期,这里的含义是可以同时两个 CPU 核心的 100 ms 时间。

而限流指标存储在这里:

1
2
3
4
5
cat /sys/fs/cgroup/cpu/cpu.stat

nr_periods 2759610
nr_throttled 5592
throttled_time 30204498882

其中,

  • nr_periods 已经使用的周期数
  • nr_throttled 被限制的周期数
  • throttled_time 被限制的时钟长度,单位纳秒

在 Pod 的监控指标中,可以看到:

  1. Prometheus 对指标的抓取
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
prometheus.yml: |
  global:
    evaluation_interval: 1m
    external_labels:
      cluster: mycluster
    scrape_interval: 60s
    scrape_timeout: 20s
  remote_write:
  - queue_config:
      batch_send_deadline: 2s
      capacity: 5000
      max_backoff: 5s
      max_samples_per_send: 500
      min_backoff: 100ms
      max_shards: 10000
    remote_timeout: 120s
    url: http://victoria-metrics-insert/insert/0/prometheus  

抓取周期较长,为 60s,对于 Counter 类型的指标只会损失精度,而不会错过特征,这与 Gauge 形成了鲜明的对比。

从采集到抓取,没有发现可能错过 CPU 限流监控的地方。

3.3 告警系统会不会错过异常特征

在告警系统中,使用的是 irate 并且时间段是 10s。10s 间隔能看到更多细节,如下图:

但不利于监控告警,rate 能查看到趋势,5m 间隔有利于监控告警。

因此可能,有一些 CPU 限流的毛刺,告警系统不能检测到。

但很遗憾的是,在这个场景下,我反复找了很多次,没有发现 CPU 限流的毛刺。

4. 会不会是网络流量转发慢

4.1 kube-proxy 中的异常日志

这里有一段异常的日志引起来了我的注意。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
E1103 07:33:18.156949       1 proxier.go:900] Failed to ensure that nat chain KUBE-MARK-DROP exists: error creating chain "KUBE-MARK-DROP": exit status 4: Another app is currently holding the xtables lock; still 4s 100000us time ahead to have a chance to grab the lock...
Another app is currently holding the xtables lock; still 3s 100000us time ahead to have a chance to grab the lock...
Another app is currently holding the xtables lock; still 2s 100000us time ahead to have a chance to grab the lock...
Another app is currently holding the xtables lock; still 1s 100000us time ahead to have a chance to grab the lock...
Another app is currently holding the xtables lock; still 0s 100000us time ahead to have a chance to grab the lock...
Another app is currently holding the xtables lock. Stopped waiting after 5s.
I1103 07:33:18.157007       1 proxier.go:876] Sync failed; retrying in 30s
I1103 07:33:28.798252       1 trace.go:205] Trace[1195266045]: "iptables save" (03-Nov-2023 07:33:26.573) (total time: 2225ms):
Trace[1195266045]: [2.225081921s] [2.225081921s] END
I1103 07:33:31.892549       1 trace.go:205] Trace[964002782]: "iptables restore" (03-Nov-2023 07:33:28.966) (total time: 2925ms):
Trace[964002782]: [2.925513421s] [2.925513421s] END
I1103 07:33:43.900533       1 trace.go:205] Trace[431351666]: "iptables restore" (03-Nov-2023 07:33:40.759) (total time: 3141ms):

原来 kube-proxy 挂载了 /run/xtables.lock,以此仅有一个地方能够操作 iptables。

1
2
3
4
5
volumeMounts:
  - name: kube-proxy
    mountPath: /var/lib/kube-proxy
  - name: xtables-lock
    mountPath: /run/xtables.lock

如果主机有其他进程也在操作 iptables,kube-proxy 就会等待。但这个日志时不时出现一次,并且也不符合异常时的时间点。

4.2 kube-proxy 启动配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat /var/lib/kube-proxy/config.conf

apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: 0.0.0.0
bindAddressHardFail: false
clusterCIDR: 10.239.0.0/16
configSyncPeriod: 15m0s
conntrack:
  maxPerCore: 32768
  min: 131072
  tcpCloseWaitTimeout: 1h0m0s
  tcpEstablishedTimeout: 24h0m0s
detectLocalMode: ""
enableProfiling: false
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
  masqueradeAll: false
  masqueradeBit: 14
  minSyncPeriod: 0s
  syncPeriod: 30s
mode: iptables

其中,syncPeriod 意味着每隔 30s 全量同步一次 iptables 规则。那么,如果没有同步完成,碰到了下一个 30s 的同步周期,会怎么样呢?

4.3 kube-proxy 更新 iptables 真的很慢

应用 app-b 会大批量下载文件并存储在内存中,导致节点的网络 IO 压力、NF_CONNTRACK 表项、节点的 CPU 压力都剧增。

虽然 Kube-proxy 没有重启、没有 OOMKilled,但 kube-proxy 的 kubeproxy_network_programming_duration_seconds_bucket P99 指标却有异常波动。如下图:

kube-proxy 同步 iptables 规则慢,可能会导致 Pod 的流量摘除不及时,将流量转发到不存在的 Pod 上。

但应用 app-a 异常 timeout 时,并没有 Pod 的重新调度、Pod IP 发生变化的情况发生。

接着,我看了下 nf_conntrack table 的监控值,也没有满,而且上限极高。

至此,我浪费了很多时间,也没有找到问题的根源。

5. 应用使用 CPU 推理慢

幸运的是,我不仅是 SRE 还负责开发 CICD,直接就去翻了下应用 app-a 的代码。原来应用 app-a 使用 PyTorch 框架在 CPU 上运行推理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
self.vocab = pickle.load(open(os.path.join(env.LOCAL_DATA_DIR, 'vocab_xxx.pkl'), 'rb'))

cpu_num = env.CPU_NUM
cpu_num = int(cpu_num)
if cpu_num > 0:
    os.environ["OMP_NUM_THREADS"] = str(cpu_num)
    torch.set_num_threads(cpu_num)

self.device = torch.device('cpu')

self.model_path = os.path.join(env.CLOUD_DATA_DIR, "xxx_model.ckpt")

config = Config()
config.n_vocab = len(self.vocab)
self.model = xxx_model.Model(config)
self.model = self.model.to(config.device)
if os.path.exists(self.model_path):
    self.model.load_state_dict(torch.load(self.model_path, map_location=torch.device("cpu")), False)
else:
    raise Exception("Model file not exists")

CPU 运行的推理应用与普通应用不同:

虽然提供了大量的 CPU,但 CPU 使用率很低,同时有大量推理任务依然需要 3-257s,高峰期慢推理能达到 800 /秒。

至此,没有其他线索了,并不全是 IaaS 层资源的问题,主要是 CPU 扛不住低延时、大吞吐的推理任务。

6. 总结

本篇主要是记录了一次应用慢的排查过程。最后没有找到问题的根源,但发现了不少目前大集群的问题。

排查应用慢时的思路主要如下:

  1. CPU 限流,看 Pod 的 CPU 指标、节点的 CPU 指标是否有异常
  2. 磁盘 IO 慢,看 iowait、iops、io 队列数、读写延迟等是否有异常
  3. 网络慢,沿着流量路径,查看网络指标是否有异常
  4. 应用依赖的服务,本篇中的 app-a 没有外部依赖,因此跳过了这个环节

由于这次碰到的是一个推理应用,其实还会涉及输入文本的长度、BATCH_SIZE 大小、模型的复杂度等因素,但这些因素都是应用层的问题,不在本篇的讨论范围。