Docker容器让云计算领域再次活跃起来,那些无人问津的后端技术,被再次拿出来讨论。其中最多的一个便是,Docker容器和传统的虚拟机到底有什么区别? 有人说,Docker只是轻量化的虚拟技术?这种说法是否正确,我们往下看。

Docker容器和虚拟机的区别

虚拟机技术开启了云计算时代,云计算简单的说就是把你的软件运行在云主机上,而云主机就是云计算厂商放在机房中的虚拟机(VM) . 虚拟机是采用硬件虚拟化技术将一台物理机器分隔成多个逻辑隔离的单元,每个单元都是一个虚拟的计算机,有一个完整的操作系统。虚拟机和使用一台真实的物理机没有区别,只是不同的虚拟机共享了某些相同硬件。而一般云厂商的机房,规模都很大,所以只需要你有需要,你可以购买“无穷”多的计算资源。

虚拟机是在物理机的基础上做分隔,每个虚拟机都是独立的一个操作系统,互不干扰,所以它的隔离性非常好。下图中的GuestOS就是一个独立的操作系统,而Docker 中的应用都是在同一个操作系统中。

Docker是一种沙盒技术,只是一种将进程隔离起来的技术手段,它的目的就是将运行起来的应用,隔离在一个有边界的进程中,仿佛这个边界(容器)里只有它自己,而看不到操作系统周围的其他进程。这种特性,使得在一个系统中运行的容器,到另外一个系统中,也能无差异的运行,因为它的运行只依赖它容器内部的环境,而不受其他影响,这一点非常重要,要知道,最令开发和运维头疼的问题就是,开发环境明明没有问题,到生产环境,就各种报错!这其中最重要的原因就是,应用运行依赖的环境发生了变化。

Docker实现进程隔离的主要原理就是依赖Linux的Namespace技术,我们如果在Linux中执行如下指令:

[root@k8s test]# ps
  PID TTY          TIME CMD
 7238 pts/0    00:00:00 bash
 8846 pts/0    00:00:00 ps

可以看到bash程序的进程号是7238,我们每次运行一个程序,操作系统就会分配一个进程号给程序。如果用top命令查看,操作系统把 1号进程号分给了一个叫systemd的程序,systemd其实就相当与操作系统这个大家庭的家长,用来管理其他服务和整个系统相关的一些东西。

[root@k8s ~]# top
top - 23:18:12 up 51 days, 22:50,  1 user,  load average: 0.52, 0.39, 0.36
Tasks: 180 total,   1 running, 179 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.0 us,  0.7 sy,  0.0 ni, 94.8 id,  0.5 wa,  0.0 hi,  0.0 si,  3.0 st
KiB Mem :  3877620 total,   183516 free,  1392092 used,  2302012 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  2019708 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 6664 root      20   0  642764 434596  43008 S   3.7 11.2 794:02.88 kube-apiserver
 2667 root      20   0 1433016  32888  19104 S   2.0  0.8   0:01.37 calico-node
  671 root      20   0 1384596  85836  23572 S   1.3  2.2   1666:33 kubelet
 5534 root      20   0  213572  78704  31624 S   1.3  2.0 292:49.94 kube-controller
 5605 root      20   0   10.1g  67568  13620 S   1.3  1.7 326:17.60 etcd
 4935 root      20   0  893868  93800  29256 S   0.7  2.4 247:44.80 dockerd
 7371 root      20   0 3471660 246632  13680 S   0.7  6.4  96:41.40 java
  675 root      20   0  474580   7720   5416 S   0.3  0.2 375:59.10 NetworkManager
  922 root      20   0 1438788  56644  16980 S   0.3  1.5 137:30.09 containerd
 6150 root      20   0  146144  35268  17804 S   0.3  0.9  61:23.35 kube-scheduler
 7325 root      20   0  145744  21312  14224 S   0.3  0.5  50:04.45 coredns
28051 root      20   0       0      0      0 S   0.3  0.0   0:00.15 kworker/0:2
    1 root      20   0  126640   4716   2096 S   0.0  0.1  42:50.63 systemd
    2 root      20   0       0      0      0 S   0.0  0.0   0:01.33 kthreadd
    4 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H
    6 root      20   0       0      0      0 S   0.0  0.0  17:03.33 ksoftirqd/0
    7 root      rt   0       0      0      0 S   0.0  0.0   0:57.45 migration/0
    8 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh
    9 root      20   0       0      0      0 S   0.0  0.0 108:32.84 rcu_sched
   10 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 lru-add-drain
   11 root      rt   0       0      0      0 S   0.0  0.0   0:23.46 watchdog/0
   12 root      rt   0       0      0      0 S   0.0  0.0   0:21.17 watchdog/1
   13 root      rt   0       0      0      0 S   0.0  0.0   0:56.57 migration/1
   14 root      20   0       0      0      0 S   0.0  0.0  10:50.26 ksoftirqd/1
   16 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/1:0H
   18 root      20   0       0      0      0 S   0.0  0.0   0:00.00 kdevtmpfs
   19 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 netns
   20 root      20   0       0      0      0 S   0.0  0.0   0:02.73 khungtaskd
   21 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 writeback
   22 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kintegrityd
   23 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 bioset
   24 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 bioset
   25 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 bioset
   26 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kblockd
   27 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 md

但是如果我运行一个容器,并进入到容器中,执行ps指令,你看到的就是另一番景象:

root@k8s2:~/k8s2/postgre-exporter# docker run -it busybox /bin/sh
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    8 root      0:00 ps

docker run指令就是运行一个容器,这里 -it是告诉容器,启动后分配一个输入输出环境(-i即stdin, -t 即tty) 可以直接认为 tty 就是 Linux 给用户提供的一个常驻小程序,用于接收用户的标准输入,返回操作系统的标准输出。当然,为了能够在 tty 中输入信息,你还需要同时开启 stdin(标准输入流).

所以整个指令就是告诉Docker(我们安装的Docker程序)说:请帮我启动一个容器(这里并不存在一个真实的容器,这里的容器指构建一个隔离的环境),并在容器里启动/bin/sh这个 程序。

可以看到 sh这个程序的进程号是1,显然有某种东西遮住了它的双眼,让它以为自己就是老大。其实这里Docker就是采用NameSpace进行了隔离,让sh程序,只能看到隔离环境下的一切。

总结

所以总结一下就是,虚拟机是在硬件之上,模拟多个操作系统,用来做资源隔离。而Docker容器本质上只是Linux上的一个特殊的进程,这个进程被做了一些限制,从而达到了隔离的作用。所以“Docker是一种更轻量的虚拟化技术“ 这种常见的说法其实是说不通的。

除去隔离,Docker还用Cgroup技术,对容器内的进程做资源限制,用rootfs 来修改它的文件系统。利用这三种手段,让Docker的运行不再受环境差异的影响。

附录:

Linux NameSpace

NameSpace是Linux中用来做资源隔离和虚拟化的特性。每个进程都绑定在特定的NameSpace中,且只能查看和操作此命名空间内的资源。

Linux提供了如下几种NameSpace

Namespace   变量               隔离资源
       Cgroup      CLONE_NEWCGROUP   Cgroup 根目录
       IPC         CLONE_NEWIPC      System V IPC, POSIX 消息队列等
       Network     CLONE_NEWNET      网络设备,协议栈、端口等
       Mount       CLONE_NEWNS       挂载点
       PID         CLONE_NEWPID      进程ID
       User        CLONE_NEWUSER     用户和group ID
       UTS         CLONE_NEWUTS      Hostname和NIS域名

Namespace API提供了三种系统调用接口:

  • clone():创建新的进程
  • setns():允许指定进程加入特定的namespace
  • unshare():将指定进程移除指定的namespace

参考

https://pdai.tech/md/devops/docker/docker-01-docker-vm.html

Docker基础技术:Linux Namespace(上)

1 对 “Docker容器的本质”的想法;

发表回复

您的电子邮箱地址不会被公开。