23 Star 133 Fork 26

範輝 / Rocker

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 11.32 KB
一键复制 编辑 原始数据 按行查看 历史
範輝 提交于 2020-10-20 16:10 . update README.md.

Contents

  • ROCKER
    • 1.1. 亮点与特性
    • 1.2. 用户指南
    • 1.3. 架构说明
    • 1.4. 开发路线
    • 1.5. BUG
    • 1.6. 待办事宜

1. ROCKER

English Version

ROCKER 是一个在线解压进程沙箱实现, 针对的是基于 Linux 的资源受限的(即使是 docker 这样的容器实现自身的资源占用都是不能容忍的) IoT 系统, 旨在提高其资源利用率和系统安全性, 并且避免产生额外的性能开销.

通过将 App 程序文件及其它合适的文件压缩成squashfs包, 通常可以带来超过 60% 的磁盘空间节省, 这对于资源受限的 IoT 系统来说是非常有意义的. 与此同时, 由于 Linux 内核原生提供了动态按需解压支持, 相对于常规的 App 运行模式, 并不会产生额外的内存开销.

沙箱功能的安全性保证, 来自于Linux namespaces/cgroups, overlayfs, Rust 等基础设施, 以及大量的测试用例. 感谢 linus 大神, Rust 团队创造出这些伟大的基础设施.

本项目当前适合用于学习研究或教学目的, 不建议用于生产环境.

欢迎关注本人新项目 "TTstack".

1.1. 亮点与特性

  • 比 Docker 更优异的性能和资源利用率, 启动容器不需要额外的镜像;
  • client 库使用纯 C 开发, 裸金属开发模式, 除了 libc, 没有任何依赖;
  • server 端(库)使用 rust 开发, 坚如磐石的稳定性, 媲美 C/C++ 的运行效率和内存利用率;
  • 使用 crosstool-ng 组织交叉工具链, 可稳植性好, 稳定性高;
  • 代码风格强制统一, 整洁优雅;
  • 注重文档与测试;
  • 更多亮点, 欢迎深入源码探索...

1.2. 用户指南

1.2.1. 代码结构(简略)

整个项目分成 Client 端与 Server 端两部分, 类似于一个传统的 CS 架构, Client 端以库的形式给调用方使用.

..
├── core/                     # [Rust 代码] 服务端核心逻辑实现
├── rocker_server/            # [Rust 代码] CS server 端实现
├── librocker_client/         # [C    代码] CS client 库实现
├── librocker_client_wrapper/ # [Rust 代码] 通过 ffi 封装的 librocker_client 库, 用于测试
├── tests/                    # [Rust 代码] 测试用例
├── README.md                 # 项目主文档
└── tools/

1.2.2. 环境配置与编译

1.2.2.1. 环境需求

对应的官方说明文档, 链接如下:

1.2.2.2. 编译

理论上本项目可在任何 crosstool-ng 和 rust 支持的架构体系上编译并运行, 非 x86(_x64) 平台上默认静态链接 musl 或 uclibc 库, 以简化对运行环境的依赖.

如下以TARGET=armv7-linux-musleabihf为例:

# 编译安装, 安装路径是项目根目录内的 install_dir
make TARGET=armv7-linux-musleabihf release

# 功能测试
make TARGET=armv7-linux-musleabihf test

# 性能测试
make TARGET=armv7-linux-musleabihf bench

1.2.3. 客户端库

调用示例如下:

// 由调用方定义的, 将在 ROCKER 中执行的回调函数及其参数
void *your_args = NULL;
int start_your_APP(void *args){};

// 初始化一个空白的 RockerRequest 结构体
RockerRequest req = ROCKER_request_new();

// 为 RockerRequest 结构体赋值
req.app_id = 1000;
req.uid = 1000;
req.gid = 1000;
req.app_pkg_path = "/tmp/your_APP.squashfs";
req.app_exec_dir = "/var/your_APP/execdir";
req.app_data_dir = "/var/your_APP/datadir";
req.app_overlay_dirs = { "/usr", "/var", "/etc", "/home", "/root" };

// 尝试在 ROCKER 中运行 APP
RockerResult res = ROCKER_enter_rocker(&req, start_my_APP, my_args);

if (ROCKER_ERR_success != res.err_no) {
    // 处理错误
}

// APP 运行结束, 清理环境
kill(res.guard_pid, SIGKILL);

// 更稳健的清理方法
RockerResult res2 = ROCKER_get_guardname(res.guard_pid);
if (ROCKER_ERR_success == res2.err_no && \
        0 == strcmp(res.guard_name, res2.guard.name)) {
    kill(res.guard_pid, SIGKILL);
}

详情参见 librocker_client.

1.3. 架构说明

以下将以'时序图'的形式论述具体的逻辑架构.

1.3.2. 时序图

节点名称 定义
App 将要启动的前台业务进程
AppMaster 资源调度进程, App 大管家
RockerClient 提供给 AppMaster 使用的 client 库,负责与 RockerMaster 及 RockerGuard 交互
RockerGuard App 所在虚拟沙箱的 1 号进程, 由 RockerMaster 创建,负责执行 '在线解压' 和 '虚拟沙箱' 机制的具体逻辑
RockerMaster ROCKER 服务端资源调度接口
Kernel Linux 内核

NOTE: RockerClient 通常与 AppMaster 属于同一进程, 文档中出于可读性的考虑, 对二者做了逻辑层面的拆分.

1.3.2.1. 概览

_

(0) start App; (1) request new rocker

AppMaster 调用 RockerClient 的 API, 向 RockerMaster 请求创建新的 rocker, 并在其中运行指定的 App. 新 Rocker 的具体配置信息由 AppMaster 在其请求信息中指定.

(2) create new rocker-guard

RockerMaster 收到请求, 创建一个新的 rocker 虚拟沙箱环境, 其中的 1 号进程就是 RockerGuard, RockerGuard 扮演的角色类似于常规 Linux 系统中的 init 或 systemd 进程, 若该进程退出, 即意味着其所在的虚拟沙箱环境的完全销毁(所有的进程及磁盘挂载都会被 Kernel 自动清理).

(3)

RockerMaster 创建完对应的 RockerGuard 实例, 后续的与 AppMaster 的交互, 将由 RockerGuard 直接执行, RockerMaster 不再参与.

(4) preparing, need CAP_SYS_ADMIN {#1}

RockerGurad 根据 AppMaster 的要求, 创建对应的 rocker 环境, 此过程需要 CAP_SYS_ADMIN 能力或 root 权限, 具体的细节将在接下来的 1.3.1.1 节中描述.

(5) send rocker-entrance

RockerGuard 将创建好的 rocker 入口, 发送给 RockerClient.

(6) start App in rocker {#2}

RockerClient 进入新创建的 rocker 环境, 在其中将 App 启动起来, 具体的细节将在 1.3.1.3 节中描述.

(7) PID of guard\App

App 启动完成之后, RockerClient 将 Guard 及 App 的 PID 等相关信息返回 AppMaster, 以供 AppMaster 的后续管理. 至此, RockerClient 完成其在单次 App 启动中使命, 不再参与后续环节.

(8) stop all; (10) kill

AppMaster 停止 App 的方式有两种, 一种是按照常规的逻辑自行管理, 另一种是通过 kill RockerGuard 的方式自动完成.

两种方式各有利弊,

  • 前者对 AppMaster 来说可以有更细粒度的控制, 但环境清理的彻底性需由自身保证(参见接下来第 9 步的说明);
  • 后者通过 kill RockerGuard 触发 Kernel 的自动清理机制, 可以简化 AppMaster 的实现逻辑, 并保证环境清理的彻底性, 但 AppMaster 将不能对这个清理过程进行任何定制.

(9) exit self

RockerGuard 自动退出的条件是其所在的 rocker 虚拟沙箱中的其它进程已经全部退出, 若 AppMaster 遗漏了某个进程, 将会导致此 rocker 的资源永远不被释放.

(11) send SIGCHLD

RockerGuard 退出后, RockerMaster 收到 SIGCHLD 信号, 可选的执行一些对应的内部逻辑.

(12) rocker's [PID 1] exited

内核探知到某个 rocker(pid namespace) 中的 1 号进程退出.

(13) broadcast SIGKILL(auto, very clean); (14) umount overlay; (15) destroy useless loop device

内核自动清理其中的所有资源(包括递归产生的衍生资源).

1.3.2.2. 子图 {#1}

_

(0) clone(MNT|PID)

RockerMaster 以带 CLONE_MNT 与 CLONE_PID 标志的 clone 接口创建 RockerGuard 实例, 参见 man clone(2).

(1) create loop device

RockerGuard 调用 ioctl 调口获取可用的 loop 设备, 参见 man loop(4).

(2) bind App.sqfs to loop; (3) mount loop to exec-path

将打包成 squashfs 格式的 App 程序包, 绑定到新获取的 loop 设备上, RockerGuard 将该 loop 设备挂载到 AppMaster 要求的执行路径.

创建 App.sqfs 及绑定挂载的过程, 类似于如下的命令行逻辑:


#将 App 程序文件制作成 squashfs 包, 需先安装 squashfs-tools 工具
mksquashfs ./AppDir ./App.sqfs

#将 squashfs 包绑定到 loop 设备
losetup /dev/loop8 ./App.sqfs

#挂载到指定目录
mount ./App.sqfs /mnt/AppExecDir

(4) build overlay {#1}

RockerGuard 创建 overlay 读写隔离层, 具体细节将在 1.3.1.2 节说明.

(5) remount /proc

重新挂载 /proc, 以使新 pid_namespace 内的 pid 信息正常显示.

(6) unshare(USER)

前述工作完成以后, RockerGuard 进程调用 unshare 接口进入新的专用 user_namespace, 之后 rocker 内的任何操作都是在这个权限受限的 user_namespace 中进行, 参见 man unshare(2).

(7) done

RockerGuard 准备工作完成, 通知 RockerMaster.

(8) set uid_map

RockerMaster 为 RockerGuard 设置 uid_map(需要 CAP_SETUID) 及 gid_map(需要 CAP_SETGID). 设置 gid_map 之前, 须向 /proc/[RockerGuard PID]/setgroups 文件中写入 "deny" 值, 参见 man user_namespaces(7).

1.3.2.3. 子图 {#1} {#1}

_

(0) get all visiable top-dir except /proc,/sys,/dev,/run

遍历根下的所有顶层目录, 排除动态目录, 如 /proc, /sys, /dev, /run 等.

(1) top-dir act as 'lowerdir', and finally merged to themself

根下所有可见的顶层目录, 均在独立的 mnt_namespace 中原地叠加 overlay 隔离层, 以使每个 App 均拥有独立的可读写虚拟文件系统, 参见 内核文档 overlayfs.

以 ID 为 1000 的 App 挂载 /usr 为例, 假设其 upperdir 与 workdir 分别为 /private/1000/upperdir 与 /private/1000/workdir, overlay 挂载过程类似于如下的命令行逻辑:

mount -t overlay overlay /usr \
    -o lowerdir=/usr,upperdir=/private/1000/upperdir,workdir=/private/1000/workdir

1.3.2.4. 子图 {#2}

_

(0) rocker created

RockerGuard 准备好新的 rocker 环境后, 通知 RockerClient 端.

(1) fork out a child process, (2) setns(USER|MNT|PID)

RockerClient 创建一个子进程, 子进程调用系统的 setns 接口进入新的 rocker 环境.

(3) run App in child's brother process

在 (1) 中创建的子进程中再创建一个子进程, 在其中启动前台 App 进程.

这一步新建的子进程, 实质是 (1) 中子进程的兄弟进程, 通过带 CLONE_PARENT 标志的 clone 实现, 其父进程与 (1) 中子进程的父进程一致, 如此以来 AppMaster 可以收到 App 进程终止时发送的 SIGCHLD 信号, 参见 man clone(2).

(4) PID of guard\App

RockerClient 将 RockerGuard 及 App 的 PID 等信息返回给 AppMaster.

1.4. 开发路线

添加更多的实用功能, 如下所示的 App 进程智能调度算法 就是其中之一:

_

1.5. BUG

  • ...

1.6. 待办事宜

  • 完善英文版文档;
  • 添加日志落盘功能;
  • 添加更多的功能与性能测试用例;
  • ...
Rust
1
https://gitee.com/kt10/rocker.git
git@gitee.com:kt10/rocker.git
kt10
rocker
Rocker
master

搜索帮助