Docker原理笔记

[正在不断更新中…]
Docker是一个使用了Linux NamespaceCgroups的虚拟化工具
Linux Namespace是Kernel的一个功能,可以隔离一系列系统资源(PIDUIDNetwork),帮助进程隔离出自己的单独的空间。
Cgroups限制一组进程及将来子进程的资源的大小,保证不会相互争抢,这些资源包括CPU、内存、存储、网络等,并进行监控和统计信息。

Namespace

  • Namespace类型及系统调用参数:

 Namespace类型             系统调用参数
Mount Namespace         CLONE_NEWNS
 UTS Namespace          CLONE_NEWUTS
 IPC Namespace            CLONE_NEWIPC
 PID Namespace            CLONE_NEWPID
Network Namespace      CLONE_NEWNET
 User Namespace          CLONE_NEWUSER

Namespace的API主要有三个系统调用:
clone()创建新进程
unshare()将进程移除某个Namespace
setns()将进程加入到某个Namespace

  • UTS Namespace
    UTS Namespace主要来隔离nodenamedomainname两个系统标识。在UTS Namespace里面,每个Namespace有自己的hostname
    Go实现代码:
package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main(){
    cmd := exec.Command("bash")
    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(“bash”)用来制定被fork出来的新进程内的初始命令,默认用bash来执行。

验证UTS隔离:

[jin1ming@ML ~]$ echo $$
5576
[ML myDockerLite]# echo $$
5780
$ readlink /proc/5780/ns/uts 
uts:[4026532587]
$ readlink /proc/5576/ns/uts 
uts:[4026531838]
[ML myDockerLite]# hostname dockelite
[ML myDockerLite]# hostname
dockelite
[jin1ming@ML ~]$ hostname
ML
  • IPC Namespace
    IPC Namespace用来隔离System V IPCPOSIX message queue
    略微修改一下代码:
package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main(){
    cmd := exec.Command("bash")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags:syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run();err!=nil{
        log.Fatal(err)
    }
}

验证 IPC 隔离:

[jin1ming@ML ~]$ ipcs

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

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态      
0x512c001f 131072     jin1ming   600        33024      1                       
0x00000000 1441793    jin1ming   600        16777216   2          目标       
0x00000000 327682     jin1ming   600        524288     2          目标       
0x00000000 1769475    jin1ming   600        22528      2          目标       
0x00000000 1474564    jin1ming   700        16472      2          目标       
0x00000000 1638405    jin1ming   700        130544     2          目标       

--------- 信号量数组 -----------
键        semid      拥有者  权限     nsems     
0x512c001e 32768      jin1ming   600        1         
[ML myDockerLite]# ipcs

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

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态      

--------- 信号量数组 -----------
键        semid      拥有者  权限     nsems     
  • PID Namespace
    PID Namespace用来隔离进程ID,同一个进程在不同的PID Namespace有不同的PID
    略微修改代码:
package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main(){
    cmd := exec.Command("bash")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags:syscall.CLONE_NEWUTS | 
            syscall.CLONE_NEWIPC | 
            syscall.CLONE_NEWPID,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run();err!=nil{
        log.Fatal(err)
    }
}

验证PID隔离:

[ML linux_shell]# cat testgo.sh 
#!/bin/bash

echo PID:$$
read input
echo $input

1.未进行隔离时:

[ML linux_shell]# ./testgo.sh 
PID:14141
[jin1ming@ML linux_shell]$ pgrep -f testgo
14141

2.进行隔离时:

[ML linux_shell]# ./testgo.sh 
PID:7
$ pgrep -f testgo
14586
  • Mount Namespace
    Mount Namespace 用来隔离各个进程看到的挂载点视图。在不同的Namespace中看到的文件系统层次是不一样的。在Mount Namespace中调用mount()umount()仅仅只会影响到当前Namespace内的文件系统,而对全局的文件系统是没有影响的。
    略微修改代码:
package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main(){
    cmd := exec.Command("bash")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags:syscall.CLONE_NEWUTS |
            syscall.CLONE_NEWIPC |
            syscall.CLONE_NEWPID |
            syscall.CLONE_NEWNS,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run();err!=nil{
        log.Fatal(err)
    }
}

验证Mount Namespace 隔离:

[ML myDockerLite]# mount -t proc proc /proc
[ML myDockerLite]# ls /proc
1          crypto       ioports        loadavg       schedstat      timer_list
7          devices      irq            locks         scsi           tty
acpi       diskstats    kallsyms       meminfo       self           uptime
asound     dma          kcore          misc          slabinfo       version
buddyinfo  driver       keys           modules       softirqs       vmallocinfo
bus        execdomains  key-users      mounts        stat           vmstat
cgroups    fb           kmsg           mtrr          swaps          zoneinfo
cmdline    filesystems  kpagecgroup    net           sys
config.gz  fs           kpagecount     pagetypeinfo  sysrq-trigger
consoles   interrupts   kpageflags     partitions    sysvipc
cpuinfo    iomem        latency_stats  sched_debug   thread-self
[ML myDockerLite]# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 10:56 pts/2    00:00:00 bash
root         8     1  0 10:58 pts/2    00:00:00 ps -ef
[jin1ming@ML ~]$ ls /proc
1      1176   1382   16675  247   479   613   80    962          kmsg
10     1177   1386   16765  25    48    615   81    963          kpagecgroup
1000   1184   139    16770  2552  484   62    810   9659         kpagecount
10075  1185   1395   16776  26    488   63    815   982          kpageflags
1021   1188   14     1724   269   489   637   8175  987          latency_stats
1027   1189   140    1781   27    49    639   82    989          loadavg
1029   1195   14001  18     275   493   64    824   993          locks
10459  1197   14004  1800   28    496   65    826   994          meminfo
#........此处省略一部分
[jin1ming@ML ~]$ ps -ef
Error, do this: mount -t proc proc /proc
  • User Namespace
    User Namespace 主要是隔离用户的用户组ID。一个进程的User IDGroup ID在User Namespace内外可以是不同的。
    对代码略作修改:
package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main(){
    cmd := exec.Command("bash")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags:syscall.CLONE_NEWUTS |
            syscall.CLONE_NEWIPC |
            syscall.CLONE_NEWPID |
            syscall.CLONE_NEWNS |
            syscall.CLONE_NEWUSER,
    }

    cmd.SysProcAttr.Credential = &syscall.Credential{
        Uid:0,
        Gid:0,
    }
//此处代表root

    cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
    cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
//此处指用户的真实权限

    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr


    if err := cmd.Run();err!=nil{
        log.Fatal(err)
    }
    os.Exit(-1)
}

验证User Namespace隔离:

[ML home]# id
uid=0(root) gid=0(root) 组=0(root),65534(nobody)
[ML home]# cd jin1ming/
bash: cd: jin1ming/: 权限不够

如果具有真正的root权限将可以访问

  • Network Namespace
    Network Namespace 是用来隔离网络设备IP地址端口等网络栈的Namespace。Network Namespace可以让每个容器拥有自己独立的网络设备(虚拟的),而且容器内的应用可以绑定到自己的端口,每个Namespace内的端口都不会冲突。
    略微修改代码:
package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main(){
    cmd := exec.Command("bash")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags:syscall.CLONE_NEWUTS |
            syscall.CLONE_NEWIPC |
            syscall.CLONE_NEWPID |
            syscall.CLONE_NEWNS |
            syscall.CLONE_NEWUSER |
            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.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr


    if err := cmd.Run();err!=nil{
        log.Fatal(err)
    }
    os.Exit(-1)
}

验证Network隔离:

[jin1ming@ML ~]$ ifstat 
#kernel
Interface        RX Pkts/Rate    TX Pkts/Rate    RX Data/Rate    TX Data/Rate  
                 RX Errs/Drop    TX Errs/Drop    RX Over/Rate    TX Coll/Rate  
lo                    48 0            48 0          4072 0          4072 0      
                       0 0             0 0             0 0             0 0      
enp1s0                 0 0             0 0             0 0             0 0      
                       0 0             0 0             0 0             0 0      
wlp2s0             11052 0          9055 0        11277K 0         2388K 0      
                       0 0             0 0             0 0             0 0     
[ML myDockerLite]# ifstat 
#kernel
Interface        RX Pkts/Rate    TX Pkts/Rate    RX Data/Rate    TX Data/Rate  
                 RX Errs/Drop    TX Errs/Drop    RX Over/Rate    TX Coll/Rate  

Linux Cgroups

Cgroups中的3个组件

  • cgroup 是对进程分组管理的一种机制,一个cgroup包含一组进程,并可以在这个cgroup上增加Linux subsystem的各种参数配置,将一组进程和一组subsystem的系统参数关联起来。
  • subsystem是一组资源控制的模块,一般包含如下几项:
    blkio设置对块设备(比如硬盘)输入输出的访问控制。
    cpu设置cgroup中进程的CPU被调度的策略。
    cpuacct可以统计cgroup中进程的cpu占用。
    cpuset在多核机器上设置cgroup中的进程可以使用的CPU内存(此处内存仅使用于NUMA架构)
    devices 控制cgroup中进程对设备的访问
    freezer 用于挂起(suspends)恢复(resumes) cgroup中的进程
    memory 用于控制cgroup中进程的内存占用
    net_cls用于将cgroup中进程产生的网络包分类(classify),以便Linux的tc(traffic controller)可以根据分类(classid)区分出来自某个cgroup的包并做限流监控
    net_prio 设置cgroup中进程产生的网络流量的优先级
    ns 这个subsystem比较特殊,它的作用是cgroup中进程在新的namespacefork新进程(NEWNS)时,创建出一个新的cgroup,这个cgroup包含新的namespace中进程。
    原文作者:靳一鸣
    原文地址: https://www.jianshu.com/p/1a3f81a8de77
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞