从2015年5月初开始创业开发 HyperContainer (runV) 到现在,也快五年了,在这个时候还来写一篇什么是安全容器,显得略有尴尬。不过,也正是经过这五年,越来越多到人开始感到,我需要它却说不清它,这个时候来给大家重新解释 “安全容器” 也正是时候。
Phil Karlton 有一句名言——
计算机科学界只有两个真正的难题——缓存失效和命名。
就容器圈而言,我相信命名绝对配得上这句话,这毫无疑问是一件让老开发者沉默,让新人落泪的事情。
仅就系统软件而言,我们当今比较通行地称为 Linux 容器(LinuxContainer)的这个概念,曾经用过的名字大概还有——jail, zone, virtualserver, sandbox... 而同样,在早期的虚拟化技术栈里,也曾经把一个虚拟机环境叫做容器。毕竟这个词本身就指代着那些用来包容、封装和隔离的器物,实在是太过常见。以至于,以严谨著称的 Wikipedia 里,这类技术的词条叫做“系统级虚拟化”,从而回避了“什么是容器”这个问题。
当2013年 Docker 问世之后,容器这个概念伴随着“不可变基础设施”、“云原生”这一系列概念,在随后的几年间,以摧枯拉朽之势颠覆了基于“软件包+配置”的细粒度组合的应用部署困境,用简单地声明式策略+不可变容器就清爽地描述了软件栈。应用怎么部署似乎离题了,我们这里想要强调的是——
云原生语境下的容器,实质是“应用容器”——以标准格式封装的,运行于标准操作系统环境(常常是Linux ABI)上的应用打包——或运行这一应用打包的程序/技术。
这个定义是我在这里写下的,但是它并不是我的个人意志,这是基于 OCI (Open ContainerInitiative) 规范这一共识的,这个规范规定了容器之中的应用将被放置在什么环境中,如何运行,比如启动容器根文件系统上的哪个可执行文件,用什么用户,需要什么样的 CPU、内存资源,有什么外置存储,有什么样的共享需求等等。
有了这一共识做基础,我们来说安全容器,这又是一段命名血泪史。当年,我的联合创始人赵鹏是用“虚拟化容器”命名的我们的技术的,为了搏人眼球,用了“Secure as VM, Fast asContainer”的大字标语,自此,被容器安全性问题戳中心坎的人们立刻用“Secure Container”来称呼这类东西,一发而不可收。而我们的内心中,这项技术提供了一层额外的隔离,隔离可能意味着安全性中的一环,也意味着某些运维效率、某些优化可能或者其他的功能。实际上,给安全容器这样的定义更合理——
一种运行时技术,为容器应用提供一个完整的操作系统执行环境(常常是LinuxABI),但将应用的执行与宿主机操作系统隔离开,避免应用直接访问主机资源,从而可以在容器主机之间或容器之间提供额外的保护。
安全问题的唯一正解在于允许那些(导致安全问题的)Bug发生,但通过额外的隔离层来阻挡住它们。
—— LinuxCon NA 2015, Linus Torvalds
为了安全,为什么要引入间接层?因为以 Linux 之类的目前主流宿主机操作系统的规模,是无法从理论上验证程序是没有 Bug 的,而一旦合适的 Bug 被合适地利用,安全性风险就变成了安全性问题了,框架和修补并不能确保安全,所以,进行额外的隔离、缩减攻击面,就成了缓解安全问题的法宝——我们不能确保没有漏洞,但我们通过组合来减少漏洞被彻底攻破的风险。
2017年12月,我们在 KubeCon上对外发布了 Kata Containers 安全容器项目,这个项目的两个前身——我们发起的的 runV 和Intel 发起的 Clear Container 都发布于2015年5月(是的,早于上面 Linus 的引语)。这组项目的思路很简单 ——
1. 操作系统本身的容器机制没办法解决安全性问题,需要一个隔离层;
2. 虚拟机是一个现成的隔离层,AWS这样的云服务已经让全世界相信,对户来说,"secure of VM" 是可以满足需求的;
3. 虚机里面只要有个内核,就可以支持 OCI 规范的语义,在内核上跑个 Linux 应用这并不太难实现;
4. 虚机可能不够快,阻碍了它在容器环境的应用,那么可不可以拥有 "speed of container" 呢?
现在,如果最后一个问题可以解决,那么它就是我们要的“安全的容器”了——这就是 Kata Containers。
目前 Kata Containers 通常是在 Kubernetes 环境中使用的,Kubelet 通过CRI 接口让 containerd 或 CRI-O 执行运行时操作,通常镜像操作由这些 CRI Daemon 来进行,而根据请求,把 Runtime 操作写成一个 OCI Spec 交给 OCI Runtime 执行。这里,对于 1.2 以上的 containerd 和 1.5 版本上后的 Kata Containers(对应 ),通常是利用 containerd 来完成的:
可以看到,首先 Kata Containers 是个全功能的容器运行时引擎,它用起来完全不像是传统虚机,分明就是个容器引擎,并且,通过“少用不必要的内存”和“共享能共享的内存”来降低内存的开销,更小的内存不仅开销更小,启动也更轻快,对于大多数场景来说,这实现了"secure of VM, speed of container"。在安全性之外,它比起传统的虚机,更具有容器的弹性,更少了机器的那种物理操作手感,我们把这种技术称为“云原生化虚拟化”或者“面向云原生的虚拟化”技术。
在 Kata Containers 之后半年的哥本哈根KubeCon 上,Google 开源了他们内部开发了五年的 gVisor 安全容器作为回应。
如果说 Kata Containers 是对通过对有隔离技术进行组合和改造来构建容器间的隔离层的话,gVisor 的设计显然是更加简洁的——gVisor 是一个用 Go 语言重写的运行在用户态的操作系统内核,称为 Sentry,它并不依赖于虚拟机,相反,它借助“平台(Platform)”的能力,让宿主机把应用的所有访问都重新转交给 Sentry,在 Sentry 中处理后再将一些必要的操作请宿主机帮忙来完成。
可以说 gVisor 在做的是一个纯粹的面向应用的隔离层,从 Linux ABI 到 Linux ABI 的“过滤器”。全新写作的优势在于不需要迁就太多已有技术栈的桎梏,可以写得更轻,启动肯定也会更快,事实上,资源的伸缩也更方便,或者说更容器一些。好多操作系统圈的朋友都毫不掩饰地说,他们更喜欢 gVisor 的架构,如果能解决一些目前不容易解决掉的问题的话。
gVisor 作为隔离层,它的安全性依据在于:
当然,gVisor 的架构是很漂亮,但重新实现一个内核这件事情,除了 Google 这样的巨头恐怕也没有几家可以做(类似的基本做到的也就是微软的初代 WSL了),而且这个超前的设计还是有些现实问题:
所以,短时间内 gVisor 方案并不能成为一个终极解决方案,当然,它不仅可以适应一些特定的场景,而且带来的启示性可能对未来的操作系统乃至 CPU 指令集的演进发生作用,从而推动我们可以拥有一个更完美的安全容器解决方案。
安全容器的隔离层让应用的问题——不论是恶意攻击,还是意外错误——都不至于影响宿主机,也不会在不同的 Pod 之间相互影响。而且实际上,额外隔离层带来的影响并不仅是安全,对于调度、服务质量和应用信息的保护都有好处。
传统的操作系统容器技术是内核进程管理的一个延伸,容器进程本身是一组关联的进程,对于宿主机的调度器来说是完全可见的,一个 Pod 里的所有容器或进程,同时也都被宿主机调度和管理。这就意味着,在有大量容器的环境下,宿主机内核的负担很重,在很多实际环境中已经可以观察到这个负担带来的开销了。而采纳安全容器之后,从宿主机上是看不到这些完整的信息的,隔离层同时也降低了宿主机的调度开销,减少了维护负担,避免了容器之间、容器和宿主机之间的服务质量干扰。从另一个方向看,安全容器作为一道屏障,可以让宿主机的运维管理操作不能直接访问到应用的数据,这样,把用户的应用数据直接保护在沙箱里就可以降低对用户的授权要求,保障用户的数据私密性。
当我们的目光向投向未来,可以看到,安全容器不仅仅是在做安全隔离,因为安全容器隔离层的内核,相对于宿主机的内核是独立的,专门对应用服务,从这个角度说,主机/应用的功能之间做合理的功能分配和优化,展现出让人期待的潜力,将来的安全容器,可能不仅是隔离性开销的降低,甚至是提升应用的效能——
隔离,让云原生基础设施更完美。
Kata Containers开源地址:
https://katacontainers.io/