container technology become a necessary for developer during operating the linux system.

一、namespace

  • 定义

    namespace是 Linux 内核的一项功能,该功能对内核资源进行分区,以使一组进程看到一组资源,而另一组进程看到另一组资源。namespace 的工作方式通过为一组资源和进程设置相同的 namespace 而起作用,但是这些 namespace 引用了不同的资源。资源可能存在于多个 namespace 中。这些资源可以是进程ID、主机名、用户ID、文件名、与网络访问相关的名称和进程间通信。

  • 特征

    可以实现在同一主机系统中对进程ID、主机名、用户名ID、文件名、网络和进程间通信等资源的隔离。

  • 类型

namespacesummarykerneldescribe
Mount(mnt)隔离挂载点2.4.19隔离不同的进程或进程组看到的挂载点,实现容器内只能看到自己的挂在信息,在容器内的挂载操作不会影响主机的挂载目录
Process ID(pid)隔离进程 ID2.6.24
Network(net)隔离网络设备,端口号等2.6.29
Interprocess Communication(ipc)隔离 System VIPC 和 POSIX message queues2.6.19
UTS Namespace(uts)隔离主机名和域名2.6.19
User Namespace(user)隔离用户和用户组3.8
Control group(cgroup) Namespce隔离 Cgroups 根目录4.6
Time Namespace隔离系统时间5.6

1.1、Mount Namespace

使用 unshare 命令可以新建 Mount Namespace,并且在新建的 Mount Namespace 内 mount 是和外部完全隔离的。

  • 创建一个bash 进程并且新建一个 Mount Namespace

    1
    2
    
    [vagrant@localhost ~]$ sudo unshare --mount --fork /bin/bash
    [root@localhost vagrant]#
    
  • 验证在独立的 namespce 内挂载文件,不影响别的

    在 /tmp 目录下创建一个目录

    1
    
    [root@localhost vagrant]# mkdir /tmp/tmpfs
    
  • 使用 mount 命令挂载一个 tmpfs 类型的目录

    1
    
    [root@localhost vagrant]# mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
    
  • 使用 df 命令查看已经挂载的目录信息

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    [root@localhost vagrant]# df -h
    Filesystem                                                                          Size  Used Avail Use% Mounted on
    /dev/sda1                                                                            40G  3.8G   37G  10% /
    devtmpfs                                                                            457M     0  457M   0% /dev
    tmpfs                                                                               464M     0  464M   0% /dev/shm
    tmpfs                                                                               464M     0  464M   0% /sys/fs/cgroup
    tmpfs                                                                               464M   13M  451M   3% /run
    tmpfs                                                                                93M     0   93M   0% /run/user/1000
    //172.29.0.1/vgt-2469b42ebff188de622646551002b263-6ad5fdbcbf2eaa93bd62f92333a2e6e5  466G  171G  295G  37% /vagrant
    tmpfs                                                                                20M     0   20M   0% /tmp/tmpfs
    
  • 新打开一个命令窗口,执行 df 命令查看主机的挂载信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    [vagrant@localhost ~]$ df -h
    Filesystem                                                                          Size  Used Avail Use% Mounted on
    devtmpfs                                                                            457M     0  457M   0% /dev
    tmpfs                                                                               464M     0  464M   0% /dev/shm
    tmpfs                                                                               464M   13M  451M   3% /run
    tmpfs                                                                               464M     0  464M   0% /sys/fs/cgroup
    /dev/sda1                                                                            40G  3.8G   37G  10% /
    //172.29.0.1/vgt-2469b42ebff188de622646551002b263-6ad5fdbcbf2eaa93bd62f92333a2e6e5  466G  171G  295G  37% /vagrant
    tmpfs                                                                                93M     0   93M   0% /run/user/1000
    
  • 基本验证完毕。接着我们查看新的mount namespace 信息

    1
    2
    3
    4
    5
    6
    7
    8
    
    [root@localhost vagrant]# ls -l /proc/self/ns/
    total 0
    lrwxrwxrwx. 1 root root 0 Feb 26 09:56 ipc -> ipc:[4026531839]
    lrwxrwxrwx. 1 root root 0 Feb 26 09:56 mnt -> mnt:[4026532117]
    lrwxrwxrwx. 1 root root 0 Feb 26 09:56 net -> net:[4026531956]
    lrwxrwxrwx. 1 root root 0 Feb 26 09:56 pid -> pid:[4026531836]
    lrwxrwxrwx. 1 root root 0 Feb 26 09:56 user -> user:[4026531837]
    lrwxrwxrwx. 1 root root 0 Feb 26 09:56 uts -> uts:[4026531838]
    
  • 新打开一个命令窗口,使用相同的命令查看主机上的 namespace 信息

    1
    2
    3
    4
    5
    6
    7
    8
    
    [vagrant@localhost ~]$ ls -l /proc/self/ns/
    total 0
    lrwxrwxrwx. 1 vagrant vagrant 0 Feb 26 09:57 ipc -> ipc:[4026531839]
    lrwxrwxrwx. 1 vagrant vagrant 0 Feb 26 09:57 mnt -> mnt:[4026531840]
    lrwxrwxrwx. 1 vagrant vagrant 0 Feb 26 09:57 net -> net:[4026531956]
    lrwxrwxrwx. 1 vagrant vagrant 0 Feb 26 09:57 pid -> pid:[4026531836]
    lrwxrwxrwx. 1 vagrant vagrant 0 Feb 26 09:57 user -> user:[4026531837]
    lrwxrwxrwx. 1 vagrant vagrant 0 Feb 26 09:57 uts -> uts:[4026531838]
    

1.2、PID Namespace

用来隔离进程,在不同的namespace内可以拥有相同的进程号

  • 创建一个 bash 进程,并且新建一个 PID Namespace

    1
    2
    
    [vagrant@localhost ~]$ sudo unshare --pid --fork --mount-proc /bin/bash
    [root@localhost vagrant]#
    
  • 在当前的命令行窗口使用 ps aux 命令查看进程信息

    1
    2
    3
    4
    
    [root@localhost vagrant]# ps aux
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.2  15792  2616 pts/3    S    13:59   0:00 /bin/bash
    root        16  0.0  0.1  55192  1844 pts/3    R+   14:02   0:00 ps aux
    

1.3、UTS Namespace

它允许每个UTS Namespace 拥有一个独立的主机名

  • 使用 unshare 命令创建一个 UTS Namespace

    1
    2
    
    [vagrant@localhost ~]$ sudo unshare --uts --fork /bin/bash
    [root@localhost vagrant]#
    
  • 使用 hostname 命令设置主机

    1
    2
    3
    
    [root@localhost vagrant]# hostname -b docker
    [root@localhost vagrant]# hostname
    docker
    
  • 新打开一个命令行窗口,使用相同的命令查看主机的hostname

    1
    
    [root@localhost vagrant]# localhost
    

1.4、IPC Namespace

主要用来隔离进程间通信的。PID Namespace 和 IPC Namespace一起使用可以实现同一 IPC Namespace 内的进程彼此可以通信,不同 IPC Namespace 的进程却不能通信。

  • 使用 unshare 命令创建一个 IPC Namespace

    1
    
    [vagrant@docker ~]$ sudo unshare --ipc --fork /bin/bash
    
  • 另起一个窗口,查看系统的通信进程

    1
    2
    3
    4
    
    [vagrant@docker ~]$ ipcs -q
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages
    
  • 在新的ipc namespace下新建一个进程通信队列

    1
    2
    
    [root@docker vagrant]# ipcmk -Q
    Message queue id: 0
    
  • 查看当前ipc namespace 下的系统通信队列列表

    1
    2
    3
    4
    5
    
    [root@docker vagrant]# ipcs -q
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages
    0x2ae315e9 0          root       644        0            0
    
  • 再次查看主机的系统通信队列

    1
    2
    3
    4
    
    [vagrant@docker ~]$ ipcs -q
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages
    

1.5、User Namespace

主要用来隔离用户和用户组。可以实现进程在容器内拥有 root 权限, 而在主机上却只是普通用户。

  • 以普通用户的身份创建一个 User Namespace

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    [vagrant@docker ~]$ unshare --user --fork /bin/bash
    unshare: unshare failed: Invalid argument
    # 查看内核版本
    [vagrant@docker ~]$ uname -a
    Linux docker 3.10.0-1127.el7.x86_64 #1 SMP Tue Mar 31 23:36:51 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
    # 查阅资料
    # max_user_namespaces文件记录了允许创建的user namespace数量,我的CentOS 7.5默认是0,修改之
    echo 2147483647 > /proc/sys/user/max_user_namespaces
    # 再次运行
    [vagrant@docker ~]$ unshare --user -r /bin/bash
    [root@docker ~]#
    
  • 执行 id 命令查看当前的用户信息

    1
    2
    
    [root@docker ~]# id
    uid=0(root) gid=0(root) groups=0(root),65534(nfsnobody) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
    
  • 在当前窗口执行 reboot 命令

    1
    2
    3
    
    [root@docker ~]# reboot
    Failed to open /dev/initctl: Permission denied
    Failed to talk to init daemon.
    

1.6、Net Namespace

用来隔离网络设备、IP地址和端口等信息。

可以让每个进程拥有自己独立的IP地址,端口和网卡信息

  • 首先查看主机上的ip 信息

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    [vagrant@docker ~]$ ip a
    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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
        link/ether 00:15:5d:25:01:09 brd ff:ff:ff:ff:ff:ff
        inet 172.29.5.132/20 brd 172.29.15.255 scope global noprefixroute dynamic eth0
           valid_lft 82979sec preferred_lft 82979sec
        inet6 fe80::215:5dff:fe25:109/64 scope link
           valid_lft forever preferred_lft forever
    3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
        link/ether 02:42:f0:82:11:e0 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
           valid_lft forever preferred_lft forever
    
  • 创建 net namespace

    1
    2
    
    [vagrant@docker ~]$ sudo unshare --net --fork /bin/bash
    [root@docker vagrant]#
    
  • 使用 ip a命令查看当前namespace的网络信息

    1
    2
    3
    
    [root@docker vagrant]# ip a
    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
    

1.7、为什么 docker 需要 namespace?

Linux 内核从2002年2.4.19版本开始加入了 Mount Namspace

内核3.8版本加入了 User Namespace 为容器提供了足够的支持功能

当 docker 新建一个容器时

会创建这六种 namespace, 然后将容器中的进程加入这些 namespace之中

二、Cgroups

  • 定义

    全程是 control groups, 是Linux 内核的一个功能。可以实现限制进程或者进程组的资源(cpu、mem、磁盘IO等)

  • 特征

    资源限制:限制资源的使用量

    优先级控制: 不同的组可以有不用的资源使用优先级

    审计:计算控制组的资源使用情况

    控制: 控制进程的挂起或恢复

  • 核心

    subsystem: 一个内核的组件,代表一类资源调度控制器

    cgroup: 表示一组进程和一组带有参数的子系统的关联关系

    hierarchy:由一些列的控制组按照树状结构排列组成的子控制组默认拥有父控制组的属性

2.1、subsystem

  • 查看当前主机使用了哪些子系统

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    [vagrant@docker ~]$ sudo mount -t cgroup
    cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
    cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
    cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu)
    cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
    cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
    cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
    cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
    cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
    cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls)
    cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
    cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
    
  • 以 cpu 子系统为例,演示 cgroups 如何限制进程的 cpu使用时间

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    #在cpu子系统下创建测试文件夹
    mkdir /sys/fs/cgroup/cpu/mydocker
    #查看
    [root@docker vagrant]# ls /sys/fs/cgroup/cpu/mydocker -l
    total 0
    -rw-r--r--. 1 root root 0 Feb 26 15:15 cgroup.clone_children
    --w--w--w-. 1 root root 0 Feb 26 15:15 cgroup.event_control
    -rw-r--r--. 1 root root 0 Feb 26 15:15 cgroup.procs
    -r--r--r--. 1 root root 0 Feb 26 15:15 cpuacct.stat
    -rw-r--r--. 1 root root 0 Feb 26 15:15 cpuacct.usage
    -r--r--r--. 1 root root 0 Feb 26 15:15 cpuacct.usage_percpu
    -rw-r--r--. 1 root root 0 Feb 26 15:15 cpu.cfs_period_us
    -rw-r--r--. 1 root root 0 Feb 26 15:15 cpu.cfs_quota_us
    -rw-r--r--. 1 root root 0 Feb 26 15:15 cpu.rt_period_us
    -rw-r--r--. 1 root root 0 Feb 26 15:15 cpu.rt_runtime_us
    -rw-r--r--. 1 root root 0 Feb 26 15:15 cpu.shares
    -r--r--r--. 1 root root 0 Feb 26 15:15 cpu.stat
    -rw-r--r--. 1 root root 0 Feb 26 15:15 notify_on_release
    -rw-r--r--. 1 root root 0 Feb 26 15:15 tasks
    # 将当前shell进程加入cgroup
    [root@docker mydocker]# echo $$> tasks
    # 查看tasks文件内容
    [root@docker mydocker]#
    
  • mem

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    
    # 在memory子系统下创建cgroup
    [root@docker mydocker]# mkdir /sys/fs/cgroup/memory/mydocker
    
    # 查看新建目录中的内容
    [root@docker mydocker]# ls
    cgroup.clone_children
    cgroup.event_control
    cgroup.procs
    memory.failcnt
    memory.force_empty
    memory.kmem.failcnt
    memory.kmem.limit_in_bytes
    memory.kmem.max_usage_in_bytes
    memory.kmem.slabinfo
    memory.kmem.tcp.failcnt
    memory.kmem.tcp.limit_in_bytes
    memory.kmem.tcp.max_usage_in_bytes
    memory.kmem.tcp.usage_in_bytes
    memory.kmem.usage_in_bytes
    memory.limit_in_bytes
    memory.max_usage_in_bytes
    memory.memsw.failcnt
    memory.memsw.limit_in_bytes
    memory.memsw.max_usage_in_bytes
    memory.memsw.usage_in_bytes
    memory.move_charge_at_immigrate
    memory.numa_stat
    memory.oom_control
    memory.pressure_level
    memory.soft_limit_in_bytes
    memory.stat
    memory.swappiness
    memory.usage_in_bytes
    memory.use_hierarchy
    notify_on_release
    tasks
    
    # 限制内存使用
    [root@docker mydocker]# echo 1073741824 > memory.limit_in_bytes
    
    # 将当前shell写入tasks内
    [root@docker mydocker]# echo $$>tasks
    [root@docker mydocker]# cat tasks
    3356
    3484
    # 申请内存,当达到1G时 被杀死
    [root@docker mydocker]# memtester 1500M 1
    memtester version 4.3.0 (64-bit)
    Copyright (C) 2001-2012 Charles Cazabon.
    Licensed under the GNU General Public License version 2 (only).
    
    pagesize is 4096
    pagesizemask is 0xfffffffffffff000
    want 1500MB (1572864000 bytes)
    got  1500MB (1572864000 bytes), trying mlock ...Killed
    
    # 修改申请大小,完美
    [root@docker mydocker]# memtester 500M 1
    memtester version 4.3.0 (64-bit)
    Copyright (C) 2001-2012 Charles Cazabon.
    Licensed under the GNU General Public License version 2 (only).
    
    pagesize is 4096
    pagesizemask is 0xfffffffffffff000
    want 500MB (524288000 bytes)
    got  500MB (524288000 bytes), trying mlock ...locked.
    Loop 1/1:
      Stuck Address       : ok
      Random Value        : ok
      Compare XOR         : ok
      Compare SUB         : ok
      Compare MUL         : ok
      Compare DIV         : ok
      Compare OR          : ok
      Compare AND         : ok
      Sequential Increment: ok
      Solid Bits          : ok
      Block Sequential    : ok
      Checkerboard        : ok
      Bit Spread          : ok
    
  • docker 是如何使用cgroups的?

    1
    
    docker run -ti -m=1g nginx
    

三、联合文件系统