Code
,ENV
,System
等等包装在一个完整的文件系统中Kernel
|----------------|
| CODE - Image |
|----------------|
| JRE - Image |
|----------------|
| KERNEL |
|----------------|
复制代码
Linux Namespace
是Kernel
的一个功能,它提供了内核级别隔离系统资源的方法,经过将系统的全局资源放到不一样的Namespace
来实现隔离资源的目的.目前用到Namespace
有:node
名称 | 宏定义 | 隔离内容 |
---|---|---|
IPC | CLONE_NEWIPC | 实现容器与宿主机、容器与容器之间的IPC隔离。IPC资源包括信号量、消息队列和共享内存 |
Network | CLONE_NEWNET | 提供了关于网络资源的隔离,包括网络设备、IPv4和IPv6协议栈、IP路由表、防火墙,套接字等 |
Mount | CLONE_NEWNS | 实现隔离文件系统挂载点,使容器内有独立的挂载文件系统 |
PID | CLONE_NEWPID | 实现容器内有独立的进程树 (也就意味着每一个容器都有本身的PID为1的进程) |
User | CLONE_NEWUSER | 实现用户能够将不一样的主机用户映射到容器,好比user 用户映射到容器内的root 用户上 |
UTS | CLONE_NEWUTS | 实现容器能够拥有独立的主机名和域名,在网络上能够视为独立的节点 |
Cgroup | CLONE_NEWCGROUP | 实现资源的限制(CPU,Memory等等) |
Namespace
的API
主要用到下面3个系统调用:docker
CLONE
建立新进程,而且系统调用参数会判断哪一个类型的Namespace
被建立(如上表格中的CLONE_NEWUSER
),而且子进程也会包含到这些Namespace
中UNSHARE
将进程移出某个Namespace
SETNS
将进程切换/加入到某个Namespace
中UTS Namespace
UTS Namespace
主要用来隔离nodename
和domainname
两个系统标识,每一个Namespace
容许有本身的hostname
shell
下面使用将使用Go
来作一个UTS Namespace
的例子.bash
// UTS/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWUTS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
复制代码
解释下代码, exec.Command("sh")
是来指定被fork
出来的新进程内的初始命令,默认用sh
来执行(固然能够换成bash
或zsh
之类的)网络
下面就是设置系统调用参数,使用CLONE_NEWUTS
这个标识符来建立一个UTS Namespace
dom
syscall
库封装好对clone()
函数的调用,这段代码被执行后就会进入到一个sh
运行环境中函数
执行go run clone.go
的命令后,在这个交互式环境里面,使用pstree -pl
查看下系统中进程之间的关系,以下:工具
init(1)───init(537)───init(538)───sh(539)───sh(540)───sh(545)───node(547)───node(597)──zsh(612)───go(2005)───clone(2098)───sh(2103)───pstree(2104)
复制代码
而后输出一下当前的PID
oop
# echo $$
2103
复制代码
验证父进程和子进程是否不在同一个UTS Namespace
中ui
# readlink /proc/2098/ns/uts
uts:[4026532185]
# readlink /proc/2103/ns/uts
uts:[4026532195]
复制代码
能够看到它们确实不在同一个UTS Namespace
中,因为咱们对进程的CLONE
进了一个新的UTS Namespace
内,因此这个环境下修改hostname
/NIS domain name
对宿主主机没有任何影响,下面进行下实验
## 在clone出来的sh执行
# hostname -b air
# hostname
air
复制代码
另外在宿主机另启动一个shell
root@DESKTOP-UMENNVI:~# hostname
DESKTOP-UMENNVI
复制代码
能够看到,外部的hostname
并无被内部的修改所影响,由此能够了解到UTS Namespeace
的做用
IPC Namespace
IPC Namespace
用来隔离System V IPC
和POSIX message queues
.每个IPC Namespace
都有本身的System V IPC
和POSIX message queue
建立IPC Namespace
跟以前的方法相似,只须要把CLONE_NEWUTS
替换成CLONE_NEWIPC
// IPC/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWIPC,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
复制代码
shell
root@DESKTOP-UMENNVI:~# ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
复制代码
message queue
root@DESKTOP-UMENNVI:~# ipcmk -Q
消息队列 id:0
# 查看消息队列
root@DESKTOP-UMENNVI:~# ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0x9dce743a 0 root 644 0 0
复制代码
queue
了,下面将使用另外一个shell
去运行IPC/clone.go
新建IPC Namespace
root@DESKTOP-UMENNVI:# go run clone.go
# ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
复制代码
能够发现,在新建立的Namespace
里,看不到宿主机上已经建立的message queue
,说明IPC Namespace
建立成功,已经被隔离了
PID Namespace
PID Namespace
是用来隔离进程ID(PID).一样在不一样的PID Namespace
里能够拥有不一样的进程树.在docker container
里面,使用ps -ef
就会发现前台运行的进程PID
为1,那是由于每一个容器都建立了独自的PID Namespace
基于上面的基础,把CLONE_NEWIPC
替换成CLONE_NEWPID
// PID/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWPID,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
复制代码
咱们须要分别在宿主机和go run PID/clone.go
内运行shell
go run PID/clone.go
查看shell的PIDroot@DESKTOP-UMENNVI:# go run PID/clone.go
# echo $$
1
复制代码
PID
init(1)─┬─init(11)─┬─at-spi-bus-laun(294)─┬─dbus-daemon(307)
├─init(537)───init(538)───sh(539)───sh(540)───sh(545)───node(547)─┬─node(597)─┬─node(722)─┬─{node}(723)
│ │ │ ├─{node}(724)
│ │ │ ├─{node}(725)
│ │ │ ├─{node}(726)
│ │ │ ├─{node}(727)
│ │ │ └─{node}(728)
│ │ ├─zsh(610)───bash(3571)───pstree(4090)
│ │ ├─zsh(612)───bash(3601)───go(3965)─┬─clone(4074)─┬─sh(4079)
│ │ │ │ ├─{clone}(4075)
│ │ │ │ ├─{clone}(4076)
│ │ │ │ ├─{clone}(4077)
│ │ │ │ └─{clone}(4078)
│ │ │ ├─{go}(3966)
复制代码
能够看到 go run PID/clone.go
的真实PID
应该是4074
,也就是说这个4074
被映射到PID Namespace
里后为1
. 固然这里还不能用ps
/top
来查看,由于会依赖/proc
文件系统,还须要挂载/proc
文件系统后才行.
Mount Namespace
Mount Namespace
用来隔离各个容器的挂载节点. 在不一样的Namespace
的容器中看到的文件系统层次是不同的. 在Mount Namespace
中调用mount()
和umount()
仅仅只会影响当前Namespace
内的文件系统,对全局文件系统没有影响
Mount Namespace
是Unix & Linux
第一个实现的Namespace
类型,因此它的系统调用参数是NEWNS
(New Namespace
的缩写)
基于上面的基础,添加syscall.CLONE_NEWNS
// MOUNT/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWNS | syscall.CLONE_NEWPID,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
复制代码
go run MOUNT/clone.go
后,查看/proc
的文件内容proc 是一个(伪)文件系统,提供访问系统内核数据的操做提供接口
root@DESKTOP-UMENNVI:# go run MOUNT/clone.go
# ls /proc
1 307 4553 545 acpi crypto interrupts kmsg modules self tty
11 309 4651 547 buddyinfo devices iomem kpagecgroup mounts softirqs uptime
192 339 4656 597 bus diskstats ioports kpagecount mtrr stat version
248 3571 4657 610 cgroups dma irq kpageflags net swaps vmallocinfo
272 4079 537 679 cmdline driver kallsyms loadavg pagetypeinfo sys vmstat
273 420 538 68 config.gz execdomains kcore locks partitions sysvipc zoneinfo
294 4315 539 69 consoles filesystems keys meminfo sched_debug thread-self
299 4374 540 722 cpuinfo fs key-users misc schedstat timer_list
复制代码
能够看到这里的/proc
仍是宿主机的,下面将/proc
mount到新建的Namespace
中来
# mount -t proc proc /proc
# ls /proc
1 cmdline diskstats interrupts keys loadavg mtrr self thread-self vmstat
5 config.gz dma iomem key-users locks net softirqs timer_list zoneinfo
acpi consoles driver ioports kmsg meminfo pagetypeinfo stat tty
buddyinfo cpuinfo execdomains irq kpagecgroup misc partitions swaps uptime
bus crypto filesystems kallsyms kpagecount modules sched_debug sys version
cgroups devices fs kcore kpageflags mounts schedstat sysvipc vmallocinfo
复制代码
能够看到少了不少文件,如今用top
/ps
来查看系统进程
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:47 pts/5 00:00:00 sh
root 6 1 0 14:58 pts/5 00:00:00 ps -ef
复制代码
在当前的Namespace
中,sh
进程的PID
为1
的进程,说明Mount && PID Namespace
和宿主机是隔离的,mount
操做没有影响到宿主机 Docker Volume
也是利用了这个特性(还有USER Namespace
)
User Namespace
User Namespace
主要用来隔离用户&用户组ID. 一个进程的User ID
和Group ID
在User Namespace
内外能够是不一样的,比较常见的是在宿主机上以一个非root
用户建立一个User Namespace
,而后在User Namespace
里面却映射成root
用户 从Linux Kernel 3.8开始,非root
进程也能够建立User Namespace
,而且此用户在建立的Namespace
里面能够被映射成root
而且拥有root
权限
具体代码以下:
// USER/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUSER,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
复制代码
root
用户在宿主机上看一下当前的用户和用户组root@DESKTOP-UMENNVI:# id
uid=0(root) gid=0(root) 组=0(root)
复制代码
能够看到咱们是root
用户,接下来运行程序
root@DESKTOP-UMENNVI:# go run USER/clone.go
$ id
uid=65534(nobody) gid=65534(nogroup) 组=65534(nogroup)
复制代码
能够看到它们的UID
是不一样的说明User Namespace
建立&&隔离完成了
Network Namespace
Network Namespace
是用来隔离网络设备,IPV4/IPV6等网络栈的Namespace
Network Namespace
可让每一个容器拥有独立的虚拟网络设备,而且容器内的应用能够绑定到容器内的端口,每一个Namespace
内的端口都不会互相冲突 在宿主机上搭建网桥后,就能很方便地实现容器之间的通讯
在上面的基础上改为syscall.CLONE_NEWNET
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWNET,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
复制代码
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.25.50.16 netmask 255.255.240.0 broadcast 172.25.63.255
inet6 fe80::215:5dff:fe50:5608 prefixlen 64 scopeid 0x20<link>
ether 00:15:5d:50:56:08 txqueuelen 1000 (以太网)
RX packets 766496 bytes 148694705 (148.6 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 688919 bytes 3273950212 (3.2 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (本地环回)
RX packets 2 bytes 100 (100.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2 bytes 100 (100.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
复制代码
能够宿主机上有eth0
,lo
等网络设备
go run NET/clone.go
root@DESKTOP-UMENNVI:# go run NET/clone.go
# ifconfig
#
复制代码
能够看到网络设备什么都没有,由于Network Namespace
与宿主机之间的网络是处于隔离状态了