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

推荐订阅源

博客园_首页
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
Y
Y Combinator Blog
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
S
SegmentFault 最新的问题
IT之家
IT之家
Recent Announcements
Recent Announcements
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
Martin Fowler
Martin Fowler
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
WordPress大学
WordPress大学
博客园 - Franky
L
LangChain Blog
人人都是产品经理
人人都是产品经理
小众软件
小众软件
博客园 - 叶小钗
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
雷峰网
雷峰网
腾讯CDC
Google DeepMind News
Google DeepMind News
博客园 - 三生石上(FineUI控件)
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Help Net Security
Help Net Security
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
N
News and Events Feed by Topic
V2EX - 技术
V2EX - 技术
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Schneier on Security
Schneier on Security
博客园 - 聂微东
A
Arctic Wolf
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
C
Cyber Attacks, Cyber Crime and Cyber Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News

陈少文的网站

巨变与机遇的未来十年 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 自动化部署流程
优化 Tekton 执行克隆任务慢问题,节省约 30 秒
微信公众号 · 2023-02-16 · via 陈少文的网站

1. 现象 - Tekton 克隆代码任务慢

在执行克隆任务时,Tekton 很费时间,多仓库下一般都需要 2 分 30 秒左右。如下图:

仅克隆的流水线就需要执行 2 分钟 16 秒,而克隆脚本实际上仅执行 1-3 秒。其中大部分时间花在了哪里?能不能减少?这是本篇主要想讨论的问题。

2. 分析克隆任务的时间开销

Tekton 运行流水线时,每个 Task 都会在一个独立 Pod 中运行。在上述场景下,一个 git clone task 只克隆一个仓库,如果有 N 个代码仓库,那么就需要创建至少 N 个 Pod。

这样就出现两个优化点:

  • 并行执行任务
  • 缩短单个执行时间

并行克隆可以从运维侧优化,先看看单个 Pod 执行的时间序列。

下面这个例子总时长 34s,第一个容器启动花了 29s,约占 85%,克隆代码只有 1s

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pod -> 14:36:52
    distroless-base -> 14:37:21
    distroless-base <- 14:37:21
    pipeline-entrypoint -> 14:37:21
    pipeline-entrypoint <- 14:37:21
    pipeline-git-init -> 14:37:22
        clone -> 14:37:25
        clone <->  14:37:26
    pipeline-git-init <- 14:37:26
pod <- 14:37:26

下面这个例子总时长 107s,第一个容器启动花了 24s,约占 22%,git-init 容器启动到执行脚本花了 78秒,约占 72%,克隆代码只有 2s

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pod -> 10:42:07
    distroless-base -> 10:42:31
    distroless-base <- 10:42:31
    pipeline-entrypoint -> 10:42:32
    pipeline-entrypoint <- 10:42:32
    pipeline-git-init -> 10:42:34
        clone -> 10:43:52
        clone <-> 10:43:54
    pipeline-git-init <- 10:43:54
pod <- 10:43:54

从上面的例子可以看到两点:

  • 从 Pod 创建到第一个容器运行很慢,大约需要 20-30 秒
  • git-init 启动之后,到开始运行克隆脚本时间不稳定

因此考虑,能不能通过加速容器启动来减少执行时间?

3. 使用 tuned 将主机 CPU 设置为高性能模式,加快容器启动

CICD 构建使用的是物理机,在交付使用时不一定对其 CPU 工作模式进行了合理设置。CPU 的工作模式会对 CPU 工作频率产生影响,有可能导致 Pod 的启动速度慢[1]。

  • 查看 CPU 工作频率
1
2
3
grep -i mhz /proc/cpuinfo

cpu MHz		: 1641.500
  • 查看 CPU 工作模式
1
2
3
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

powersave
  • 切换为 aliyun Ubuntu 源
1
2
3
4
cp /etc/apt/sources.list /etc/apt/sources.list.bak
sed -i "s/cn.archive.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list
sed -i '/^#/d' /etc/apt/sources.list
apt-get update
  • 安装 tuned
1
apt install -y tuned tuned-utils tuned-utils-systemtap
  • 启动并查看状态
1
2
3
systemctl start tuned
systemctl enable tuned
systemctl status tuned
  • 获取当前模式
  • 设置为性能模式
1
tuned-adm profile throughput-performance

可选项有:

latency-performance 延迟性能优化
network-latency 网络延迟优化
network-throughput 网络吞吐量优化
throughput-performance 吞吐性能优化
virtual-guest 虚拟机优化
virtual-host 虚拟机宿主机优化

throughput-performance 下 CPU 会以最高频率运行,Pod 启动第一个容器需要 23 秒,比之前的 46 秒提升不少。

全部机器设置为性能模式之后,大量测试时发现代码克隆的总时长并不会显著降低。原因是,构建机配置为 40C/125GB,已经具有足够 CPU;虽然主机 CPU 处于省电模式,但是其大部分工作频率接近最高频率,并没有处于很低的状态。出现上面 46 秒 减少到 23 秒的优化,可能只是偶现效果,CPU 全部以最高频率工作时应该能抑制这种波动。

CPU 的性能模式有利于构建加速,提供平稳的响应时间。在构建环境下,强烈建议开启 CPU 性能模式。

4. Tekton 使用 ReadWriteMany 存储提高并行度

默认情况下 Tekton 会使用 ReadWriteOnce 存储,因为其更加通用。使用 ReadWriteMany 的前提是集群的存储系统支持这种模式。下面以 Longhorn 为例对 ReadWriteMany 进行测试:

  • 安装 NFS Client

Longhorn 的 ReadWriteMany 卷依赖于 NFS Client。

1
apt-get -y install nfs-common
  • 在提交 PipelineRun 时,设置存储为 ReadWriteMany
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "kind": "PipelineRun",
  "apiVersion": "tekton.dev/v1beta1",
  "metadata": {
    "name": "test"
  },
  "spec": {
    "pipelineRef": {
      "name": "test"
    },
    "workspaces": [
      {
        "volumeClaimTemplate": {
          "spec": {
            "accessModes": ["ReadWriteMany"]
          }
        }
      }
    ]
  }
}

经过测试,发现 ReadWriteMany 与 ReadWriteOnce 模式耗时没有明显差别。

原因是: 如果多个 Pod 在同一个节点上,ReadWriteOnce 模式下也允许同时访问。ReadWriteOnce、ReadOnlyMany、ReadWriteMany 指的是 Node 与 PV 的对应关系,而不是 Pod 与 PV 的对应关系 ,这可能是被很多人忽略的一点。在 Kubernetes 1.22+ 版本,新增的 ReadWriteOncePod 针对的才是 Pod 与 PV 的对应关系[2]

回到 Tekton 构建的场景,由于开启了 affinity-assistant 导致一条流水线都在一个节点执行,ReadWriteOnce 与 ReadWriteMany 模式此时差别不大。

5. 关闭 affinity-assistant 分散任务到多节点

affinity-assistant 使得单条流水线创建的 Pod 都在一个节点上。为了让 Pod 启动更快,这里尝试将克隆多个仓库的任务分散到多个节点上,以减少 IO 和创建 Pod 的压力。

5.1 关闭 Tekton 的 affinity-assistant

编辑 Tekton 的配置文件[3]:

1
kubectl edit configmap feature-flags -n tekton-pipelines

disable-affinity-assistant 设置为 true。

这里发现另一个很有用的参数 pruner,能够自动清理 taskrun、pipelinerun。这给构建集群的运维提供了极大便利。

此时,同一条流水线创建的 Pod 不再强行绑定在一个节点上运行,而是可以分散到其他节点。这样做的优劣如下:

优势

  • 充分利用多节点,创建 Pod 并执行任务
  • 在 Pod 调度方面有更多调优、定制的空间

劣势

  • 对存储系统有要求,不能用 hostpath 方式
  • 增加节点之间的网络传输
  • 可能导致 task 之间产物传递故障,比如上一步产生的镜像,下一步调度到其他节点之后主机上找不到

5.2 测试验证

  • disable-affinity-assistant + ReadWriteOnce ,执行时间明显增长

下面是截取的部分执行时长数据:

原因在于,克隆的 Pod 被分散到多个 Node 之后,Node 之间出现了对存储使用上的竞争。也就是 Node2 上的任务需要等待 Node1 上的任务执行完成之后,才能执行。

  • disable-affinity-assistant + ReadWriteMany ,执行时长无明显变化

下面是截取的部分执行时长数据:

ReadWriteMany 模式下不同 Node 上的 Pod 能同时使用存储,但是额外增加了网络开销。一加一减,整体执行时长没有太大波动。从 Pod Status 和 Log 中获取的数据,也验证了上述观点。以下为执行的时间线:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pod -> 00:12:04
    distroless-base -> 00:12:22
    distroless-base <- 00:12:22
    pipeline-entrypoint -> 00:12:24
    pipeline-entrypoint <- 00:12:24
    pipeline-git-init -> 00:12:25
        clone -> 00:12:33
        clone <-> 00:12:48
    pipeline-git-init <- 00:12:48
pod <- 00:12:48

创建容器只花了 18 秒,但是克隆脚本的执行时长平均都超过 10 秒,出现明显增长。

存储这部分,还有一个优化是使用 Longhorn 的 strict-local 模式。编辑 Longhorn 的配置文件:

1
kubectl -n longhorn-system edit cm longhorn-storageclass

将 numberOfReplicas 设置为 1,将 dataLocality 设置为 strict-local。 strict-local 是 Longhorn 1.4 提供的新特性,直接使用本地 Unix Socket 代理 IO 操作,而不是网络 TCP。但在构建场景下,经过测试此处不是瓶颈。

6. 优化 Etcd 以加快集群响应

在查看系统各组件时,Etcd 的日志引起了我的注意。

6.1 将 Etcd 迁移到更快的磁盘,降低延时

  • etcd 大量 warning 日志
1
2
3
4
5
6
7
journalctl -u etcd.service -f

08:42:37 node1 etcd[1091]: read-only range request "key:\"/registry/minions/node1\" " with result "range_response_count:1 size:14068" took too long (107.736306ms) to execute
08:42:37 node1 etcd[1091]: read-only range request "key:\"/registry/pods/xxx/p-cfhlvsaf16letp8btsp0-fetch--rw-7qrpm\" " with result "range_response_count:1 size:20887" took too long (103.499181ms) to execute
08:42:37 node1 etcd[1091]: read-only range request "key:\"/registry/pods/xxx/p-cfhlvi2f16letp8btsmg-docker-build-rbrzl-pod-fjk2l\" " with result "range_response_count:1 size:22666" took too long (115.939841ms) to execute
08:42:37 node1 etcd[1091]: read-only range request "key:\"/registry/tekton.dev/pipelines/xxx/p-cbqcugeb23tefrmhs5jg\" " with result "range_response_count:1 size:12536" took too long (157.681896ms) to execute
08:42:37 node1 etcd[1091]: read-only range request "key:\"/registry/pods/xxx/p-cfhlvsaf16letp8btsp0-fetch--rw-7qrpm\" " with result "range_response_count:1 size:20853" took too long (147.333596ms) to execute
  • 通过监控可以看到 Etcd 磁盘延时很高,接近 200ms

  • 关闭 Etcd 服务
  • 更新 Etcd 数据目录
1
2
3
vim /etc/etcd.env

ETCD_DATA_DIR=/data/etcd

/data 目录挂载的是一块 SSD,而 /var/lib/ 是系统盘 HDD。

  • 迁移数据
  • 启动 Etcd
  • 使用 SSD 之后 Etcd 磁盘延时有所降低,接近 100ms 但远没有达到目标值 25ms 以下

另外一个可能的原因在于 kube-status-metrics 开启了 labels 和 annotations 采集,导致 kube-apiserver 的压力上升。因此将关掉 kube-state-metrics,再观察 Etcd 指标,但并没有看到有明显优化效果。

6.2 提高 Etcd 进程的 IO 优先级

持续集成极其消耗 CPU、Mem、IO、Network 资源,而 Tekton 的基础运行时是 Kubernetes ,Etcd 又是 Kubernetes 的存储核心。因此,有必要保持 Etcd 进程具有最高的优先级,以减少管理平面的时间消耗。

1
ionice -c2 -n0 -p $(pgrep etcd)

6.3 拆分 Event 事件到新的 Etcd 集群

  • 部署 Etcd

这里比较特殊的是,我采用的是 Kubekey 部署的集群,默认 Etcd 证书已经包含全部集群节点 IP。因此,我直接将其中一个 Etcd 拷贝到新节点运行一个新的 Etcd 集群,修改 ETCD_INITIAL_CLUSTER_STATE=new 即可。

否则,如果 Etcd 集群采用 TLS 连接,可能得重新生成并更新 kube-apiserver 中的 Etcd 证书。

  • 编辑全部 master 节点的 kube-apiserver ,添加 etcd 配置
1
vim /etc/kubernetes/manifests/kube-apiserver.yaml

新增如下配置[4]

1
    - --etcd-servers-overrides="/events#https://5.5.5.5:2379"

等待 kube-apiserver 重启完成。

  • 在新的 Etcd 集群查看节点状态
1
2
3
4
5
6
7
etcdctl --write-out=table endpoint status

+----------------+------------------+---------+---------+-----------+-----------+------------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+----------------+------------------+---------+---------+-----------+-----------+------------+
| 127.0.0.1:2379 | 173a84b520278cc1 |  3.4.13 |   18 MB |      true |         2 |      11223 |
+----------------+------------------+---------+---------+-----------+-----------+------------+

如果看到 DB SIZE 不断增加,就说明 Event 事件已经拆分到了新的 Etcd 集群。

  • 查看优化效果

这是在工作时间段的监控截图,有些难以置信的是经过 SSD、剥离 Event 事件之后,Etcd 磁盘延时竟然只有 10 ms。

但 Etcd 部分的优化,可能对构建时长有 1-2 秒的优化效果,这远不是理想的结果。在分析 Kubelet 日志、源码之后,使用 NFS 是一个不错的优化点。

7. 使用 NFS 存储能有效加快带存储卷的 Pod 创建

创建时,如果对存储有依赖,Pod 会持续地等待,会导致容器创建慢。下面是一个简化之后的例子,其中,nodeName 指定了节点避免集群调度的干扰;imagePullPolicy 设置为 IfNotPresent 避免拉取镜像的干扰。

  • 无存储的负载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: Pod
metadata:
  name: test-pv
spec:
  nodeName: node1
  containers:
    - image: nginx
      imagePullPolicy: IfNotPresent
      name: nginx

下面是时间序列:

1
2
3
4
pod -> 03:01:18
    nginx -> 03:01:19
    nginx <-
pod <-

在镜像已经提前拉取的情况下,启动第一个容器花了 1 秒。

  • 有存储的负载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: v1
kind: Pod
metadata:
  name: test-pv
spec:
  nodeName: node1
  containers:
    - image: nginx
      imagePullPolicy: IfNotPresent
      name: nginx
      volumeMounts:
        - mountPath: /data
          name: data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: test-pv
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pv
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

下面是时间序列:

1
2
3
4
5
6
7
pvc -> 09:55:09
pv -> 09:55:12

pod -> 09:55:09
    nginx -> 09:55:27
    nginx <-
pod <-

以下为 Pod 使用 PV 的流程为[5],时间点主要从 Kubelet、iscsid 日志中分析得出:

  • 存储组件创建 pv -> 09:55:12
  • attach 挂载到 Pod 所在 Node -> 09:55:14(iscsi connected)-09:55:25(kubelet attached)
  • mount 挂载到 Pod -> 09:55:26

可以看到 attach 花费了太多时间,因此换为免 attach 的 NFS 作为后端存储。

经过测试大约能有 21 秒的加速效果。

从原来的执行时长:

2 分钟 13 秒、2 分钟 34 秒、2 分钟 37 秒、2 分钟 28 秒、2 分钟 25 秒

缩短为:

1 分钟 58 秒、2 分钟 20 秒、2 分钟 16 秒、1 分钟 58 秒、2 分钟 1 秒

在配合 disable-affinity-assistant 之后,大约又能节约 10 - 20 秒,下图是测试数据:

8. 总结

最近一直在尝试从运维的角度优化构建慢的问题。

本篇是关于 Tekton 执行克隆任务慢问题的优化。通过使用 NFS 存储,大约能减少 20 秒 Kubelet 创建 Pod 的时间;关闭 affinity-assistant 功能将单条流水线的 Pod 分散到多个节点,大约能减少 10-20 秒启动速度 ;由于测试数据集有限,目前观测到的效果是之前 2 分 30 秒的克隆流水线,现在 2 分钟以内能完成,大约有 30 秒的优化提升。当然,更快的构建方式是,一个 Pod 多仓库克隆、保持 PV 不销毁,但调整过大,不在本次运维优化范围。

以下为本文主要观点:

  • CPU 高性能模式有利于 Pod 快速启动
  • ReadWriteOnce、ReadWriteMany 描述的是 Pod 与 Node 的关系,ReadWriteOnce 模式下,同一个 Node 的多个 Pod 可以同时使用 PV
  • 带存储卷的 Pod 启动速度比不带存储的 Pod 慢很多,大约能慢 10 多秒
  • Tekton 默认配置下,一条流水线只能在一个节点构建;通过参数 disable-affinity-assistant 可以关闭这一特征,提高并行 task 的并行度
  • 使用 SSD、拆分 Event 能够显著降低 Etcd 的磁盘压力、提高响应速度
  • NFS 下带存储卷的 Pod 创建速度明显快于 OpenEbs、Longhorn

9. 参考

  1. https://zhangguanzhang.github.io/2019/04/28/k8s-java-start-time-not-same/
  2. https://kubernetes.io/zh-cn/docs/concepts/storage/persistent-volumes/#access-modes
  3. https://tekton.dev/docs/operator/tektonconfig/
  4. https://imroc.cc/kubernetes/best-practices/ops/etcd-optimization.html
  5. https://www.lixueduan.com/posts/kubernetes/14-pv-dynamic-provision-process/