K8S容器环境下disk IO性能调优

背景

某云原生 application 在部署阶段对磁盘 I/O 有明确要求:
在挂载的 volume 上执行如下命令时,返回结果必须 ≥ 1MB/s。

1
dd if=/dev/zero of=./test2.img bs=4k count=10240 oflag=direct
  • 顺序写
  • 使用 O_DIRECT(无 page cache)
  • 无 fsync
  • 无元数据更新
  • 无 WAL / journal
  • 无随机写
  • 无多层 flush

这是一个 4K 小块、无缓存、直写磁盘的测试场景。
如果在该条件下仍能满足性能要求,基本可以覆盖关系型数据库的所有严苛 I/O 场景。

系统架构与部署环境

  1. 3 台 Baremetal(HP DL380 Gen11)

    • CPU:96 Core × 4
    • 内存:512 GB
    • 磁盘:4 × 4T HDD + 4 × 4T SSD
    • 网络:10G 光口
  2. HDD 做 RAID1 用于 Host OS,SSD 不做 RAID

  3. Host OS:Ubuntu 24.04,启用 KVM

  4. 每台 Host 上运行多个 VM,其中部分 VM 挂载 SSD

  5. VM 上部署 OpenShift

  6. 3 个挂载 SSD 的 VM 作为存储节点,部署 ODF(Ceph)

    • 每块 SSD 对应一个 Ceph OSD
  7. OpenShift 内部署 MariaDB 集群

    • 1 Master + 2 Slave
    • 使用 Ceph RWO Volume
  8. 在 MariaDB 数据卷中执行 I/O 测试

调优过程

初始测试结果约 500~600 KB/s,远低于目标。

  • Host 与 VM 层磁盘性能正常(SSD >100MB/s)
  • VM 磁盘使用 virtio + cache=none + io=native
1
2
3
4
5
6
<disk type='block' device='disk'>
  <driver name='qemu' type='raw' cache='none' io='native' iothreads='8'/>
  <source dev='/dev/nvme0n1'/>
  <target dev='sda' bus=virtio'/>
  <address type='pci' controller='0' bus='0' target='0' unit='0'/>
</disk>

徘徊多次后,仍旧提升非常有限,检查ceph的性能指标是发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@service:~#  oc exec $(oc get pod -n openshift-storage | grep tools | head -n 1) -n openshift-storage -it -- bash
bash-5.1$ ceph osd perf
osd commit_latency(ms) apply_latency(ms)
11 0 0
10 0 0
9 0 0
8 0 0
7 0 0
6 0 0
5 4 4
4 0 0
3 0 0
2 4 4
1 2 2
0 5 5

这里很明显的发现问题,osd网络延时有问题,不在同一个host上的osd延时明显较大,对比site上的情况,osd之间的延时是低于1ms的。
进行下一步验证网络延时发现,VM和VM之间,甚至HOST之间的ping值也是高于1ms的,所以基本锁定,性能上不去基本上是网络延时引起的,而不是disk I/O本身性能瓶颈引起。
为了进一步验证,把ceph的副本个数从三个改成一个,测试的结果有了明显的提升。

错误的尝试

中间尝试把HOST的网卡开启SR-IOV,网络延时瞬间降到0.1ms,提升明显,但是部署OCP的过程遇到很多问题,而且考虑到NUMA的影响,资源会浪费一半,无法走通,遂放弃。

尝试更改MTU,从1500增加到8500,有提升到700K/s,仍是无法满足要求,而且OCP更改MTU的成本也挺高。

核心瓶颈

在同另外一套lab环境对比以后,发现有几不同,我们的网卡类型,英特尔的网卡+10G光口,另一套的lab是高通的网卡+10G电口,网速ping值的差距非常明显,一个是1ms,一个是0.1ms,怀疑网卡配置,但是调试多次以后,甚至交换网卡,仍然没有明显的改变。
最后猜测是不是CPU的调度问题,发现两个OS,一个是Ubuntu,一个是redhat,查询以后发现Ubuntu在很久前就引入了CPU idle(C-states),Ubuntu 默认启用内核的 cpuidle 子系统,允许 CPU 进入多级低功耗状态(如 C1, C2, C3 … 更深 C-states),Ubuntu 24.04 引入并默认使用 power-profiles-daemon 来统一管理电源策略,包括 idle、freq 和 sleep profiles。这意味着除了 CPU idle states 外,Ubuntu 24.04 会:

  • 在桌面环境中提供电源模式(Balanced/Power-Saver/Performance)选项

  • 默认是 Balanced 模式,结合 P-states + idle states 使功耗更低

https://help.ubuntu.com/stable/ubuntu-help/power-profile.html.en

检查Host OS以后,果然是默认开启了这个配置,修改成Performance模式,果然网速ping值里面提升到0.1ms, ceph的性能立马延时也降到1ms内。
CPU Power info
再次测试dd,效果立竿见影达到1.4M/s,达成目标。

总结

  1. 确认问题是网络延时引起的,明确瓶颈方向后持续深挖,避免无效调参。
  2. HostOS尽量使用成熟的商业版本,推荐RedHat/RockyLinux。
  3. 上层有软件级别的存储服务(Ceph),物理层就不要做raid了。
  4. KVM跨node进行部署的时候还是会有小问题,优选还是安装openstack作为VNF层。

P.S. 以下命令会直接破坏磁盘数据,注意of的指向目录,请勿在生产环境使用:

1
dd if=/dev/zero of=/dev/vdd bs=4k count=10240 oflag=direct

2025年的足球比赛记录,定格在63场

今天中午在公司的比赛场地完成了今年的最后一次足球比赛,完美收官了。

统计记录来到了63场,122个小时,总消耗了超过了12万千卡,还是挺有成就感的。

大大超过了今年的预期,和去年的比例相当。(2024年6月-12月,37次)

统计

明年加油!!!

一分钟快乐两次

在给XBot官方反馈了两次以后,APP得到了更新,每次的视频录像合并和处理的功能基本上满足日常需求了。

这周日的比赛,前三个星期一直没有踢,所以安全为上,踢了大半场的中后卫,最后几分钟冲上去打了一会饼锋,没想到特别快乐,斯特林附体。

请欣赏这一分钟两次的快乐足球。

一个困扰四年的python公钥解密的问题

在这篇文章中,介绍了如何使用飞天信诚的加密狗设备进行加密解密。
但是文章的最后,有一个遗憾,就是需要借助官方给的php代码来进行解密,自己尝试了几次用python实现,但是总是失败,今天终于发现了解决之道。

需要安装一个pycryptodome。

1
pip3 install pycryptodome -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

最后的代码:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def check_rsa_code(encrypted_str: str) -> str:
"""
使用公钥解密(等效于 PHP 的 openssl_public_decrypt)
适配 PKCS#1 v1.5 两种 padding 格式 (0x01/0x02)

:param encrypted_str: URL 编码 + Base64 编码的密文
:return: 解密后的明文字符串
"""
# === 1. RSA 公钥 ===
public_key_pem = """-----BEGIN PUBLIC KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxx
-----END PUBLIC KEY-----"""

# === 2. 处理输入:urldecode + base64 decode ===
# PHP 里是 rawurldecode(urlencode(urldecode(...))),等效这里直接 unquote 一次
encrypted_str = unquote(encrypted_str)
encrypted_bytes = base64.b64decode(encrypted_str)

# === 3. 导入 RSA 公钥,并获取模数 (n) 和指数 (e) ===
rsa_key = RSA.import_key(public_key_pem)
n = rsa_key.n
e = rsa_key.e

# === 4. 转换为整数,执行 RSA 运算 (cipher^e mod n) ===
cipher_int = int.from_bytes(encrypted_bytes, byteorder="big")
plain_int = pow(cipher_int, e, n)

# === 5. 转回字节数组,长度按密钥大小补齐 ===
k = (rsa_key.size_in_bits() + 7) // 8
plain_bytes = plain_int.to_bytes(k, byteorder="big")

# === 6. 处理 PKCS#1 v1.5 Padding ===
# 格式有两种:
# - 00 01 FF FF ... FF 00 <data>
# - 00 02 xx xx ... xx 00 <data>
if len(plain_bytes) > 2 and plain_bytes[0] == 0x00:
if plain_bytes[1] == 0x01: # 类型 1:FF 填充
sep_idx = plain_bytes.find(b"\x00", 2)
if sep_idx > 0:
plain_bytes = plain_bytes[sep_idx+1:]
elif plain_bytes[1] == 0x02: # 类型 2:随机字节填充
sep_idx = plain_bytes.find(b"\x00", 2)
if sep_idx > 0:
plain_bytes = plain_bytes[sep_idx+1:]
else:
# 如果不是标准 padding,就去掉前导 0
plain_bytes = plain_bytes.lstrip(b"\x00")
else:
plain_bytes = plain_bytes.lstrip(b"\x00")

# === 7. 返回解码结果(明文是 UTF-8 字符串,如 "3238095759") ===
return plain_bytes.decode("utf-8")

这样就解决了这一问题,完全不再依赖PHP了,真好!
困扰了四年的问题,就这样解决了~

一次精彩的助攻

前一阵得到一笔意外收入,为了在葫芦娃俱乐部踢的更开心,和老边商量决定买一个xbot足球比赛录像设备。

开始用着感觉不错,但是有两点挺鸡肋,一是这个设备不能自己录像,设备只提供运动跟踪和云台功能,录像需要用手机的摄像头,这样就需要占用手机,如果被电话等程序打断,录制有可能中断。二是如果想快速剪辑,就需要在录像的时候用遥控器操作打点,这样如果我在场上踢比赛,是无法操作的,没有打点记录,剪辑的时候时间成本就比较高了。

但是,昨天的比赛中,当他录到我的一个高光时刻的时候,我对他的印象分又提高了不少。

请欣赏这一记精彩的后脚跟磕球助攻。

现在的两个解决方案:

  • 录像现在直接传到B站,清晰度,速度都可以接受,这样不剪辑也可以。录制完直接传,占用不了多少时间。

  • 视频录像上有实时的时间水印,这一点比较好,那么就需要在精彩时刻发生的时候,想办法快速的记录下时间,并且编辑的时候,直接补打上点,就可以快速的剪辑出来精彩集锦了。

所以目前唯一的不足,是如何在场上踢球的时候,也能多次快速的记录下准确的时间呢?

一段空档期

无锡的项目搞的差不多了,从忙碌的疯狂进代码的阶段进入了短暂的空闲期。
啤酒城的项目今年没有啥大的改动,等合同敲定,活动开业前,把服务器重新部署上线即可。
不夜城街区的项目,肥城的已经完成,等剑阁项目和临夏项目开业,服务器部署一下,活也不是太多。

那么,得琢磨个事情干干,这段空档期干点啥好呢~

version
软件的版本,又何尝不是人生的版本呢?

阿里的云小店

最近在把分散在各地的项目进行了整合,计划合并到一个服务器上,搬家工作搞了整整一天。

研究了一番,最后把去年项目剩的服务器全给取消了,买了一个阿里云的云小站 https://www.aliyun.com/minisite/goods 的ECS。

配置有两种:

  1. 2核2G,3M固定带宽,40G ESSD Entry云盘 ¥99.00/1年起 官网折扣价: ¥956.64/1年

  2. 2核4G,5M固定带宽,80G ESSD Entry云盘 ¥199.00/1年起 官网折扣价: ¥2507.70/1年

物超所值,几乎不到1折的价格,更重要的是,能不限年限的续费,一个账户只能嫖一个。

直接199的配置走起,2核4G完全够用。跑了两个Python Flask, 两个Python FastAPI, 两个静态site,还能剩余2.2G内存,妥妥够用。

感谢阿里云~

prettier格式化代码

PyCharm里如果有js, html 和css文件,社区版本的IDE是不能直接格式化这些静态文件,需要专业版的来实现。
所以,可以尝试在terminal里直接使用prettier格式化代码。

1
2
3
4
5
6
7
# 用node全局安装
npm install -g prettier

# 格式化项目里的所有的js,jsx,ts,tsx,css,html,json
# 在宽屏幕下,可以设置代码的折行宽度大一些,在我的屏幕上180是个不错的选择。
npx prettier --write "**/*.{js,jsx,ts,tsx,css,html,json}" --print-width 180

@lru_cached装饰器实现全局配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from functools import lru_cache

from fastapi import Depends, FastAPI
from typing_extensions import Annotated

from . import config

app = FastAPI()


@lru_cache
def get_settings():
return config.Settings()


@app.get("/info")
async def info(settings: Annotated[config.Settings, Depends(get_settings)]):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}

使用了 @lru_cache 装饰器,因此只有在第一次调用它时,才会创建 Settings 对象一次。
然后,在下一次请求的依赖项中对 get_settings() 进行任何后续调用时,它不会执行 get_settings() 的内部代码并创建新的 Settings 对象,而是返回在第一次调用时返回的相同对象,一次又一次。

@lru_cache 技术细节

@lru_cache 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。
因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。

对于我们的依赖项 get_settings(),该函数甚至不接受任何参数,因此它始终返回相同的值。

这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。

@lru_cache 是 functools 的一部分,它是 Python 标准库的一部分,可以在 Python 文档 lru_cache中了解有关 @lru_cache 的更多信息。

时差攻击

今天学习FastAPI时,细致了解了一下时差攻击。

时差攻击

什么是时差攻击?

假设攻击者试图猜出用户名与密码。

他们发送用户名为 johndoe,密码为 love123 的请求。

然后,Python 代码执行如下操作:

1
2
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...

但就在 Python 比较完 johndoe 的第一个字母 j 与 stanleyjobson 的 s 时,Python 就已经知道这两个字符串不相同了,它会这么想,没必要浪费更多时间执行剩余字母的对比计算了。应用立刻就会返回错误的用户或密码。

但接下来,攻击者继续尝试 stanleyjobsox 和 密码 love123。

应用代码会执行类似下面的操作:

1
2
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...

此时,Python 要对比 stanleyjobsox 与 stanleyjobson 中的 stanleyjobso,才能知道这两个字符串不一样。因此会多花费几微秒来返回错误的用户或密码。

反应时间对攻击者的帮助

通过服务器花费了更多微秒才发送错误的用户或密码响应,攻击者会知道猜对了一些内容,起码开头字母是正确的。

然后,他们就可以放弃 johndoe,再用类似 stanleyjobsox 的内容进行尝试。

专业攻击

当然,攻击者不用手动操作,而是编写每秒能执行成千上万次测试的攻击程序,每次都会找到更多正确字符。

但是,攻击者利用时间差,就能在几分钟或几小时内,以这种方式猜出正确的用户名和密码。

使用 secrets.compare_digest() 修补

在此,代码中使用了 secrets.compare_digest()。

简单的说,它使用相同的时间对比 stanleyjobsox 和 stanleyjobson,还有 johndoe 和 stanleyjobson。对比密码时也一样。

在代码中使用 secrets.compare_digest() ,就可以安全地防御全面攻击了。

1
2
3
4
5
6
7
8
9
10
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = b"stanleyjobson"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = b"swordfish"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)

Powered by Hexo and Hexo-theme-hiker

Copyright © 2012 - 2026 tiaobug.com All Rights Reserved.

鲁ICP备2024124237号-1