docker依赖的技术探索

参考资料

《自己动手写docker》
http://www.sel.zju.edu.cn/?p=556

docker原理探索

docker用于开发应用,交付应用,运行应用的一种开源的软件。docker主要是通过操作系统层的虚拟化,来打包运行程序所需的依赖环境的一种容器化的技术, 相对于通过虚拟化硬件的方式而已,docker具有轻量级、快速高效的特点。

随着docker云原生的火爆,再也憋不住来探索一下docker到底是通过什么技术特点来实现的,故简单整理了一下查询的资料。

docker的技术特点

在Linux平台上面,docker实现的技术主要依赖于Namespace和Cgroup,命名空间主要是隔离其他命令空间下的运行环境,将进程运行的环境进行分组管理;Cgroup主要是限制对应的进程的资源的消耗量,从而为运行的容器分配一定的资源而不影响剩余的资源被其他进程使用。

Namespace命名空间

Linux现在提供的命名空间的隔离当前已经有了八个类型的隔离,官网(namespace详细文档),

Namespace Flag            Page                  Isolates
       Cgroup    CLONE_NEWCGROUP cgroup_namespaces(7)  Cgroup root directory
       IPC       CLONE_NEWIPC    ipc_namespaces(7)     System V IPC,
                                                       POSIX message queues
       Network   CLONE_NEWNET    network_namespaces(7) Network devices,
                                                       stacks, ports, etc.
       Mount     CLONE_NEWNS     mount_namespaces(7)   Mount points
       PID       CLONE_NEWPID    pid_namespaces(7)     Process IDs
       Time      CLONE_NEWTIME   time_namespaces(7)    Boot and monotonic
                                                       clocks
       User      CLONE_NEWUSER   user_namespaces(7)    User and group IDs
       UTS       CLONE_NEWUTS    uts_namespaces(7)     Hostname and NIS
                                                       domain name

分别都是域名隔离,用户隔离,时间隔离,挂载隔离,网络隔离,IPC隔离和Cgroup隔离。

为了简单的熟悉一下隔离的实际操作是什么样的。

网络隔离

首先需要创建一个network的隔离空间;

# 创建一个网络的隔离空间
[root@node205 ~]# ip netns add test_ns

我们可以通过如下命令来执行隔离网络中的命令

[root@node205 ~]# ip netns exec test_ns ip link list
1: lo:  mtu 65536 qdisc noop state DOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

接着我们就启动本地环路,从上面的输出可以看出lo是处于DOWN的状态

[root@node205 ~]# ip netns exec test_ns ip link set dev lo up
[root@node205 ~]# ip netns exec test_ns ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.050 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.040 ms
^C
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.040/0.045/0.050/0.008 ms

启动lo并在隔离网络中执行ping本地的命令可以看出网络是通的;

此时,隔离网络中的lo也已经启动,现在就需要再物理机上启动一个veth0,在隔离网络中启动一个veth1来进行通信,从而完成物理机到隔离网络中的通信。

[root@node205 ~]# ip link add veth0 type veth peer name veth1
[root@node205 ~]# ip link set veth1 netns test_ns
[root@node205 ~]# ip netns exec test_ns ifconfig veth1 10.1.1.1/24 up
[root@node205 ~]# ip netns exec test_ns ifconfig
lo: flags=73  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10
        loop  txqueuelen 1  (Local Loopback)
        RX packets 6  bytes 504 (504.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6  bytes 504 (504.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth1: flags=4099  mtu 1500
        inet 10.1.1.1  netmask 255.255.255.0  broadcast 10.1.1.255
        ether 06:d4:03:63:ba:90  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  
[root@node205 ~]# ifconfig veth0 10.1.1.2/24 up
[root@node205 ~]# ifconfig
eth0: flags=4163  mtu 1500
        inet 192.168.10.205  netmask 255.255.255.0  broadcast 192.168.10.255
        inet6 fe80::5c77:44db:eff:7ead  prefixlen 64  scopeid 0x20
        ether 52:54:00:c6:17:78  txqueuelen 1000  (Ethernet)
        RX packets 350951315  bytes 79674104061 (74.2 GiB)
        RX errors 0  dropped 3011539  overruns 0  frame 0
        TX packets 171821220  bytes 67278483690 (62.6 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10
        loop  txqueuelen 1  (Local Loopback)
        RX packets 207967871  bytes 62177372000 (57.9 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 207967871  bytes 62177372000 (57.9 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth0: flags=4163  mtu 1500
        inet 10.1.1.2  netmask 255.255.255.0  broadcast 10.1.1.255
        inet6 fe80::7cb5:95ff:feda:807b  prefixlen 64  scopeid 0x20
        ether 7e:b5:95:da:80:7b  txqueuelen 1000  (Ethernet)
        RX packets 5  bytes 418 (418.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5  bytes 418 (418.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

从输出可以看出,在物理机上启动了veth0,在隔离网络中启动了veth1,此时我们就可以进行网络测试;

[root@node205 ~]# ping 10.1.1.1
PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.115 ms
64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.054 ms
^C
--- 10.1.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.054/0.084/0.115/0.031 ms
[root@node205 ~]# ip netns exec test_ns ping 10.1.1.2
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.062 ms
64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.048 ms
^C
--- 10.1.1.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.048/0.055/0.062/0.007 ms

分别从物理机上ping 隔离网络,然后再从隔离网络中ping物理机,发现网络环路现在已经完成。

用户隔离
package main

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

func main() {
        cmd := exec.Command("sh")
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr

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

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

首先先编译go build main.go 得到main

接着通过starce来跟踪一下代码的执行;

[root@node205 docker_t]# go build main.go
[root@node205 docker_t]# strace ./main
execve("./main", ["./main"], [/* 33 vars */]) = 0
....
rt_sigprocmask(SIG_SETMASK, NULL, [], 8) = 0
rt_sigprocmask(SIG_SETMASK, ~[], NULL, 8) = 0
clone(child_stack=0, flags=CLONE_VM|CLONE_VFORK|CLONE_NEWUTS|CLONE_NEWIPC|CLONE_NEWPID|SIGCHLD) = 10914
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
close(4)                                = 0
read(3, "", 8)                          = 0
close(3)                                = 0
waitid(P_PID, 10914,
sh-4.2#
sh-4.2# echo $$
1
sh-4.2# hostname
node205.ceshi.com
sh-4.2# hostname testhost
sh-4.2# hostname
testhost
sh-4.2# exit
exit
{si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10914, si_uid=0, si_status=0, si_utime=0, si_stime=0}, WEXITED|WNOWAIT, NULL) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10914, si_uid=0, si_status=0, si_utime=0, si_stime=1} ---
rt_sigreturn({mask=[]})                 = 0
futex(0x58de70, FUTEX_WAKE_PRIVATE, 1)  = 1
futex(0x58dd70, FUTEX_WAKE_PRIVATE, 1)  = 1
wait4(10914, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, {ru_utime={0, 15400}, ru_stime={0, 60284}, ...}) = 10914
exit_group(4294967295)                  = ?
+++ exited with 255 +++
[root@node205 docker_t]# hostname
node205.ceshi.com
[root@node205 docker_t]# echo $$
7725

执行如上的操作可知,修改了sh内的hostname在退出之后并没有影响到物理机上的hostname,并且在sh中输出当前的pid的时候,显示的是1从而完成了对pid的一个环境的隔离。其他的一些特性大家有兴趣可自行验证一下。

Cgroup探索

cgroup官方文档,官网中比较明确的描述了cgroup是组织为分层的功能,然后可以限制其各种类型资源的使用的组并进行监控,对一组进程及其生成的子进程进行资源限制控制与监控,这些资源包括 CPU、内存、存储、网络等 ,可以方便地限制某个进程的资源占用,并且可以实时地监控进程的监控和统计信息 。

有这么高级的功能那我们可以尝试一下;

限制内存
[root@node205 wuzh]# cat c_dead_while.py
while True:
    pass
[root@node205 wuzh]# python c_dead_while.py

编写一个死循环的脚本,该脚本就是啥也不敢,一般情况下,该脚本会一直占用一个CPU,并且CPU利用率为100%,

top - 14:47:07 up 69 days, 21:11,  3 users,  load average: 0.30, 0.12, 0.08
Tasks: 212 total,   2 running, 208 sleeping,   0 stopped,   2 zombie%Cpu(s): 13.5 us,  0.8 sy,  0.0 ni, 85.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.3 stKiB Mem : 16267272 total,  2505172 free,  3305924 used, 10456176 buff/cacheKiB Swap:        0 total,        0 free,        0 used. 11886264 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND13017 root      20   0  123368   4556   1992 R 100.0  0.0   0:24.62 python

查看CPU利用率确实是100%,接下来我们通过cgroup来限制该进程的CPU使用,

[root@node205 cgroup]# cd /sys/fs/cgroup/cpu
[root@node205 cpu]# ls
cgroup.clone_children  cgroup.procs          cpuacct.stat   cpuacct.usage_percpu  cpu.cfs_quota_us  cpu.rt_runtime_us  cpu.stat           release_agent  wucg
cgroup.event_control   cgroup.sane_behavior  cpuacct.usage  cpu.cfs_period_us     cpu.rt_period_us  cpu.shares         notify_on_release  tasks
[root@node205 cpu]# cd wucg/
[root@node205 wucg]# ls
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks
[root@node205 wucg]# echo "30000">cpu.cfs_quota_us
[root@node205 wucg]# cat cpu.cfs_quota_us
30000
[root@node205 wucg]# echo "13017">tasks
[root@node205 wucg]#

先在/sys/fs/cgroup/cpu里面创建一个分组wucg,然后再改分组中,输入cpu.cfs_quota_us为30000即运行的CPU的占用时间,此时cpu.rt_period_us的时间为1000000,即会将cpu运行时间限制在30%,此时查看该进程状态;

top - 14:54:35 up 69 days, 21:19,  4 users,  load average: 0.99, 0.59, 0.31
Tasks: 213 total,   2 running, 209 sleeping,   0 stopped,   2 zombie%Cpu(s):  4.0 us,  0.1 sy,  0.0 ni, 95.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.1 stKiB Mem : 16267272 total,  2501452 free,  3308644 used, 10457176 buff/cacheKiB Swap:        0 total,        0 free,        0 used. 11883408 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND13017 root      20   0  123368   4556   1992 R  30.2  0.0   4:10.18 python

查看监控可知,该进程的CPU的使用率限制在了30%。

限制内存
[root@node205 wuzh]# cat c_memory_oom.py
c = []
while True:
    c.extend([1]*100000)
    import time
    time.sleep(0.1)
[root@node205 wuzh]# python c_memory_oom.py

该脚本就是不断的申请内存,每个一秒钟就扩充列表的大小,列表大小的扩展会一直去申请内存。

接下来我们就在cgroup中配置一下该运行脚本的进程来达到如果该进程超过了配置的内存使用则oom,杀死该进程。

top - 14:59:00 up 69 days, 21:23,  4 users,  load average: 0.02, 0.27, 0.25
Tasks: 212 total,   2 running, 208 sleeping,   0 stopped,   2 zombie%Cpu(s):  0.3 us,  4.2 sy,  0.0 ni, 95.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.2 stKiB Mem : 16267272 total,  2410244 free,  3399452 used, 10457576 buff/cacheKiB Swap:        0 total,        0 free,        0 used. 11792732 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND14339 root      20   0  219404  97496   2072 R  39.5  0.6   0:03.75 python

通过监控可以看出该进程在一直申请内存,内存在飙升,

blkio  cpu  cpuacct  cpu,cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls  net_cls,net_prio  net_prio  perf_event  pids  systemd
[root@node205 cgroup]# cd /sys/fs/cgroup/memory/
[root@node205 memory]# ls
cgroup.clone_children  memory.kmem.failcnt             memory.kmem.tcp.max_usage_in_bytes  memory.memsw.limit_in_bytes      memory.pressure_level       notify_on_release
cgroup.event_control   memory.kmem.limit_in_bytes      memory.kmem.tcp.usage_in_bytes      memory.memsw.max_usage_in_bytes  memory.soft_limit_in_bytes  release_agent
cgroup.procs           memory.kmem.max_usage_in_bytes  memory.kmem.usage_in_bytes          memory.memsw.usage_in_bytes      memory.stat                 tasks
cgroup.sane_behavior   memory.kmem.slabinfo            memory.limit_in_bytes               memory.move_charge_at_immigrate  memory.swappiness           wucg
memory.failcnt         memory.kmem.tcp.failcnt         memory.max_usage_in_bytes           memory.numa_stat                 memory.usage_in_bytes
memory.force_empty     memory.kmem.tcp.limit_in_bytes  memory.memsw.failcnt                memory.oom_control               memory.use_hierarchy
[root@node205 memory]# cd wucg/
[root@node205 wucg]# ls
cgroup.clone_children  memory.kmem.limit_in_bytes          memory.kmem.tcp.usage_in_bytes  memory.memsw.max_usage_in_bytes  memory.soft_limit_in_bytes  tasks
cgroup.event_control   memory.kmem.max_usage_in_bytes      memory.kmem.usage_in_bytes      memory.memsw.usage_in_bytes      memory.stat
cgroup.procs           memory.kmem.slabinfo                memory.limit_in_bytes           memory.move_charge_at_immigrate  memory.swappiness
memory.failcnt         memory.kmem.tcp.failcnt             memory.max_usage_in_bytes       memory.numa_stat                 memory.usage_in_bytes
memory.force_empty     memory.kmem.tcp.limit_in_bytes      memory.memsw.failcnt            memory.oom_control               memory.use_hierarchy
memory.kmem.failcnt    memory.kmem.tcp.max_usage_in_bytes  memory.memsw.limit_in_bytes     memory.pressure_level            notify_on_release
[root@node205 wucg]# echo "64k">memory.limit_in_bytes
[root@node205 wucg]# cat memory.limit_in_bytes
65536
[root@node205 wucg]# echo "14339">tasks
[root@node205 wucg]#

当我们将该进程输入到tasks的时候,运行该脚本的进程就退出了

[root@node205 wuzh]# python c_memory_oom.py
已杀死

因为配置了该进程的内存使用量为64k,如果超过了该内存使用量则该进程被杀死。

总结

从流程上来看的话,docker的轻量级主要体现在了通过隔离技术与资源限制的技术达到运行进程的快速的启动,启动过程相比较与传统的硬件虚拟化还是不同的,启动方式更为轻量一些,在程序通通过一些内核提供的参数可以方便的隔离与控制进程,本文只是资料的整理与简单的实践。

你可能感兴趣的:(服务端)