Linux 容器与未来云
基于 Linux 的容器基础设施是一种新兴的云技术,它基于快速且轻量级的进程虚拟化。它为其用户提供尽可能接近标准 Linux 发行版的环境。与半虚拟化解决方案 (Xen) 和硬件虚拟化解决方案 (KVM)(它们提供虚拟机 (VM))相反,容器不创建操作系统内核的其他实例。由于容器比 VM 更轻量级,因此在同一主机上,容器可以比 VM 实现更高的密度(实际上,在同一主机上,您可以部署比 VM 更多的容器实例)。
容器相对于 VM 的另一个优势是,启动和关闭容器比启动和关闭 VM 快得多。主机下的所有容器都在同一个内核下运行,而像 Xen 或 KVM 这样的虚拟化解决方案中,每个 VM 都运行自己的内核。有时,在给定主机下的所有容器中在同一个内核下运行的约束可能被认为是缺点。此外,您不能在基于 Linux 的容器中运行 BSD、Solaris、OS/x 或 Windows,有时这个事实也可能被认为是缺点。
进程级虚拟化的想法本身并不新鲜,Solaris Zones 以及 BSD jails 几年前就已经实现了它。其他实现进程级虚拟化的开源项目已经存在多年。然而,它们需要自定义内核,这通常是一个主要的挫折。LXC 项目在主流内核上对基于 Linux 的容器的全面且稳定的支持是相对较新的,正如您将在本文中看到的那样。这使得容器对云基础设施更具吸引力。越来越多的托管和云服务公司正在采用基于 Linux 的容器解决方案。在本文中,我将介绍一些基于 Linux 的开源容器项目及其使用的内核功能,并展示一些使用示例。我还将介绍用于创建 LXC 容器的 Docker 工具。
现代基于 Linux 的容器的底层基础设施主要由两个内核功能组成:命名空间和 cgroups。命名空间有六种类型,它们提供以下操作系统资源的按进程隔离:文件系统 (MNT)、UTS、IPC、PID、网络和用户命名空间(用户命名空间允许在用户命名空间和主机的全局命名空间之间映射 UID 和 GID)。例如,通过使用网络命名空间,每个进程都可以拥有自己的网络堆栈实例(网络接口、套接字、路由表和路由规则、netfilter 规则等等)。
创建网络命名空间非常简单,可以使用以下 iproute
命令完成:ip netns add myns1
。使用 ip netns command
,也可以轻松地将一个网络接口从一个网络命名空间移动到另一个网络命名空间,监控网络命名空间的创建和删除,找出指定的进程属于哪个网络命名空间等等。同样地,当使用 MNT 命名空间时,当挂载文件系统时,其他进程将看不到此挂载,并且当使用 PID 命名空间时,通过从该 PID 命名空间运行 ps
命令,您将只看到从该 PID 命名空间创建的进程。
cgroups 子系统提供资源管理和计费。例如,它可以让您轻松定义进程可能使用的最大内存。这是通过使用 cgroups VFS 操作完成的。cgroups 项目由两位 Google 开发人员 Paul Menage 和 Rohit Seth 于 2006 年启动,最初称为“进程容器”。命名空间和 cgroups 都不干预内核的关键路径,因此它们不会产生很高的性能损失,但内存 cgroup 除外,在某些工作负载下,内存 cgroup 可能会产生显著的开销。
基于 Linux 的容器基本上,容器是一个 Linux 进程(或多个进程),它具有特殊的功能,并且在主机上配置的隔离环境中运行。您有时可能会遇到诸如虚拟环境 (VE) 和虚拟专用服务器 (VPS) 之类的术语来表示容器。
此容器的功能取决于容器的配置方式以及使用的基于 Linux 的容器,因为基于 Linux 的容器在几个项目中以不同的方式实现。我在本文中提到了最重要的几个。
-
OpenVZ:OpenVZ 项目的起源在于名为 Virtuozzo 的专有服务器虚拟化解决方案,Virtuozzo 最初由一家名为 SWsoft 的公司于 1997 年创立。2005 年,Virtuozzo 产品的一部分作为开源项目发布,并被命名为 OpenVZ。后来,在 2008 年,SWsoft 与一家名为 Parallels 的公司合并。OpenVZ 用于提供托管和云服务,它是 Parallels Cloud Server 的基础。与 Virtuozzo 一样,OpenVZ 也基于修改后的 Linux 内核。此外,它还具有用于管理容器的命令行工具(主要是
vzctl
),并且它使用模板为各种 Linux 发行版创建容器。OpenVZ 也可以在某些未修改的内核上运行,但功能集有所减少。OpenVZ 项目旨在将来完全纳入主线,但这可能需要很长时间。 -
Google 容器:2013 年,Google 发布了其容器堆栈 lmctfy(代表 Let Me Contain That For You)的开源版本。目前,它仍处于 beta 阶段。lmctfy 项目基于使用 cgroups。目前,Google 容器不使用内核命名空间功能,该功能被其他基于 Linux 的容器项目使用,但使用此功能已列入 Google 容器项目路线图。
-
Linux-VServer:一个开源项目,于 2001 年首次公开发布,它提供了一种在主机上安全地分区资源的方法。主机应运行修改后的内核。
-
LXC:LXC(LinuX Containers)项目提供了一组用户空间工具和实用程序来管理 Linux 容器。许多 LXC 贡献者来自 OpenVZ 团队。与 OpenVZ 相反,它在未修改的内核上运行。LXC 完全用用户空间编写,并支持 Python、Lua 和 Go 等其他编程语言的绑定。它在大多数流行的发行版中都可用,例如 Fedora、Ubuntu、Debian 等。红帽企业 Linux 6 (RHEL 6) 将 Linux 容器作为技术预览引入。您可以在 x86 以外的架构上运行 Linux 容器,例如 ARM(例如,Web 上有几个关于在 Raspberry PI 上运行容器的指南)。
我还应该提到 libvirt-lxc 驱动程序,您可以使用它来管理容器。这是通过定义 XML 配置文件,然后运行 virsh start
、virsh console
和 visrh destroy
分别运行、访问和销毁容器来完成的。请注意,libvirt-lxc 和用户空间 LXC 项目之间没有公共代码。
首先,您应该通过运行 lxc-checkconfig
来验证您的主机是否支持 LXC。如果一切正常,您可以使用几个现成的模板之一来创建容器。在 lxc-0.9 中,有 11 个这样的模板,主要用于流行的 Linux 发行版。如果需要,您可以轻松地根据您的要求定制这些模板。因此,例如,您可以使用以下命令创建一个名为 fedoraCT 的 Fedora 容器
lxc-create -t fedora -n fedoraCT
默认情况下,容器将在 /var/lib/lxc/fedoraCT 下创建。您可以通过添加 --lxcpath PATH
选项为生成的容器设置不同的路径。
-t
选项指定要使用的模板的名称(在本例中为 fedora
),-n
选项指定容器的名称(在本例中为 fedoraCT
)。请注意,您还可以在 Fedora 上创建其他发行版的容器,例如 Ubuntu(您需要 debootstrap
软件包)。并非所有组合都保证有效。
您可以在添加 --
后将参数传递给 lxc-create
。例如,您可以使用 -R
或 -r
选项创建几个发行版的旧版本,具体取决于发行版模板。要在运行 Fedora 20 的主机上创建旧版本的 Fedora 容器,您可以运行
lxc-create -t fedora -n fedora19 -- -R 19
您可以使用以下命令从文件系统中删除 LXC 容器的安装
lxc-destroy -n fedoraCT
对于大多数模板,当首次使用模板时,几个必需的软件包文件将被下载并缓存在磁盘上的 /var/cache/lxc 下。当使用同一模板创建新容器时,将使用这些文件,因此,下次创建使用同一模板的容器将更快。
您可以使用以下命令启动您创建的容器
lxc-start -n fedoraCT
并使用以下命令停止它
lxc-stop -n fedoraCT
默认情况下,lxc-stop
使用的信号是 SIGPWR。为了在之前的示例中使用 SIGKILL,您应该在 lxc-stop
中添加 -k
lxc-stop -n fedoraCT -k
您还可以通过添加 -d
将容器作为守护程序启动,然后使用 lxc-console
登录到其中,如下所示
lxc-start -d -n fedoraCT
lxc-console -n fedoraCT
您为给定容器运行的第一个 lxc-console
会将您连接到 tty1。如果 tty1 已经被使用(因为那是您为该容器运行的第二个 lxc-console),您将被连接到 tty2,依此类推。请记住,tty 的最大数量由容器配置文件中的 lxc.tty
条目配置。
您可以使用以下命令创建非运行容器的快照
lxc-snapshot -n fedoraCT
这将在 /var/lib/lxcsnaps/fedoraCT 下创建一个快照。您创建的第一个快照将被称为 snap0
;第二个快照将被称为 snap1
,依此类推。您可以在稍后使用 -r
选项恢复快照——例如
lxc-snapshot -n fedoraCT -r snap0 restoredFdoraCT
您可以使用以下命令列出快照
lxc-snapshot -L -n fedoraCT
您可以通过运行以下命令显示正在运行的容器
lxc-ls --active
还可以通过脚本使用脚本语言来管理容器。例如,以下简短的 Python 脚本启动 fedoraCT 容器
#!/usr/bin/python3
import lxc
container = lxc.Container("fedoraCT")
container.start()
容器配置
为每个新创建的容器生成默认配置文件。默认情况下,此配置文件在 /var/lib/lxc/<containerName>/config 中创建,但您可以使用 --lxcpath PATH
选项更改它。您可以配置各种容器参数,例如网络参数、cgroups 参数、设备参数等。以下是容器配置文件中一些流行的配置项的示例
-
您可以通过在配置文件中为
lxc.cgroup.[子系统名称]
条目设置值来设置各种 cgroups 参数。子系统名称是 cgroup 控制器的名称。例如,将容器可以使用的最大内存配置为 256MB 是通过将lxc.cgroup.memory.limit_in_bytes
设置为 256MB 来完成的。 -
您可以通过设置
lxc.utsname
来配置容器主机名。 -
您可以使用
lxc.network.type
参数设置五种类型的网络接口:empty
、veth
、vlan
、macvlan
和phys
。使用veth
非常常见,以便能够将容器连接到外部世界。通过使用phys
,您可以将网络接口从主机网络命名空间移动到容器网络命名空间。 -
有一些功能可用于加强 LXC 容器的安全性。您可以通过使用安全计算模式或
seccomp
策略以及配置文件中的lxc.seccomp
条目来避免从容器内部调用某些指定的系统调用。您还可以使用lxc.cap.drop
条目从容器中删除功能。例如,设置lxc.cap.drop = sys_module
将创建一个没有 CAP_SYS_MDOULE 功能的容器。尝试从该容器内部运行insmod
将会失败。您还可以为您的容器定义 Apparmor 和 SELinux 配置文件。您可以在 LXC README 和man 5 lxc.conf
中找到示例。
Docker 是一个开源项目,它可以自动化容器的创建和部署。Docker 于 2013 年 3 月首次发布,采用 Apache License Version 2.0。它最初是一家名为 dotCloud 的平台即服务 (PaaS) 公司的内部项目,当时称为 Docker Inc.。最初的原型是用 Python 编写的;后来整个项目用 Go 重写,Go 是一种最初在 Google 开发的编程语言。2013 年 9 月,红帽宣布将与 Docker Inc. 合作开发红帽企业 Linux 和红帽 OpenShift 平台。Docker 需要 Linux 内核 3.8(或更高版本)。在 RHEL 系统上,Docker 在 2.6.32 内核上运行,因为必要的补丁已向后移植。
Docker 利用 LXC 工具包,因此目前仅适用于 Linux。它在 Ubuntu 12.04、13.04;Fedora 19 和 20;RHEL 6.5 及更高版本等发行版以及 Amazon EC2、Google Compute Engine 和 Rackspace 等云平台上运行。
Docker 镜像可以存储在公共存储库中,可以使用 docker pull
命令下载——例如,docker pull ubuntu
或 docker pull busybox
。
要显示主机上可用的镜像,您可以使用 docker images
命令。您可以使用 docker images fedora
将命令缩小到特定类型的镜像(例如 fedora)。
在 Fedora 上,运行 Fedora docker 容器很简单;安装 docker-io package
后,您只需使用 systemctl start docker
启动 docker 守护程序,然后您可以使用 docker run -i -t fedora /bin/bash
启动 Fedora docker 容器。
Docker 具有类似 git 的功能来处理容器。如果您销毁容器,则您在容器中所做的更改将丢失,除非您使用 docker commit <containerId> <containerName/containerTag>
提交您的更改(很像您在 git 中所做的那样)。这些镜像可以上传到公共注册表,并且可供任何想要下载它们的人下载。或者,您可以设置私有 Docker 存储库。
Docker 能够使用内核设备映射器功能创建快照。在早期版本(Docker 0.7 版本之前)中,它是使用 AUFS(联合文件系统)完成的。Docker 0.7 添加了“存储插件”,因此人们可以在设备映射器和 AUFS 之间切换(如果他们的内核支持它),以便 Docker 可以在不支持 AUFS 的 RHEL 版本上运行。
您可以通过手动运行命令并提交生成的容器来创建镜像,但您也可以使用 Dockerfile 描述它们。就像 Makefile 将代码编译成二进制可执行文件一样,Dockerfile 将从简单的指令构建一个随时可运行的容器镜像。从 Dockerfile 构建镜像的命令是 docker build
。Docker 网站上有一个关于 Dockerfile 及其命令语法的教程。例如,以下简短的 Dockerfile 用于为 Fedora 镜像安装 iperf
软件包
FROM fedora
MAINTAINER Rami Rosen
RUN yum install -y iperf
您可以免费将您的镜像上传并存储在 Docker 公共索引上。就像 GitHub 一样,存储公共镜像是免费的,只需要您注册一个帐户。
检查点/恢复功能CRIU(用户空间中的检查点/恢复)项目主要在用户空间中实现,并且在内核中散布着 100 多个小补丁来支持它。之前曾多次尝试完全在内核空间中实现检查点/恢复,其中一些尝试来自 OpenVZ 项目。然而,内核社区拒绝了所有这些尝试,因为它们太复杂了。
检查点/恢复功能使您能够将进程状态保存在几个镜像文件中,并在稍后的时间在同一主机或不同的主机上从冻结点恢复此进程。此进程也可以是 LXC 容器。镜像文件是使用 Google 的协议缓冲区 (PB) 格式创建的。检查点/恢复功能使您能够执行维护任务,例如在将应用程序检查点保存到持久存储后,在该主机上升级内核或进行硬件维护。稍后,应用程序将在该主机上恢复。
在 HPC 中非常重要的另一个功能是使用实时迁移的负载均衡。检查点/恢复功能还可以用于创建增量快照,这些快照可以在崩溃发生后使用。如前所述,支持 CRIU 需要一些内核补丁;以下是其中一些
-
添加了一个名为
kcmp()
的新系统调用;它比较两个进程以确定它们是否共享内核资源。 -
为 UNIX 套接字添加了一个名为
sock_diag
的套接字监视接口,以便能够找到 UNIX 域套接字的对等方。在此更改之前,依赖于解析/proc
条目的ss
工具未显示此信息。 -
添加了 TCP 连接修复模式。
-
添加了
procfs
条目 (/proc/PID/map_files)。
让我们看一个使用 criu
工具的简单示例。首先,您应该通过运行 criu check --ms
来检查您的内核是否支持检查点/恢复。查找指示 "Looks good."
的响应。
基本上,检查点是通过以下方式完成的
criu dump -t <pid>
您可以通过添加 -D folderName
来指定进程状态文件将保存到的文件夹。
您可以使用 criu restore <pid>
进行恢复。
在本文中,我介绍了基于 Linux 的容器是什么,并简要解释了底层的 cgroups 和命名空间内核功能。我讨论了一些基于 Linux 的容器项目,重点介绍了有前途且流行的 LXC 项目。我还研究了基于 LXC 的 Docker 引擎,它提供了一种简单方便的方式来创建和部署 LXC 容器。几个实践示例展示了使用用户空间 LXC 工具和 Docker 工具配置、管理和部署 LXC 容器是多么简单。
由于 LXC 和 Docker 开源项目的优势,以及本文中描述的用于创建、部署和配置 LXC 容器的便捷和简单工具,我们大概会在不久的将来看到越来越多的云基础设施将集成 LXC 容器而不是使用虚拟机。然而,正如本文所解释的那样,像 Xen 或 KVM 这样的解决方案相对于基于 Linux 的容器具有一些优势,并且仍然是需要的,因此它们可能在未来几年内不会从云基础设施中消失。
致谢感谢 Docker Inc. 的 Jérôme Petazzoni 和 Michael H. Warfield 审阅本文。
资源Google 容器:https://github.com/google/lmctfy
OpenVZ: http://openvz.org/Main_Page
Linux-VServer: http://linux-vserver.org
LXC: https://linuxcontainers.cn
libvirt-lxc: http://libvirt.org/drvlxc.html
Docker: https://www.docker.io
Docker 公共注册表: https://index.docker.io