从刚刚接触Docker到很长一段时间,自己都是把Docker看做一个开箱即用的黑盒来看,简单地将它和虚拟机混作一类,只大概知道是用Namespace和Cgroup实现的,对Namespace和Cgroup也只有模糊的概念,实践出真知,如果你也对Docker的隔离机制比较模糊,那么我的实操做个抛砖引玉,可以通过文末参考再更加深入的了解
服务的部署形态
最开始我们直接部署在服务器上,配置服务所需的各种环境,比如Java项目配置JDK,Python项目配置python版本等,但逐渐发现,随着服务的增多,不同服务需要的基础环境不一样,A服务需要JDK1.7,B服务需要JDK1.8,C服务需要Python 2.x,D服务需要Python3.x,服务器部署模式,每台服务器针对每种开发语言只能提供一套环境,很难满足日益增多的服务需求,现有服务器资源池吃不满的情况下,还需要重新购买新的服务器。
为了解决这个问题,产生了虚拟化技术,在一台金属服务器上,使用虚拟化技术分出若干个虚拟机,多个虚拟机资源隔离,每台虚拟机都可以当做一台单独的服务器来使用,这样就解决了环境隔离问题,在一台服务器上分成多个小的虚拟机,每台虚拟机配置不同的基础环境&部署不同的服务。随着业务发展,虚拟机的劣势也显现出来,新增服务的前提是新建一个虚拟机,同时虚拟机得资源就固化下来了,如果想应对突发流量等场景,就得通过不断新建虚拟机的方式扩充实例或者调整资源。
容器化技术的出现,解决了虚拟化技术的痛点,以Docker为例,Docker使用namespace和cgroup技术,实现了资源隔离,容器间共享内核,容器中包含服务运行时环境以及服务本身,可以说容器就是轻量级的虚拟机,在资源使用上更加灵活,但仍然需要手动操作创建、删除、调整容器,同时服务的访问还需要手动配置端口映射。
Docker的隔离实现
Docker通过Linux的Namespace和cgroup,实现不同容器间的资源隔离和限制,这里我参考他人博客实操一下,自己想更详细的写一写,但无奈积累太少,自己还没构建这块的知识体系
想更细致的了解,推荐读一下这位大佬的文章(把Cgroup比作土地,把Namespace比作房子大小,这比喻简直绝了)
Namespace
Namespace是Linux内核的一项功能,该功能对内核资源进行分区,使得不同进程组看到不同的资源。Docker 利用 Linux 内核的 Namespace 特性,实现了每个容器的资源相互隔离,从而保证容器内部只能访问到自己 Namespace 的资源。
Linux内核提供了8种Namespace,Docker使用到了其中的6种。
这里使用unshare命令进行演示,unshare在CentOS中已经集成,unshare意为使用与父进程不共享的名称空间运行程序
我们使用的unshare的如下语法,可选择的隔离类型有mount、UTS、IPC、network、pid、user共6种,--fork
参数意为创建指定的派生程序,/bin/bash为示例创建的一个bash进程
unshare --{隔离类型} --fork /bin/bash
1. Mount Namespace
作用是隔离不同进程、进程组看到的挂载点
# 创建一个 bash 进程并且新建一个 Mount Namespace
$ sudo unshare --mount --fork /bin/bash
# 在 /tmp 目录下创建一个目录
[root@centos7 centos]# mkdir /tmp/tmpfs
# 使用 mount 命令挂载一个 tmpfs 类型的目录
[root@centos7 centos]# mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
df 命令查看一下已经挂载的目录信息,可以看到 /tmp/tmpfs 目录已经被正确挂载
重新打开一个命令行窗口,df命令查看主机的挂载信息
在两个窗口,查看进程的Namespace信息,进行对比,发现除了Mount Namespace,其他都一致,由此,实现了在两个窗口进程中挂载资源的差异,Mount Namepace验证成功
2. PID Namespace
PID Namespace,用来做进程的隔离,可以实现在不同的PID Namespace中,进程可以拥有相同的PID号。
在Linux中,操作系统的1号进程为init初始化进程,由系统内核的0号进程调用系统init函数创建,用于管理用户态进程等。
创建一个bash进程,并创建一个PID Namespace,查看进程信息,发现当前Namespace下,bash为1号进程
在Docker中,每个Container都是Docker Daemon的子进程,创建一个Docker容器时,就会创建一个PID Namespace。容器的启动进程,在该容器(或者新创建的PID Namespace)中,PID为1,PID为1的进程结束后,Docker会销毁对应PID Namespace,并且向容器中的子进程发送SIGKILL信号。
这大概就是为啥Docker容器必须要维持一个前台进程(可能只是tail -t xx打印)吧,为了让PID为1的进程活下去,PID1活了,容器才能活
插一句突然想到的,这里的发送信号,属于IPC进程通信的一部分,为啥linux杀进程使用kill -9 {PID}
来完成,这里的9其实代表了一个信号,通过kill -l
可以查看信号的序号,可以看到9代表了SIGKILL
进程接收信号,如果不对信号做处理,会导致程序退出,如果捕获了信号并做处理,就不会退出。SIGKILL不能被进程捕获,进程收到这个信号后,一定会导致进程的程序退出。
3.UTS Namespace
隔离主机名,每个UTS Namespace可以拥有一个独立的主机名
新建一个bash进程,并新建一个UTS Namespace。使用hostname命令设置主机名为tuopashi,使用hostname查看主机名,结果为自定义的tuopashi。
另起一个终端,使用hostname查看主机名,为原主机名
4. IPC Namespace
IPC Namespace用来隔离进程间通信,我们创建bin进程,并且创建IPC Namespace
ipcmk -Q
创建一个系统通信队列,通过ipcmk -q
对比当前终端和新建终端,发现通信队列已经产生了隔离
5. User Namespace
# 创建一个 bash 进程,并且新建一个 User Namespace
[centos@centos7 ~]$ unshare --user -r /bin/bash
# CentOS7 默认允许创建的 User Namespace 数量为 0,如果执行上述命令失败( unshare 命令返回的错误为 unshare: unshare failed: Invalid argument ),需要使用以下命令修改系统允许创建的 User Namespace 数量,命令为:echo 65535 > /proc/sys/user/max_user_namespaces,然后再次尝试创建 User Namespace。
# 执行 id 命令查看一下当前的用户信息
[root@centos7 ~]# id
uid=0(root) gid=0(root) groups=0(root),65534(nfsnobody) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
# 可以看到,在新的 User Namespace 内已经是 root 用户了
# 使用只有主机 root 用户才可以执行的 reboot 命令来验证一下,在当前命令行窗口执行 reboot 命令
[root@centos7 ~]# reboot
Failed to open /dev/initctl: Permission denied
Failed to talk to init daemon.
# 在隔离的 User Namespace 中,并不能获取到主机的 root 权限
6. Net Namespace
用来隔离网络设备,Net Namespace可以让每个进程拥有自己独立的IP地址、端口和网卡信息。
和之前一样,创建bash进程以及Net Namespace,使用ip a
查看当前终端和新建终端的网络信息,可以看到网络隔离已经生效。
Cgroups
Cgroups也是Linux内核的一个功能,全称是Control Groups,可以限制进程or进程组的资源,包括CPU、内存、磁盘IO的资源,具体可以限制哪些资源,可以在/sys/fs/cgroup
中看到。
进入对应资源目录,查看tasks
文件,可以看到多个PID,表示当前cgroup进程组中包含哪些进程
每种资源中,存在一些配置文件,表示在该cgroup中,对于该类资源的某些使用限制,比如在memory中,limit_in_bytes代表cgroup中多个进程总共可以使用多少内存
参考
blog.csdn.net/weixin_4140…
blog.csdn.net/songxi_bo/a…
juejin.cn/post/692275…
blog.csdn.net/weixin_4140…