docker 基础概念 Linux Namespace

Linux Namespace 是 Linux 提供的一种内核级别环境隔离方法,以前的 Unix 有一个叫 chroot 的系统调用,针对正在运作的软件行程和它的子进程,改变它外显的根目录,使它不能访问给它指定的目录以外路径。Linux 的 Namespace 在此基础上,提供了对 UTS、IPC、Mount、PID、Network、User 等的隔离机制。

UTS Namespace

UTS(UNIX Timesharing System) Namespace 可以用来隔离 nodename 和 domainname 两个系统标识。在 UTS Namespace 中,每个 Namespace 可以有自己的 hostname。


package main
import (
    "os"
    "log"
    "syscall"
    "os/exec"
)
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)
    }
}

执行 go run namespace.go

使用命令 pstree -pl | grep 'go(' 查看进程之间的关系:

|-zsh(1501)---sudo(1668)---go(1669)-+-namespace(1780)-+-sh(1785)

查看这几个父子进程是否属于同一个 UTS namespace

  • 首次查看 go 进程所属 uts:

sudo readlink /proc/1669/ns/uts
uts:[4026531838]

  • 再查看 namespace进程:

sudo readlink /proc/1780/ns/uts

uts:[4026531838]

  • 上面可以看出: go 进程和 namespace 进程是属于同一 uts namespace 的,在查看一下fork的进程的uts:

sudo readlink /proc/1780/ns/uts
uts:[4026533208]

IPC Namespace

IPC(Inter-Process Communication) Namespace 用来隔离 System V IPCPOSIX message queues。每一个 IPC Namespace 都有自己的 System V IPC 和 POSIX message queue。

在上面的代码的基础上修改为:


   cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC,
    }

然后查看 fork 出来的进程是否共享 主进程的 IPC 消息队列:


# 先查看 当前的消息队列:

ipcs -q            

--------- 消息队列 -----------

键      msqid      拥有者      权限      已用字节数     消息 

# 然后创建一个 ipc 消息队列:

(base) ➜ ~ ipcmk -Q

消息队列 id:65536
(base) ➜ ~ ipcs -q 
--------- 消息队列 -----------
键      msqid     拥有者      权限      已用字节数     消息      
0x69147b74     65536      hongyu      644      0      0

# 查看 fork 出的进程的 IPC 队列:

# ipcs -q
--------- 消息队列 -----------
键      msqid      拥有者      权限      已用字节数     消息

上面的实验可以看出,在创建的 IPC namespace 中是看不到宿主机的 IPC 队列的,它们已被隔离。

PID Namespace

PID(Process ID) Namespace 可以用来隔离进程 ID。同一个进程在不同的 PID Namespace 中可以拥有不同的 PID。在 Docker Container 中,使用 ps -ef 可以看到启动容器的进程 PID 为 1,但是在宿主机上,该进程却又有不同的 PID。

再在上面代码的基础上修改:


    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID,
    }

然后执行,在 fork 出的进程外查看 该进程 的pid:


(base) ➜ ~ pstree -pl | grep 'go('
|-zsh(1501)---sudo(7914)---go(7915)-+-namespace(8023)-+-sh(8028)

可以看到 fork 出的进程的id 为: 8028, 然后在该进程中查看进程id:


(base) ➜ go-workspace sudo /usr/local/go/bin/go run my-docker/namespace/namespace.go
# echo $$
1

Mount Namespace

Mount Namespace 用来隔离各个进程看到的挂载点视图。在不同的 Namespace 中,看到的挂载点文件系统层次是不一样的。在 Mount Namespace 中调用 mount 和 unmount 仅仅会影响当前 Namespace 内的文件系统,而对全局文件系统是没有影响的。

在上面代码的基础上修改:


    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID |
        syscall.CLONE_NEWNS,
    }

执行程序后对比:(关于 /proc 文件夹介绍, /proc存在于内存中,是挂载在根目录的,所以可以用来对比)


# fork 的进程外:

(base) ➜ ~ ls /proc 
1 12998 2322 275 3765 41 66 8423 fs
10 13 23505 276 3767 4136 661 8502 interrupts
100 1301 2379 277 377 42 6787 8564 iomem
1001 1302 2382 278 3771 4201 68 865 ioports
1002 1308 2385 28 3773 4204 6811 8676 irq
101 1311 2387 287 3775 4221 6812 8750 kallsyms
1011 1313 24 288 3786 428 688 879 kcore
1013 1318 2417 289 3790 4351 69 88 keys
1018 1320 2432 29 3798 44 6921 89 key-users
102 1329 2434 290 38 45 70 890 kmsg
1020 1337 24342 3 380 4532 71 891 kpagecgroup
1023 1338 2435 30 3814 4533 72 9 kpagecount
1025 1341 2436 301 3831 46 728 90 kpageflags
104 13665 2438 302 3835 465 729 907 loadavg
106 13668 2439 312 3839 466 730 908 locks
107 13700 2441 3165 3844 467 74 91 mdstat
1076 1371 2442 318 3848 47 741 912 meminfo
108 14 2446 32 3852 48 75 92 misc
1092 14121 2447 322 3854 50 76 93 modules
1098 14228 2449 3223 3855 51 77 94 mounts
11 14259 2452 325 3856 5147 775 944 mtrr
11047 14260 2454 326 3857 52 776 95 net
......

......



# 查看 fork 出的进程的 `/proc` 目录:

#      首先给此进程挂载 `proc` 若没有挂载,它显示的是外面环境的:



# mount -t proc proc /proc

#      这里可以看出 `/proc` 文件夹下的东西少了好多
# ls /proc    
1 devices irq mdstat scsi tty
4 diskstats kallsyms meminfo self uptime
acpi dma kcore misc slabinfo version
asound driver keys modules softirqs version_signature
buddyinfo execdomains key-users mounts stat vmallocinfo
bus fb kmsg mtrr swaps vmstat
cgroups filesystems kpagecgroup net sys zoneinfo
cmdline fs kpagecount pagetypeinfo sysrq-trigger
consoles interrupts kpageflags partitions sysvipc
cpuinfo iomem loadavg sched_debug thread-self
crypto ioports locks schedstat timer_list
# ps -ef 
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:49 pts/0 00:00:00 sh
root 5 1 0 13:58 pts/0 00:00:00 ps -ef

User Namespace

User Namespace 主要是隔离用户的用户组 ID。也就是说,一个进程的 User ID 和 Group ID 在 User Namespace 内外可以是不同的。比较常用的是,在宿主机上以一个非 root 用户运行创建一个 User Namespace,然后在 User Namespace 中被映射为了 root 用户。这意味着这个进程在 User Namespace 中有 root 权限,但是在宿主机上却没有 root 权限。

在上面的代码基础上修改:


    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID |
        syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
    }

查看id(这里使用了sudo为了和在执行 go 代码时一样):


(base) ➜ ~ sudo id
uid=0(root) gid=0(root) 组=0(root)



# 在 fork 的进程中:

$ id
uid=65534(nobody) gid=65534(nogroup) 组=65534(nogroup)

可以看到它们的id不同,说明 User Namespace 隔离生效了。

Network Namespace

Network Namespace 用来隔离网络设置、IP 地址和端口号等网络栈的 Namespace。Network Namespace 可以让每个容器拥有自己独立的网络设备,而且容器内的应用可以绑定到自己的端口,每个 Namespace 的端口都不会有冲突。在宿主机搭建网桥后,就能很方便地实现容器之间的通信。

在上面的代码上修改:


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

查看网络设备信息:

在 fork 的进程中只有回环接口:


$ ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

在外部环境:


(base) ➜ ~ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp9s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether b4:2e:99:19:83:ed brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.5/24 brd 192.168.1.255 scope global dynamic noprefixroute enp9s0
       valid_lft 84007sec preferred_lft 84007sec
    inet6 fe80::1e33:7ee:c607:631c/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: wlp7s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether c8:21:58:2c:21:71 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.6/24 brd 192.168.1.255 scope global dynamic noprefixroute wlp7s0
       valid_lft 84004sec preferred_lft 84004sec
    inet6 fe80::d46:c27f:8e60:c0f8/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

可以看出 fork 出来的进程和外部环境已经处于网络隔离的状态了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值