RunD – A Lightweight Secure Container Runtime

RunD – A Lightweight Secure Container Runtime for High-density Deployment and High-concurrency Startup in Serverless Computing
rund 是阿里提出的一种新的轻量级容器运行时技术。
不过目前从论文内容来看,更多是一些技术点的优化,而不是架构层面的创新

1. 设计目标

实现 serverless 场景下,pod的高密度部署、高频、高速启动
高密度部署:随着机型规格越来越大,比如 AMD milan 就有256核,可以部署数千个 pod
高频:faas 和 batch job 等负载,每天上百万的实例创建量,上亿次系统调用
高速:faas 场景的毫秒级启动,极致弹性

2. 问题分析

kata 容器的技术栈:
0
启动一个 kata 容器,首先需要通过qemu(或者其他hypervisor,比如fire cracker)拉起一个虚机,然后还需要再虚机内启动一个agent,来实现完整的oci语义
基本过程如下:
0

2.1 并发启动的开销

(1)在准备容器rootfs的可写层时有很长的耗时:同时启动200个kata container,准备rootfs需要耗时207ms,会产生4500iops和100MB/s的IO带宽,带来很高的cpu overhead
(2)同时启动多个kata containers时,涉及到host侧cgroup的创建及维护,在内核层面,凡涉及到cgroup 操作,需要持有全局粒度的自旋锁,导致cgroup 的创建及维护是一个串行过程

2.2 高密部署的瓶颈

(1)虚机(guest系统 + kata-agent + guest kernel)耗损
容器不是虚机,但是实现安全容器就必须依赖虚机。容器的规格一般都是很小的,比如内存100m 0.1vcpu,但是这个规格对虚机来说太小了,都起不来。所以为了能开一个100m的容器,你就得把虚机开到200m甚至更大,这就产生了 overhead
对于kata-qemu,一个内存规格为128MB的kata-containers,其内存overhead可以达到168MB;当部署密度从1提升到1500时,平均每个内存规格为128MB的kata-containers,其内存overhead 仍然会有145MB。
0
对于小内存规格的kata容器,guest kernel image所占内存占用了很大的比重。AWS数据:47% 的function computer的内存规格时128MB,Azure数据:90%的应用内存规格小于400MB。
(2)rootfs 内存耗损
rootfs基于块的主流解决方案在Host和Guest中生成相同的Page Cache,导致重复的内存开销。


kata 系统架构解读

https://github.com/kata-containers/kata-containers/tree/main/docs/design/architecture

https://github.com/kata-containers/kata-containers/tree/main/docs/design

1. 整体架构

如下图:

kata-containers 的核心 binary 就2个:

  1. containerd-shim-kata-v2,对应源代码目录 src/runtime
  2. kata-agent,对应源码目录 src/agent

containerd 实现了 cri 协议,可以天然直接无缝对接到 kublet 上,而 containerd-shim-kata-v2 实现了 containerd 的 shim-v2 协议,直接实现了对 kata-agent 以及 hypervisor 通讯的封装。


nr_dying_descendants 引发的在线业务性能抖动

最近发现一则比较奇怪的业务性能抖动问题

简单来说,就是离线作业频繁的触发oom,导致在线业务指标陡增。理论上来说,离线作业触发自身硬限,不应该导致其他容器出问题才是

先来说一下我们的 memory cgroup 层级结构,可以简单理解为如下:

/cgroups/v2/kubepods/

/cgroups/v2/kubepods/online/a -> a为在线服务

/cgroups/v2/kubepods/offline/job1 -> job1 为离线服务

通常来说,为了解决混部的内存争抢问题,我们会对整个 offline 大框设置一个内存上限,防止离线作业使用内存太多,导致在线服务出现内存压力。这看起来似乎没什么问题。

不过最近一个问题,发现离线 job1 频繁 oom,引发了在线的性能抖动

最后定位的结论如下:

  1. /cgroups/v2/kubepods/offline/cgroup.stat 下看到有大量的 nr_dying_descendants
  2. offline 大框频繁出发 oom
  3. oom 的回收过程有个效率不太好的地方,导致 oom 回收效率底下 -> 导致内核各种锁争抢严重 -> 进程sys高 -> cpu排队严重 -> 在线业务性能抖动

根因分析:nr_dying_descendants 的原因是由于容器被销毁时,容器内还有未完全回收干净的 page cache,所以内核不会立即释放这个 cgroup(用户态操作系统从 /cgroups 文件系统下已经看不到了,但是内核态的数据结构还在)

cat /cgroups/v2/kubepods/online/cgroup.stat

nr_descendants 10

nr_dying_descendants 4172

这就有一个问题,如果有大量的dying cgroups,内核在oom处理过程中:

  1. 如果是cgroup oom,会优先尝试从 dying cgroups 回收内存,但是最多只回收5个 -> 这个地方有效率问题
  2. 如果是整机回收,不处理dying cgroups内存

 


TMO – Transparent Memory Offloading in Datacenters

论文:https://www.pdl.cmu.edu/ftp/NVM/tmo_asplos22.pdf
内存是数据中心最大的瓶颈。百度也是如此,因为内存不足,导致大量机器cpu空闲但是利用率无法提升的主要原因
这篇论文的思路和 google 的 software-defined far memory 思路几乎是一样的,都是把 colder memory 卸载到 cheaper memory 上
google 论文是19年发表的,实际上项目2016年就已经上线了
facebook 论文是22年发表的,线上已经运行一年多。另外,论文里也隐约的提了,facebook就是在google的基础上做的,但是发现效果和 google 说的不一样,于是做了很多改进。如果我们要落地,也会有类似的问题。

1. INTRODUCTION

下文所有对比的地方,我简称:
  1. google的方案,g-swap
  2. facebook的方案,tmo
现状:
cpu的发展速度比memory要快
随着AI算法的发展,业务对内存的需求正在快速膨胀
硬件上,nvm和nvme ssd等技术的出现,Non-dram cheaper memory 开始越来越多的被使用
还有 CXL 这种 non-DDR memory bus 技术,可以接近 DDR 的原生性能
方向:内存分层技术,不管是 google 的 software-defined far memory 也有还是 facebook 的 TMO(透明内存卸载),本质上都是将低频访问的内存卸载到低速设备上
With memory tiering, less frequently accessed data is migrated to slower memory.
g-swap的一些问题:
  1. 核心是压缩
  2. 非常简单
    1. 好处:不用解决异构这些复杂的问题(感觉解决了?做了负载分类)
    2. 坏处:不利于最大化的 memory-cost saving
  3. promotion rate 的设定依赖离线分析
  4. 所有应用的 promotion rate 是一样的。没有考虑不同业务对内存的敏感性,以及不同分层设备的 performance(后者其实有简单的考虑,具体的 P% 的设备就取决于 far memory 到 near memory 的性能表现)
    1. g-swap的观点:为了保证应用程序的性能,memory offloading 应该控制在一个预先配置好的比例下面
    2. tmo的观点:facebook 对它们部署规模最大的程序,发现,在一些高速设备上,提高 memory offloading 比例,可以提高性能。 –> 反人类假设??
0
为了解决这些问题,facebook 提出了 tmo 方案,优点:
  1. 核心是卸载(但是卸载的backend 支持 compressed memory pool,也支持 NVMe SSDs)
  2. 不需要离线分析,依赖 PSI 的反馈,直接单机决定不同的 offloading 比例。不依赖app的任何先验知识(g-swap的方案就有一个冷启动问题,新app默认不回收任何内存)
  3. PSI accounts for application’s sensitivity to memory-access slowdown(这里其实是比较误导的,facebook认为psi观测出来的内存延迟一定是对业务敏感的,但是实际上不同的业务对延迟的敏感性是不一样的,简单来说这个是充分不必要条件)
收益空间:20-32% 收益,百万服务器,其中7-19%由应用贡献,13%由sidecar等基础服务贡献


How to install cuda and pytorch dev environment on thinkpad w530 & ubuntu 20.04

最近发现在笔记本上用 cpu 来训练模型效率还是太慢了,正好我笔记本上有一块 nvidia gpu,准备折腾一下把这块 gpu 用起来

由于在笔记本环境下安装 gpu 开发环境比较痛苦,虽然最终失败了,但是过程比较有意义,值得备忘一下

我的开发环境主要是 pytorch + cuda

硬件环境就是 thinkpad w530 + 32g + K1000M

由于笔记本的 gpu 通常型号比较老,并不是大部分的 pytorch 都支持

1. 安装 cuda

网上有很多资料,怎么安装 nvidia-cuda-toolkit 的,apt install nvidia-cuda-toolkit 就可以直接装

但是我发现装完了之后没法用,pytorch 没法检测到 cuda 环境。这两个东西不完全是一个东西,有挺多差异的。

python3 -c ‘import torch; print(torch.cuda.is_available())’ 必须返回 true

最后用官方的安装方法成功了

https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=20.04&target_type=deb_local

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600

wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installers/cuda-repo-ubuntu2004-11-7-local_11.7.0-515.43.04-1_amd64.deb

sudo dpkg -i cuda-repo-ubuntu2004-11-7-local_11.7.0-515.43.04-1_amd64.deb
sudo cp /var/cuda-repo-ubuntu2004-11-7-local/cuda-*-keyring.gpg /usr/share/keyrings/
sudo apt-get update
sudo apt-get -y install cuda

2. 安装 nvidia-driver

ubuntu 默认源里面会有一些基本的 nvidia-driver

ubuntu-drivers devices 可以看到

# ubuntu-drivers devices
== /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
modalias : pci:v000010DEd00000FFCsv000017AAsd000021F5bc03sc00i00
vendor : NVIDIA Corporation
model : GK107GLM [Quadro K1000M]
manual_install: True
driver : nvidia-driver-390 - distro non-free
driver : nvidia-340 - distro non-free recommended
driver : nvidia-driver-418-server - distro non-free
driver : xserver-xorg-video-nouveau - distro free builtin

这里面会推荐你使用 nvidia-340

但是我的实际测试来看,nvidia-340是没法用的,安装完了之后显示器各种异常,最后安装到 nvidia-driver-390 解决了显示器异常的问题

但是nvidia-driver-390 版本太低,cuda 现在依赖的 nvidia-driver 版本都是470以上了,所以光有 nvidia-driver 是不行的,必须和 cuda 配套。

最终在安装 cuda 的时候会自动安装 nvidia-driver 配套驱动,其实不用自己搞

安装完,需要重启机器,重启后 nvidia-smi 检查能不能看到显卡信息


Stock Selection via Spatiotemporal Hypergraph Attention Network – A Learning to Rank Approach

论文原址:https://www.aaai.org/AAAI21Papers/AAAI-7907.SawhneyR.pdf

这几年研究把深度学习应用到量化交易和传统投资决策的论文并不少,但都有2个问题:

  1. They do not directly optimize the target of investment in terms of profit
  2. and treat each stock as independent from the others, ignoring the rich signals between related stocks’ temporal price movemen

为了解决这些问题,作者提出了一种叫 STHAN-SR 的超图神经网络。并通过对美3大交易所最近6年的数据,证明这个网络在当前所有股票预测算法里面是最优的

Introduction

美股市场,68万亿,19年

forecasting:预测

highly volatile and non-stationary nature – 股票市场具有很强的波动性

synergy 协同

当前主流的股票预测算法,最大的弊端就是不以投资收益为优化目标。因为这些算法,要么是基于回归来预测价格,要么基于分类来做交易决策,这种分类和回归的优化目标是为了提高股票价格走向的准确性或最小化预测股票收益的误差,而不一定是为了直接利润

(有什么不一样吗??一脸懵比)

0

第二个弊端就是不考虑股票之间的相关性,总是把这些股票作为独立的个体来分析,或者使用一个过于简化的股票市场模型,一个由单个股票之间的成对关系组成的图表,但在现实中,这与真正的市场是相反的


阿里云 GPU Sharing in Kubernetes

文档:https://github.com/AliyunContainerService/gpushare-scheduler-extender/blob/master/docs/designs/designs.md

阿里云的 gpu sharing 只是实现了资源的按需分配和调度,并没有解决算力 & 显存隔离的问题

基于k8s原生的Scheduler Extender、Extended Resource、DevicePlugin机制来实现

提供2个接口:

  • aliyun.com/gpu-mem: 单位从  number of GPUs 变更为 amount of GPU memory in MiB,如果一个Node有多个GPU设备,这里计算的是总的GPU Memory
  • aliyun.com/gpu-count:对应于Node上的GPU 设备的数目

整体架构:


dCat – Dynamic Cache Management for Efficient Performance-sensitive Infrastructure-as-a-Service

核心前提:只要独占 cache size 足够大,业务的性能是可以得到保证的
we can conclude that whether CAT can provide perfor mance isolation between workloads is highly dependent on the size of the dedicated cache

思路:基于CPI的动态反馈,来动态调整每个容器的独占 cache 和共享 cache 容量,达到每个容器都可以得到很好的性能

难点:预测 wss(业务真正需要的cache size)。这里面有一个容量失效和冲突失效的问题,冲突失效和容量失效是 有一定的overlap的,冲突失效是因为多条cache数据映射到同一个cache line上,导致的miss,这个问题会导致wss难以预估。这个问题没有什么太好的思路,所以dCat的思路是基于feedback来动态扩大cache size

dCat 的状态机是这样的:
简单来说,是一个实时feedback的过程
Reclaim:初始状态
Recevier:减少cache退化,增加cache会提升
keeper:减少cache退化,增加cache不提升
Donor:减少cache不退化,增加cache不提升
Streaming:增加cache不断提升

系统流程:

备注:

  1. baseline是基准性能,但是每个阶段,都有不同的 baseline,会动态变化 Baseline performance is only defined for a specifi workload phase, so it has to be determined again at every phase change.
  2. Categonize workloads,就是识别容器的状态,对应到上面的几个状态机上,Reciver、keeper、Doner

问题:

  1. 论文中CAT隔离,使用的是qpos,qpos是CAT的前身,只支持core级别的cache隔离,也就是cache隔离相当于是通过绑核实现的

Software-defined far memory in warehouse-scale computers

论文重点

  • 采用软件zswap的方式进行cold memory数据压缩
  • google的业务有32%的memory数据是cold memory,其中20%的数据是可压缩的,压缩效率中位数为3倍,那么TCO有4.2%的收益
  • 该压缩策略保证了SLA不受影响:通过cold memory和promotion rate的模型保证
  • 压缩解压缩的开销不大,完全可以接受
  • 使用ML进行autotuner

1 introduction

问题:
  • 随着资源的scaling,一个资源瓶颈会影响整个scaling的TCO。
  • DRAM成为scaling的瓶颈,memory的TCO减少逐渐放缓
  • 随着大数据workload的普及,主流的in-memory computing的方式加速了DRAM的需求
方向:far memory(介于DRAM和DISK之间的存储)
far memroy需要解决的问题:
  • 不影响应用的性能
  • 应用数量多且种类多样,不能对每个应用单独优化(不现实),需要对业务透明且robust的机制,有效使用Far memory
  • 动态的cold数据行为:业务的数据冷热是会变化的,比如不同该时间段

 


Concurrency management in BPF

eBPF 对并发编程的支持是非常弱的

有一些讨论:

  1. https://lists.iovisor.org/g/iovisor-dev/topic/bpf_concurrency/74407447
  2. https://lwn.net/Articles/779120/

但是你甚至在 eBPF 的官方文档 https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md 里面几乎找不到任何关于并发的描述,除了 map.atomic_increment() 接口

但是这个借口也很奇怪。为了突出这个接口是原子的,线程安全的,难道其他接口就不是了吗?但好像又不是,因为 eBPF 里有大量的示例,并没有对 map 做特殊的并发控制,所以理论上可以认为,其他大部分 map 操作接口,也是线程安全的

线程安全验证

测试代码如下:

// calculate latency
int trace_run(struct pt_regs *ctx, struct task_struct *prev)
{
    u32 pid = bpf_get_current_pid_tgid();
    u64 *tsp;
    if (pid <= 1) {
        return 0;
    }
    
    pid = 10000; 
    u64 ts = bpf_ktime_get_ns();
    start.update(&pid, &ts);
   
#define __OP__ == 
    tsp = start.lookup(&pid); if (tsp __OP__ 0) { dist.increment(bpf_log2l(1)); }
    tsp = start.lookup(&pid); if (tsp __OP__ 0) { dist.increment(bpf_log2l(1)); }
    /** 重复N次 **/
    tsp = start.lookup(&pid); if (tsp __OP__ 0) { dist.increment(bpf_log2l(1)); }
    tsp = start.lookup(&pid); if (tsp __OP__ 0) { dist.increment(bpf_log2l(1)); }
    
    start.delete(&pid);
    return 0;
}

这段代码,修改自 bbc 工具集自带的 runqlat.py

这段代码实际上是在构造一个冲突场景,来验证 trace_run 到底是并行执行还是串行执行的。代码的逻辑是在一个超高频触发的钩子(调度器进行上下文切换)函数里,先在 map 里插入一个固定key(pid)的元素,然后查询 N 次,每次查询如果 key 不存在,计数器就 +1,最后把 key 从 map 里面删除掉,如果:

  1. trance_run 是串行的,那么技术器永远都是 0,因为在一个串行执行的逻辑里,插入元素后,不管查询多少次,元素必然是存在的
  2. 如果 trace_run 是并行的,那么就存在一个条件,一定有一个 cpu,在另外一个 cpu 查询 key 是否存在的时候,执行到了函数结束的地方,把 key 从 map 里面删掉了

事实证明,trace_run 是并行执行的,并且中间 lookup 的次数越多,冲突的概率就越大