使用 Golang 操作 Linux Namespaces

本贴最后更新于 2598 天前,其中的信息可能已经水流花落

Linux 命名空间简介

Linux Namespaces(Linux 命名空间)机制提供了进程使用操作系统资源时的隔离方式,是基于内核实现轻量级虚拟化(容器化,例如 docker)的实现基础。

具体来说就是当我们创建一个进程时,可以给进程设置 flag 组合来构建进程的命名空间,处于不同命名空间的进程是相互隔离的。

命名空间分类

目前我们可以设置如下几种命名空间,它们分别从不同的资源纬度进行隔离。

CLONE_NEWPID

该标识用于创建一个新的 PID 命名空间,新进程将成为命名空间里的第一个进程。

一个 PID 命名空间为进程提供了一个独立的 PID 环境,其内部的 PID 将从 1 开始,
在该命名空间内创建的进程都将产生一个在该命名空间内独立的 PID。后续在该命名空间创建的进程都将作为 PID=1 的进程的子进程,当该进程被结束时,该命名空间内所有的进程都会被结束。

PID 命名空间是层次性的,如果新创建一个命名空间,则该命名空间将会是创建该命名空间的进程属于的命名空间的子命名空间。子命名空间中的进程对于父命名空间是可见的,一个进程将拥有不止一个 PID,在其所在的命名空间以及所有直系祖先命名空间中都将有一个 PID。

系统启动时,内核将创建一个默认的 PID 命名空间,该命名空间是所有以后创建的命名空间的祖先,因此系统所有的进程在该命名空间都是可见的。

CLONE_NEWIPC

该标识创建一个新的 IPC 命名空间,新进程将成为命名空间里的第一个进程。

一个 IPC 命名空间有一组 System V IPC objects 标识符构成,这标识符由 IPC 相关的系统调用创建。

在一个 IPC 命名空间里面创建的 IPC object 对该命名空间内的所有进程可见,但是对其他命名空间不可见。这样就使得不同命名空间之间的进程不能直接通信,就像是在不同的系统里一样。当一个 IPC 命名空间被销毁,该命名空间内的所有 IPC object 会被内核自动销毁。

PID 命名空间和 IPC 命名空间可以组合起来一起使用,只需在创建进程时,同时指定 CLONE_NEWPIDCLONE_NEWIPC,这样新创建的命名空间既是一个独立的 PID 空间又是一个独立的 IPC 空间。不同命名空间中的进程彼此不可见,也不能互相通信,这样就实现了进程的运行时隔离。

CLONE_NEWNS

该标识创建了一个新的 mount 命名空间。

每个进程都存在于一个 mount 命名空间里面,mount 命名空间为进程提供了一个文件层次视图。如果不设定这个标识,子进程和父进程将共享一个 mount 命名空间,其后子进程调用 mount 或 umount 将会影响到所有该命名空间内的进程。如果子进程在一个独立的 mount 命名空间里面,就可以调用 mount 或 umount 建立一份新的文件层次视图。

该标识配合 pivot_root 系统调用,可以为进程创建一个独立的目录空间。

CLONE_NEWNET

该标识创建了一个新的 Network 命名空间,为进程提供了一份独立的网络环境,就跟一个独立的系统网络一样。包括网络设备接口,IPv4 和 IPv6 协议栈,IP 路由表,防火墙规则,sockets 等等。

虚拟网络设备(virtual network device)提供了一种类似管道的抽象,可以在不同的命名空间之间建立隧道。利用虚拟化网络设备,可以建立到其他命名空间中的物理设备的桥接。

当一个 Network 命名空间被销毁时,物理设备会被自动移回 init Network 命名空间,即系统最开始的命名空间。

CLONE_NEWUTS

该标识创建了一个新的 UTS 命名空间。一个 UTS 命名空间就是一组被 uname 返回的标识符。

新的 UTS 命名空间中的标识符通过复制调用进程所属的命名空间的标识符来初始化。新创建的进程可以通过相关系统调用改变这些标识符,比如调用 sethostname 来改变该命名空间的 hostname。这一改变对该命名空间内的所有进程可见。

CLONE_NEWUTSCLONE_NEWNET 一起使用,可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。

CLONE_NEWUSER

该标识创建了一个新的 User 命名空间,用于隔离用户和用户组 ID。

换句话说,同一个进程的 uid 和 gid 在命名空间内外可以不同。就是说,host 环境下的普通用户可以是某个命名空间的 root 用户,以此来完成一些需要 root 权限的操作。

代码示例

上述命名空间可以进行组合使用,全部使用的话相当于完整的进程隔离。在介绍完概念后,下面我们来介绍在 Golang 中为进程设置命名空间的方法。

syscallos/exec 对 Linux 命名空间的支持是 Go 1.4 引入的,细节请参考该 issue

import (
	"os/exec"
	"syscall"
)

func setNamespace(cmd *exec.Cmd) {
	// XXX: keep move with Go 1.4 and later's

	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC | syscall.CLONE_NEWNET
	cmd.SysProcAttr.Credential = &syscall.Credential{
		Uid: 0,
		Gid: 0,
	}

	cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
	cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
}

上面的示例代码将待执行的 cmd 放到一个完全新的命名空间中,并设置该进程在新命名空间中以 root 用户执行。而这个 root 用户则是映射到 host 上用户 id 为 1001、组 id 为 1001 的用户。

这样是为了:

  • cmd 是以 root 执行的
  • cmd 在 host 上权限受限于 uid=1001、gid=1001

也就相当于 cmd 进程认为自己是以 root 执行的,但其实最终的操作受制于 1001 这个用户。

总结

Linux 命名空间历时 10 多年时间(2002-2013)陆续实现了 6 个命名空间,从而使进程在使用操作系统资源时的隔离成为可能。

Go 1.4 加入了对命名空间的支持,使我们可以在创建进程是通过设置克隆标识(CLONE_XXX)组合来实现进程命名空间设置。

命名空间支持对于一些应用场景非常有效,例如需要在服务器上执行用户自编程序,启动这些可能不安全的程序进程时通过设置命名空间而进行安全隔离。

参考

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1083 引用 • 3461 回帖 • 288 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    476 引用 • 899 回帖
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    915 引用 • 931 回帖
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    491 引用 • 1383 回帖 • 368 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...