Docker中关于Namespace隔离机制全面解析

目录
  • 前言
  • 1. Docker基本架构
    • 服务端
    • 客户端
  • 2. Namespace
    • Namespace介绍
    • Namespace的类型
      • Mount namespace
      • UTS namespace
      • IPC namespace
      • PID namespace
      • Network Namespace
      • User namespace
    • 深入理解Namespace
    • Namespace的劣势
      • 隔离不彻底
      • 有些资源和对象不能被Namespace化
      • 安全问题

前言

Docker 容器能够在服务器中高效运行,离不开容器底层技术的支持。
 
为了更好地理解容器的运行原理,本篇文章将会以 Linux 宿主机为例,介绍容器的底层技术,包括容器的命名空间、控制组、联合文件系统等。

1. Docker基本架构

Docker 目前采用标准的 C/S 架构,即服务端—客户端架构,服务端用于管理数据,客户端负责与用户交互,将获取的用户信息交由服务器处理,如图所示

那么上面的名词是什么意思呢?我这里重新画了一幅图来解析

服务器与客户机既可以运行在同一台机器上,也可以运行在不同机器上,通过 Socket(套接字)或者 RESTful API 进行通信。

服务端

Docker 服务端也就是 Docker daemon,一般在宿主机后台运行,接收来自客户的请求、并处理这些请求。在设计上,Docker 服务端是一个模块化的架构,通过专门的 Engine 模块来分发、管理各个来自客户端的任务。

Docker 服务端默认监听本地的 unix:///var/run/Docker.sock 套接字,只允许本地的 root 用户或 Docker 用户组成员访问,可以通过 -H 参数来修改监听的方式。

例如,让服务器监听本地的 TCP 连接 1234 端口,代码如下所示:

此外,Docker 还支持通过 HTTPS 认证的方式来验证访问。

Debian/Ubuntu14.04 等使用 upstart 管理启动服务的系统中,Docker 服务端的默认启动配置文件在 /etc/default/Docker
 
在使用 systemd 管理启动服务的系统,配置文件在 /etc/systemd/system/Docker.service.d/Docker.conf

客户端

用户不能与服务端直接交互,Docker 客户端为用户提供一系列可执行命令,用户通过这些命令与 Docker 服务端进行交互。

用户使用的 Docker 可执行命令就是客户端程序。与 Docker 服务端不同的是,客户端发送命令后,等待服务端返回信息,收到返回信息后,客户端立刻执行结束并退出。用户执行新的命令时,需要再次调用客户端命令。

同样,客户端默认通过本地的 unix:///var/run/Docker.sock 套接字向服务端发送命令。如果服务端不在默认监听的地址,则需要用户在执行命令时指定服务端地址,如图所示

例如:假设服务端监听本地的 TCP 连接 1234 端口 tcp://127.0.0.1:1234,只有通过 -H 参数指定了正确的地址信息才能连接到服务端。

首先,查看 Docker 信息,示例代码如下:

从以上示例中可以看到,Docker 并没有连接到服务端,但 Docker 客户端仍可以为用户提供服务。

然后,通过命令指定正确的地址信息,再次查看 Docker 信息,示例代码如下:

从以上示例中可以看到,指定了正确的地址信息之后,Docker 顺利连接到服务端。

Docker 服务端运行在主机上, 通过 Socket 连接从客户端访问,服务端从客户端接受命令并管理运行在主机上的容器。

2. Namespace

Namespace介绍

Linux 操作系统中,容器用来实现“隔离”的技术称为 Namespace(命名空间)。

Namespace 技术实际上修改了应用进程看待整个计算机的 “视图”,即应用进程的 “视线” 被操作系统做了限制,只能 “看到” 某些指定的内容,如图所示

但对于宿主机来说,这些被进行“隔离”的进程跟其他进程并没有太大区别。

下面运行一个 CentOS 7 容器,示例代码如下:

从以上示例中可以看到,bash 是这个容器内部的第 1 号进程,即 PID=1,而这个容器里一共只有两个进程在运行,这就意味着,前面执行的 /bin/sh,以及刚刚执行的 ps,已经被 Docker 隔离在一个与宿主机完全不同的空间当中。

理论上,每当在宿主机上运行一个 /bin/sh 程序,操作系统都会给它分配一个进程编号,例如,PID=100。这个编号是进程的唯一标识,就像员工的工号一样。所以,PID=100,可以粗略地理解为这个 /bin/sh 是公司里的第 100 号员工。

而现在,要通过 Docker/bin/sh 运行在一个容器当中。这时,Docker 就会在这个第 100 号员工入职时给他施一个 “障眼法” 让他永远看不到前面的其他 99 个员工,这样,他就会以为自己就是公司里的第 1 号员工。

这种机制其实就是对被隔离应用的进程空间做了手脚,使这些进程只能看到重新计算过的进程号,例如 PID=1。可实际上,它们在宿主机的操作系统里,还是原来的第 100号 进程,如图所示

下面通过宿主机查看容器进程号,示例代码如下:

在宿主机中通过容器的 ID 号查看其进程号,可以看出其进程号为 7548,这就是 Linux 里的 Namespace 机制。
 
Linux 系统中创建线程调用是 clone() 函数,例如:int pid = clone(main_function, stack_size, SIGCHLD, NULL); 这个调用会创建一个新的进程,并且返回它的进程号。
 
系统调用 clone() 创建一个新进程时,可以在参数中指定 CLONE_NEWPID ,例如:int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL); 这时,新创建的这个进程将会 “看到” 一个全新的进程空间,在这空间里,它的进程号是 1。在宿主机真实的进程空间里,这个进程的真实进程号不变。
 
当然,可以多次执行上面的 clone() 调用,这样就会创建多个 PID Namespace,而每个 Namespace 里的应用进程,都会认为自己是当前容器里的第 1 号进程,它们既看不到宿主机里真正的进程空间,也看不到其他 PID Namespace 里的具体情况。

Namespace的类型

命名空间分为多种类型,对应用程序进行不同程度的隔离,下面挨个介绍。

Mount namespace

Mount Namespace 将一个文件系统的顶层目录与另一个文件系统的子目录关联起来,使其成为一个整体。该子目录称为挂载点,这个动作称为挂载。

UTS namespace

UTSUNIX Time-sharing SystemUNIX 分时系统)Namespace 提供主机名和域名的隔离,使子进程有独立的主机名和域名,这一特性在 Docker 容器技术中被运用,使 Docker 容器在网络上被视作一个独立的节点,而不仅仅是宿主机上的一个进程。

IPC namespace

IPCInter-Process Communication,进程间通信)NamespaceUNIXLinux 下进程间通信的一种方式。
 
IPC 有共享内存、信号量、消息队列等方式。此外,也需要对 IPC 进行隔离,如此一来,只有在同一个 Namespace 下的进程才能相互通信。
 
IPC 需要有一个全局的 ID,既然是全局的,就意味着 Namespace 需要对这个 ID 号进行 隔离,不能让其他 Namespace 的进程 “看到”。

PID namespace

PID Namespace 用来隔离进程的 ID 空间,使不同 PID Namespace 里的进程 ID 号可以重复且相互之间不影响。
 
PID Namespace 可以嵌套,也就是说有父子关系。在当前 Namespace 里面创建的所有新的 Namespace 都是当前 Namespace 的子 Namespace
 
在父 Namespace 里面可以 “看到” 所有子 Namespace 里的进程信息,而在子 Namespace 里看不到父 Namespacelode 与其他子 Namespacelode 进程信息,如图所示

Network Namespace

每个容器拥有独立的网络设备,IP 地址,IP 路由表,/proc/net 目录,端口号等。这也使得一个 host 上多个容器内的网络设备都是互相隔离的。

User namespace

User Namespace 用来隔离 User 权限相关的 Linux 资源,包括 User IDsGroup IDs
 
这是目前实现的 Namespace 中最复杂的一个,因为 User 和权限息息相关,而权限又关联着容器的安全问题。
 
在不同的 User Namespace 中,同样一个用户的 User IDGroup ID 可以不一样。也就是说,一个用户可以在父 User Namespace 中是普通用户,在子 User Namespace 中是超级用户。

深入理解Namespace

下面通过一段简单的代码来查看 Namespace 是如何实现的,示例代码如下:

在以上示例中,代码段通过 clone() 调用,传入各个 Namespace 对应的 clone flag,创建了一个新的子进程,该进程拥有自己的 Namespace。根据以上代码可知,该进程拥有自己的 PIDMountUserNetIPC 以及 UTS Namespace

所以,Docker 在创建容器进程时,指定了这个进程所需要启动的一组 Namespace 参数。这样,容器就只能 “看到” 当前 Namespace 所限定的资源、文件、设备、状态、配置信息等。至于宿主机以及其他不相关的程序,它就完全看不到了。容器,其实是 Linux 系统中一种特殊的进程。

LinuxDocker 创建的隔离空间虽然是看不见摸不着,但是一个进程的 Namespace 信息在宿主机上是真实存在的,并且是以文件的方式存在,因为在 Linux 操作系统中,一切皆文件。

一个进程可以选择加入到某个进程已有的 Namespace 当中,从而达到 “进入” 这个进程所在容器的目的,这正是 docker exec 的实现原理。

下面通过示例进行详细讲解,首先运行一个 CentOS 容器,示例代码如下:

以上示例中在运行 Docker 容器的命令中添加了参数 -d,表示使容器在后台运行。

查看当前正在运行 Docker 容器的进程号,示例代码如下:

查看宿主机的 /proc 文件,可以看到这个 8589 进程所有 Namespace 对应的文件,示例代码如下:

可以看到,一个进程的每种 Namespace 都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件。

有了这样的文件,就可以对 Namespace 做一些实质性的操作。例如,将进程加入到一个已经存在的 Namespace 当中。

这个操作依赖一个名为 setns()Linux 系统调用,示例代码如下:

上述代码共接收了两个参数。

arvg[1],即当前进程要加入的 Namespace 文件的路径,如 /proc/8589/ns/net。用户要在这个 Namespace 里运行的进程,如 /bin/bash

代码的核心操作则是通过 open() 打开指定的 Namespace 文件,并把这个文件的描述符 fd 交给 setns() 使用。在 setns() 执行后,当前进程就加入了这个文件对应的 Namespace 当中。

Namespace的劣势

强大的 Namespace 机制可以实现容器间的隔离,是容器底层技术中非常重要的一项,但也有不可否认的不足。

下面总结基于 Namespace 的隔离机制相对于虚拟化技术的不足之处,以便在生产环境中设法克服。

隔离不彻底

容器只是运行在宿主机上的一种特殊的进程,多个容器之间使用的是同一个宿主机的操作系统内核。
 
尽管可以在容器中通过 Mount Namespace 单独挂载其他版本的操作系统文件,如 CentOS 或者 Ubuntu,但这并不能改变它们共享宿主机内核的事实。
 
Windows 宿主机上运行 Linux 容器,或者在低版本的 Linux 宿主机上运行高版本的 Linux 容器,都是行不通的。
 
相比之下,拥有硬件虚拟化技术和独立 Guest OS 的虚拟机就要好用得多。最极端的例子是 Microsoft 的云计算平台 Azure,它就是运行在 Windows 服务器集群上的,但这并不妨碍用户在上面创建各种 Linux 虚拟机。

有些资源和对象不能被Namespace化

如果容器中的程序调用 settimeofday() 修改了时间,整个宿主机的时间都会被修改。相较于在虚拟机里面可以任意做修改,在容器里部署应用时,需要用户的操作上更加谨慎。

安全问题

因为共享宿主机内核,容器中的应用暴露出来的攻击面很大。
 
尽管生产实践中可以使用 seccomp 等技术,对容器内部发起的所有系统调用进行过滤和甄别来进行安全加固,但这类方法因为多了一层对系统调用的过滤,会拖累容器的性能。
 
通常情况下,也不清楚到底该开启哪些系统调用,禁止哪些系统调用。

到此这篇关于Docker中关于Namespace隔离机制的奥秘的文章就介绍到这了,更多相关Docker Namespace隔离机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Docker探索namespace详解

    Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制(copy-on-write)实现了高效的文件操作. 1.namespace资源隔离 namepsace的6项隔离: namespace 系统调用参数 隔离内容 UTS CLONE_NEWUTS 主机名与域名 IPC CLONE_NEWIPC 信号量,消息队列和共享内存 PID CLONE_NEWPID 进程编号 Network CLONE_NEWNET 网络设备,网络栈,端口等 Mount CLON

  • Docker基础知识之Linux namespace图文详解

    前言 Docker 是"新瓶装旧酒"的产物,依赖于 Linux 内核技术 chroot .namespace 和 cgroup.本篇先来看 namespace 技术. Docker 和虚拟机技术一样,从操作系统级上实现了资源的隔离,它本质上是宿主机上的进程(容器进程),所以资源隔离主要就是指进程资源的隔离.实现资源隔离的核心技术就是 Linux namespace.这技术和很多语言的命名空间的设计思想是一致的(如 C++ 的 namespace). 隔离意味着可以抽象出多个轻量级的内核

  • Docker底层技术Namespace Cgroup应用详解

    Docker底层技术: docker底层的2个核心技术分别是Namespaces和Control groups Namespace:是容器虚拟化的核心技术,用来隔离各个容器,可解决容器之间的冲突. 主要通过以下六项隔离技术来实现: 有两个伪文件系统:/proc和/sys/ UTS:允许每个container拥有独立的hostname(主机名)和domainname(域名),使其在网络上可以被视作一个独立的节点而非Host上的一个进程. IPC:contaner中进程交互还是采用linux常见的进

  • Docker中关于Namespace隔离机制全面解析

    目录 前言 1. Docker基本架构 服务端 客户端 2. Namespace Namespace介绍 Namespace的类型 Mount namespace UTS namespace IPC namespace PID namespace Network Namespace User namespace 深入理解Namespace Namespace的劣势 隔离不彻底 有些资源和对象不能被Namespace化 安全问题 前言 Docker 容器能够在服务器中高效运行,离不开容器底层技术的

  • SqlServer中tempdb的日志机制原理解析及示例分享

    测试用例 我们分别在用户数据库(testpage),tempdb中创建相似对象t1,#t1,并在tempdb中创建创建非临时表,然后执行相应的insert脚本(用以产生日志),并记录执行时间用以比较用以比较说明tempdb"快" Code 用户数据库testpage use testpage go create table t1 ( id int identity(1,1) not null, str1 char(8000) ) declare @t datetime2=sysutcd

  • Docker 中的容器完全解析

    Docker 中的容器完全解析 Docker中的容器可以看成是镜像的一个运行环境,它带有额外的可写文件层. 一.创建容器: 1.新建容器: docker create -it --name [CONTAINERNAME] [NAME]:[TAG] 比如: docker create -it --name container ubuntu:add /bin/bash 此为根据镜像的名称创建容器,容器的名称为container 2.查看容器详情列表: docker ps -a 可以查看到容器的ID,

  • k3d入门指南之在Docker中运行K3s的详细教程

    什么是k3d? k3d是一个小型程序,用于在Docker中运行K3s集群. K3s是经过CNCF认证的轻量级Kubernetes发行和沙箱项目.它是为资源有限环境设计的,被打包为单个二进制文件,所需RAM小于512MB. 要了解有关K3s的更多信息,请查看我们之前的公众号文章及B站上的视频. k3d借助从K3s仓库构建的Docker镜像在安装了Docker的任何机器上的Docker容器中启动多个K3s节点. 这样,一台物理(或虚拟)机(称为Docker Host)可以运行多个K3s集群,每个集群

  • Spring中XML schema扩展机制的深入讲解

    前言 很久没有写关于 Spring 的文章了,最近在系统梳理 Dubbo 代码的过程中发现了 XML schema 这个被遗漏的知识点.由于工作中使用 SpringBoot 比较多的原因,几乎很少接触 XML,此文可以算做是亡羊补牢,另一方面,也为后续的 Dubbo 源码解析做个铺垫. XML schema 扩展机制是啥?从Spring2.0开始,Spring提供了XML Schema可扩展机制,用户可以自定义XML Schema文件,并自定义XML Bean解析器,并集成到Spring Ioc

  • Docker网络原理及自定义网络详细解析

    Docker在宿主机上虚拟了一个网桥,当创建并启动容器的时候,每一个容器默认都会被分配一个跟网桥网段一致的ip,网桥作为容器的网关,网桥与每一个容器联通,容器间通过网桥可以通信.由于网桥是虚拟出来的,外网无法进行寻址,也就是默认外网无法访问容器,需要在创建启动容器时把宿主机的端口与容器端口进行映射,通过宿主机IP端口访问容器.这是Docker默认的网络,它有一个弊端是只能通过IP让容器互相访问,如果想使用容器名称或容器ID互相访问需要在创建启动容器时候用link的方式修改hosts文件实现.一般

  • 一文带你彻底搞懂Docker中的cgroup的具体使用

    目录 什么是cgroup cgroup的组成 cgroup提供的功能 限制cgroup中的CPU 限制cgroup中的内存 限制cgoup的进程数 前言 进程在系统中使用CPU.内存.磁盘等计算资源或者存储资源还是比较随心所欲的,我们希望对进程资源利用进行限制,对进程资源的使用进行追踪.这就让cgroup的出现成为了可能,它用来统一将进程进行分组,并在分组的基础上对进程进行监控和资源控制管理. 什么是cgroup Linux CGroup(Linux Contral Group),它其实是Lin

  • 聊聊Docker中容器的创建与启停问题

    目录 1. 镜像和容器 2. 新建并启动容器 3. 使用第一个容器 4. 容器命名 5.重启容器 6. 附着到容器上 1. 镜像和容器 看待镜像和容器的一种方式是将它们类比成程序与进程.一个进程可以视为一个被执行的应用程序,同样,一个Docker容器可以视为一个运行中的Docker镜像. 标题Docker镜像与容器 如果大家熟悉面向对象原理,看待镜像和容器的另一种方法是将镜像看作类而将容器看作对象.对象是类的具体实例,同样,容器是镜像的实例.用户可以从单个镜像创建多个容器,就像对象一样,它们之间

  • 在AngularJS框架中处理数据建模的方式解析

    我们知道,AngularJS并没有自带立等可用的数据建模方案.而是以相当抽象的方式,让我们在controller中使用JSON数据作为模型.但是随着时间的推移和项目的成长,我意识到这种建模的方式不再能满足我们项目的需求.在这篇文章中我会介绍在我的AngularJS应用中处理数据建模的方式. 为Controller定义模型 让我们从一个简单的例子开始.我想要显示一个书本(book)的页面.下面是控制器(Controller): BookController app.controller('Book

随机推荐