Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openeuler
website
提交
f7f01fcf
W
website
项目概览
openeuler
/
website
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
W
website
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
f7f01fcf
编写于
9月 09, 2020
作者:
O
openeuler-ci-bot
提交者:
Gitee
9月 09, 2020
浏览文件
操作
浏览文件
下载
差异文件
!274 add blog for iSulad
Merge pull request !274 from haozi007/master
上级
2bd1848f
01a98b4f
变更
2
隐藏空白更改
内联
并排
Showing
2 changed file
with
1040 addition
and
0 deletion
+1040
-0
content/zh/blog/haozi007/2020-09-09-isulad-benchmark.md
content/zh/blog/haozi007/2020-09-09-isulad-benchmark.md
+433
-0
content/zh/blog/haozi007/2020-09-09-isulad-json-parse.md
content/zh/blog/haozi007/2020-09-09-isulad-json-parse.md
+607
-0
未找到文件。
content/zh/blog/haozi007/2020-09-09-isulad-benchmark.md
0 → 100644
浏览文件 @
f7f01fcf
+++
title = "iSula性能测试"
date = "2020-09-09"
tags = ["iSulad", "performance"]
archives = "2020-09"
author = "haozi007"
summary = "iSula容器引擎具有很多优点:轻、快等等。那么,如何呈现这些优点呢?这篇文章我们主要关注iSula容器引擎的“快”。"
+++
iSula容器引擎具有很多优点:轻、快等等。那么,如何呈现这些优点呢?这篇文章我们主要关注iSula容器引擎的“快”。为了证明“快”,那就需要有参照物进行对比。环视业内,我们发现几个能打的;容器引擎鼻祖Docker、红帽的Podman以及CRI-O。
目标确定了,我们开始明确对比范围了。
## 测试范围
容器引擎的使用模式主要是:
-
客户端使用模式:多见于个人开发、测试以及部分生产场景;
-
PAAS通过CRI接口使用模式:云计算的经典场景,通过CRI接口调用容器引擎能力,管理pod集群;
为了尽量覆盖应用场景,因此我们需要覆盖上述两种场景,对客户端模式和CRI模式分别进行测试对比。
### 客户端模式
由于CRI-O不具备客户端功能,所以我们选择的测试对象是:
-
Docker
-
Podman
-
iSula
### CRI模式
CRI接口,需要通过
`cri-tools`
工具进行测试。
为了对比的观赏性,我们在CRI模式下也选择三个测试对象:
-
Docker
-
CRI-O
-
iSula
## 环境准备
### 机器环境
#### X86
| 配置项 | 配置信息 |
| ------ | ---------------------------------------- |
| OS | Fedora32 X86_64 |
| 内核 | linux 5.7.10-201.fc32.x86_64 |
| CPU | 48核,Intel Xeon CPU E5-2695 v2 @ 2.4GHZ |
| 内存 | 132 GB |
#### ARM
| 配置项 | 配置信息 |
| ------ | ------------- |
| OS | Euleros |
| 内核 | linux 4.19.90 |
| CPU | 64核 |
| 内存 | 196 GB |
### 安装iSulad
参考
[
官方文档
](
https://gitee.com/openeuler/iSulad/blob/master/docs/build_guide.md
)
安装即可。
```
bash
$
isula version
Client:
Version: 2.0.3
Git commit: 3bb24761f07cc0ac399e1cb783053db8b33b263d
Built: 2020-08-01T09:40:06.568848951+08:00
Server:
Version: 2.0.3
Git commit: 3bb24761f07cc0ac399e1cb783053db8b33b263d
Built: 2020-08-01T09:40:06.568848951+08:00
OCI config:
Version: 1.0.1
Default file: /etc/default/isulad/config.json
```
### 安装cri-tools
CRI测试,使用统一的客户端工具进行测试,选择K8S对应的
`V1.15.0`
版本即可。
```
bash
$
git clone https://github.com/kubernetes-sigs/cri-tools
$
cd
cri-tools
$
git checkout v1.15.0
$
make
$
export
PATH
=
$PATH
:
$GOPATH
/bin
```
### 安装docker
根据
[
官方文档
](
https://docs.docker.com/engine/install/fedora/
)
安装即可。
```
bash
$
docker version
Client:
Version: 19.03.11
API version: 1.40
Go version: go1.14.3
Git commit: 42e35e6
Built: Sun Jun 7 21:16:58 2020
OS/Arch: linux/amd64
Experimental:
false
Server: Docker Engine - Community
Engine:
Version: 19.03.11
API version: 1.40
(
minimum version 1.12
)
Go version: go1.14.3
Git commit: 42e35e6
Built: Sun Jun 7 00:00:00 2020
OS/Arch: linux/amd64
Experimental:
false
containerd:
Version: 1.3.3
GitCommit:
runc:
Version: 1.0.0-rc10+dev
GitCommit: fbdbaf85ecbc0e077f336c03062710435607dbf1
docker-init:
Version: 0.18.0
GitCommit:
```
### 安装kubelet
我们选择
`V1.15.0`
版本作为测试版本,下载源码
`https://github.com/kubernetes/kubernetes.git`
。
#### 准备源码
如果下载失败或者太慢,可以配置代理:
```
bash
# 设置国内代理
go
env
-w
GOPROXY
=
https://goproxy.cn,direct
# 设置私有仓库地址
go
env
-w
GOPRIVATE
=
.gitlab.com,.gitee.com
# 设置sum验证服务地址
go
env
-w
GOSUMDB
=
"sum.golang.google.cn"
```
开始下载源码:
```
bash
$
cd
$GOPATH
/src/k8s.io
$
git clone https://github.com/kubernetes/kubernetes.git
$
cd
kubernetes
$
git checkout v1.15.0
$
go mod tidy
```
#### 编译
```
bash
$
make all
WHAT
=
cmd/kubelet
```
注意:
-
**K8S的版本对go的版本有要求,例如`V1.15.0`需要go 1.12版本**
-
可以使用
`go mod tidy`
,测试依赖代码下载,如果存在鉴权失败的仓库,可以使用
`go get -v -insecure`
下载
#### 安装
```
bash
$
cp
_output/bin/kubelet /usr/local/bin/kubelet
$
kubelet
--version
Kubernetes v1.15.0
```
#### 启动kubelet
```
bash
$
kubelet
--network-plugin
=
cni
--runtime-cgroups
=
/systemd/system.slice
--kubelet-cgroups
=
/systemd/system.slice
--cgroup-driver
=
"systemd"
--fail-swap-on
=
false
-v
5
--enable-controller-attach-detach
=
false
--experimental-dockershim
```
注:cgroup由systemd管理
### 安装CRI-O
由于直接通过
`dnf`
安装
`CRI-O`
的
`v1.15.4`
版本有问题,所以需要源码编译安装。
```
bash
$
dnf
install
glib-2.0 glibc-devel glibc-static container-common
$
git clone https://github.com/cri-o/cri-o.git
$
cd
crio
$
make
$
make
install
$
mkdir
-p
/etc/crio
&&
cp
crio.conf /etc/crio/
```
### 安装podman
直接使用
`dnf`
的源安装即可:
```
bash
$
dnf
install
-y
podman
$
podman
--version
podman version 2.0.3
```
## 测试方案
本文档主要关注容器引擎的容器生命周期的性能,所以测试方案如下:
-
单容器的create、start、stop、rm和run等操作的性能;
-
100个容器并发create、start、stop、rm和run等操作的性能;
-
单pod的runp、stopp和rmp等操作的性能;
-
单pod包含单容器的run、stop和rm等操作的性能;
-
100个pod并发runp、stopp和rmp等操作的性能;
-
100个包含单容器的pod并发run、stop和rm等操作的性能;
注:pod的配置,必须指定linux,不然docker会给pod创建一个默认的网卡,导致cni插件执行失败。
```
json
{
"metadata"
:
{
"name"
:
"nginx-sandbox"
,
"namespace"
:
"default"
,
"attempt"
:
1
,
"uid"
:
"hdishd83djaidwnduwk28bcsb"
},
//
linux字段必须存在
"linux"
:
{
}
}
```
### 方案详细设计
单次测试和并发测试虽然是两种测试场景,但是单次可以看成并发的特例。因此,设计测试用例的时候,通过控制并发数量来实现两种场景的区分。具体设计如下图:
```
mermaid
graph TD
classDef notestyle fill:#98A092,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5, 5;
classDef parallelstyle fill:#C969A3,stroke:#666,stroke-width:1px,color:#ffa,stroke-dasharray: 5, 5;
subgraph pretest;
A[download images] --> B[do clean]
end
subgraph dotest
C[foreach 1->10]
D{{runtest}}
E(remove max and min cases)
X(calculate avg of residual case)
C --> D
D --> E
E --> X
end
B --> C
subgraph runtest
R(begin test)
F>t1: get begin time point]
G(parallel run all cases)
H[wait all cases finish]
I>t2: get end time point]
R --> F
F --> G
F --> H
H -. wait .-> G
H --> I
end
R -. implements .-> D
subgraph posttest
Z[do clean]
end
X --> Z
class R notestyle;
class G parallelstyle;
```
### 客户端模式
#### X86环境测试结果
单容器操作性能对比
| 操作耗时 (ms) | Docker (avg) | Podman (avg) | iSula (avg) | VS Docker | VS Podman |
| ------------- | ------------- | ------------ | ----------- | --------- | --------- |
| create | 287 | 180 | 131 | -54.36% | -27.22% |
| start | 675 | 916 | 315 | -53.33% | -65.61% |
| stop | 349 | 513 | 274 | -21.49% | -46.59% |
| rm | 72 | 187 | 60 | -16.67% | -67.91% |
| run | 866 | 454 | 359 | -58.55% | -20.93% |
100容器并发操作性能对比
| 操作耗时 (ms) | Docker (avg) | Podman (avg) | iSula (avg) | VS Docker | VS Podman |
| ------------- | ------------- | ------------ | ----------- | --------- | --------- |
| 100
*
create | 4995 | 3993 | 1911 | -61.74% | -52.14% |
| 100
*
start | 10126 | 5537 | 3861 | -61.87% | -30.27% |
| 100
*
stop | 8066 | 11100 | 4268 | -47.09% | -61.55% |
| 100
*
rm | 3220 | 4319 | 1967 | -38.91% | -54.46% |
| 100
*
run | 9822 | 5979 | 4392 | -55.28% | -26.54% |
#### ARM环境测试结果
单容器操作性能对比
| 操作耗时 (ms) | Docker (avg) | Podman (avg) | iSula (avg) | VS Docker | VS Podman |
| ------------- | ------------- | ------------ | ----------- | --------- | --------- |
| create | 401 | 361 | 177 | -55.86% | -50.97% |
| start | 1160 | 1143 | 523 | -54.91% | -54.24% |
| stop | 634 | 576 | 395 | -37.70% | -31.42% |
| rm | 105 | 398 | 89 | -15.24% | -77.64% |
| run | 1261 | 1071 | 634 | -49.72% | -40.80% |
100容器并发操作性能对比
| 操作耗时 (ms) | Docker (avg) | Podman (avg) | iSula (avg) | VS Docker | VS Podman |
| ------------- | ------------- | ------------ | ----------- | --------- | --------- |
| 100
*
create | 14563 | 12081 | 4172 | -71.35% | -65.47% |
| 100
*
start | 23420 | 15370 | 5294 | -77.40% | -65.56% |
| 100
*
stop | 22234 | 16973 | 8619 | -61.24% | -49.22% |
| 100
*
rm | 937 | 10943 | 926 | -1.17% | -92.33% |
| 100
*
run | 28091 | 16280 | 9015 | -67.91% | -44.63% |
### CRI模式
#### X86环境测试结果
单pod操作
| 操作耗时 (ms) | Docker (avg) | CRIO (avg) | iSula (avg) | VS Docker | VS CRIO |
| ------------- | ------------- | ---------- | ----------- | --------- | ------- |
| runp | 681 | 321 | 239 | -64.90% | -25.55% |
| stopp | 400 | 356 | 272 | -32.00% | -23.60% |
单pod单容器操作
| 操作耗时 (ms) | Docker (avg) | CRIO (avg) | iSula (avg) | VS Docker | VS CRIO |
| ------------- | ------------- | ---------- | ----------- | --------- | ------- |
| run | 1249 | 525 | 382 | -69.42% | -27.24% |
| stop | 554 | 759 | 564 | +1.81% | -25.69% |
100并发pod操作
| 操作耗时 (ms) | Docker (avg) | CRIO (avg) | iSula (avg) | VS Docker | VS CRIO |
| ------------- | ------------- | ---------- | ----------- | --------- | ------- |
| 100
*
runp | 13998 | 4946 | 3887 | -72.23% | -21.41% |
| 100
*
stopp | 8402 | 4834 | 4631 | -44.88% | -4.20% |
| 100
*
rmp | 2076 | 1388 | 1073 | -48.31% | -22.69% |
100并发pod容器操作
| 操作耗时 (ms) | Docker (avg) | CRIO (avg) | iSula (avg) | VS Docker | VS CRIO |
| ------------- | ------------- | ---------- | ----------- | --------- | ------- |
| 100
*
run | 28158 | 9077 | 5630 | -80.01% | -37.98% |
| 100
*
stop | 9395 | 8443 | 8196 | -12.76% | -2.93% |
| 100
*
rm | 4415 | 3739 | 1524 | -65.48% | -59.24% |
#### ARM环境测试结果
单pod操作
| 操作耗时 (ms) | Docker (avg) | CRIO (avg) | iSula (avg) | VS Docker | VS CRIO |
| ------------- | ------------- | ---------- | ----------- | --------- | ------- |
| runp | 1339 | 2366 | 536 | -59.97% | -77.35% |
| stopp | 443 | 419 | 255 | -42.44% | -39.14% |
单pod单容器操作
| 操作耗时 (ms) | Docker (avg) | CRIO (avg) | iSula (avg) | VS Docker | VS CRIO |
| ------------- | ------------- | ---------- | ----------- | --------- | ------- |
| run | 2069 | 3039 | 338 | -83.66% | -88.88% |
| stop | 684 | 688 | 214 | -68.71% | -68.90% |
100并发pod操作
| 操作耗时 (ms) | Docker (avg) | CRIO (avg) | iSula (avg) | VS Docker | VS CRIO |
| ------------- | ------------- | ---------- | ----------- | --------- | ------- |
| 100
*
runp | 27802 | 29197 | 9827 | -64.65% | -66.34% |
| 100
*
stopp | 14429 | 11173 | 6394 | -55.69% | -42.77% |
| 100
*
rmp | 771 | 9007 | 1790 | +132.17% | -80.13% |
100并发pod容器操作
| 操作耗时 (ms) | Docker (avg) | CRIO (avg) | iSula (avg) | VS Docker | VS CRIO |
| ------------- | ------------- | ---------- | ----------- | --------- | ------- |
| 100
*
run | 54087 | 43521 | 5284 | -90.23% | -87.86% |
| 100
*
stop | 18317 | 19108 | 2641 | -85.58% | -86.18% |
| 100
*
rm | 1592 | 18390 | 2162 | +35.80% | -88.24% |
## 总结分析
从测试数据来看,在容器的生命周期的操作和并发操作上面,我们iSulad都是优于其他容器引擎的。尤其是在ARM上的表现尤为出色,并发性能已经接近于X86的性能了;而其他容器引擎在ARM上面的表现不尽如人意,甚至出现性能下降1倍以上。
那么,我们iSulad为什么有这么大的优势呢?我觉得,主要是从下面几个方面来看。
-
首先,iSulad是用C/C++语言写的,而Docker/Podman/CRI-O都是用golang写的;C/C++在速度方面本身就有优势;
-
架构设计上面,相对于Docker,iSulad架构更加简单,调用链更短;而Podman是serverless模式,并发更加不具备优势;
-
在容器创建流程中,减小锁粒度、消减容器的依赖(例如镜像管理模块),从而提高了并发的性能;
### 架构对比
iSulad架构设计如下:
![
arch
](
https://gitee.com/openeuler/iSulad/raw/master/docs/design/arch.jpg
)
Docker官网给的架构图如下:
![
Docker架构图
](
https://docs.docker.com/engine/images/engine-components-flow.png
)
但是,docker daemon里面还涉及到containerd和runc的流程没有描述,大体结构如下:
```
mermaid
graph LR
A(docker daemon)
B(containerd)
C(runc)
A -. grpc .-> B
B -. fork/exec .-> C
```
从架构来看,docker的容器生命周期流程涉及:客户端到docker daemon的restful通信;daemon到containerd的GRPC通信;然后fork执行runc。而iSulad的流程:客户端到服务端的GRPC通信,然后fork执行lxc-start。
## 参考文档
-
https://stackoverflow.com/questions/46726216/kubelet-fails-to-get-cgroup-stats-for-docker-and-kubelet-services
-
https://developer.aliyun.com/article/630682
-
https://blog.csdn.net/bingshiwuyu/article/details/107333259
-
https://github.com/cri-o/cri-o
-
https://gitee.com/openeuler/iSulad/blob/master/docs/build_guide.md
content/zh/blog/haozi007/2020-09-09-isulad-json-parse.md
0 → 100644
浏览文件 @
f7f01fcf
+++
title = "iSula与JSON的斗争"
date = "2020-09-09"
tags = ["iSulad", "JSON"]
archives = "2020-09"
author = "haozi007"
summary = "iSulad是如何处理JSON的呢?"
+++
对于各位习惯各种高级语言的伙伴们来说,JSON的解析和生成是如呼吸般简单自然的事情。但是对于C语言,JSON的解析和生成就麻烦了。根本原因是由于C语言不支持反射,没办法对JSON作动态解析和生成。但是,容器引擎中涉及大量的JSON解析和生成。那么,我们为了更好的和JSON进行和谐相处,做了那些努力呢?
大体上,iSula经历了几个阶段,为了更好的感受这几个阶段的差距;我觉得通过武器的不同时代来感受一下。
## 冷兵器时代
C语言还是有一些JSON解析的库的,例如
`yajl`
,
`cjson`
等等;这些库提供了把JSON字符串解析为tree结构的元素集合,然后通过遍历书可以快速的找到JSON的
`key/value`
的对应关系和值。而且也能自己构建对应的元素结合tree,然后生成JSON字符串。那么,如何通过这些库来做JSON和C结构体直接的相互转换呢?
### 用法
以
`yajl`
为例,实现一个
`isula_version`
结构体的marshal和unmarshal.
```
c
#include <yajl/yajl_tree.h>
#include <yajl/yajl_gen.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
struct
isula_version
{
int
large
;
int
middle
;
int
small
;
char
*
version
;
};
void
free_isula_version
(
struct
isula_version
*
ptr
)
{
if
(
ptr
==
NULL
)
{
return
;
}
free
(
ptr
->
version
);
ptr
->
version
=
NULL
;
free
(
ptr
);
}
static
inline
yajl_val
get_val
(
yajl_val
tree
,
const
char
*
name
,
yajl_type
type
)
{
const
char
*
path
[]
=
{
name
,
NULL
};
return
yajl_tree_get
(
tree
,
path
,
type
);
}
struct
isula_version
*
unmarshal
(
const
char
*
json_str
)
{
char
buf
[
1024
];
yajl_val
tree
;
struct
isula_version
*
result
=
NULL
;
if
(
json_str
==
NULL
)
{
return
NULL
;
}
result
=
calloc
(
1
,
sizeof
(
struct
isula_version
));
if
(
result
==
NULL
)
{
return
NULL
;
}
tree
=
yajl_tree_parse
(
json_str
,
buf
,
sizeof
(
buf
));
if
(
tree
==
NULL
)
{
printf
(
"Invalid json string: %s
\n
"
,
json_str
);
goto
err_out
;
}
{
yajl_val
val
=
get_val
(
tree
,
"Large"
,
yajl_t_number
);
if
(
val
!=
NULL
)
{
result
->
large
=
YAJL_GET_INTEGER
(
val
);
}
}
{
yajl_val
val
=
get_val
(
tree
,
"Small"
,
yajl_t_number
);
if
(
val
!=
NULL
)
{
result
->
small
=
YAJL_GET_INTEGER
(
val
);
}
}
{
yajl_val
val
=
get_val
(
tree
,
"Middle"
,
yajl_t_number
);
if
(
val
!=
NULL
)
{
result
->
middle
=
YAJL_GET_INTEGER
(
val
);
}
}
{
yajl_val
val
=
get_val
(
tree
,
"Version"
,
yajl_t_string
);
if
(
val
!=
NULL
)
{
char
*
str
=
YAJL_GET_STRING
(
val
);
result
->
version
=
strdup
(
str
);
}
}
goto
out
;
err_out:
free_isula_version
(
result
);
result
=
NULL
;
out:
yajl_tree_free
(
tree
);
return
result
;
}
char
*
marshal
(
struct
isula_version
*
ptr
)
{
char
*
result
=
NULL
;
const
unsigned
char
*
gen_buf
=
NULL
;
size_t
gen_len
=
0
;
if
(
ptr
==
NULL
)
{
return
NULL
;
}
yajl_gen
g
=
yajl_gen_alloc
(
NULL
);
yajl_gen_status
stat
=
yajl_gen_status_ok
;
stat
=
yajl_gen_map_open
((
yajl_gen
)
g
);
if
(
stat
!=
yajl_gen_status_ok
)
{
goto
free_out
;
}
/* gen struct items */
if
(
ptr
->
version
!=
NULL
)
{
stat
=
yajl_gen_string
((
yajl_gen
)
g
,
(
const
unsigned
char
*
)(
"Version"
),
strlen
(
"Version"
));
if
(
yajl_gen_status_ok
!=
stat
)
{
goto
free_out
;
}
stat
=
yajl_gen_string
((
yajl_gen
)
g
,
(
const
unsigned
char
*
)
ptr
->
version
,
strlen
(
ptr
->
version
));
if
(
yajl_gen_status_ok
!=
stat
)
{
goto
free_out
;
}
}
stat
=
yajl_gen_string
((
yajl_gen
)
g
,
(
const
unsigned
char
*
)(
"Large"
),
strlen
(
"Large"
));
if
(
yajl_gen_status_ok
!=
stat
)
{
goto
free_out
;
}
stat
=
yajl_gen_integer
((
yajl_gen
)
g
,
(
long
long
int
)
ptr
->
large
);
if
(
yajl_gen_status_ok
!=
stat
)
{
goto
free_out
;
}
stat
=
yajl_gen_string
((
yajl_gen
)
g
,
(
const
unsigned
char
*
)(
"Middle"
),
strlen
(
"Middle"
));
if
(
yajl_gen_status_ok
!=
stat
)
{
goto
free_out
;
}
stat
=
yajl_gen_integer
((
yajl_gen
)
g
,
(
long
long
int
)
ptr
->
middle
);
if
(
yajl_gen_status_ok
!=
stat
)
{
goto
free_out
;
}
stat
=
yajl_gen_string
((
yajl_gen
)
g
,
(
const
unsigned
char
*
)(
"Small"
),
strlen
(
"Small"
));
if
(
yajl_gen_status_ok
!=
stat
)
{
goto
free_out
;
}
stat
=
yajl_gen_integer
((
yajl_gen
)
g
,
(
long
long
int
)
ptr
->
small
);
if
(
yajl_gen_status_ok
!=
stat
)
{
goto
free_out
;
}
stat
=
yajl_gen_map_close
((
yajl_gen
)
g
);
if
(
stat
!=
yajl_gen_status_ok
)
{
goto
free_out
;
}
yajl_gen_get_buf
(
g
,
&
gen_buf
,
&
gen_len
);
if
(
gen_buf
==
NULL
)
{
printf
(
"gen buf failed
\n
"
);
goto
free_out
;
}
result
=
calloc
(
gen_len
+
1
,
sizeof
(
char
));
if
(
result
==
NULL
)
{
printf
(
"out of memory
\n
"
);
goto
free_out
;
}
(
void
)
memcpy
(
result
,
gen_buf
,
gen_len
);
free_out:
yajl_gen_clear
(
g
);
yajl_gen_free
(
g
);
return
result
;
}
void
show_isula_version
(
const
struct
isula_version
*
ptr
)
{
printf
(
"iSula version:
\n
"
);
if
(
ptr
==
NULL
)
{
return
;
}
printf
(
"large: %d
\n
middle: %d
\n
small: %d
\n
"
,
ptr
->
large
,
ptr
->
middle
,
ptr
->
small
);
printf
(
"version: %s
\n
"
,
ptr
->
version
);
}
int
main
()
{
const
char
*
json_str
=
"{
\"
Version
\"
:
\"
1.0.0
\"
,
\"
Large
\"
: 1,
\"
Middle
\"
: 0,
\"
Small
\"
: 0}"
;
struct
isula_version
*
ptr
=
NULL
;
char
*
marshaled
=
NULL
;
// step 1: unmarshal json string
ptr
=
unmarshal
(
json_str
);
if
(
ptr
==
NULL
)
{
printf
(
"unmarshal failed
\n
"
);
return
-
1
;
}
show_isula_version
(
ptr
);
// step 2: marshal isula version
free
(
ptr
->
version
);
ptr
->
version
=
strdup
(
"2.0.0"
);
ptr
->
large
=
2
;
ptr
->
middle
=
1
;
ptr
->
small
=
1
;
marshaled
=
marshal
(
ptr
);
printf
(
"marshal isula version:
\n\t
%s
\n
"
,
marshaled
);
free
(
marshaled
);
free_isula_version
(
ptr
);
}
```
执行效果如下:
```
bash
$
./a.out
iSula version:
large: 1
middle: 0
small: 0
version: 1.0.0
marshal isula version:
{
"Version"
:
"2.0.0"
,
"Large"
:2,
"Middle"
:1,
"Small"
:1
}
```
这种方式虽然没法和支持动态解析的语言一样高效简单,但是也算完成了任务。如果动态解析是热兵器,这个勉强能算是长矛了。
### 缺陷
从示例来看,完成一个结构体和JSON的映射大概需要160行左右的代码。而上面只是一个简单的结构体,而且有的项目有很多这种结构体需要做映射。这种原始的方式在大型项目中很难保证参与人员代码质量可控;而且效率低下。主要的缺陷总结如下:
-
映射工作量较大;
-
对每种结构体需要单独适配代码,无法实现自动化;
-
效率低下;
-
代码质量不可控;
## 伪热兵器时代
由于C不支持反射,没法做到动态解析。但是可以通过其他途径简化解析流程、提高效率、实现自动化以及实现代码质量可控。为了避免重复造论子,17年的时候发现了
[
libocispec项目
](
https://github.com/containers/libocispec
)
,提供了一个解决C语言JSON映射的思路:
-
通过
[
json schema
](
http://json-schema.org/
)
描述JSON字符串的结构信息;
-
通过python解析
`json schema`
信息;
-
根据
`json schema`
信息自动生成C结构体和JSON的映射代码;
这种方式,可以解决上面的上一章节的几个缺陷:
-
工作量大大减小,这需要写好
`json schema`
文件即可;
-
自动化解析代码工作;
-
效率很高;
-
代码质量可控,取决于于生成框架的质量;
**注:libocispec早期只能用于解析oci spec的json,在我们发现之后,多个开发人员参与社区,提供了大量的功能升级,才有了今天的强大能力。**
### iSula集成libocispec结构
iSula当前把JSON映射相关的代码,统一放到
`lcr`
项目中进行管理,通过一个动态库和头文件提供相应功能。
生成代码的开源python框架结构如下:
```
bash
$
tree third_party/libocispec/
third_party/libocispec/
├── CMakeLists.txt
├── common_c.py
├── common_h.py
├── generate.py
├── headers.py
├── helpers.py
├── read_file.c
├── read_file.h
└── sources.py
```
`json schema`
文件存放结构(由于iSula涉及的所有JSON结构都在该目录下,所以存在大量的schema文件)如下:
```
bash
$
tree
-d
1 src/json/schema/
src/json/schema/
├── cni
│ └── network
├── container
├── cri
├── docker
│ ├── image
│ └── types
├── embedded
├── host
├── image
├── imagetool
├── logger
├── oci
│ ├── image
│ └── runtime
├── plugin
├── registry
├── shim
│ └── client
└── storage
```
然后在
`cmake`
的时候,会触发python框架,根据schema目录下面所有的schema来生成对应的映射代码。会看到如下提示信息:
```
bash
$
mkdir
build
$
cd
build
$
cmake ../
......
Reflection: isulad-daemon-configs.json Success
Reflection: timestamp.json Success
Reflection: web-signature.json Success
Reflection: host-config.json Success
Reflection: defs.json Success
Reflection: config.json Success
Reflection: manifest.json Success
Reflection: layers.json Success
......
```
### 用法
那么,现在我们如果需要对一个新的结构体和JSON进行映射,需要做的事情就是在
`json schema`
目录下面新增一个对应的schema文件即可。这里以上一章节的
`isula_version`
为例。
新增schema文件
`isula_version.json`
:
```
bash
$
cat
../src/json/schema/isula_version.json
{
"
$schema
"
:
"http://json-schema.org/draft-04/schema#"
,
"type"
:
"object"
,
"properties"
:
{
"Version"
:
{
"type"
:
"string"
}
,
"Large"
:
{
"type"
:
"int32"
}
,
"Middle"
:
{
"type"
:
"int32"
}
,
"Small"
:
{
"type"
:
"int32"
}
}
}
```
重新
`cmake`
,可以看到新生成了两个文件:
```
bash
$
ls
build/json/isula_version.
*
build/json/isula_version.c build/json/isula_version.h
```
生成的代码对外的接口如下:
```
c
$
cat
build
/
json
/
isula_version
.
h
// Generated from isula_version.json. Do not edit!
#ifndef ISULA_VERSION_SCHEMA_H
#define ISULA_VERSION_SCHEMA_H
#include <sys/types.h>
#include <stdint.h>
#include "json_common.h"
#ifdef __cplusplus
extern
"C"
{
#endif
typedef
struct
{
char
*
version
;
int32_t
large
;
int32_t
middle
;
int32_t
small
;
}
isula_version
;
void
free_isula_version
(
isula_version
*
ptr
);
isula_version
*
make_isula_version
(
yajl_val
tree
,
const
struct
parser_context
*
ctx
,
parser_error
*
err
);
yajl_gen_status
gen_isula_version
(
yajl_gen
g
,
const
isula_version
*
ptr
,
const
struct
parser_context
*
ctx
,
parser_error
*
err
);
isula_version
*
isula_version_parse_file
(
const
char
*
filename
,
const
struct
parser_context
*
ctx
,
parser_error
*
err
);
isula_version
*
isula_version_parse_file_stream
(
FILE
*
stream
,
const
struct
parser_context
*
ctx
,
parser_error
*
err
);
isula_version
*
isula_version_parse_data
(
const
char
*
jsondata
,
const
struct
parser_context
*
ctx
,
parser_error
*
err
);
char
*
isula_version_generate_json
(
const
isula_version
*
ptr
,
const
struct
parser_context
*
ctx
,
parser_error
*
err
);
#ifdef __cplusplus
}
#endif
#endif
```
测试用例:
```
c
$
cat
test
.
c
#include "isula_version.h"
#include <stdio.h>
void
show_isula_version
(
const
isula_version
*
ptr
)
{
printf
(
"iSula version:
\n
"
);
if
(
ptr
==
NULL
)
{
return
;
}
printf
(
"large: %d
\n
middle: %d
\n
small: %d
\n
"
,
ptr
->
large
,
ptr
->
middle
,
ptr
->
small
);
printf
(
"version: %s
\n
"
,
ptr
->
version
);
}
int
main
()
{
const
char
*
json_str
=
"{
\"
Version
\"
:
\"
1.0.0
\"
,
\"
Large
\"
: 1,
\"
Middle
\"
: 0,
\"
Small
\"
: 0}"
;
isula_version
*
ptr
=
NULL
;
parser_error
err
=
NULL
;
char
*
marshaled
=
NULL
;
// step 1: unmarshal
ptr
=
isula_version_parse_data
(
json_str
,
NULL
,
&
err
);
if
(
ptr
==
NULL
)
{
return
-
1
;
}
show_isula_version
(
ptr
);
// step 2: marshal
free
(
ptr
->
version
);
ptr
->
version
=
strdup
(
"2.0.0"
);
ptr
->
large
=
2
;
ptr
->
middle
=
1
;
ptr
->
small
=
1
;
marshaled
=
isula_version_generate_json
(
ptr
,
NULL
,
&
err
);
if
(
ptr
==
NULL
)
{
goto
out
;
}
printf
(
"marshal isula version:
\n\t
%s
\n
"
,
marshaled
);
out:
free
(
marshaled
);
free_isula_version
(
ptr
);
return
0
;
}
```
执行结果如下:
```
bash
$
./a.out
iSula version:
large: 1
middle: 0
small: 0
version: 1.0.0
marshal isula version:
{
"Version"
:
"2.0.0"
,
"Large"
: 2,
"Middle"
: 1,
"Small"
: 1
}
```
### 缺陷
通过libocispec可以实现接近于高级语言的
`marshal`
和
`unmarshal`
了,只需要简单编写schema文件即可,极大的提高了效率,并且依托开源社区可以提高代码质量。但是,还是存在一些缺陷。
例如
`golang`
中,marshal之后的结构体可以通过map[string]interface保存,可以完整的记录JSON字符串中的信息。而我们当前的实现,只能根据schema来解析JSON字符串,因此,存在信息丢失的情况。有些场景,规范只规定了主体的JSON结构,并且支持拓展配置,例如CNI规范。
## 近乎热兵器时代
为了解决信息丢失的问题,我们通过在结构体中记录原始的元素集合tree的方案,unmarshal的时候不会丢失原始信息,marshal的时候解析记录的元素信息,从而实现原始数据完整的传递。
具体解决方案见官方PR:https://github.com/containers/libocispec/pull/56
### 用法
使用方式和上面的基本一致,差异主要包括以下几部分:
1.
生成的代码有部分差异(_residual);
```c
$ cat isula_version.h
... ....
typedef struct {
char *version;
int32_t large;
int32_t middle;
int32_t small;
yajl_val _residual;
}
isula_version;
... ....
```
2.
解析是需要指定
`struct parser_context`
参数为
`OPT_PARSE_FULLKEY`
;
```c
$ cat test.c
#include "isula_version.h"
#include <stdio.h>
void show_isula_version(const isula_version *ptr)
{
printf("iSula version: \n");
if (ptr == NULL) {
return;
}
printf("large: %d\nmiddle: %d\nsmall: %d\n", ptr->large, ptr->middle, ptr->small);
printf("version: %s\n", ptr->version);
}
int main()
{
const char *json_str = "{\"Version\":\"1.0.0\", \"Large\": 1, \"Middle\": 0, \"Small\": 0, \"resi_int\": 1, \"resi_str\": \"test\"}";
isula_version *ptr = NULL;
parser_error err = NULL;
char *marshaled = NULL;
struct parser_context ctx;
ctx.options = OPT_PARSE_FULLKEY;
// step 1: unmarshal
ptr = isula_version_parse_data(json_str, &ctx, &err);
if (ptr == NULL) {
return -1;
}
show_isula_version(ptr);
// step 2: marshal
free(ptr->version);
ptr->version = strdup("2.0.0");
ptr->large = 2;
ptr->middle = 1;
ptr->small = 1;
marshaled = isula_version_generate_json(ptr, &ctx, &err);
if (ptr == NULL) {
goto out;
}
printf("marshal isula version:\n\t%s\n", marshaled);
out:
free(marshaled);
free_isula_version(ptr);
return 0;
}
```
3.
效果如下
```bash
$ ./a.out
iSula version:
large: 1
middle: 0
small: 0
version: 1.0.0
marshal isula version:
{
"Version": "2.0.0",
"Large": 2,
"Middle": 1,
"Small": 1,
"resi_int": 1,
"resi_str": "test"
}
```
可以看到拓展的信息,完整的传递下去了。
**通过这种方式完美的解决了CNI的拓展配置的支持,从而解决了iSulad动态支持多种插件的技术瓶颈。**
### 缺陷
上面的方案已经基本和支持反射的语言实现的功能相近了,但是,还是存在部分缺陷的。例如,动态修改JSON结构的数据会比较麻烦,需要对底层的解析库比较了解,而且比较麻烦。
## 总结
虽然当前的框架还有一些缺陷,但是,我们的目标并不是实现一个完美的JSON和C结构体的映射框架,而是解决容器引擎使用JSON的问题。而上面的方案,已经完全满足iSula当前的需求。
因此,目前没有进一步优化的需求。如果后续使用场景或者其他用户有需求,可以到
[
libocispec的社区
](
https://github.com/containers/libocispec
)
进行进一步的优化。
# 参考文章
-
https://github.com/containers/libocispec
-
http://json-schema.org/
-
https://github.com/lloyd/yajl/tree/master/example
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录