Docker容器的本质
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
学习了