[转]K8S 六种存储解决方案的性能比较测试
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[转]K8S 六种存储解决方案的性能比较测试相关的知识,希望对你有一定的参考价值。
参考技术A原文地址: https://toutiao.io/posts/nmflsd/preview
大多数开发人员都认为在部署集群时选择合适的存储技术极为重要。但是,在 Kubernetes 这片市场上,人们对于存储技术的选择并没有一个标准的答案。本文将 介绍 Kubernetes 中常用的 6 种存储,分析它们的使用场景和优缺点,并对它们进行性能测试,找出哪种存储解决方案才是最理想的选择。
存储技术的选择在很大程度上取决于工程师们要运行的工作负载类型。如果你正在使用 Kubernetes,你可能偏向于通过动态配置使用 volume 来进行块存储。对于裸机集群,你就需要为你的用例选择最为合适的技术,并将其集成到你的硬件上。
此外,例如 AKS、EKS 和 GKE 等公有云可以开箱即用并带有块存储,但这并不意味着它们是最好的选择。在许多情况下,默认公有云存储类的故障转移时间较长。例如,在 AWS EBS 中存在一个故障的 VM,附在此 VM Pod 上的 volume 需要 5 分钟以上的时间才能在另一个节点上重新联机。但是,像 Portworx 和 OpenEBS 此类云原生存储就可以很快解决这种问题。
本文的目的就是寻找在 Kubernetes 中最常用的存储解决方案,并进行基本性能比较。本次测试使用以下存储后端对 Azure AKS 执行所有测试:
测试结果
*注:请将结果作为存储选择期间的标准之一,但不要仅根据本文的数据做出最终判断。
在描述过程之前,我们先总结一下最终结论。如果我们忽略本地 Azure pvc 或 hostPath,测试结果是:
那么这究竟是为什么呢?让我们从每个后端存储介绍安装说明开始,详述 AKS 测试的具体过程!
各存储解决方案的安装及优缺点
*注:本节不含 Azure hostpath 的安装介绍。
本文将把 Azure 原生 Storage Class 作为所有测试的基线。Azure 会动态创建托管磁盘并将其映射到 VM 中,其中 Kubernetes 为 Pod 的 volume。
如果只是为了使用它,你没有必要再做其他事情。当你配置新的 AKS 集群时,它会自动预定义为“default”和“managed-premium”两种存储类。Premium 类将使用基于 SSD 的高性能和低延迟磁盘。
优点:
弊端:
Ceph Rook 需要设计特定的硬件配置,根据数据类型生成 pg 组,配置日志 SSD 分区(在 bluestore 之前)并定义 crush 映射。 它是一种处理整个存储集群安装的简洁方法。
在 AKS 上安装 Ceph Rook :
优点:
弊端:
GlusterFS 是一个的开源存储解决方案。 Heketi 是 GlusterFS RESTful volume 的管理界面。它提供了一种便捷的方式让 GlusterFS 具有动态配置的能力。如果没有这种访问权限,用户就必须手动创建 GlusterFS volume 并将其映射到 Kubernetes pv 上。
*注:关于 GlusterFS 的更多信息,见: https://docs.gluster.org/en/latest/Install-Guide/Overview/
这里使用了 Heketi 快速入门指南[2]进行安装:
以下是修复该问题的 PR:
同时,动态配置问题也会对测试造成一定影响。对于 Kubernetes 控制平面,Heketi restURL 是不可用的。测试人员尝试利用 kube dns record、Pod IP 和 svc IP 来解决这个问题,但是都没有奏效。为此,他们最后选择通过 Heketi CLI 手动创建 volume。
以下是在 Kubernetes 上安装 Heketi Gluster 的更多内容:
优点:
弊端:
OpenEBS 代表了一种新的 CAS(Container Attached Storage)概念,属于云原生存储类别。 它是基于单个微服务的存储控制器和基于多个微服务的存储复制品。
作为开源项目,目前它提供 2 个后端:Jiva 和 cStor。cStor 作为控制器,它的副本部署在一个 namespace(安装 openebs 的 namespace)中,也可以说它采用的是原始磁盘而不是格式化分区。每个 Kubernetes volume 都有自己的存储控制器,它可以在节点可用存储容量范围内进行扩展。
在 AKS 上安装它非常简单:
优点:
弊端:
*注:OpenEBS 团队帮忙修改的测试用例场景,见: https://github.com/kmova/openebs/tree/fio-perf-tests/k8s/demo/dbench
最后为大家介绍一种比较新颖的解决方案。
Portworx 是另一个专为 Kubernetes 设计的云原生存储,专注于高度分散的环境。它是一个主机可寻址存储,每个 volume 都直接映射到它所连接的主机上。它根据应用程序 I/O 的类型提供自动调整。 不幸的是,它不是开源的存储方案。然而,它免费提供了 3 个节点可进行试用。
*注:关于 Portworx 更多信息,见: https://portworx.com/makes-portworx-unique/
在 AKS 上安装 Portworx 很容易,可以使用 Kubernetes 规格生成器:
优点:
弊端:
AKS 测试环境
在本次测试中,测试人员配置了具有 3 个 VM 的基本 Azure AKS 集群。为了能够连接托管的 Premium SSD,测试人员必须使用 E 类型大小的 VM。因此他们选择了 Standard_E2s_v3,只有 2 个 vCPU 和 16GB RAM。
每个 AKS 集群都会自动配置第二个资源组(RG)MC_ <name>,你可以在其中找到所有 VM、NIC 。在 RG 内部,测试人员创建了 3 个 1TB 高级 SSD 托管磁盘并手动将它们连接到每个 VM 中。
它允许我在每个专用于测试的实例中获得 1TB 的空磁盘。据 Azure 称,它的性能可以在 5000 IOPS 和 200 MB/s 吞吐量之间,具体取决于 VM 和磁盘大小。
性能结果
重要说明:单个存储性能测试的结果是无法单独评估的,它们必须相互比较才能显示出差距。测试的方法有很多种,下面是其中较为简单的一种方法。
为了进行测试,测试人员决定使用名为 Dbench 的负载测试器。 它是 Pod 的 Kubernetes 部署清单 , 同时它也是运行 FIO 的地方,并且带有 Flexible IO Tester 等 8 个测试用例。
测试在 Docker 镜像的入口点指定:
注:所有测试的完整测试输出,见:
https://gist.github.com/pupapaik/76c5b7f124dbb69080840f01bf71f924
随机读/写带宽
随机读取测试表明,GlusterFS、Ceph 和 Portworx 在读取时的性能比 AWS 本地磁盘上的主机路径快好几倍,因为它们读取的是缓存。GlusterFS 写入速度最快,它与本地磁盘几乎达到了相同的值。
随机读/写 IOPS
随机 IOPS 显示 Portworx 和 Ceph 效果最佳。Portworx 在写入时的 IOPS 与本机 Azure pvc 几乎相同,这非常好。
读/写延迟
延迟测试返回了有趣的结果,因为本机 Azure pvc 比大多数其他测试存储都慢。Portworx 和 Ceph 实现了最佳读取速度。但是对于写入,GlusterFS 比 Ceph 更好。与其他存储相比,OpenEBS 延迟非常高。
顺序读/写
顺序读/写测试显示与随机测试类似的结果,但 Ceph 的读取是 GlusterFS 的 2 倍多。除了表现非常差的 OpenEBS 之外,写入结果几乎都在同一级别上。
混合读/写 IOPS
最后一个测试用例验证了混合读/写 IOPS,即使在 mixed write 上,Portworx 和 Ceph 也提供了比原生 Azure pvc 更好的 IOPS。
以上就是所有测试结果,希望这篇文章对你有所帮助。
--
参考文献:
[1] https://github.com/rook/rook/blob/master/Documentation/ceph-quickstart.md#ceph-storage-quickstart
[2] https://github.com/gluster/gluster-kubernetes/blob/master/docs/setup-guide.md#deployment
性能测试之-wrk(转)
性能测试之-wrk(转)
转载地址:http://zjumty.iteye.com/blog/2221040
http://www.cnblogs.com/rainy-shurun/p/5867946.html
测试先行是软件系统质量保证的有效手段. 在单元测试方面, 我们有非常成熟的 xUnit 方案. 在集成测试方面, 我们 selenium 等自动化方案. 在性能测试方面也有很多成熟的工具, 比如 LoadRunner, Jmeter 等. 但是很多工具都是给专门的性能测试人员使用的, 功能虽然强大, 但是安装和操作不太方便. 作为开发人员, 我们有些时候想快速验证我们的解决方案是不是存在性能问题, 或者在并发情况下是否有意想不到的问题. 安装 LoadRunner 这样工具, 录制脚本很麻烦, 用起来就像在用大炮打蚊子.
wrk 是一个很简单的 http 性能测试工具. 也可以叫做 http benchmark 工具. 只有一个命令行, 就能做很多基本的 http 性能测试.
wrk 的开源的, 代码在 github 上. https://github.com/wg/wrk
首先要说的一点是: wrk 只能运行在 Unix 类的系统上. 比如 linux, mac, solaris 等. 也只能在这些系统上编译.
这里不得不说一下, 为什么很多人说 mac 是最好的开发环境. 不是因为使用 mac 逼格有多高. 而是你可以同时得到 windows 和 linux 的好处. 多数 linux 下的开发工具都可以在 mac 上使用. 很多都是预编译好的, 有些只需要编译一下就能用.
wrk 的一个很好的特性就是能用很少的线程压出很大的并发量. 原因是它使用了一些操作系统特定的高性能 io 机制, 比如 select, epoll, kqueue 等. 其实它是复用了 redis 的 ae 异步事件驱动框架. 确切的说 ae 事件驱动框架并不是 redis 发明的, 它来至于 Tcl的解释器 jim, 这个小巧高效的框架, 因为被 redis 采用而更多的被大家所熟知.
要用 wrk, 首先要编译 wrk.
你的机器上需要已经安装了 git 和基本的c编译环境. wrk 本身是用 c 写的. 代码很少. 并且没有使用很多第三方库. 所以编译基本不会遇到什么问题.
git clone https://github.com/wg/wrk.git cd wrk make
就 ok了.
make 成功以后在目录下有一个 wrk 文件. 就是它了. 你可以把这个文件复制到其他目录, 比如 bin 目录. 或者就这个目录下执行.
如果编译过程中出现:
src/wrk.h:11:25: fatal error: openssl/ssl.h: No such file or directory #include <openssl/ssl.h>
是因为系统中没有安装openssl的库.
sudo apt-get install libssl-dev
或
sudo yum install openssl-devel
我们先来做一个简单的性能测试:
wrk -t12 -c100 -d30s http://www.baidu.com
30秒钟结束以后可以看到如下输出:
Running 30s test @ http://www.baidu.com 12 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 538.64ms 368.66ms 1.99s 77.33% Req/Sec 15.62 10.28 80.00 75.35% 5073 requests in 30.09s, 75.28MB read Socket errors: connect 0, read 5, write 0, timeout 64 Requests/sec: 168.59 Transfer/sec: 2.50MB
先解释一下输出:
12 threads and 100 connections
这个能看懂英文的都知道啥意思: 用12个线程模拟100个连接.
对应的参数 -t 和 -c 可以控制这两个参数.
一般线程数不宜过多. 核数的2到4倍足够了. 多了反而因为线程切换过多造成效率降低. 因为 wrk 不是使用每个连接一个线程的模型, 而是通过异步网络 io 提升并发量. 所以网络通信不会阻塞线程执行. 这也是 wrk 可以用很少的线程模拟大量网路连接的原因. 而现在很多性能工具并没有采用这种方式, 而是采用提高线程数来实现高并发. 所以并发量一旦设的很高, 测试机自身压力就很大. 测试效果反而下降.
下面是线程统计:
Thread Stats Avg Stdev Max +/- Stdev Latency 538.64ms 368.66ms 1.99s 77.33% Req/Sec 15.62 10.28 80.00 75.35%
Latency: 可以理解为响应时间, 有平均值, 标准偏差, 最大值, 正负一个标准差占比.
Req/Sec: 每个线程每秒钟的完成的请求数, 同样有平均值, 标准偏差, 最大值, 正负一个标准差占比.
一般我们来说我们主要关注平均值和最大值. 标准差如果太大说明样本本身离散程度比较高. 有可能系统性能波动很大.
接下来:
5073 requests in 30.09s, 75.28MB read Socket errors: connect 0, read 5, write 0, timeout 64 Requests/sec: 168.59 Transfer/sec: 2.50MB
30秒钟总共完成请求数和读取数据量.
然后是错误统计, 上面的统计可以看到, 5个读错误, 64个超时.
然后是所以线程总共平均每秒钟完成168个请求. 每秒钟读取2.5兆数据量.
可以看到, 相对于专业性能测试工具. wrk 的统计信息是非常简单的. 但是这些信息基本上足够我们判断系统是否有问题了.
wrk 默认超时时间是1秒. 这个有点短. 我一般设置为30秒. 这个看上去合理一点.
如果这样执行命令:
./wrk -t12 -c100 -d30s -T30s http://www.baidu.com
可以看到超时数就大大降低了, Socket errors 那行没有了:
Running 30s test @ http://www.baidu.com 12 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.16s 1.61s 14.42s 86.52% Req/Sec 22.59 19.31 108.00 70.98% 4534 requests in 30.10s, 67.25MB read Requests/sec: 150.61 Transfer/sec: 2.23MB
通过 -d 可以设置测试的持续时间. 一般只要不是太短都是可以的. 看你自己的忍耐程度了.
时间越长样本越准确. 如果想测试系统的持续抗压能力, 采用 loadrunner 这样的专业测试工具会更好一点.
想看看响应时间的分布情况可以加上--latency参数:
wrk -t12 -c100 -d30s -T30s --latency http://www.baidu.com
Running 30s test @ http://www.baidu.com 12 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.22s 1.88s 17.59s 89.70% Req/Sec 14.47 9.92 98.00 77.06% Latency Distribution 50% 522.18ms 75% 1.17s 90% 3.22s 99% 8.87s 3887 requests in 30.09s, 57.82MB read Socket errors: connect 0, read 2, write 0, timeout 0 Requests/sec: 129.19 Transfer/sec: 1.92MB
可以看到50%在0.5秒以内, %75在1.2s 以内. 看上去还不错.
看到这里可能有人会说了, HTTP 请求不会总是这么简单的, 通常我们会有 POST,GET 等多个 method, 会有 Header, 会有 body 等.
在我第一次知道有 wrk 这个工具的时候他确实还不太完善, 要想测试一些复杂的请求还有点难度. 现在 wrk 支持 lua 脚本. 在这个脚本里你可以修改 method, header, body, 可以对 response 做一下自定义的分析. 因为是 lua 脚本, 其实这给了你无限的可能. 但是这样一个强大的功能如果不谨慎使用, 会降低测试端的性能, 测试结果也受到影响.
一般修改method, header, body不会影响测试端性能, 但是操作 request, response 就要格外谨慎了.
我们通过一些测试场景在看看怎么使用 lua 脚本.
POST + header + body.
首先创建一个 post.lua 的文件:
wrk.method = "POST" wrk.body = "foo=bar&baz=quux" wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
就这三行就可以了, 当然 headers 可以加入任意多的内容.
然后执行:
wrk -t12 -c100 -d30s -T30s --script=post.lua --latency http://www.baidu.com
当然百度可能不接受这个 post 请求.
对 wrk 对象的修改全局只会执行一次.
通过 wrk 的源代码可以看到 wrk 对象的源代码有如下属性:
local wrk = { scheme = "http", host = "localhost", port = nil, method = "GET", path = "/", headers = {}, body = nil, thread = nil, }
schema, host, port, path 这些, 我们一般都是通过 wrk 命令行参数来指定.
wrk 提供的几个 lua 的 hook 函数:
setup 函数
这个函数在目标 IP 地址已经解析完, 并且所有 thread 已经生成, 但是还没有开始时被调用. 每个线程执行一次这个函数.
可以通过thread:get(name), thread:set(name, value)设置线程级别的变量.
init 函数
每次请求发送之前被调用.
可以接受 wrk 命令行的额外参数. 通过 -- 指定.
delay函数
这个函数返回一个数值, 在这次请求执行完以后延迟多长时间执行下一个请求. 可以对应 thinking time 的场景.
request函数
通过这个函数可以每次请求之前修改本次请求的属性. 返回一个字符串. 这个函数要慎用, 会影响测试端性能.
response函数
每次请求返回以后被调用. 可以根据响应内容做特殊处理, 比如遇到特殊响应停止执行测试, 或输出到控制台等等.
function response(status, headers, body) if status ~= 200 then print(body) wrk.thread:stop() end end
done函数
在所有请求执行完以后调用, 一般用于自定义统计结果.
done = function(summary, latency, requests) io.write("------------------------------\\n") for _, p in pairs({ 50, 90, 99, 99.999 }) do n = latency:percentile(p) io.write(string.format("%g%%,%d\\n", p, n)) end end
下面是 wrk 源代码中给出的完整例子:
local counter = 1 local threads = {}
function setup(thread) thread:set("id", counter) table.insert(threads, thread) counter = counter + 1 end
function init(args) requests = 0 responses = 0 local msg = "thread %d created" print(msg:format(id)) end
function request() requests = requests + 1 return wrk.request() end function response(status, headers, body) responses = responses + 1 end
function done(summary, latency, requests) for index, thread in ipairs(threads) do local id = thread:get("id") local requests = thread:get("requests") local responses = thread:get("responses") local msg = "thread %d made %d requests and got %d responses" print(msg:format(id, requests, responses)) end end
测试复合场景时, 也可以通过 lua 实现访问多个 url.
例如这个复杂的 lua 脚本, 随机读取 paths.txt 文件中的 url 列表, 然后访问.:
counter = 1 math.randomseed(os.time()) math.random(); math.random(); math.random() function file_exists(file) local f = io.open(file, "rb") if f then f:close() end return f ~= nil end
function shuffle(paths) local j, k local n = #paths for i = 1, n do j, k = math.random(n), math.random(n) paths[j], paths[k] = paths[k], paths[j] end return paths end
function non_empty_lines_from(file) if not file_exists(file) then return {} end lines = {} for line in io.lines(file) do if not (line == \'\') then lines[#lines + 1] = line end end return shuffle(lines) end paths = non_empty_lines_from("paths.txt") if #paths <= 0 then print("multiplepaths: No paths found. You have to create a file paths.txt with one path per line") os.exit() end print("multiplepaths: Found " .. #paths .. " paths") request = function() path = paths[counter] counter = counter + 1 if counter > #paths then counter = 1 end return wrk.format(nil, path) end
关于 cookie
有些时候我们需要模拟一些通过 cookie 传递数据的场景. wrk 并没有特殊支持, 可以通过 wrk.headers["Cookie"]="xxxxx"实现.
下面是在网上找的一个离职, 取 Response的cookie作为后续请求的cookie
function getCookie(cookies, name) local start = string.find(cookies, name .. "=") if start == nil then return nil end return string.sub(cookies, start + #name + 1, string.find(cookies, ";", start) - 1) end response = function(status, headers, body) local token = getCookie(headers["Set-Cookie"], "token") if token ~= nil then wrk.headers["Cookie"] = "token=" .. token end end
wrk 本身的定位不是用来替换 loadrunner 这样的专业性能测试工具的. 其实有这些功能已经完全能应付平时开发过程中的一些性能验证了.
以上是关于[转]K8S 六种存储解决方案的性能比较测试的主要内容,如果未能解决你的问题,请参考以下文章