Docker隔离技术
前言
Docker系列文章:
此篇是Docker系列的第九篇,之前的文章里面或多或少的提到Docker的隔离技术,但是没有很清楚的去聊这个技术,但是经过这么多文章大家一定对Docker使用和概念有了一定的理解,接下来我们聊下底层一些技术,帮助大家解解惑,先从隔离技术开始吧。此外大家一定要按照我做的Demo都手敲一遍,印象会更加深刻的,加油!
如何理解Namespace
进入一个容器内部:
docker exec -it ad9342449b86 /bin/bash
通过ps命令查看容器内部的进程,容器内部只有两个进程在运行,看不到操作系统的其他进程,一个进程执行的/bin/bash,另外一个执行的ps,说明此刻容器被 Docker 隔离在了一个跟宿主机在不同的环境中。
对于宿主机来说相当于我们在宿主机上执行一个/bin/bash的进程,宿主机给这个进程起一个独一无二名字,比如叫PID=800,用来区分与其他进程的不同,Docker在运行/bin/bash的时候,会与宿主机上的其他进程进行隔离,让他看不到其他进程运行状况,并且重新计算进程号,也就是我们看到202,但是这个进程实际上是宿主机的进程,这种技术,其实就是对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号,这就是Linux里面的Namespace机制,也就是Docker采用的隔离技术。
总结一下,Namespace是Linux内核用来隔离内核资源的方式。通过Namespace可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个Namespace中。Linux Namespace是对全局系统资源的一种封装隔离,使得处于不同Namespace的进程拥有独立的全局系统资源,改变一个Namespace中的系统资源只会影响当前Namespace里的进程,对其他Namespace中的进程没有影响。
Namespace类型介绍
目前,Linux 内核实现了6种 Namespace:
六种命名空间:
UTS Namespace:
UTS Namespace 对主机名和域名进行隔离。为什么要隔离主机名?因为主机名可以代替IP来访问。如果不隔离,同名访问会出冲突。
IPC Namespace
Linux 提供很多种进程通信机制,IPC Namespace 针对 System V 和 POSIX 消息队列,这些 IPC 机制会使用标识符来区别不同的消息队列,然后两个进程通过标识符找到对应的消息队列。IPC namespace使得相同的标识符在两个Namespace代表不同的消息队列,因此两个Namespace 中的进程不能通过 IPC 来通信。
PID Namespace
PID Namespace 用来隔离进程的 PID 空间,使得不同 PID Namespace 里的进程 PID 可以重复且互不影响。PID Namespace 对容器类应用特别重要, 可以实现容器内进程的暂停/恢复等功能,还可以支持容器在跨主机的迁移前后保持内部进程的 PID 不发生变化。
Mount Namespace
Mount Namespace 为进程提供独立的文件系统视图。可以这么理解,Mount Namespace 用来隔离文件系统的挂载点,这样进程就只能看到自己的Mount Namespace中的文件系统挂载点。进程的Mount Namespace中的挂载点信息可以在 /proc/[pid]/mounts、/proc/[pid]/mountinfo 和 /proc/[pid]/mountstats 这三个文件中找到。在一个 Namespace 里挂载、卸载的动作不会影响到其他 Namespace。
Network Namespace
Network Namespace 在逻辑上是网络堆栈的一个副本,它有自己的路由、防火墙规则和网络设备。默认情况下,子进程继承其父进程的 Network Namespace。每个新创建的 Network Namespace 默认有一个本地环回接口 lo,除此之外,所有的其他网络设备(物理/虚拟网络接口,网桥等)只能属于一个 Network Namespace。每个 socket 也只能属于一个 Network Namespace。
User Namespace
User Namespace 用于隔离安全相关的资源,包括 user IDs and group IDs,keys, 和 capabilities。同样一个用户的 user ID 和 group ID 在不同的User Namespace 中可以不一样(与 PID Namespace 类似)。可以这样理解,一个用户可以在一个User Namespace中是普通用户,但在另一个User Namespace中是root用户。
Namespace原理介绍
Linux Namespace 是 Linux 提供的一种内核级别环境隔离的方法,因此Linux 提供了多个 API 用来操作 Namespace,它们是 clone()、setns() 和 unshare() 函数,简单介绍一下三个系统调用的功能:
clone() : 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述系统调用参数达到隔离的目的; unshare() : 使某进程脱离某个 namespace; setns() : 把某进程加入到某个 namespace;
为了确定隔离的到底是哪项 Namespace,在使用这些 API 时,通常需要指定一些调用参数:CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER、CLONE_NEWUTS 和 CLONE_NEWCGROUP。下图是各个Namespace对应的调用参数和Linux内核:
如果要同时隔离多个 Namespace,可以使用 | (按位或)组合这些参数。同时我们还可以通过 /proc 下面的一些文件来操作 Namespace。下面就让让我们看看这些接口的用法:
查看进程所属的Namespace
从版本号为 3.8 的内核开始,/proc/[pid]/ns 目录下会包含进程所属的 Namespace 信息,使用下面的命令可以查看当前进程所属的 Namespace 信息:
ls -l /proc/$$/ns
这些 Namespace 文件都是链接文件。链接文件的内容的格式为 xxx:[inode number]。其中的 xxx 为 Namespace 的类型,inode number 则用来标识一个 Namespace,我们也可以把它理解为 Namespace 的 ID。如果两个进程的某个 Namespace 文件指向同一个链接文件,说明其相关资源在同一个 Namespace 中。
clone() 函数
我们可以通过 clone() 在创建新进程的同时创建 Namespace。clone() 在 C 语言库中的声明如下:
#define _GNU_SOURCE
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
实际上,clone() 是在 C 语言库中定义的一个封装(wrapper)函数,它负责建立新进程的堆栈并且调用对编程者隐藏的 clone() 系统调用。四个参数代表的意思是:
child_func : 传入子进程运行的程序主函数; child_stack : 传入子进程使用的栈空间; flags : 表示使用哪些 CLONE_* 标志位; args : 用于传入用户参数;
clone() 与 fork() 类似,都相当于把当前进程复制了一份,但 clone() 可以更细粒度地控制与子进程共享的资源(可以通过 flags 来控制),包括虚拟内存、打开的文件描述符和信号量等等。一旦指定了标志位 CLONE_NEW*,相对应类型的 Namespace 就会被创建,新创建的进程也会成为该 Namespace 中的一员。
setns() 函数
setns() 函数可以将当前进程加入到已有的 Namespace 中。setns() 在 C 语言库中的声明如下:
#define _GNU_SOURCE
#include <sched.h>
int setns(int fd, int nstype);
和 clone() 函数一样,C 语言库中的 setns() 函数也是对 setns() 系统调用的封装:
fd:表示要加入 Namespace 的文件描述符。它是一个指向 /proc/[pid]/ns 目录中文件的文件描述符,可以通过直接打开该目录下的链接文件或者打开一个挂载了该目录下链接文件的文件得到; nstype:参数 nstype 让调用者可以检查 fd 指向的 Namespace 类型是否符合实际要求。若把该参数设置为 0 表示不检查;
unshare() 函数
unshare() 函数可以在原进程上进行 Namespace 隔离。也就是创建并加入新的 Namespace 。unshare() 在 C 语言库中的声明如下:
#define _GNU_SOURCE
#include <sched.h>
int unshare(int flags);
unshare() 函数也是对 unshare() 系统调用的封装。调用 unshare() 的主要作用就是:不启动新的进程就可以起到资源隔离的效果,相当于跳出原先的 Namespace 进行操作。
使用PID Namesapce达到Docker线程隔离状态
Linux下的每个进程都有一个对应的 /proc/PID 目录,该目录包含了大量的有关当前进程的信息。 对一个 PID Namespace 而言,/proc 目录只包含当前 Namespace 和它所有子孙后代 Namespace 里的进程的信息。
创建一个新的 PID Namespace 后,如果想让子进程中的 top、ps 等依赖 /proc 文件系统的命令工作,还需要挂载 /proc 文件系统。使用如下命令创建一个新的PID Namespace:
#查看当前进程的PID
echo $$
#查看该线程对应PID Namespace
readlink /proc/$$/ns/pid
#使用unshare命令创建新的PID Namespace
#该命令会同时创建新的PID和Mount namespace
unshare --pid --mount --fork /bin/bash
#查看创建的线程的PID Namespace
readlink /proc/$$/ns/pid
上图中新生成的PID Namespace的Id并没有发生改变,我们看到在新创建的PID Namespace也可以看到其他的进程的运行情况,显然这个和我们说的隔离是不一致的,接下来我们看下原因是什么:
#查看当前进程的PID
echo $$
#查看当前进程的详情
ps 1
这个例子说明当前进程被认为是该 PID namespace 中的 1 号进程了,通过PS命名查看1号进程的详情,发现这个进程是系统的启动相关(CentOS 7开始也由systemd取代了init作为默认的系统进程管理工具)。
造成混乱的原因是当前进程没有正确的挂载 /proc 文件系统,由于我们新的 Mount Namespace 的挂载信息是从老的 Namespace 拷贝过来的,所以这里看到的还是老 Namespace 里面的进程号为 1 的信息。执行下面的命令挂载 /proc 文件系统:
mount -t proc proc /proc
接下来我们再来检查相关的信息,会发现新建的 PID namespace 进程看不到宿主机的进程信息了。
我们也可以使用如下命令来创建进程,
unshare --pid --mount-proc --fork /bin/bash
这样在创建了 PID 和 Mount Namespace 后,会自动挂载 /proc 文件系统,就不需要我们手动执行 mount -t proc proc /proc 命令了。
总结
Docker容器是在创建容器进程时,指定了这个进程所需要启用的一组Namespace参数,这样容器就只能看到到当前Namespace所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到了,因此容器本质上就是一个特殊的进程。
结束
欢迎大家点点关注,点点赞!
Docker隔离技术的更多相关文章
- 微服务架构:基于微服务和Docker容器技术的PaaS云平台架构设计(微服务架构实施原理)
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 基于微服务架构和Docker容器技术的PaaS云平台建设目标是给我们的开发人员提供一套服务快速开发.部署.运维管理.持续开发持续集成的流程 ...
- k8s 重要概念 - 每天5分钟玩转 Docker 容器技术(117)
在实践之前,必须先学习 Kubernetes 的几个重要概念,它们是组成 Kubernetes 集群的基石. Cluster Cluster 是计算.存储和网络资源的集合,Kubernetes 利用这 ...
- Docker 基础技术之 Linux cgroups 详解
PS:欢迎大家关注我的公众号:aCloudDeveloper,专注技术分享,努力打造干货分享平台,二维码在文末可以扫,谢谢大家. 推荐大家到公众号阅读,那里阅读体验更好,也沉淀了很多篇干货. 前面两篇 ...
- Network Policy - 每天5分钟玩转 Docker 容器技术(171)
Network Policy 是 Kubernetes 的一种资源.Network Policy 通过 Label 选择 Pod,并指定其他 Pod 或外界如何与这些 Pod 通信. 默认情况下,所有 ...
- Docker容器技术的PaaS云平台架构设计***
基于微服务架构和Docker容器技术的PaaS云平台建设目标是给我们的开发人员提供一套服务快速开发.部署.运维管理.持续开发持续集成的流程.平台提供基础设施.中间件.数据服务.云服务器等资源,开发人员 ...
- 当公有云Azure拥抱Docker容器技术
本文转载至 http://3387405.blog.51cto.com/3377405/1598977 预见未来看似是一件不太可能的事情,然而现在企业科技高速发展的态势完全超乎想象. 就在几周前Inf ...
- Docker容器技术的核心原理
目录 1 前言 2 docker容器技术 2.1 隔离:Namespace 2.2 限制:Cgroup 2.3 rootfs 2.4 镜像分层 3 docker容器与虚拟机的对比 1 前言 上图是百度 ...
- 庐山真面目之十四微服务架构的Docker虚拟技术深入探究
庐山真面目之十四微服务架构的Docker虚拟技术深入探究 一.我的开场白 曾几何时,分布式的发展也影响了后来的微服务架构的实现方式.到了现在,只要涉及到互联网技术领域,就会设计一个概念,那就是微服务. ...
- docker容器技术基础之联合文件系统OverlayFS
我们在上篇介绍了容器技术中资源隔离与限制docker容器技术基础之linux cgroup.namespace 这篇小作文我们要尝试学习容器的另外一个重要技术之联合文件系统之OverlayFS,在介绍 ...
随机推荐
- jmeter--JSON Extractor 用法
JMeter处理大部分请求返回的结果,都是json.对于请求返回的结果,处理以后作为其他请求的参数,有一个方便使用的插件:JSON Extractor JSON Extractor中文叫做json提取 ...
- 【NX二次开发】Block UI 超级截面
属性说明 属性 类型 描述 常规 BlockID String 控件ID Enable Logical 是否可操作 Group ...
- 使用 Hexo 搭建静态博客
目录 Hexo 简介 什么是 Hexo? Hexo 安装 Hexo 建站 Hexo 配置 Hexo 自定义主题 Hexo 写作 Hexo 服务器 Hexo 生成与发布 一键部署 Hexo 站点到 gi ...
- 合宙Luat | Cat.1 Socket数据收不到?学会两招不掉线
1 服务器收不到Socket数据的原因 Socket是大家使用Cat.1模块常用的功能之一,但Cat.1模块不是直接跟服务器连接,而是通过NAT(即网络地址转换)与服务器连接. 一个会话建立后会在NA ...
- Linux命令大全之基本命令
命令提示符中: ~:表示家目录 #:表示超级用户 $:表示普通用户 命令 [选项] [参数] ls(list):查询目录中的内容 ls [选项] [文件或目录] -a:显示所有文件, ...
- 4、基本数据类型(init、bool)
4.1.数字: 1.age = 21 weight = 64 fight = 5 2.数字的特点: (1)数字是不可变数据类型(不可以增加,删除,修改元素) (2)数字可以直接访问 (3)数字不可使用 ...
- 23、ORA-00439:DEFERRED_SEGMENT_CREATION 问题解决
23.1.说明: 1. 众所周知,在清空表内所有数据时,truncate比delete要快很多,原因是,delete语句每次删除一行,都在事务日志中为所删除的每行记录一项. truncate通过释放存 ...
- layui--入门(helloWorld)
具体可参考官方文档:https://www.layui.com/doc/ 由于引入layui 需要用到node.js 安装过程可参考: https://www.cnblogs.com/liuchenx ...
- ubuntu 替换某一内核模块
流程 方法一 以下配置仅执行一次,并以 linux kernel 3.13.0 为例 $ cd ~ $ apt-get source linux-source-3.13.0 $ cd linux-3. ...
- Hadoop0.20.2中MapReduce读取gb2312文件出现乱码问题
单位用的是Linux系统的字符编码是gb2312,所以生成的文件都是按照默认编码生成的.给我的文件也都是gb2312的,在hadoop中运行mapreduce出现乱码,在网上查资料说是因为hadoop ...