设计方案
- 1: 节点内存态统计和计算Node-metrics
- 2: 高性能的序列化和反序列化算法库xxxPack
- 3: 用于Pingmesh直观展现 Pingmesh Heatmap Panel
- 4: 基于eBPF exporter 实现 Kubernetes 和云原生基础设施的可观察性
- 5: 基于Cosign容器镜像签名/验证工具能力,实现Pod可信验证
- 6: 基于节点真实负载情况调度:crane-scheduler-plus
- 7: Pod资源视图隔离
- 8: Kubernetes Pod进程网络带宽 流量控制
- 9: 基于kata direct volume特性, 实现安全容器KataContainer的 CSI block volume直通方案
- 10: Kubernetes Pod进本地磁盘(local,disk,LVM) 进行流量控制
- 11: Kubernetes Namespace 和 Node 做亲和部署
- 12: 基于blackbox构建的Pingmesh体系
- 13: 基于wayne构建的Kubenetes 多集群管理平面 Basa
1 - 节点内存态统计和计算Node-metrics
节点内存态统计和计算agent - Node-metrics
背景
请查看第一篇:https://stack.kubeservice.cn/docs/%E8%AE%BE%E8%AE%A1%E6%96%87%E6%A1%A3/k8s-crane-plus-schduler/
实现
Node Metrics
是内存态统计计算模块,实现metrics的avg
、min
、max
等级的数据聚合查询。
Node Metrics
= Node exporter
+ Prometheus PromSQL
Node Metrics中添加了:
Memory TSDB
, 添加轻量内存化内存存储Statistics
, 实现通用内存avg
、min
、max
等静态function方法Scheduler
, 实现定时采集,数据从proc
中采集统一方法Server Handler
, 数据通过metrics
和statistics
方法对外提供
以存储一天数据为例: 每10s
存储一次,每次存储cpu
、memory
和disk
原生数据 3个
整个存储数量为: 也就是 300KB
不到.
(38Byte(float64)+8Byte(time数据)) 24 * 3600/10 = 276480Byte = 270KB
使用
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app: node-metrics
name: node-metrics
namespace: crane-system
spec:
selector:
matchLabels:
app: node-metrics
template:
metadata:
labels:
app: node-metrics
spec:
containers:
- image: dongjiang1989/node-metrics:latest
name: node-metrics
args:
- --web.listen-address=0.0.0.0:19101
resources:
limits:
cpu: 102m
memory: 180Mi
requests:
cpu: 102m
memory: 180Mi
hostNetwork: true
hostPID: true
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/master
两类接口:
- 接口“/metrics”接口
...
# HELP node_cpu_usage_active cpu usage active.
# TYPE node_cpu_usage_active gauge
node_cpu_usage_active 6.801955214695443
# HELP node_cpu_usage_avg_5m cpu usage avg 5m.
# TYPE node_cpu_usage_avg_5m gauge
node_cpu_usage_avg_5m 6.8018810008297335
# HELP node_cpu_usage_max_avg_1d cpu usage max avg 1d.
# TYPE node_cpu_usage_max_avg_1d gauge
node_cpu_usage_max_avg_1d 6.801955214695443
# HELP node_cpu_usage_max_avg_1h cpu usage max avg 1h.
# TYPE node_cpu_usage_max_avg_1h gauge
node_cpu_usage_max_avg_1h 6.801955214695443
# HELP node_mem_usage_active mem usage active.
# TYPE node_mem_usage_active gauge
node_mem_usage_active 44.272822236553765
# HELP node_mem_usage_avg_5m mem usage avg 5m.
# TYPE node_mem_usage_avg_5m gauge
node_mem_usage_avg_5m 43.68676937682602
# HELP node_mem_usage_max_avg_1d mem usage max avg 1d.
# TYPE node_mem_usage_max_avg_1d gauge
node_mem_usage_max_avg_1d 44.447325557125225
# HELP node_mem_usage_max_avg_1h mem usage max avg 1h.
# TYPE node_mem_usage_max_avg_1h gauge
node_mem_usage_max_avg_1h 44.447325557125225
...
- 接口“/statistics”接口
{
"cpu_usage_active": 6.801955214695443,
"cpu_usage_avg_5m": 6.8018810008297335,
"cpu_usage_max_avg_1d": 6.801955214695443,
"cpu_usage_max_avg_1h": 6.801955214695443,
"mem_usage_active": 44.272822236553765,
"mem_usage_avg_5m": 43.68676937682602,
"mem_usage_max_avg_1d": 44.447325557125225,
"mem_usage_max_avg_1h": 44.447325557125225
}
真实使用
线上每一个DaemonSet
的node-metrics
占用7MB
内存.
以1000个node节点为例子: 7MB*1000 ~= 7GB
比2台32Core 64GB机器节约不少
Source
2 - 高性能的序列化和反序列化算法库xxxPack
对比目前业界高性能库:
- encoding/gob
- encoding/json
- github.com/vmihailenco/msgpack/v5
- labix.org/v2/mgo/bson
- github.com/valyala/fastjson
- github.com/json-iterator/go
- 自研序列化算法
验证数据集合:
type Data struct {
Name string
BirthDay time.Time
Phone string
Siblings int
Spouse bool
Money float64
ExInfo interface{}
}
Benchmark Results
2023-02-09
Results with
Go 1.19.4
MacPro Apple M2 8G macOS Ventura
Darwin 192.168.1.7 22.2.0 Darwin Kernel Version 22.2.0: Fri Nov 11 02:06:26 PST 2022; root:xnu-8792.61.2~4/RELEASE_ARM64_T8112 arm64
环境下,数据:
goos: darwin
goarch: amd64
pkg: github.com/kubeservice-stack/serialization-benchmarks
cpu: VirtualApple @ 2.50GHz
Benchmark_Bson_Marshal-8 2272702 499.2 ns/op 110.0 B/serial 376 B/op 10 allocs/op
Benchmark_Bson_Unmarshal-8 1617357 716.2 ns/op 110.0 B/serial 224 B/op 19 allocs/op
Benchmark_FastJson_Marshal-8 4016576 296.9 ns/op 133.8 B/serial 504 B/op 6 allocs/op
Benchmark_FastJson_Unmarshal-8 1527366 778.7 ns/op 133.8 B/serial 1704 B/op 9 allocs/op
Benchmark_Gob_Marshal-8 467408 2475 ns/op 163.6 B/serial 1616 B/op 35 allocs/op
Benchmark_Gob_Unmarshal-8 95322 12386 ns/op 163.6 B/serial 7688 B/op 207 allocs/op
Benchmark_JsonIter_Marshal-8 1941692 591.5 ns/op 148.7 B/serial 216 B/op 3 allocs/op
Benchmark_JsonIter_Unmarshal-8 1307601 871.8 ns/op 148.7 B/serial 359 B/op 14 allocs/op
Benchmark_Json_Marshal-8 1406618 850.8 ns/op 148.6 B/serial 208 B/op 2 allocs/op
Benchmark_Json_Unmarshal-8 494430 2153 ns/op 148.6 B/serial 399 B/op 9 allocs/op
Benchmark_xxxPack_Marshal-8 3624224 312.8 ns/op 119.0 B/serial 344 B/op 6 allocs/op
Benchmark_xxxPack_Unmarshal-8 2070897 574.7 ns/op 119.0 B/serial 112 B/op 3 allocs/op
Benchmark_Msgpack_Marshal-8 2961548 401.9 ns/op 92.00 B/serial 264 B/op 4 allocs/op
Benchmark_Msgpack_Unmarshal-8 2209035 536.9 ns/op 92.00 B/serial 160 B/op 4 allocs/op
PASS
ok github.com/kubeservice-stack/serialization-benchmarks 23.928s
总结
- xxxPack
一次序列化+一次反序列化
,性能最优
综合排第1 :574.7 ns/op + 312.8 ns/op = 887.5 ns/op - xxxPack 整体性能是 golang原生json库的 3.38倍 :(2153+850.8)/ 887.5 = 3.3845倍
- xxxPack 每次申请操作平均内存申请大小(119B/serial)排 第3; xxxPack Unmarshal 内存申请最小(112 B/op)排第1
- xxxPack Unmarshal 每次操作申请了内存次数 排第2; Marshal 排第4;
3 - 用于Pingmesh直观展现 Pingmesh Heatmap Panel
基于baidu Echarts
库实现变种 heatmap
图形, 对于 pingmesh 进行二维图形化直观展现
为了Grafana
实现此类展现, 通过扩展插件完成
Quick Start
cd pingmesh-heatmap-panel/
yarn install
grunt
安装
cd /var/lib/grafana/plugins
git clone https://github.com/kubeservice-stack/pingmesh-heatmap-panel.git
sudo service grafana-server restart
效果截图
效果图
数据配置
展示效果调整
常见问题
错误诊断
查看grafana
日志
在 mac
日志目录是 /usr/local/var/log/grafana
在 linux
日志目录是 /var/log/grafana
- /var/lib/grafana/plugins/pingmesh-heatmap-panel/*: permission denied , 需要授予插件目录下执行权限:
$ chmod 777 /var/lib/grafana/plugins/pingmesh-heatmap-panel/
grafana > 7.0
参考 Backend plugins: Unsigned external plugins should not be loaded by default #24027
修改grafana配置文件
在mac上一般为 /usr/local/etc/grafana/grafana.ini
在linux上一般为 /etc/grafana/grafana.ini
在[plugins]
标签下设置参数
allow_loading_unsigned_plugins = pingmesh-heatmap-panel
开源工程
4 - 基于eBPF exporter 实现 Kubernetes 和云原生基础设施的可观察性
1. Linux系统性能采集
目前对Linux系统进行性能采集:主要有两个exporter
- node_exporter
- cAdvisor
node_exporter
提供有关基础知识的信息,例如按类型细分的 CPU使用
、内存使用
、磁盘 IO 统计
、文件系统
和网络使用
情况;
cAdvisor
提供类似的指标,但深入到容器级别
。可以看到哪些容器(和 systemd
单元也是 的容器cAdvisor
)使用了多少全局资源
,而不是查看总 CPU
使用率。
但这两类监控都是: 应用层
的统计结果,但是如何直观的监控
,呈现
Linux内核中的io操作, 就需要用到eBPF
解决方案
2. 什么是eBPF
?
eBPF
是相对 BPF
(Berkeley Packet Filter
), eBPF
(extended BPF
) 在为内核追踪Kernel Tracing
、应用性能调优/监控、流控Traffic Control
等
本身eBPF
已经包含在 Linux 内核中包含了。需要在内核中运行的低开销沙盒
用户定义字节码, 需要符合eBPF
规范.
规范要求:
- 低开销: 否则我们不能在生产中运行它;
- 通用的: 不会仅仅局限于 io tracing;
- 开箱即用: 第三方内核模块和补丁不太实用;
- 安全: 不能以崩溃为代价,来获得一些指标;
- CO-RE: 一次编译,在不同内核版本上都可以使用;
通过c语言编写eBPF程序
,然后通过clang/llvm
编译器,将BPF
程序编译为BPF字节码
。然后通过bpf系统调用
,将BPF字节码
注入到内核
中,在注入的时候,我们必须要进过BPF程序的验证,来保证我们写的BPF程序
没有问题,以防干掉我们的系统。然后,在判断是否开启了JIT
,然后开启了,还需要将BPF字节码编译为本机机器码,以加快运行速度。
当我们BPF程序attach
的事件触发了,就会执行我们的BPF程序
,然如是经过JIT编译
过后的就能够直接执行,然后没有开启JIT就需要通过虚拟机进行解析在执行。在执行BPF程序的过程中,会将需要保存的数据存储到map空间
中,用户时候可以从map空间
读取出数据。
BPF是基于事件触发的。
比如: eBPF程序attach
到kprobe类型
的事件上,这个kprobe
事件是个函数,当cpu
执行到这个函数的时候,就会触发。然后就会执行我们的BPF程序。
3. ebpf_exporter
代码分析
使用 YAML
文件进行配置,可自定义eBPF要提取的系统数据,配置文件主要包括三个部分:
- prometheus可识别的特定格式的数据;
- 附加到BPF程序的探针;
- 要执行的BPF程序。
3.1 配置文件
timer.yaml
定义指标名称和labels信息
metrics:
counters:
- name: timer_starts_total
help: Timers fired in the kernel
labels:
- name: function
size: 8
decoders:
- name: ksym
timer.bpf.c
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include "maps.bpf.h"
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u64);
__type(value, u64);
} timer_starts_total SEC(".maps");
SEC("tracepoint/timer/timer_start") // 使用tracepoint跟踪timer_start,其中,跟踪点是timer:timer_start
int do_count(struct trace_event_raw_timer_start* ctx)
{
u64 function = (u64) ctx->function;
increment_map(&timer_starts_total, &function, 1);
return 0;
}
char LICENSE[] SEC("license") = "GPL"; // 内核要求必须是GPL license
3.2 配置代码分析
metrics:
counters:
- name: timer_starts_total
help: Timers fired in the kernel
labels:
- name: function
size: 8
decoders:
- name: ksym
timer.yaml
中定义了一个名为timers的程序
。定义了一个名为timer_start_total
的指标,用于对系统中计时器的使用次数进行计数。定义了一个名为function
的标签,用来向prometheus
提供调用了计时器的函数名,指定了转换函数为ksym
。
SEC("tracepoint/timer/timer_start") // 使用tracepoint跟踪timer_start,其中,跟踪点是timer:timer_start
SEC("tracepoint/timer/timer_start")
定义了使用tracepoint跟踪timer_start,其中,跟踪点是timer:timer_start,这个在内核/sys/kernel/debug/tracing/events/timer中可以找到
第三部分 需要执行BPF程序片段
int do_count(struct trace_event_raw_timer_start* ctx)
{
u64 function = (u64) ctx->function;
increment_map(&timer_starts_total, &function, 1);
return 0;
}
4. eBPF exporter支持hook点
Hook Point | 功能 | 描述 |
---|---|---|
timers | 统计系统中启动计时器的次数 | |
uprobe | bbp k/uprobe 计数统计 | |
shrinklat | 磁盘刷新脏页统计直方图 | |
usdt | USDT 探针 | |
oomkill | 跟踪 OOMKill内核统计次数 | kernel 5.x 版本以上支持 |
biolatency | 将块设备 I/O 延迟统计直方图 | |
accept-latency | Trace TCP accepts延时直方图 | |
bpf-jit | bpf prog JIT 次数 | |
cachestat | 计数缓存内核函数调用统计 | |
cgroup | BPF 程序的检查和简单操作 | |
llcstat | 按 PID 汇总缓存引用和缓存未命中 | |
raw-tracepoints | 原始tracepoints统计 | |
tcp-syn-backlog | TCP SYN backlog | |
tcp-window-clamps | 测量 tcp 拥塞控制状态持续时间 |
5. 适配低版本内核
对于 Kerenl
高版本 5.x
到目前的 6.x
, 都满足功能.
对于 Kerenl3.10+
需要安装部署tot(TCP Option Tracing)
插件暴露接
6. 依赖要求
- BCC 迁移到 libbpf 1.1.0: https://github.com/iovisor/bcc/tree/master/libbpf-tools
- Kernel 要求 4.15+
7. 部署
7.1 ebpf exporter 安装
helm 方式部署: 支持arm64和amd64环境 https://artifacthub.io/packages/helm/kubservice-charts/kubeservice-ebpf-exporter
7.2 grafana 监控
grafana dashboard:https://grafana.com/grafana/dashboards/18612-kernel-ebpf-hook/
8. 其他来源
5 - 基于Cosign容器镜像签名/验证工具能力,实现Pod可信验证
腾讯云TKE集群,在23年5月,上线了一特性: Cerberus 组件支持对签名镜像进行可信验证
支持在Kubernetes
集群中只部署可信授权方签名
的容器镜像,降低在容器环境中的镜像安全
风险。
因此,cosign-webhook
, 功能上类似腾讯Cerberus
. 为了实现一套多云可用
,并支持Docker hub
、Harbor
和Quay
镜像仓库 签名sig
存储的通用解决方案
背景:Cosign 工具
[Cosign](https://github.com/sigstore/cosign)
是 谷歌开源容器镜像的签名和验证工具。 此二进制
工具支持 容器镜像 签名与验证。并继承到谷歌内部容器基础设施distroless中。
Cosign 除了提供给用户可以方便的对镜像签名和验证,还提供免费 OIDC PKI (Fucio)
和内置二进制透明度和时间戳服务 Rekor
, 来托管签名和验证过程中的公钥和私钥
cosign基本使用
1. 签名:cosign keyless
模式,签名证书托管在googleapi open OIDC PKI
上
以Dockerhub
上的dongjiang1989/node-metrics:latest
镜像为例:
因为,是keyless
模式,直接行cosign sign <image>@sha256:<digest>
dongjiang@MacBook Pro:~ $ cosign sign dongjiang1989/node-metrics@sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282
Generating ephemeral keys...
Retrieving signed certificate...
The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/.
Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record.
This may include the email address associated with the account with which you authenticate your contractual Agreement.
This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/.
By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above.
Are you sure you would like to continue? [y/N] y
error opening browser: exec: "xdg-open": executable file not found in $PATH
Go to the following link in a browser:
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=L_clEcpY7BXtgaJ0W5qZJq74ovXNnznXpwYluzCUPEQ&code_challenge_method=S256&nonce=2TSKHjXGVJMxbj4TgFNe6wtXXKg&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=openid+email&state=2TSKHg5tyauZ3PaaVJHyNfRAf1D
Enter verification code: yql4si3wqdvkzrm25ohncuk45
Successfully verified SCT...
tlog entry created with index: 29886419
Pushing signature to: index.docker.io/dongjiang1989/node-metrics
执行期间会跳转到到 https://oauth2.sigstore.dev/auth/auth?xxx
连接 需要用github
或者 google
或者 microsoft
统一登入,验证邮箱, 并在平台上保留公钥和私钥
.
完成验证后,会将signature
文件,提交到镜像仓库
2. 验证:cosign keyless
模式
keyless
模式,直接通过:
cosign verify --certificate-identity <keyless邮箱地址> --certificate-oidc-issuer <认证平台地址> <image>@sha256:<digest>
其中,认证平台地址根据sign in平台,不通进行设置
- github认证: https://github.com/login/oauth
- google认证: https://accounts.google.com
- 微软认证:https://login.microsoftonline.com
dongjiang@MacBook Pro:~ $ cosign verify --certificate-identity dongjiang1989@126.com --certificate-oidc-issuer https://github.com/login/oauth dongjiang1989/node-metrics@sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282
Verification for index.docker.io/dongjiang1989/node-metrics@sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate was verified using trusted certificate authority certificates
[{"critical":{"identity":{"docker-reference":"index.docker.io/dongjiang1989/node-metrics"},"image":{"docker-manifest-digest":"sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282"},"type":"cosign container image signature"},"optional":{"1.3.6.1.4.1.57264.1.1":"https://github.com/login/oauth","Bundle":{"SignedEntryTimestamp":"xxxxxxxxx","Payload":{"body":"xxxxxxxxxxxx=","integratedTime":1691030975,"logIndex":29886419,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}},"Issuer":"https://github.com/login/oauth","Subject":"dongjiang1989@126.com"}}]
3. 本地key方式签名和验证
a. 先创建创建密钥对
dongjiang@MacBook Pro:~ $ cosign generate-key-pair
Enter password for private key:
Enter password for private key again:
Private key written to cosign.key
Public key written to cosign.pub
dongjiang@MacBook Pro:~ $ ls
aaa cosign.key cosign.pub go src tools
b. 通过cosign.key
, 镜行容器镜行签名 cosign sign --key cosign.key <image>@sha256:<digest>
dongjiang@MacBook Pro:~ $ cosign sign --key cosign.key dongjiang1989/node-metrics:sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282
Enter password for private key:
Error: signing [dongjiang1989/node-metrics:sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282]: parsing reference: could not parse reference: dongjiang1989/node-metrics:sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282
main.go:74: error during command execution: signing [dongjiang1989/node-metrics:sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282]: parsing reference: could not parse reference: dongjiang1989/node-metrics:sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282
[root@VM-0-5-centos ~]# cosign sign --key cosign.key dongjiang1989/node-metrics@sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282
Enter password for private key:
The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/.
Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record.
This may include the email address associated with the account with which you authenticate your contractual Agreement.
This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/.
By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above.
Are you sure you would like to continue? [y/N] y
tlog entry created with index: 29887689
Pushing signature to: index.docker.io/dongjiang1989/node-metrics
c. 通过cosign.pub
, 镜行容器镜行验证 cosign verify --key cosign.pub <image>@sha256:<digest>
[root@VM-0-5-centos ~]# cosign verify --key cosign.pub dongjiang1989/node-metrics@sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282
Verification for index.docker.io/dongjiang1989/node-metrics@sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The signatures were verified against the specified public key
[{"critical":{"identity":{"docker-reference":"index.docker.io/dongjiang1989/node-metrics"},"image":{"docker-manifest-digest":"sha256:c1aa0f2861d2eb744efb8f82a1d7d5f1b74919d1cc6501e799daeac1991fc282"},"type":"cosign container image signature"},"optional":{"Bundle":{"SignedEntryTimestamp":"xxxxxxxxxx","Payload":{"body":"xxxxxxxxxx=","integratedTime":1691032440,"logIndex":29887689,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}}]
由于国内对国外google,github访问限制,使用本地签名
方式更有利于与私有化 和 跨云验证
具体实现方式
先通过 CRD
描述 namespace
下 CosignKey
扩展限制
设计如下:
apiVersion: cosignkey.kubeservice.cn/v1
kind: CustomCosignKey
metadata:
name: test-cosignkey
namespace: xxxxx
spec:
authorities:
key:
- |
-----BEGIN PUBLIC KEY-----
......
-----END PUBLIC KEY-----
在namespace
可以是添加label
设置 cosignkey.kubernetes.io/verify : disabled
, 可支持 ignore
namespace下Cosign
验证
注意
- 只有
namespace
添加label
设置cosignkey.kubernetes.io/verify : enabled
且namespace
CustomCosignKey被定义, 才生效 - 同一个
namespace
CRD 只能背定义一次,重复定义出错; - 会对pod中的 initContainer、Container 都会进行验证;
1.为什么CRD需要说明支持多个key?
一个namespace(属于1个租户
),他部署的pod中的容器,可能有几个来源
。 这几个来源的public key可能不通,支持轮训检查
2.配置namespace label
flag 作用?
用于 功能上线
,持续替换 或者 特殊场景下排ta处理
周边生态
- Harbor 直接镜像同步 支持 cosign 签名和验证:https://github.com/goharbor/harbor/releases/tag/v2.5.0
- Skopeo 镜像迁移工具 支持 cosign 镜像迁移: https://github.com/containers/skopeo/pull/1849
- KMS: aws、google 和 azure kms都支持 cosign 容器验证
source
https://github.com/kubeservice-stack/cosign-webhook
其他
6 - 基于节点真实负载情况调度:crane-scheduler-plus
crane-scheduler 解决了Kubernetes
仅仅基于资源的 resource request
进行调度,然而 Pod
的真实资源使用率 real-used
,往往与其所申请资源的 request/limit
差异很大,这直接导致了集群负载不均
, 跟严重者会导致节点Pod被驱逐
通常遇到的现象:
-
1.集群中的部分节点,资源的真实使用率远低于 resource request,却没有被调度更多的 Pod,这造成了比较大的资源浪费;
-
2.而集群中的另外一些节点,其资源的真实使用率事实上已经过载,却无法为调度器所感知到,这极大可能影响到业务的稳定性;
与上云
的最初目的相悖,为业务投入了足够的资源,却没有达到理想的效果。
1. 原生crane-scheduler
Crane-scheduler 基于集群的真实负载数据
构造了一个简单却有效的模型,作用于调度过程中的 Filter
与 Score
阶段,并提供了一种灵活的调度策略配置方式,从而有效缓解了 kubernetes
集群中各种资源的负载不均
问题。换句话说,Crane-scheduler 着力于调度层面,让集群资源使用最大化的同时排除了稳定性的后顾之忧,真正实现「降本增效」
Crane-Scheduler 与社区同类型的调度器最大的区别之一:
- 前者提供了一个泛化的调度策略配置接口,给予了用户极大的灵活性;
- 后者往往只能支持 cpu/memory 等少数几种指标的感知调度,且指标聚合方式,打分策略均受限。
在 Crane-scheduler 中,用户可以为候选节点配置任意的评价指标类型(只要从 Prometheus
能拉到相关数据),不论是常用到的 CPU/Memory
使用率,还是 IO
、Network Bandwidth
或者 GPU
使用率,均可以生效,并且支持相关策略的自定义配置。
2. 正式使用中的问题
- 问题一:当
Kubernetes
节点中Node
节点过于庞大时,Prometheus
资源消耗会是一个很大开销!1000个Node节点
,Prometheus
基本需要32core 64G
的 至少2个node
部署HA版本; 并且还有Prometheus
周边的一堆东西:Operator
、ServiceMonitor
、CRD
等一堆东西 - 问题二:
Prometheus
从旁路监控(offline
系统),变成(online
系统), 降低整个体系稳定性;
基于以上问题,对现有的 scheduler-controller
进行增强:
去掉Prometheus
的依赖,并对node-exporter-plus
进行增强,支持内存计算;直接通过 scheduler-controller
轮询方式 node-exporter-plus
并将指标添加到 node annotations
如上图所示,Crane-scheduler-plus
仅依赖 Node-exporter-plus
组件.
-
Scheduler-Controller
会周期性轮训
到Node-exporter-plus
真实负载数据, 再以Annotation
的形式标记在各个节点上; -
Scheduler
则直接在从候选节点的Annotation
读取负载信息,并基于这些负载信息在Filter
阶段对节点进行过滤以及在Score
阶段对节点进行打分; -
Node-exporter-plus
会更加配置 在内存中计算好各类指标, 比如:cpu_usage_avg_5m、cpu_usage_max_avg_1h、mem_usage_avg_5m、mem_usage_max_avg_1h
等,通过metrics
的gauge
指标方式暴露出去
基于上述架构,最终实现了基于真实负载对 Pod
进行有效调度。
并且对Scheduler-Controller过载保护
:
-
- 如果通过
endpoints
来访问的node
节点过多, 在一个周期(比如15s
)处理不完, 会保证本次处理完成后,在执行下一次, 优先一个轮回完成
- 如果通过
-
- metrics中是通过
Promethrus Gauge
, 在每个周期中单独计算, 保证即使网络异常丢失 pull 请求,也可以通过下一次
请求进行补足
- metrics中是通过
3. 设计方式
3.1 Scheduler-Controller 变化
将PromClient
拉取 方式变更为 ClientSet
请求 service endpoints
方式 获得各个结果metrics数据
type PromClient interface {
// QueryByNodeIP queries data by node IP.
QueryByNodeIP(string, string) (string, error)
// QueryByNodeName queries data by node IP.
QueryByNodeName(string, string) (string, error)
// QueryByNodeIPWithOffset queries data by node IP with offset.
QueryByNodeIPWithOffset(string, string, string) (string, error)
}
//变更为:
type ClientSet interface {
// QueryByNodeIP queries data by node IP.
QueryByNodeIP(string, string) (string, error)
// QueryByNodeName queries data by node IP.
QueryByNodeName(string, string) (string, error)
// QueryByNodeIPWithOffset queries data by node IP with offset.
QueryByNodeIPWithOffset(string, string, string) (string, error)
// metrics 结果解析
QueryParse(string, string) (model.Vector, error)
}
3.2 Node-exporter 变化
设计 BaseCollector
实现 prometheus.GaugeValue
数据内存计算和收集
type BaseCollector struct {
metric []typedDesc
logger log.Logger
}
// NewBaseCollector returns a new Collector exposing base average stats.
func NewBaseCollector(logger log.Logger) (Collector, error) {
return &BaseCollector{
metric: []typedDesc{
{prometheus.NewDesc(namespace+"_avg_1m", "1m base average.", nil, nil), prometheus.GaugeValue},
{prometheus.NewDesc(namespace+"_avg_5m", "5m base average.", nil, nil), prometheus.GaugeValue},
{prometheus.NewDesc(namespace+"_avg_1d", "1d base average.", nil, nil), prometheus.GaugeValue},
},
logger: logger,
}, nil
}
func (c *BaseCollector) Update(ch chan<- prometheus.Metric) error {
loads, err := getData() // 实时指标
if err != nil {
return fmt.Errorf("couldn't get load: %w", err)
}
for i, load := range loads {
level.Debug(c.logger).Log("msg", "return load", "index", i, "load", load)
ch <- c.metric[i].mustNewConstMetric(load)
}
return err
}
通过配置
metrics:
- name: cpu_usage_avg_5m
period: 3m
- name: cpu_usage_max_avg_1h
period: 15m
- name: cpu_usage_max_avg_1d
period: 3h
- name: mem_usage_avg_5m
period: 3m
- name: mem_usage_max_avg_1h
period: 15m
- name: mem_usage_max_avg_1d
period: 3h
//...
4. 发布
7 - Pod资源视图隔离
Pod 容器内资源可见性:让Pod的资源视角真实、准确
❓是否有个发现:Pod
中限定了CPU
、MEM
等资源大小,然而登入的POD
中查询资源,却还是Node
总的资源大小?
对于业务上云, java(识别内存资源
开辟堆
大小)、golang(识别CPU个数
开启runtime线程个数
) 等语言,在OOM
、GC
方面的问题,有时常发生的原因
利用lxcfs将容器
中读取出来的CPU
、MEM
、disk
、swaps
的信息是宿主机的信息,与容器实际分配和限制的资源量相同。 解决低层通过os.syscall
获得的资源信息一致。
复现步骤
部署一个lxcfs-demo
应用pod
apiVersion: v1
kind: Pod
metadata:
name: lxcfs-demo
namespace: default
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: busybox
resources: #限制了pod资源大小
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
restartPolicy: Always
登入到pod中查看真实资源视角
, 如下:
dongjiangdeMacBook-Pro:kubernetes $ kubectl exec -it lxcfs-demo "/bin/sh"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # free -h
total used free shared buff/cache available
Mem: 2.9G 802.9M 117.0M 333.4M 2.0G 1.6G
Swap: 512.0M 1.3M 510.7M
/ # cat /proc/cpuinfo| grep "cpu cores"| uniq //物理Core数
cpu cores : 1
/ # cat /proc/cpuinfo| grep "processor"| wc -l //逻辑Core数
2
/ #
Pod 资源视角 与 部署要求限定的完全不一样
。 Pod 内看到的系统信息,完全是Node的信息
Lxcfs介绍
lxcfs
是一个FUSE文件系统
,使得Linux容器的文件系统更像虚拟机。lxcfs
是一个常驻进程运行在宿主机上,从而来自动维护宿主机cgroup
中容器的真实资源信息与容器内/proc
下文件的映射关系。
原理
lxcfs
实现的基本原理: 通过文件挂载
的方式,把POD OCI cgroup
中容器相关的信息读取出来,存储到lxcfs相关的目录下,并将相关目录映射到容器内的/proc目录
下,从而使得容器内执行top, free等命令时拿到的/proc下的数据是真实的cgroup分配给容器的CPU和内存数据。
lxcfs 的 Kubernetes使用
为了让 Node
上所有 Pod
多支持 lxcfs
资源视角. Kubernetes 中通过 daemonset
方式在每个 work节点上都启动
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: lxcfs
labels:
app: lxcfs
spec:
selector:
matchLabels:
app: lxcfs
template:
metadata:
labels:
app: lxcfs
spec:
hostPID: true
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: lxcfs
image: dongjiang1989/lxcfs:v4.0.12
imagePullPolicy: Always
securityContext:
privileged: true
volumeMounts:
- name: cgroup
mountPath: /sys/fs/cgroup
- name: lxcfs
mountPath: /var/lib/lxcfs
mountPropagation: Bidirectional
- name: usr-local
mountPath: /usr/local
volumes:
- name: cgroup
hostPath:
path: /sys/fs/cgroup
- name: usr-local
hostPath:
path: /usr/local
- name: lxcfs
hostPath:
path: /var/lib/lxcfs
type: DirectoryOrCreate
lxcfs-admission-webhook
实现了一个动态的准入webhook
,更准确的讲是实现了一个修改性质的webhook
,即监听pod的创建
,然后对pod执行patch的操作,从而将lxcfs与容器内的目录映射关系植入到pod创建的yaml中从而实现自动挂载。
apiVersion: apps/v1
kind: Deployment
metadata:
name: lxcfs-admission-webhook-deployment
namespace: kube-system
labels:
app: lxcfs-admission-webhook
spec:
replicas: 1
selector:
matchLabels:
app: lxcfs-admission-webhook
template:
metadata:
labels:
app: lxcfs-admission-webhook
spec:
serviceAccountName: lxcfs-webhook-serviceaccount
containers:
- name: lxcfs-admission-webhook
image: dongjiang1989/lxcfs-webhook:latest
imagePullPolicy: Always
args:
- -tlsCertFile=/etc/webhook/certs/tls.crt
- -tlsKeyFile=/etc/webhook/certs/tls.key
- -alsologtostderr
- -v=4
- 2>&1
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 10m
memory: 64Mi
volumeMounts:
- mountPath: /etc/webhook/certs/
name: cert
readOnly: true
volumes:
- name: cert
secret:
defaultMode: 420
secretName: lxcfs-webhook-server-cert
需要Linux OS,开启FUSE模块支持. Mac 是unix的裁剪系统,不支持FUSE
验证结果
测试Demo
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd-test
spec:
replicas: 1
selector:
matchLabels:
app: httpd-test
template:
metadata:
labels:
app: httpd-test
spec:
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: httpd
image: httpd:2.4.32
imagePullPolicy: Always
resources:
requests:
memory: "256Mi"
cpu: "1"
limits:
memory: "256Mi"
cpu: "1"
验证信息
- Node机器资源:
16G内存、2CPU 4核
[root@kcs-cpu-test-m-qm2dd /]# free -h
total used free shared buff/cache available
Mem: 15G 2.0G 2.5G 4.4M 10G 12G
Swap: 0B 0B 0B
[root@kcs-cpu-test-m-qm2dd /]# cat /proc/cpuinfo | grep "physical id"
physical id : 0
physical id : 0
physical id : 1
physical id : 1
[root@kcs-cpu-test-m-qm2dd /]# cat /proc/cpuinfo | grep processor
processor : 0
processor : 1
processor : 2
processor : 3
- Pod内信息
Pod 内 内存是256M
; CPU是1Core
[root@kcs-cpu-test-m-qm2dd /]# kubectl get pod | grep "httpd-test"
httpd-test-68b9b9d74f-5tmnh 1/1 Running 0 11m
[root@kcs-cpu-test-m-qm2dd /]# kubectl exec -it httpd-test-68b9b9d74f-5tmnh "/bin/bash"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@httpd-test-68b9b9d74f-5tmnh:/usr/local/apache2# free -h
total used free shared buffers cached
Mem: 256M 8.4M 247M 268K 0B 272K
-/+ buffers/cache: 8.1M 247M
Swap: 0B 0B 0B
[root@kcs-cpu-test-m-qm2dd /]# top
top - 09:31:23 up 15 min, 0 users, load average: 0.00, 0.00, 0.00
Tasks: 6 total, 1 running, 5 sleeping, 0 stopped, 0 zombie
%Cpu0 : 4.5 us, 2.1 sy, 0.0 ni, 93.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 262144 total, 9072 used, 253072 free, 0 buffers
KiB Swap: 0 total, 0 used, 0 free. 276 cached Mem
8 - Kubernetes Pod进程网络带宽 流量控制
背景
混合云场景业务Pod直接相互干扰
、 在离线混部
(在离线服务同时在一台机器上服务用户) 等场景下,除了对cpu
、mem
、fd
、inode
、pid
等进行隔离,还需要对 网络带宽bandwidth
、磁盘读写速度IPOS
、NBD IO
、L3 Cache
、内存带宽MBA
等都需要做到隔离和限制
因此,本章节介绍下 网络带宽bandwidth limit
的使用和实现
Kubernetes 具体使用和实现
容器拉起,是通过运行时接口
对底层cni网络插件
来生产虚拟网络,bind到容器实现。对容器进行网络限制,底层需要cni网络插件
的限制,而cni网络插件
会将网络限制指令,将具体配置提交给 Linux
流量控制 (tc) 子系统,tc 包含一组机制和操作,数据包通过这些机制和操作在网络接口上排队等待传输/接收(令牌桶过滤器TBF),从而达到流量控制
CNI 对 Linux TC 操作
{
"name": "k8s-pod-network",
"cniVersion": "0.3.0", #必须0.3.0 containernetworking plugin 目前最高版本
"plugins":
[
{
"type": "calico",
"log_level": "info",
"datastore_type": "kubernetes",
"nodename": "127.0.0.1",
"ipam": { "type": "host-local", "subnet": "usePodCidr" },
"policy": { "type": "k8s" },
"kubernetes": { "kubeconfig": "/etc/cni/net.d/calico-kubeconfig" },
},
{
"type": "bandwidth",
"capabilities": {
"bandwidth": true #支持cri-o json配置提交
},
/* 以下是对cni插件网络限流操作, capabilities和一下4个配置二选一
"ingressRate": 123,
"ingressBurst": 456,
"egressRate": 123,
"egressBurst": 456
*/
},
]
}
cni插件支持本配置,也支持cri-o
、contaierd
、dockershim
等通过json
配置提交
func cmdAdd(args *skel.CmdArgs) error {
// cni 配置解析
conf, err := parseConfig(args.StdinData)
if err != nil {
return err
}
//...
// 从配置中活动 ingress Rate和Burst
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
// TC TBF 中创建流控规则
err = CreateIngressQdisc(bandwidth.IngressRate, bandwidth.IngressBurst, hostInterface.Name)
if err != nil {
return err
}
}
// 从配置中活动 egress Rate和Burst
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
// ...
// 对特定本地Device设置出口流控规则
err = CreateEgressQdisc(bandwidth.EgressRate, bandwidth.EgressBurst, hostInterface.Name, ifbDeviceName)
if err != nil {
return err
}
}
return types.PrintResult(result, conf.CNIVersion)
}
OCR 流控配置
通过Pod配置annotations
apiVersion: v1
kind: Pod
metadata:
name: iperf-slow
annotations:
kubernetes.io/ingress-bandwidth: 10M
kubernetes.io/egress-bandwidth: 10M
...
Kubenetes 代码支持在 pod annotations解析和使用
kubernetes.io/ingress-bandwidth
和 kubernetes.io/egress-bandwidth
值只是支持 1k-1P, 超过32G需要调整Kernel参数
// 配置值在 1k-1p之间
var minRsrc = resource.MustParse("1k")
var maxRsrc = resource.MustParse("1P")
// 获取pod annotations并传递给 runc
func ExtractPodBandwidthResources(podAnnotations map[string]string) (ingress, egress *resource.Quantity, err error) {
if podAnnotations == nil {
return nil, nil, nil
}
str, found := podAnnotations["kubernetes.io/ingress-bandwidth"]
if found {
ingressValue, err := resource.ParseQuantity(str)
if err != nil {
return nil, nil, err
}
ingress = &ingressValue
if err := validateBandwidthIsReasonable(ingress); err != nil {
return nil, nil, err
}
}
str, found = podAnnotations["kubernetes.io/egress-bandwidth"]
if found {
egressValue, err := resource.ParseQuantity(str)
if err != nil {
return nil, nil, err
}
egress = &egressValue
if err := validateBandwidthIsReasonable(egress); err != nil {
return nil, nil, err
}
}
return ingress, egress, nil
}
以contaierd
为例, kubelet 活动 pod yaml信息后续,传递给containerd runtime
,并继续传递给cni插件
func cniNamespaceOpts(id string, config *runtime.PodSandboxConfig) ([]cni.NamespaceOpts, error) {
opts := []cni.NamespaceOpts{
cni.WithLabels(toCNILabels(id, config)),
cni.WithCapability(annotations.PodAnnotations, config.Annotations),
}
portMappings := toCNIPortMappings(config.GetPortMappings())
if len(portMappings) > 0 {
opts = append(opts, cni.WithCapabilityPortMap(portMappings))
}
// pod annotations中获得配置,最后传递给cni
bandWidth, err := toCNIBandWidth(config.Annotations)
if err != nil {
return nil, err
}
if bandWidth != nil {
opts = append(opts, cni.WithCapabilityBandWidth(*bandWidth))
}
// ...
}
验证和测试
** 流控依赖Linux TC子系统。目前只支持Linux K8s集群 **
apiVersion: apps/v1
kind: Deployment
metadata:
name: iperf-server-deployment
labels:
app: iperf-server
spec:
replicas: 1
selector:
matchLabels:
app: iperf-server
template:
metadata:
labels:
app: iperf-server
#添加注解
annotations:
kubernetes.io/ingress-bandwidth: 1M
kubernetes.io/egress-bandwidth: 1M
spec:
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
containers:
- name: iperf3-server
image: dongjiang1989/iperf
args: ['-s', '-p', '5001']
ports:
- containerPort: 5001
name: server
terminationGracePeriodSeconds: 0
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: iperf-client
labels:
app: iperf-client
spec:
replicas: 1
selector:
matchLabels:
app: iperf-client
template:
metadata:
labels:
app: iperf-client
spec:
containers:
- name: iperf-client
image: dongjiang1989/iperf
command: ['/bin/sh', '-c', 'sleep 1d']
terminationGracePeriodSeconds: 0
对于未添加网络限流注解
$ kubectl get pod | grep iperf
iperf-client-7874c47d95-t7hph 1/1 Running 0 5m58s
iperf-server-deployment-74d94bdd59-dzdl4 1/1 Running 0 5m58s
kubectl exec iperf-client-7874c47d95-t7hph -- iperf -c 10.1.0.173 -p 5001 -i 10 -t 100
------------------------------------------------------------
Client connecting to 10.1.0.173, TCP port 5001
TCP window size: 85.0 KByte (default)
------------------------------------------------------------
[ 1] local 10.1.0.172 port 56296 connected with 10.1.0.173 port 5001
[ ID] Interval Transfer Bandwidth
[ 1] 0.00-10.00 sec 19.7 GBytes 16.9 Gbits/sec
[ 1] 10.00-20.00 sec 18.9 GBytes 16.2 Gbits/sec
[ 1] 20.00-30.00 sec 20.0 GBytes 17.2 Gbits/sec
[ 1] 30.00-40.00 sec 20.4 GBytes 17.5 Gbits/sec
[ 1] 40.00-50.00 sec 18.5 GBytes 15.9 Gbits/sec
[ 1] 50.00-60.00 sec 19.3 GBytes 16.5 Gbits/sec
[ 1] 60.00-70.00 sec 17.6 GBytes 15.1 Gbits/sec
[ 1] 70.00-80.00 sec 17.1 GBytes 14.7 Gbits/sec
[ 1] 80.00-90.00 sec 18.4 GBytes 15.8 Gbits/sec
[ 1] 90.00-100.00 sec 15.1 GBytes 13.0 Gbits/sec
[ 1] 0.00-100.00 sec 185 GBytes 15.9 Gbits/sec
未做限流,Bandwidth
可以到15.9Gbits/sec
对于添加网络限流注解
$ kubectl get pod | grep iperf
iperf-clients-rcsh6 1/1 Running 0 7h7m
iperf-server-deployment-59675c8f78-g52pm 1/1 Running 0 6h52m
$ kubectl exec iperf-clients-rcsh6 -- iperf -c 10.1.0.170 -p 5001 -i 10 -t 100
------------------------------------------------------------
Client connecting to 10.1.0.170, TCP port 5001
TCP window size: 45.0 KByte (default)
------------------------------------------------------------
[ 1] local 10.1.0.170 port 54652 connected with 10.1.0.170 port 5001
[ ID] Interval Transfer Bandwidth
[ 1] 0.00-10.00 sec 3.50 MBytes 2.94 Mbits/sec
[ 1] 10.00-20.00 sec 2.25 MBytes 1.89 Mbits/sec
[ 1] 20.00-30.00 sec 2.04 MBytes 1.71 Mbits/sec
[ 1] 30.00-40.00 sec 892 KBytes 731 Kbits/sec
[ 1] 40.00-50.00 sec 954 KBytes 781 Kbits/sec
[ 1] 50.00-60.00 sec 1.36 MBytes 1.14 Mbits/sec
[ 1] 60.00-70.00 sec 1.18 MBytes 993 Kbits/sec
[ 1] 70.00-80.00 sec 87.1 KBytes 71.4 Kbits/sec
[ 1] 80.00-90.00 sec 0.000 Bytes 0.000 bits/sec
[ 1] 90.00-100.00 sec 2.97 MBytes 2.50 Mbits/sec
[ 1] 0.00-100.69 sec 15.5 MBytes 1.29 Mbits/sec
限制1Mbits/sec
, 流控真实表现是 1.29 Mbits/sec
- 为啥限制
1Mbits/sec
, 流控真实表现略大约1Mbits/sec
? - 原因:在Linux系统中,
1M = 1024k
的; 而 K8s中使用Resource
对象实现的1M = 1000k
的. - 因此,真实 设置
1Mbits/sec
在 Linux 中的表现应该是1024*1024(bits/sec)/(1000*1000) = 1.048Mbits/sec
. - 在0-1s之间,TC控制不准确,会有数据平均
增大
的问题
总结
-
- docker
1.18
支持runc runtime json传递;containerd作为runtime,1.4
版本才能支持;
- docker
-
- calico需要
2.1
版本; cilium需要1.12.90
版本; kube-ovn需要版本1.9.0版本;但是需要支持
`ovn.kubernetes.io/ingress_rate` : Ingress 流量的速率限制,单位:Mbits/s `ovn.kubernetes.io/egress_rate` : Egress 流量的速率限制,单位:Mbits/s
- calico需要
-
- 不能动态更新annotation里面的流量限制大小,更新之后必须删除pod重建;
因此,需要通过webhook
来将丰富配置namespcae
下的limitrange
含义拉齐, 并支持默认填充
具体实现方式
先通过 CRD
描述 namespace
下 limitrange
扩展限制
设计如下:
apiVersion: custom.xxx.com/v1
kind: CustomLimitRange
metadata:
name: test-rangelimit
spec:
limitrange:
type: pod # 对pod类型限制,以后扩展到 contianer类型、ingress类型、service类型
max: # max和min是限制的上下线,如果pod自定义的值不在其中,ValidatingAdmissionWebhook校验报错
ingress-bandwidth: "1G"
egress-bandwidth: "1G"
min:
ingress-bandwidth: "10M"
egress-bandwidth: "10M"
default: # 定义了default,如果pod annotation为空,MutatingAdmissionWebhook自动注入此数据;未定义default,不作强注入操作
ingress-bandwidth: "128M"
egress-bandwidth: "128M"
在pod
可以是支持设置 customlimitrange.kubernetes.io/limited : disable
, 可支持 ignore
namespace下CustomLimitRange
限制
注意
本身CustomLimitRange
自身校验必不可少:
- max value >= default value >= min value
- value range [1k, 1P] && value 类型 Kbits/sec, Mbits/sec, Gbits/sec, Tbits/sec 和 Pbits/sec
- type 类型 enum
- max、min 和 default 可缺省
- 内部适配:kube-ovn annotation
使用方式
-
- Pod和Deployment添加注解annotation
# Pod
apiVersion: v1
kind: Pod
metadata:
name: xxxx
annotations:
kubernetes.io/ingress-bandwidth: 1M
kubernetes.io/egress-bandwidth: 1M
...
# Deployment
...
spec:
template:
metadata:
#添加注解
annotations:
kubernetes.io/ingress-bandwidth: 1M
kubernetes.io/egress-bandwidth: 1M
...
-
- 通过定义Custom LimitRange 自动添加annotation. 如以上
下一章节
9 - 基于kata direct volume特性, 实现安全容器KataContainer的 CSI block volume直通方案
在 Kubernetes
中集成 Kata Containers
可以为容器运行时提供更好的安全性
和隔离性
,但在存储方面
仍然还存在一些限制与不足。
目前方案:virtiofs协议
Kata Containers
在 2.4 版本之前,挂载 PV
的整个过程与 CSI
之间是没有任何交互的,而且也不能直接使用 CSI
挂载的 PV
,只能通过 virtiofs 协议
将宿主机上的存储卷以文件共享的方式提供给 Kata Containers
虚拟机中的 container
使用。
这种方式虽然能够解决 PV
存储挂载的问题,但与直接在宿主机上使用存储卷相比,由于 virtiofs
实现方式的 I/O
路径过长,会带来不少的性能损耗。
经过环境实测,总结出以下几方面问题:
-
稳定性方面
:在实际环境中,对virtiofs
共享的盘进行fio 测试
,我们经常能观察到 io 不连续的现象,并且在对盘进行加压测试时,会影响到容器中其他进程的响应,比如 ssh 进程响应会超时; -
性能方面
:与直接在宿主机上使用存储卷相比,virtiofs
共享盘在iops
和带宽
方面有不少差距; -
功能方面
:virtiofs
共享盘方式无法在线调整PV
大小,需要通过重启Pod
才能使VM
能感知到PV
大小变化;
优化方案
基于 virtiofs 现存问题,Kata Containers
在 2.4 版本提供了 direct assigned volume
功能,能够将文件系统挂载操作从宿主机移动到 Guest
中,相当于是一种 block volume
直通方案。和 virtiofs
相比,不仅能提供接近直接宿主机上使用存储卷的性能,而且还能支持 native FS
。由于不需要借助于 virtiofs
,在使用上会更加稳定,也能带来安全性方面的提升。同时,这个特性还支持在线修改 PV
存储大小。
具体kata direct assigned volume
特性设计:https://github.com/kata-containers/kata-containers/blob/main/docs/design/direct-blk-device-assignment.md
在 CSI 中实现 direct volume
前提条件
-
由于需要在
host
上创建文件,CSI node
服务在部署时需要把/run/kata-containers/shared/direct-volumes
目录以hostpath
方式挂载到pod
里。 -
CSI
在挂载时需要明确知道所挂载的volume
是否是以direct volume
这种方式挂载,所以需要有一种机制能通知到CSI
,可以借助以下三种方式:通过
StorageClass
指定direct volume
属性在
PVC
对象里通过annotation
打上direct volume
属性,同时 CSI 插件需要打开--extra-create-metadata
属性来帮助CSI
能从K8s apiserver
查询到PVC
的annotation
信息通过查询
Pod
的runtimeclass
信息来判断是否是Kata direct volume
挂载
实现步骤
以下步骤主要都在 CSI NodePublishVolume
接口里实现:
- 把远程存储的
block device
挂载到host
上。 - 根据实际需求场景对
block device
做文件系统格式化。 - 生成
mountinfo.json
信息并把mountinfo.json
信息传递给 Kata。
mountInfo
的内容是 json 格式,主要数据结构如下:
// MountInfo contains the information needed by Kata to consume a host block device and mount it as a filesystem inside the guest VM.
type MountInfo struct {
// The type of the volume (ie. block)
VolumeType string `json:"volume-type"`
// The device backing the volume.
Device string `json:"device"`
// The filesystem type to be mounted on the volume.
FsType string `json:"fstype"`
// Additional metadata to pass to the agent regarding this volume.
Metadata map[string]string `json:"metadata,omitempty"`
// Additional mount options.
Options []string `json:"options,omitempty"`
}
其中 CSI 侧主要需要传递以下三个字段信息即可,例如:
mountInfo := &volume.MountInfo{
VolumeType: "block", // 设备类型
Device: dev/sdd, // 块设备路径
FsType: ext4, // 文件系统类型
}
volume.Add("/run/kata-containers/shared/direct-volumes/volume-path(base64加密)/", mountInfo)
CSI
负责把 mountinfo.json
传递给 Kata
,Kata
会在容器所在 host
的 /run/kata-containers/shared/direct-volumes/volume-path(base64加密)/
目录下生成 mountinfo.json
文件,目前 CSI
有两种方式可以传递 mountinfo.json
信息
- 通过调用
kata-container
代码里direct volume
模块的add
方法传递mountinfo.json
信息,部分代码实例如下:
import (
"encoding/json"
volume "github.com/kata-containers/kata-containers/src/runtime/pkg/direct-volume"
"google.golang.org/grpc/status"
klog "k8s.io/klog/v2"
)
// NodePublishVolume 中发布
func AddDirectVolume(volumePath, device, fsType string) error {
mountInfo := &volume.MountInfo{
VolumeType: "block",
Device: device,
FsType: fsType,
}
mi, err := json.Marshal(mountInfo)
if err != nil {
klog.Errorf("addDirectVolume - json.Marshal failed: ", err.Error())
return status.Errorf(codes.Internal, "json.Marshal failed: %s", err.Error())
}
if err := volume.Add(volumePath, string(mi)); err != nil {
klog.Errorf("addDirectVolume - add direct volume failed: ", err.Error())
return status.Errorf(codes.Internal, "add direct volume failed: %s", err.Error())
}
klog.Infof("add direct volume done: %s%s", volumePath, string(mi))
return nil
}
// NodeUnpublishVolume 中remove掉
if err := volume.Remove(targetPath); err != nil {
log.Errorf("NodeUnpublishVolume: kata direct volume remove failed: %s", err.Error())
}
- 通过
kata-runtime CLI
命令传递mountinfo.json
信息:
$ kata-runtime direct-volume add --volume-path [volumePath] --mount-info [mountinfo.json]
$ kata-runtime direct-volume remove --volume-path [volumePath] --mount-info [mountinfo.json]
最后会在容器所在 host 的 /run/kata-containers/shared/direct-volumes/volume-path(base64加密)/
目录下生成 mountinfo.json
文件,然后 Kata Containers
会在启动容器时检查该目录是否有 mountinfo.json
文件并解析该文件,同时更新容器 spec
中 mount
信息,将直通卷的信息加入进去,然后将修改后的 spec
传给 kata-agent
;
方案限制:
- 使用
direct volume
方式的PV
只能给一个Pod
使用,所以在创建PVC
时需要指定accessMode
为ReadWriteOnce
- 使用
direct volume
方式不支持更高级的volume
功能,比如:fsGroup
、fsGroupChangePolicy
和subPath
未来:kata 联动 kubelet/kube-apiserver,实现CSI 卷的运行时辅助挂载
社区未通过的最终方案:KEP-2857:持久卷的运行时辅助安装
Demo
---
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
runtime-class: kata-qemu # 方式一:通过设置runtime-class,使用kata 运行时; 其下面的卷都是直通卷
containers:
- name: app
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: ebs-claim
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
skip-hostmount: "true" # 方式二:通知csi为卡通直通卷
name: ebs-claim
spec:
accessModes:
- ReadWriteOncePod
volumeMode: Filesystem
storageClassName: ebs-sc
resources:
requests:
storage: 1Gi
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: ebs-sc
provisioner: ebs.csi.aws.com # 方式三:特定的csi driver实现 kata direct assigned volume
volumeBindingMode: WaitForFirstConsumer
parameters:
csi.storage.k8s.io/fstype: ext4
10 - Kubernetes Pod进本地磁盘(local,disk,LVM) 进行流量控制
一. 继承上一章节
混合云场景业务Pod直接相互干扰
、 在离线混部
(在离线服务同时在一台机器上服务用户) 等场景下,除了对cpu
、mem
、fd
、inode
、pid
等进行隔离,还需要对 网络带宽bandwidth
、磁盘读写速度IPOS
、NBD IO
、L3 Cache
、内存带宽MBA
等都需要做到隔离和限制
因此,本章节介绍下 磁盘读写速度IPOS
的使用和实现
二. Kubernetes 具体使用和实现
Kube-apiserver控制
PersistentVolume(PV)
是 持久存储卷,集群级别资源。PersistentVolumeClaim(PVC)
是持久存储卷声明,namespace级别资源。 是用户对使用存储卷的使用需求声明
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test
namespace: test
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: csi-cephfs-sc
volumeMode: Filesystem
StorageClass
是创建PV
模板信息, 集群级别,用于动态创建pv
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-rbd-sc
parameters:
clusterID: ceph01
imageFeatures: layering
imageFormat: "2"
mounter: rbd
pool: kubernetes
provisioner: rbd.csi.ceph.com
reclaimPolicy: Delete
volumeBindingMode: Immediate
VolumeAttachment
记录了pv的相关挂载信息,如挂载到哪个node节点,由哪个volume plugin来挂载等。AD Controller
创建一个VolumeAttachment
,而External-attacher
则通过观察该VolumeAttachment
,根据其状态属性来进行存储的挂载和卸载操作。
apiVersion: storage.k8s.io/v1
kind: VolumeAttachment
metadata:
name: csi-123456
spec:
attacher: cephfs.csi.ceph.com
nodeName: 172.1.1.10
source:
persistentVolumeName: pvc-123456
status:
attached: true
CSINode
是记录csi plugin
的相关信息(如nodeId
、driverName
、拓扑信息
等). 当Node Driver Registrar
向kubelet
注册一个csi plugin
后,会创建(或更新)一个CSINode
对象,记录csi plugin
的相关信息。
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
name: 172.1.1.10
spec:
drivers:
- name: cephfs.csi.ceph.com
nodeID: 172.1.1.10
topologyKeys: null
- name: rbd.csi.ceph.com
nodeID: 172.1.1.10
topologyKeys: null
CSI Volume Plugin
扩展各种存储类型的卷的管理能力,实现第三方存储的各种操作能力与k8s存储系统的结合。调用第三方存储的接口或命令,从而提供数据卷的创建/删除、attach/detach、mount/umount的具体操作实现,可以认为是第三方存储的代理人。前面分析组件中的对于数据卷的创建/删除、attach/detach、mount/umount操作,全是调用volume plugin来完成。
csi plugin
: csi plugin分为ControllerServer与NodeServer,各负责不同的存储操作。external plugin
: 负责watch pvc、volumeAttachment等对象,然后调用volume plugin来完成存储的相关操作Node-Driver-Registrar
: 负责实现csi plugin(NodeServer)的注册,让kubelet感知csi plugin的存在
kube-controller-manager
PV controller
: 负责pv
、pvc
的绑定与生命周期管理(如创建/删除底层存储,创建/删除pv
对象,pv
与pvc
对象的状态变更)。AD controller
: 负责创建、删除VolumeAttachment
对象,并调用volume plugin
来做存储设备的Attach/Detach
操作(将数据卷挂载到特定node
节点上/从特定node
节点上解除挂载),以及更新node.Status.VolumesAttached
等。
注意 AD controller
的Attach/Detach
操作只是修改VolumeAttachment
对象的状态,而不会真正的将数据卷挂载到节点/从节点上解除挂载,真正的节点存储挂载/解除挂载操作由kubelet
中volume manager
调用csi plugin
来完成。
kubelet
管理卷的Attach/Detach
(与AD controller
作用相同,通过kubelet
启动参数控制哪个组件来做该操作)、mount/umount
等操作。
对于csi
来说,volume manager
的Attach/Detach
操作只创建/删除VolumeAttachment
对象,而不会真正的将数据卷挂载到节点/从节点上解除挂载;csi-attacer
组件也不会做挂载/解除挂载操作,只是更新VolumeAttachment
对象,真正的节点存储挂载/解除挂载操作由kubelet
中volume manager
调用调用csi plugin
来完成。
三、磁盘限速 通用方式
本身Volume
限制分为两类:
本地磁盘/类本地磁盘
: 类似于lvm
,local disk
,NAS云盘
,底层通过底层Linux
的xfs
、ext4
、Btrfs
底层文件系统接口,进行通信,实现操作存储,从而提供容器存储服务。远程目录
: 类似与S3
、NFS
等远程目录mount
到pod
; 底层是通过kubernetes
通过grpc
接口与存储卷插件系统
进行通信,来操作存储,从而提供容器存储服务。
因此当前业绩通用作为,只能对本地磁盘/类本地磁盘
, 通过blkio
的cgroup subsystem
进行限速
.
方式一:Runtime
运行时层面限制
dongjiangdeMacBook-Pro:~ $ docker help run | grep -E 'bps|IO'
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
--blkio-weight uint16 Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)
--blkio-weight-device list Block IO weight (relative device weight) (default [])
--device-read-bps list Limit read rate (bytes per second) from a device (default [])
--device-read-iops list Limit read rate (IO per second) from a device (default [])
--device-write-bps list Limit write rate (bytes per second) to a device (default [])
--device-write-iops list Limit write rate (IO per second) to a device (default [])
通过kubelet
内置dockershim
, 解析 pod
的annotaion
,将 配置设置到blkio
配置中
# Pod
apiVersion: v1
kind: Pod
metadata:
name: xxxx
annotations:
io.kubernetes.container.blkio: '{"weight":200,"weight_device":[{"device":"rootfs","value":"200k"}],"device_read_bps":[{"device":"/dev/sda1","value":"20m"}],"device_write_bps":[{"device":"rootfs","value":"20m"}],"device_read_iops":[{"device":"rootfs","value":"200"}]"device_write_iops":[{"device":"rootfs","value":"300"}]}'
...
优势:
a. 不仅仅支持容器卷volume,对pod本身 rootfs等都可以进行设置;
缺点:
a. Container Runtime
必须是docker
;
b. 不能根据创建pvc生命周期,自动进行配置;
c. 仅支持对于direct
本地磁盘生效;
d. 对于一写多读
的 多个pod
共享的本地卷,设置是后者覆盖前者
代码解析:
// +build linux
// 只是对liunx系统生效
func UpdateBlkio(containerId string, docker libdocker.Interface) (err error) {
info, err := docker.InspectContainer(containerId) //获得docker inspect info
blkio := Blkio{}
err = json.Unmarshal([]byte(blkiolable), &blkio) //添加blkio设置
if err != nil {
return fmt.Errorf("failed to unmarshal blkio config,%s, sandboxID:%s, containerId:%s", err.Error(), sandboxID, containerId)
}
//...
blkioResource, err := getBlkioResource(&blkio, containerRoot) //获得 blkioResource object
if err != nil {
return fmt.Errorf("getBlkioResource failed. sandboxID:%s, containerId:%s, %v", sandboxID, containerId, err.Error())
}
cg := &configs.Cgroup{
Path: cpath,
Resources: &blkioResource,
}
err = blkioSubsystem.Set(cpath, cg) // blkio设置到cgroup
if err != nil {
return fmt.Errorf("blkioSubsystem.Set failed. sandboxID:%s, containerId:%s, %v", sandboxID, containerId, err.Error())
}
glog.V(4).Infof("set Blkio cgroup success. sandboxID:%s, containerId:%s, cgroup path:%v, cgroup:%+v", sandboxID, containerId, cpath, cgroupToString(cg))
return nil
}
**总结:**目前这情况对本地卷各种缺点局限性
,对kubelet
源码级别侵害比较大;
方式二:Pod 卷中 blkio 添加与设置
在kubelet
开启feature. 比如: PVCQos=true
, 可以通过如下注解添加 pod qos。
# Pod
apiVersion: v1
kind: Pod
metadata:
name: xxxx
annotations:
qos.volume.storage.cloud.cmss.com: >-
{"pvc": "snap-03", "iops": {"read": 2000, "write": 1000}, "bps":
{"read": 1000000, "write": 1000000}}
...
再通过kubelet
就可以得到pvc的挂载点和设备id。然后我们使用 cgroup
来限制 pod
的 iops
和 bps
。
我们可以只编辑 pod /sys/fs/cgroup/blkio/kubepods/pod/<Container_ID>/...
下的 cgroup
限制文件,
例如,限制 pod 的读取 iops:
echo "<block_device_maj:min> <value>" > /sys/fs/cgroup/blkio/kubepods/pod<UID>/blkio.throttle.read_iops_device
优势:
a. 用户可以使用任何容器运行时
b. k8s的方式,用户必须知道 pod 的 pv。
缺点:
a. pod
使用的rootfs
的限制不支持;(当您节点中的一个 pod 大量使用 rootfs 时,可能会影响同一节点上的其他 pod。)
b. 只能对本地磁盘生效;
c. 对于一写多读
的 多个pod
共享的本地卷,设置是后者覆盖前者;
此方法明显好一些,但是blkio不能对外挂目录生效;并且对于共享的Volume
(N个Pod中的不同container共享一个卷),配置在pod层面不合适
方案三: 基于CSI PVC和StorageClass进行 本地卷设置
集合以上两种方式,限制支持范围:
a. 仅支持本地磁盘/类本地磁盘
的iops;
b. 通过csi插件方式实现,不更改kubelet;
c. 配置在PVC
和StorageClass
, 以整个pv的生命周期做限制,不限制在pod
和container
层面;
d. 仅对pvc生效,不对pod本身rootfs做限流(rootfs 本身就是静态数据,不建议做存储和数据平凡读写);
在这个前提下实现io的blkio读写限制。
先通过storageclass.yaml
设置pv模版
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-local
provisioner: local.csi.cmsss.com
parameters:
volumeType: LVM
vgName: volumegp
fsType: ext4
lvmType: "striping"
readBPS: 1M # read 此类卷的带宽是1MB/s
writeBPS: 100K # 写 此类卷的带宽是100KB/s
readIOPS: 2000 # read 此类卷的tps是2000
writeIOPS: 1000 # write 此类卷的tps是1000
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
也可以支持pmem
和QuotaPath
的volumeType
再通过persistentVolumeClaim.yaml
申请具体pv:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: lvm-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: csi-local
最后通过 deployment.yaml
挂载符合的pv :
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-lvm
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
volumeMounts:
- name: lvm-pvc
mountPath: "/data"
volumes:
- name: lvm-pvc
persistentVolumeClaim:
claimName: lvm-pvc
优势在于: 对于pv的模版 添加 iops
和bps
配置, 可以动态生效pvc整个生命周期
。
四、总结
-
- 以支持任何容器运行时;
-
- 支持本地不同pv的限速,保证整个pv生命周期的限制不被更改;
-
- 在社区原生应用不支持情况下,可以不破坏
kubernetes
生态实现;
- 在社区原生应用不支持情况下,可以不破坏
-
- 对于
一写多读
的 多个pod
共享的本地卷,pv速率限制整个生命周期有效;
- 对于
特别说明下:
使用内置(in-tree
) 卷 , 不支持 限流
. 这一类的卷比如: empty
、 hostpath
等
最核心原因是: blkio
只能对 direct
读写请求生效
五、具体实现方式
对 csi
的 nodeserver
创建 pvc
过程中, 使用具体的storageclass
, 实现本地卷 VolumeIOLimit
func SetVolumeIOLimit(devicePath string, req *csi.NodePublishVolumeRequest) error {
readIOPS := req.VolumeContext["readIOPS"]
writeIOPS := req.VolumeContext["writeIOPS"]
readBPS := req.VolumeContext["readBPS"]
writeBPS := req.VolumeContext["writeBPS"]
// IOlimit 值解析
readBPSInt, err := getBpsLimt(readBPS)
if err != nil {
log.Errorf("Volume(%s) Input Read BPS Limit format error: %s", req.VolumeId, err.Error())
return err
}
writeBPSInt, err := getBpsLimt(writeBPS)
if err != nil {
log.Errorf("Volume(%s) Input Write BPS Limit format error: %s", req.VolumeId, err.Error())
return err
}
readIOPSInt := 0
if readIOPS != "" {
readIOPSInt, err = strconv.Atoi(readIOPS)
if err != nil {
log.Errorf("Volume(%s) Input Read IOPS Limit format error: %s", req.VolumeId, err.Error())
return err
}
}
writeIOPSInt := 0
if writeIOPS != "" {
writeIOPSInt, err = strconv.Atoi(writeIOPS)
if err != nil {
log.Errorf("Volume(%s) Input Write IOPS Limit format error: %s", req.VolumeId, err.Error())
return err
}
}
// 获得 Device major/minor 值
majMinNum := getMajMinDevice(devicePath)
if majMinNum == "" {
log.Errorf("Volume(%s) Cannot get major/minor device number: %s", req.VolumeId, devicePath)
return errors.New("Volume Cannot get major/minor device number: " + devicePath + req.VolumeId)
}
// 获得pod uid
podUID := req.VolumeContext["csi.storage.k8s.io/pod.uid"]
if podUID == "" {
log.Errorf("Volume(%s) Cannot get poduid and cannot set volume limit", req.VolumeId)
return errors.New("Cannot get poduid and cannot set volume limit: " + req.VolumeId)
}
// 写具体的pod blkio文件
podUID = strings.ReplaceAll(podUID, "-", "_")
podBlkIOPath := filepath.Join("/sys/fs/cgroup/blkio/kubepods.slice/kubepods-besteffort.slice", "kubepods-besteffort-pod"+podUID+".slice")
if !IsHostFileExist(podBlkIOPath) {
podBlkIOPath = filepath.Join("/sys/fs/cgroup/blkio/kubepods.slice/kubepods-burstable.slice", "kubepods-besteffort-pod"+podUID+".slice")
}
if !IsHostFileExist(podBlkIOPath) {
log.Errorf("Volume(%s), Cannot get pod blkio/cgroup path: %s", req.VolumeId, podBlkIOPath)
return errors.New("Cannot get pod blkio/cgroup path: " + podBlkIOPath)
}
// 设置具体pod blkio文件值
if readIOPSInt != 0 {
err := writeIoLimit(majMinNum, podBlkIOPath, "blkio.throttle.read_iops_device", readIOPSInt)
if err != nil {
return err
}
}
if writeIOPSInt != 0 {
err := writeIoLimit(majMinNum, podBlkIOPath, "blkio.throttle.write_iops_device", writeIOPSInt)
if err != nil {
return err
}
}
if readBPSInt != 0 {
err := writeIoLimit(majMinNum, podBlkIOPath, "blkio.throttle.read_bps_device", readBPSInt)
if err != nil {
return err
}
}
if writeBPSInt != 0 {
err := writeIoLimit(majMinNum, podBlkIOPath, "blkio.throttle.write_bps_device", writeBPSInt)
if err != nil {
return err
}
}
log.Infof("Seccessful Set Volume(%s) IO Limit: readIOPS(%d), writeIOPS(%d), readBPS(%d), writeBPS(%d)", req.VolumeId, readIOPSInt, writeIOPSInt, readBPSInt, writeBPSInt)
return nil
}
自测验证
配置要求
具有所需 RBAC 权限的服务帐户
功能状态
编译打包
local.csi.ecloud.cmss.com
可以编译成容器的形式。
构建容器:
$ docker build -f hack/local/Dockerfile .
用法
先决条件
使用localdisk 或者 挂载clouddisk方式,挂载或生成 lvm pvcreate
或 lvm vgcreate
$ fdisk -l
Disk /dev/sdc: 68.7 GB, 68719476736 bytes, 134217728 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
$ fdisk /dev/sdc
Command (m for help): p
Disk /dev/sdc: 68.7 GB, 68719476736 bytes, 134217728 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x72a4d30c
Device Boot Start End Blocks Id System
Command (m for help): n
Partition type:
p primary (0 primary, 0 extended, 4 free)
e extended
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-134217727, default 2048):
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-134217727, default 134217727): 64217727
Partition 1 of type Linux and of size 30.6 GiB is set
Command (m for help): t
Selected partition 1
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'
Command (m for help): w
The partition table has been altered!
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sdb 8:16 0 100G 0 disk
|-sdb4 8:20 0 25G 0 part
`-sdb2 8:18 0 75G 0 part
sr0 11:0 1 506K 0 rom
sdc 8:32 0 64G 0 disk
`-sdc1 8:33 0 30.6G 0 part
sda 8:0 0 100G 0 disk
`-sda1 8:1 0 100G 0 part /
$ pvcreate /dev/sdc1
Physical volume "/dev/sdc1" successfully created.
$ vgcreate volumegroup1 /dev/sdc1
Volume group "volumegroup1" successfully created
$ vgdisplay
--- Volume group ---
VG Name volumegroup1
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 2
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 1
Open LV 1
Max PV 0
Cur PV 1
Act PV 1
VG Size <30.62 GiB
PE Size 4.00 MiB
Total PE 7838
Alloc PE / Size 512 / 2.00 GiB
Free PE / Size 7326 / <28.62 GiB
VG UUID V6TVTh-AcIi-hLmR-bozc-9QeA-EBnU-Mhhd6y
执行步骤
第 1 步:创建 CSI Provisioner
$ kubectl create -f ./deploy/local/provisioner.yaml
第 2 步:创建 CSI
插件
$ kubectl create -f ./deploy/local/plugin.yaml
第 3 步:创建存储类
$ kubectl create -f ./examples/storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-lvm
provisioner: local.csi.ecloud.cmss.com
parameters:
vgName: volumegroup1
fsType: ext4
pvType: localdisk
nodeAffinity: "false"
readIOPS: "2000"
writeIOPS: "1000"
readBPS: "10000"
writeBPS: "5000"
reclaimPolicy: Delete
用法:
-
vgName:定义存储类的卷组名;
-
fsType:默认为ext4,定义lvm文件系统类型,支持ext4、ext3、xfs;
-
pvType:可选,默认为云盘。定义使用的物理磁盘类型,支持clouddisk、localdisk;
-
nodeAffinity:可选,默认为 true。决定是否在 PV 中添加 nodeAffinity。 —-> true:默认,使用 nodeAffinity 配置创建 PV; —-> false:不配置nodeAffinity创建PV,pod可以调度到任意节点
-
volumeBindingMode:支持 Immediate/WaitForFirstConsumer —-> Immediate:表示将在创建 pvc 时配置卷,在此配置中 nodeAffinity 将可用; —-> WaitForFirstConsumer:表示在相关的pod创建之前不会创建volume;在配置中,nodeAffinity 将不可用;
第 4 步:使用 lvm
创建 nginx
部署
$ kubectl create -f ./examples/pvc.yaml
$ kubectl create -f ./examples/deploy.yaml
第 5 步:检查 PVC/PV 的状态
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
lvm-pvc Bound lvm-29def33c-8dae-482f-8d64-c45e741facd9 2Gi RWO csi-lvm 3h37m
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
lvm-29def33c-8dae-482f-8d64-c45e741facd9 2Gi RWO Delete Bound default/lvm-pvc csi-lvm 3h38m
第 6 步:检查 pod 的状态
- 检查 pod 中的目录
$ kubectl get pod | grep deployment-lvm
deployment-lvm-57bc9bcd64-j7r9x 1/1 Running 0 77s
$ kubectl exec -ti deployment-lvm-57bc9bcd64-j7r9x sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# df -h | grep data
/dev/mapper/volumegroup1-lvm--9e30e658--5f85--4ec6--ada2--c4ff308b506e 2.0G 6.0M 1.8G 1% /data
- 检查主机中的目录:
$ kubectl describe pod deployment-lvm-57bc9bcd64-j7r9x | grep Node:
Node: kcs-cpu-test-m-8mzmj/172.16.0.67
$ ifconfig | grep 172.16.0.67
inet 172.16.0.67 netmask 255.255.0.0 broadcast 172.16.255.255
$ mount | grep volumegroup
/dev/mapper/volumegroup1-lvm--9e30e658--5f85--4ec6--ada2--c4ff308b506e on /var/lib/kubelet/pods/c06d5521-3d9c-4517-bdc2-e6df34b9e8f1/volumes/kubernetes.io~csi/lvm-9e30e658-5f85-4ec6-ada2-c4ff308b506e/mount type ext4 (rw,relatime,data=ordered)
/dev/mapper/volumegroup1-lvm--9e30e658--5f85--4ec6--ada2--c4ff308b506e on /var/lib/paascontainer/kubelet/pods/c06d5521-3d9c-4517-bdc2-e6df34b9e8f1/volumes/kubernetes.io~csi/lvm-9e30e658-5f85-4ec6-ada2-c4ff308b506e/mount type ext4 (rw,relatime,data=ordered)
- 检查pod disk iops和bps设置,是否生效:
$ pwd
/sys/fs/cgroup/blkio/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-podc06d5521_3d9c_4517_bdc2_e6df34b9e8f1.slice
$ cat blkio.throttle.read_bps_device
253:1 10000
$ cat blkio.throttle.write_bps_device
253:1 5000
$ cat blkio.throttle.write_iops_device
253:1 1000
$ cat blkio.throttle.read_iops_device
253:1 2000
六、未来
期待下,k8s
对 cgroup v2
, 核心支持了 读对于每一个pod
中的container
限速 和 对于远端目录
的 GRPC
请求限速
目前1.25
版本中对cgroup v2
已经达到beta状态,期待它release状态 😄
七、下一章节
11 - Kubernetes Namespace 和 Node 做亲和部署
背景
在共享集群(多租户共享底层硬件资源)中, 遇到特殊租户
需要独享
特定资源(比如:独占GPU资源、独占Node节点等)时候,可以对namespace
下的Pod
请求统一做亲和性
操作
Kubernetes 具体使用和实现
kubernetes中,最最核心部署通通过: Pod节点亲和性Affinity
和 Pod污点Taint和容忍度Toleration
组合方式实现 Pod真实部署在具体那个Node上
节点亲和性Affinity
亲和性主要分为两类:nodeAffinity
和podAffinity
nodeSelector
Label
是kubernetes
中一个非常重要的概念,用户可以非常灵活的利用 label
来管理集群中的资源,比如最常见的一个就是 service
通过匹配 label
去选择 POD
的。而 POD
的调度也可以根据节点的 label
进行特定的部署。
$ kubectl label nodes docker-desktop key=value
node/docker-desktop labeled
$ kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
docker-desktop Ready control-plane,master 109d v1.22.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,key=value,kubernetes.io/arch=amd64,kubernetes.io/hostname=docker-desktop,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
可以对Pod进行nodeSelector
亲和性部署
apiVersion: v1
kind: Pod
metadata:
labels:
app: busybox
name: test
spec:
containers:
- command:
- sleep
- "1d"
image: busybox
name: test
nodeSelector:
# 通过nodeSelector强制进行亲和部署,对于有 key: value label的node没有资源等限制,Pod会直接失败
key: value
nodeSelector
的方式比较直观,但是还够灵活,控制粒度偏大
nodeAffinity
nodeAffinity
就是节点亲和性, 可以进行一些简单的逻辑组合了,不只是简单的相等匹配.
nodeAffinity调度可以分成软策略
和硬策略
两种方式.
软策略
就是如果你没有满足调度要求的节点的话,POD 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓了的策略;硬策略
就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然我就不干的策略。
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
labels:
app: node-affinity-test
spec:
containers:
- name: node-affinity
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In #可以有 In、NotIn、Gt、Lt、Exists、DoesNotExist
values:
- Linux
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- Window
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 1
preference:
matchExpressions:
- key: key
operator: In
values:
- value
- value1
如果
nodeSelectorTerms
下面有多个选项的话,满足任何一个条件就可以了;如果matchExpressions
有多个选项的话,则必须同时满足这些条件才能正常调度POD
污点Taint和容忍度Toleration
Taint
在一类服务器上打上污点,让不能容忍这个污点的Pod
不能部署在打了污点的服务器上。(锁)
Toleration
是让Pod
容忍节点上配置的污点,可以让一些需要特殊配置的Pod
能够调用到具有污点和特殊配置的节点上。(钥匙)
污点Taint
Taint Effect
对 Node
打 Label
包括以下3
种:
NoSchedule:禁止调度到该节点,已经在该节点上的Pod不受影响
NoExecute:禁止调度到该节点,如果不符合这个污点,会立马被驱逐(或在一段时间后驱逐,默认300s,可单独设置驱逐时间)
PreferNoSchedule:尽量避免将Pod调度到指定的节点上,如果没有更合适的节点,可以部署到该节点
$ kubectl get node
NAME STATUS ROLES AGE VERSION
docker-desktop Ready control-plane,master 109d v1.22.5
$ kubectl taint nodes docker-desktop key=value:PreferNoSchedule
node/docker-desktop tainted
$ kubectl get node docker-desktop -o go-template --template {{.spec.taints}}
[map[effect:PreferNoSchedule key:key value:value]]
对Node
打了基于key=value
的PreferNoSchedule
类型污点
容忍度Toleration
容忍度Toleration 就是Pod
对 Node Taints的选择关系
- 完全匹配
tolerations:
- key: "keu"
operator: "Equal"
value: "value"
effect: "PreferNoSchedule"
- 不完全匹配
tolerations:
- key: "key"
operator: "Exists"
effect: "PreferNoSchedule"
tolerationSeconds: 600 # 节点不健康,600秒后再驱逐
- node.kubernetes.io/not-ready:节点未准备好,相当于节点状态Ready的值为False
- node.kubernetes.io/unreachable:Node Controller访问不到节点,相当于节点状态Ready的值为Unknown
- node.kubernetes.io/out-of-disk:节点磁盘耗尽
- node.kubernetes.io/memory-pressure:节点存在内存压力
- node.kubernetes.io/disk-pressure:节点存在磁盘压力
- node.kubernetes.io/network-unavailable:节点网络不可达
Namespace 和 Node 做亲和部署设计
首先,对于特定的Namespace
通过 Label
, 通过namespace-node-affinity: enabled
进行开启。
apiVersion: v1
kind: Namespace
metadata:
name: test-demo
labels:
# namespace级生效,对本namespace开启
namespace-node-affinity: enabled
通过configmap
对namespace下的亲和性最全局配置
apiVersion: v1
kind: ConfigMap
metadata:
name: namespace-node-affinity-webhook
namespace: kube-system # 这个namespace是 webhook所在的namespace
data:
# namespace 名称: test-demo、default
setting-namespace-name: |
nodeSelectorTerms: #做 nodeAffinity 下的 硬策略
- matchExpressions:
- key: "kubernetes.io/os"
operator: In
values:
- "linux"
tolerations: #做 tolerations 策略
- key: "example-key"
operator: "Exists"
effect: "NoSchedule"
excludedLabels: #如果匹配其中全部key-value 可跳过强绑定
namespace-node-affinity.cmss.com: disabled
对于Pod
和Deployment
, 可以通过统一标签,跳过以上白名单,namespace-node-affinity.cmss.com: disabled
# Pod
apiVersion: v1
kind: Pod
metadata:
name: xxxx
annotations:
namespace-node-affinity.cmss.com: disabled
...
# Deployment
...
spec:
template:
metadata:
#添加注解
annotations:
namespace-node-affinity.cmss.com: disabled
...
逻辑: 通过webhook
监听Pod
的Create
事件,如果监听到事件后,先判断Pod
中是否有亲和性策略,并且判断namespace的 node亲和性是否开启,
如果都开启,将亲和性指令merge一起
//...
func buildNodeSelectorTermsPath(podSpec coreV1.PodSpec) PatchPath {
var path PatchPath
//pod没有亲和性设置
if podSpec.Affinity == nil {
path = CreateAffinity
} else if podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity == nil { //pod有亲和性设置,但是没有NodeAffinity
path = CreateNodeAffinity
} else if podSpec.Affinity.NodeAffinity != nil && podSpec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil { //pod有亲和性设置,且有NodeAffinity,但是没有强策略
path = AddRequiredDuringScheduling
} else {
path = AddNodeSelectorTerms
}
return path
}
//...
func buildTolerationsPath(podSpec coreV1.PodSpec) PatchPath {
// pod是否有Tolerations
if podSpec.Tolerations == nil {
return CreateTolerations
}
return AddTolerations
}
12 - 基于blackbox构建的Pingmesh体系
背景
数据中心自身是极为复杂的,其中网络涉及到的设备很多就显得更为复杂,一个大型数据中心都有成百上千的节点、网卡、交换机、路由器以及无数的网线、光纤。在这些硬件设备基础上构建了很多软件,比如搜索引擎、分布式文件系统、分布式存储等等。在这些系统运行过程中,面临一些问题:如何判断一个故障是网络故障?如何定义和追踪网络的 SLA?出了故障如何去排查?
网络性能数据监控
就比较困难实现。 如果单纯直接使用 ping
命令收集结果,每台
服务器去 ping 剩下 (N-1)
台,也就是 N^2
的复杂度,稳定性和性能都存在一些问题。
举个例子:
如果IDC中有10000台服务器,ping的任务就有,10000*9999
任务, 如果一台机器有多IP请求,结果再翻倍。
对于数据存储也是一个问题,如果是每30s进行一次ping, 一次ping 需要 payload大小是64bytes
数据存储量: 10000*9999*2*64*24*3600/30
= 3.6860314e+13 bytes
= 33.52TB
是否只记录fail
和timeout
的记录,可以节约99.99%
的存储空间
业界实现
本体系是基于微软Pingmesh论文
一种增强
实现.
原微软Pingmesh论文地址: 《Pingmesh: A Large-Scale System for Data Center Network Latency Measurement and Analysis》
对于微软Pingmesh
是网络监控中一个很好突破。(具体可认真读原文)
但是在实际使用中也有不少局限性:
-
agent数据流: 对于
Agent
每次ping完都是记录到log中,再通过基础设施进行log
数据收集,使用日志分析
系统加大了系统复杂性。 -
Ping 模式支持: 只能支持
UDP
模式, 对于DNS tcp
、ICMP ping
等支持比较缺少。 -
Ping维度:只能支持
IPv4
ping。 但很多场景需要支持 是否公网互联互通等domain/dns
ping -
不支持手动实时尝试ping: 可基于
balckbox-exporter
网络探测实现 -
不支持ipv6
Pingmesh升级后的架构
Controller
Controller
主要负责生成 pinglist.yaml
文件。 pinglist
的生成来源有3个方向:
通过
IP Controller
自动获取到整个集群的podIP 和 nodeIp list
通过
Pinglist Controller
活动Agent Setting
配置
通过
Custom Define Pinglist
在pinglist.yaml
文件中补充 外部地址。 支持dns地址
、外部http地址
、domain地址
、ntp地址
、Kubenetes apiserver地址
等等
Controller
在生成 pinglist
文件后,通过 HTTP/HTTPS
提供出去,Agent
会定期获取 pinglist
来更新 agent
自己的配置,也就是我们说的拉
模式。Controller
需要保证高可用,因此需要在 Service
后面配置多个实例,每个实例的算法一致,pinglist
文件内容也一致,保证可用性
Agent
每个 ping 动作都开启一个新的连接,为了减少 Pingmesh
造成的 TCP
并发. 两个server ping 的周期最小是 10s,Packet 大小最大 64kb。
setting:
# the maximum amount of concurrent to ping, uint
concurrent_limit: 20
# interval to exec ping in seconds, float
interval: 60.0
# The maximum delay time to ping in milliseconds, float
delay: 200
# ping timeout in seconds, float
timeout: 2.0
# send ip addr
source_ip_addr: 0.0.0.0
# send ip protocal
ip_protocol: ip6
mesh:
add-ping-public:
name: ping-public-demo
type: OtherIP
ips :
- 127.0.0.1
- 8.8.8.8
- www.baidu.com
- kubernetes.default.svc.cluster.local
并且做了过载保护
- 如果
pinglist
中 数据很多, 在一个周期(比如10s
)处理不完, 会保证本次处理完成后,在执行下一次, 优先一个轮回完成 - 配置可以设置
agent
并发线程数,确保pingmesh agent
对整个集群影响小于千分之一
- metrics中是通过
Promethrus Gauge
, 在每个周期中单独计算
# HELP pingmesh_fail ping fail
# TYPE pingmesh_fail gauge
pingmesh_fail{target="8.8.8.8",tor="ping-public-demo"} 1
# HELP pingmesh_duration_milliseconds duration of ping rtt
# TYPE pingmesh_duration_milliseconds gauge
pingmesh_duration_milliseconds{target="docker.io",tor="ping-public-demo"} 245
- 为了确保 ping的请求在一个
时间窗口interval
中平均发出, 对请求job 做了内存态计算,在并发协程
上做了ratelimit
网络状况设计
通过pinglist.yaml
设置中的interval
时间窗口:
- 请求超过了
timeout
时间, 将请求标记为ping_fail
- 请求超过了
delay
但没有超过timeout
时间, 将请求标记为ping_duration_milliseconds
- 请求没有超过
delay
,在metrics接口中不记录
与promtheus集成
将以下文本添加到promtheus.yaml的scrape_configs部分, pingmeship
为server的ip
scrape_configs:
- job_name: net_monitor
honor_labels: true
honor_timestamps: true
scrape_interval: 60s
scrape_timeout: 5s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- $pingmeship:9115
与监控grafana结合
13 - 基于wayne构建的Kubenetes 多集群管理平面 Basa
Basa (体验版) 是一个支持 Multi-cluster Kubernetes集群的 可视化管理平台, 通过直观的页面操作便可完成Kubernetes中资源的创建、部署等操作。
采用plugin
架构,通过插件化的方式将不同功能尽量的分离
,更利于各种定制化功能的扩展。
云平台体验版地址:https://play.kubeservice.cn
云平台体验版账号:kubeservice
云平台体验版地址:kubeservice
动机
业界已经有很好的Kubernetes
集群管理平台比如:Kubesphere和Rancher, 也有很好的桌面工具Lens 都基于Kubernetes
之上封装了接口和UI更方便开发者上手和部署
但是对于企业来说:上云/云原生改造
的初衷: 上线效率
、成本核算
、权限控制
和 企业内全部资源的管理
等维度
因此设计的核心点:
- 融入
部门
、项目
、模块
概念: 通过RBAC
的方式细化了资源控制的权限,适合建立企业内部的私有云平台; - 纳管多套集群:
pod
,集群
,命名空间
等元数据自管理
; 可快速纳管全部集群 - 权限
细力度
控制: 除了kubeconfig
和RBAC
的访问权限外,平台按人员角色(管理员、部门负责人、项目负责人、开发人员、测试人员、运维人员等)
设计不同权限;运维视角
与研发视角
隔离 FaaS
能力: 方便业务方更好接入, 支持openFaaS Serverless
无差别部署- 成本核算:
FinOps
对部门、团队、项目级别
资源可做到成本分摊
- 上线效率: 即支持
webssh
通过kubectl apply
部署 也支持 页面引导式 部署 - 报表: 数字化报表, 支持
部门
、项目
、模块
的呈现 - 多集群、多租户能力:支持一键式发布不通集群,并可以动态
协同总发布实例数
目标
构建基于企业架构
, 支持成本分摊
的多云管理平台. 服务好更好的企业级私有云
平台
特性
- 支持多集群、多租户。支持K8s机器部署 和 多集群k8s集群纳管;
- 支持企业级别应用申请;
- 支持用户权限分类。支持项目管理员、项目负责人、项目成员、访客权限;
- 支持ldap登入;
- 保留完整的发布历史。支持历史发布、更改的回溯;
- 支持严密权限校验的webssh。用户可以通过 Web shell 的形式进入发布的 Pod 进行操作,自带完整的权限校验;
- 支持webhook通知:方便管理员推送集群、业务通知和故障处理报告等;
- 支持Serverless openFaaS 接入
- 支持 ingress 自动发现Pod 和 serverless function
架构
项目整体采用前后端分离的方案实现。
- 前端采用Angular框架进行数据交互和展示,使用Ace编辑器进行Kubernetes资源模版编辑
- 后端采用Beego框架做数据接口处理,持久层采用MySQL存储,使用client-go与Kubernetes进行交互
特点
图形化方式
: 易用的Web管理界面,降低企业上云
部署复杂度;基础设施编排
: 支持以使用任何公有云或者私有云
的Linux主机资源,也支持Kubernetes
内部署;容器编排与调度
: 支持Kubernetes 1.16.6 +
所有版本企业级权限管理
: 支持企业架构LDAP
、Oauth2.0
等认证方式,并支持对人员赋予不同执行权利;其他小特点
: 基于模板复制workload
;运维视角
与研发视角
隔离; 应用误删除回滚
等等20+
特性
使用
TODO
Helm
应用商店 接入- 监控报警 通过
webhook
报警 - 集群规划能力
欢迎大家加入 KubeServiceStack Community
Basa 开源工程地址: https://github.com/kubeservice-stack/basa