容器彻底改变了开发,打包和部署应用程序的方式。但是,暴露给容器的系统表面足够多,以至于很多安全人员并不建议使用。
越来越希望运行更多应用担任更多的角色,同时也出现不同的安全问题,这引发了对沙盒容器 - 容器的新兴趣,这些容器有助于在主机操作系统和容器内运行的应用程序之间提供安全的隔离边界。
为此,我们想介绍一种新型沙箱gVisor,它有助于为容器提供安全隔离,同时比虚拟机(VM)更轻量级。gVisor与Docker和Kubernetes集成,使得在生产环境中运行沙盒容器变得简单易行。
传统的容器不是沙箱
在传统Linux容器中运行的应用程序以与常规(非容器化)应用程序相同的方式访问系统资源:通过直接向主机内核进行系统调用。内核以特权模式运行,允许它与必要的硬件交互并将结果返回给应用程序
对于传统容器,内核对应用程序可以访问的资源施加了一些限制。这些限制是通过使用Linux cgroup和命名空间来实现的,但并非所有资源都可以通过这些机制进行控制。此外,即使有这些限制,内核仍会暴露出恶意应用程序可以直接攻击的很大的表面区域。
像seccomp过滤器这样的内核功能可以在应用程序和主机内核之间提供更好的隔离,但是它们需要用户创建预定义的系统调用白名单。实际上,通常很难知道应用程序预先需要哪些系统调用。在应用程序需要的系统调用中发现漏洞时,过滤器也几乎没有帮助。
现有的基于VM的容器技术
一种改进容器隔离的方法是在其自己的虚拟机(VM)中运行每个容器。这为每个容器提供了自己的“机器”,包括内核和虚拟化设备,完全独立于主机。即使guest虚拟机中存在漏洞,虚拟机管理程序仍会隔离主机以及主机上运行的其他应用程序/容器。
在不同的VM中运行容器可提供出色的隔离,兼容性和性能,但可能还需要更大的资源占用空间。
Kata容器是一个开源项目,它使用精简的VM来保持最小的资源占用空间并最大化容器隔离的性能。与gVisor一样,Kata包含一个与Docker和Kubernetes兼容的Open Container Initiative(OCI)运行时API。
带有gVisor的沙盒容器
gVisor比VM更轻量级,同时保持相似的隔离级别。gVisor的核心是一个内核,它作为一个普通的,无特权的进程运行,支持大多数Linux系统调用。这个内核是用Go编写的,它是根据内存和类型安全性选择的。就像在VM中一样,在gVisor沙箱中运行的应用程序获得自己的内核和一组虚拟化设备,与主机和其他沙箱不同。
gVisor通过拦截应用程序系统调用并充当客户内核提供强大的隔离边界,同时在用户空间中运行。与创建时需要一组固定资源的VM不同,gVisor可以随着时间的推移适应不断变化的资源,就像大多数普通的Linux进程一样。gVisor可以被认为是一个极其半虚拟化的操作系统,具有灵活的资源占用空间和比完整VM更低的固定成本。但是,这种灵活性的代价是更高的每系统调用开销和应用程序兼容性 - 更多内容如下。
与Docker和Kubernetes集成
gVisor在运行时通过runsc(“运行沙盒容器”的缩写)与Docker和Kubernetes无缝集成,符合OCI运行时API。
docker的默认运行容器时候,runsc
运行时是可以互换runc
。安装简单;,一旦安装,它只需要一个额外的标志来在Docker中运行沙盒容器:目前支持产品的页面:https://gvisor.dev/docs/user_guide/compatibility/
- 安装
[root@linuxea.com ~]# wget https://storage.googleapis.com/gvisor/releases/nightly/latest/runsc
[root@linuxea.com ~]# wget https://storage.googleapis.com/gvisor/releases/nightly/latest/runsc.sha512
[root@linuxea.com ~]# sha512sum -c runsc.sha512
[root@linuxea.com ~]# chmod a+x runsc
如果此刻无法安装请尝试如下:
github
wget https://raw.githubusercontent.com/marksugar/dockerMops/master/runsc/runsc -O /usr/local/bin/runsc
wget https://raw.githubusercontent.com/marksugar/dockerMops/master/runsc/runsc.sha512 -O /usr/local/bin/runsc.sha512
cd /usr/local/bin/
sha512sum -c runsc.sha512
chmod a+x runsc.sha512
添加到daemon.json文件中
[root@linuxea.com ~]# cat /etc/docker/daemon.json
{
"bip": "172.31.0.1/16",
"insecure-registries": ["registry.linuxea.com"],
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc"
}
}
}
重启
[root@linuxea.com ~]# systemctl restart docker
- 使用
--runtime=runsc
[root@linuxea.com ~]# docker run --name redis -d --runtime=runsc -p 6379:6379 marksugar/redis:5.0.0
c59fcbc99d295a9b0a3fda760dbc88c078c7dfa24d25842f2db9f3c1b1f0ee1c
[root@linuxea.com ~]# docker logs redis
[i] Start configuration /etc/redis
[ok] /etc/redis/redis.conf config ready
[i] If you want to use variables, please turn on REDIS_CONF=on
[i] No variable substitution /etc/redis/redis.conf
[i] Start up /usr/local/bin/redis-server /etc/redis/redis.conf
[root@linuxea.com ~]# docker exec -i redis ps aux
PID USER TIME COMMAND
1 root 0:00 {Initialization.} /bin/sh /Initialization.sh
2 root 0:00 /usr/local/bin/redis-server /etc/redis/redis.conf
6 root 0:00 ps aux
为了方便期间,使用ubuntu验证
[root@LinuxEA /etc/docker]# docker run -it --rm --runtime=runsc ubuntu dmesg
[ 0.000000] Starting gVisor...
[ 0.174239] Forking spaghetti code...
[ 0.611425] Preparing for the zombie uprising...
[ 0.631965] Waiting for children...
[ 0.968107] Granting licence to kill(2)...
[ 1.057559] Letting the watchdogs out...
[ 1.267850] Creating cloned children...
[ 1.536684] Creating process schedule...
[ 2.013456] Reading process obituaries...
[ 2.202789] Generating random numbers by fair dice roll...
[ 2.205842] Digging up root...
[ 2.644056] Ready!
这样并不然是方便的,我们在加一个默认参数来配合使用 "default-runtime": "runsc"
[root@LinuxEA ~]# cat /etc/docker/daemon.json
{
"bip": "10.10.10.1/24",
"default-runtime": "runsc",
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc"
}
}
}
而后重启不需要添加--runtime=runsc参数也可
[root@LinuxEA ~]# systemctl restart docker
[root@LinuxEA ~]# docker run -it --rm ubuntu dmesg
[ 0.000000] Starting gVisor...
[ 0.218754] Creating cloned children...
[ 0.635654] Creating bureaucratic processes...
[ 0.765353] Generating random numbers by fair dice roll...
[ 0.778448] Gathering forks...
[ 1.029718] Searching for needles in stacks...
[ 1.140639] Digging up root...
[ 1.425273] Granting licence to kill(2)...
[ 1.708911] Forking spaghetti code...
[ 2.032427] Segmenting fault lines...
[ 2.209941] Rewriting operating system in Javascript...
[ 2.665411] Ready!
倘若你使用的是--network=host,你可能还需要修改如下
[root@Linuxea.com /data/linuxea/build]# cat /etc/docker/daemon.json
{
"bip": "10.10.10.1/24",
"default-runtime": "runsc",
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc",
"runtimeArgs": [
"--network=host"
]
}
}
}
而后重启即可
[root@linuxea.com /data/linuxea/build]# docker exec -it mariadb dmesg
[ 0.000000] Starting gVisor...
[ 0.176162] Gathering forks...
[ 0.337078] Consulting tar man page...
[ 0.366665] Forking spaghetti code...
[ 0.619321] Feeding the init monster...
[ 0.796898] Letting the watchdogs out...
[ 0.846904] Segmenting fault lines...
[ 0.874947] Generating random numbers by fair dice roll...
[ 1.122600] Mounting deweydecimalfs...
[ 1.598290] Reading process obituaries...
[ 1.650848] Searching for socket adapter...
[ 1.761147] Ready!
在Kubernetes中,大多数资源隔离发生在pod级别,使pod自然适合gVisor沙箱边界。Kubernetes社区目前正在正式化沙盒pod API,但现在可以获得实验性支持。
在runsc
运行时可以通过使用任一的运行沙盒在KubernetesCRI-O或CRI-containerd项目,其中转换消息从Kubelet成OCI运行命令。
gVisor实现了Linux系统API的大部分(200个系统调用和计数),但不是全部。目前不支持某些系统调用和参数,/proc和/sys文件系统的某些部分也是如此。因此,并非所有应用程序都将在gVisor中运行,但许多应用程序运行得很好,包括Node.js,Java 8,MySQL,Jenkins,Apache,Redis,MongoDB等等。更多的使用参考https://github.com/google/gvisor,https://gvisor.dev/docs/user_guide/docker/
更多阅读
有关如何将密码信息传递到容器的更多信息的一些建议是:
- linuxea:kubernetes secret简单用法(25)
- linuxea:kubernetes 介绍ConfigMap与Secret(23)
- linuxea:docker的安全实践
学习更多
学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。
- docker目录
- 白话容器
- docker-compose