V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
wewin
V2EX  ›  Linux

Linux 容器实现的底层原理是什么?和虚拟化技术一样吗? -- 一个菜鸟的学习笔记分享

  •  
  •   wewin · 2020-11-27 16:13:10 +08:00 · 3197 次点击
    这是一个创建于 1467 天前的主题,其中的信息可能已经有所发展或是发生改变。
    • 昨天的文章发送节点涉嫌违规,实实在在的学习笔记,尤其最后 namespace 相关的内容,相信对学习相关内容的朋友还是有所帮助的,所以还是想让其他人看到,我删除了可能是导致违规的内容,今天重新发送。

    如今云原生技术可以说是互联网最火的概念之一,作为云原生技术的重要基石 -- “容器” 技术,想必从业者早有学习并大量的运用在工作和日常开发中。

    假如有人问你 “什么是容器?”, 你的答案是什么?

    容器就是 Docker 吗?它和虚拟化技术有什么区别和联系?它的实现原理又是什么?

    这篇文章主要通过 容器和虚拟化技术的对比,容器和 Linux Namespace 的关系,以及 Namespace 的初体验 三个模块让大家更了解什么是容器,以及容器实现的底层技术支撑 -- Liunx Namespace 。

    容器和虚拟化

    有 Linux 学习经验的同学,想必有使用 VMware 和 VirtualBox 这种软件来安装 Linux 虚拟机的经历。一般是这样的流程,我们先在 windows 机器上安装 VMware 或者 VirtualBox 这类 Hypervisor 虚拟化软件,然后使用这类虚拟化软件将 Linux 的某种发行版镜像安装成为一个 Linux 虚拟机,这样我们就拥有了一个 Linux 操作系统。

    这种虚拟化软件是一种运行在基础物理服务器和操作系统之间的中间软件层,可允许多个操作系统和应用共享硬件,通过这种方式我们得到的虚拟机软硬件、内核一应俱全,可以说是功能完备的操作系统,它和宿主机隔离,我们在操作系统里可以放心大胆的做任何的修改,而不用担心对宿主机造成损害,这中资源的隔离型和功能的相似性,和容器表象上可以说是特别的相似。

    刚开始接触 Linux 容器的我,习惯性的拿容器和虚拟机对比,认为容器就是一种轻量级的虚拟化技术,只是少了硬件资源的虚拟化,外加运行应用程序所必须的最小的文件系统。不知道有多少人也曾有过和我一样的理解,不得不说这种理解确实对刚接触容器时,理解容器有不少的帮助,但是这种说法却是不妥的,因为容器的实现机制和 Hypervisor 这种虚拟化技术是有本质却别的。

    容器和 Namespace

    容器本质上只是 Linux 上运行的特殊的进程,之所以说特殊,是因为它和操作系统上的其他进程环境进行了隔离,就像一个集装箱一样,站在容器里面,只能看到它本身。容器技术的基础是 Linux namespacecgroups 内核特性。

    隔离的特性确保了不同容器进程互不干扰,拥有各自独立的运行环境。我们知道,在操作系统上 PID 这种东西是唯一的,我们无法拥有一个 PID = 1 的进程 A, 同时拥有一个 PID = 1 的进程 B ;端口号也是唯一的,我们无法让多个进程同时监听同一个端口( fork 子进程的特殊方式除外)。我们也无法让操作系统 hostname 为 server1, 同时让 hostname 为 server2 。但是我们可以在容器 A 里存在一个进程他的 PID = 1, 同时容器 B 里也存在一个进程他的 PID 也等于 1,这就是容器隔离性的体现,它自己就是自己的全世界,开辟任意的空间(自己的文件系统),拥有自己的网络设备,拥有自己的 hostname 。

    这种隔离性依靠的就是内核特性 -- Namespace,Namespace 可以让一个进程运行在独立的命名空间中,命名空间里的进程和系统进程相互隔离,不同命名空间中的进程之间相互隔离,所以我们需要了解容器,就需要先了解下 Namespace.

    namespace 是内核 2.4 开始有的特性,namespace 一共有如下几种:

    |Namespace|隔离的资源| |---|---| |Cgroup|Cgroup root directory| |IPC|System V IPC,POSIX message queues| |Network|网络设备,端口| |Mount|挂载点| |PID|进程 ID| |Time|时钟| |User|用户和用户组 ID| |UTS|主机名、域名|

    需要注意的是以上的类型的 namespace 是从内核 2.4 开始逐步加入的,2.4 实现了 mount,2.6 加入了 IPC 、Network 、PID 、和 UTS,User 则是从 2.6 开始出现,但到了 3.8 才宣布完成,Cgroup 则是在 4.6 中才有

    上面我们提到的,不同容器中可以拥有相同的 PID,就是因为 PID 这种隔离特性的 namespace, User 和 UTS 这种隔离特性的 namespace 则可以让同一台主机上的不同容器具有相同的 user id 、group id 和 主机名。Namespace 这种内核特性,就是让一组进程运行在一个独立的空间中,让其和操作系统上的进程隔离。

    namespace 初体验

    相信很多朋友第一次接触 namespace 也是一脸懵逼,没关系,我们可以直接上手对 namespace 来个直观的感受

    以下的操作在 ubuntu16.03 ,内核主动升级到了 5.8.12-050812-generic 的系统中完成的

    开始之前,需要说明的是 Linux 提供了诸如 clone 、setns 、unshare 、ioctl 这些系统调用 API 来实现对 Namespace 和其进程的操作,不过这是后话,本次初体验我们是使用 Linux 中一个和系统调用函数 unshare 同名的命令行工具(它实际上是调用了系统调用 unshare )

    看看它的用法:

    # unshare -h
    
    Usage:
     unshare [options] [<program> [<argument>...]]
    
    Run a program with some namespaces unshared from the parent.
    
    Options:
     -m, --mount[=<file>]      unshare mounts namespace
     -u, --uts[=<file>]        unshare UTS namespace (hostname etc)
     -i, --ipc[=<file>]        unshare System V IPC namespace
     -n, --net[=<file>]        unshare network namespace
     -p, --pid[=<file>]        unshare pid namespace
     -U, --user[=<file>]       unshare user namespace
     -C, --cgroup[=<file>]     unshare cgroup namespace
     -f, --fork                fork before launching <program>
         --mount-proc[=<dir>]  mount proc filesystem first (implies --mount)
     -r, --map-root-user       map current user to root (implies --user)
         --propagation slave|shared|private|unchanged
                               modify mount propagation in mount namespace
     -s, --setgroups allow|deny  control the setgroups syscall in user namespaces
    
     -h, --help                display this help
     -V, --version             display version
    

    UTS Namespace 初体验

    UTS Namespace 可以实现 hostname 和 domainname 的隔离

    # hostname      # 检查当前的 hostname 是 server0
    server0
    # unshare -u /bin/sh      # 创建一个新的进程,并处于 UTS namespace 中 
    # hostname new-hostname      # 修改 hostname
    # hostname      # 当前进程的 hostname 已经改变
    new-hostname
    # exit      # 退出  UTS namespace
    # hostname      # 可以确定原本的 hostname 并未改变
    server0
    

    通过上面的例子我们可以看到 UTS Namespace 的隔离特性,它可以让进程拥有自己独立的 hostname,位于 UTS Namespace 中的进程,修改 hostname 不会影响到主机原本的 hostname.

    PID Namespace 初体验

    PID Namespace 可以实现 PID 的隔离

    # ps -aux |wc -l      # 系统原本的进程数
    146
    # unshare -u /bin/sh     # 没有使用 -p 参数
    # ps -aux |wc -l     # UTS Namespace 没有 pid 的隔离
    147
    # exit
    # unshare -fp --mount-proc /bin/bash     # PID Namespace, --mount-proc 选项以确保外壳程序将在新创建的名称空间中获得 PID 1, -f 标志从 unshare 以下位置派生 shell 
    # ps -aux |wc -l
    4
    

    通过上面的例子,我们特意对比了 UTS 和 PID 两种 namespace,可以更加直观的了解到 PID Namespace 的隔离特性。位于 PID Namespace 中的进程,它可以拥有和主机 PID 重复的进程。

    Network Namespace 初体验

    Network 可以进行网络的隔离

    # ifconfig -a |grep flags  # 原本主机的网卡数量
    br-0218842a6d4f: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
    br-9a4009fc0074: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
    br-ccc197daa6b7: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
    cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
    docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
    enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    enp0s8: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
    lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
    vethb960cbb3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
    vethe6e2d00a: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
    # unshare -n /bin/bash # 启动 Network Namespace
    # ifconfig -a # 启动 Network Namespace 只有 lo 这种网络设备
    lo: flags=8<LOOPBACK>  mtu 65536
            loop  txqueuelen 1000  (Local Loopback)
            RX packets 0  bytes 0 (0.0 B)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 0  bytes 0 (0.0 B)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    

    通过上例我们可以看到,主机的网卡有多个,而位于 Network Namespace 中的进程只有一个 lo 网卡,网络设备和主机的网路设备进行了隔离。

    通过以上的例子,相信大家对 Namespace 的隔离特性有了很直观的了解,容器技术的隔离性质正是基于 Namespace 这种内核特性实现,是一种内核级别的特性。

    上面通过 unshare 指令我们对 Namespace 有了初步的体验,相信大家对容器的隔离实现也会有了不同的认识。下一个章节,我会用 golang 代码的方式操作 Namespace 。

    我们知道容器除了资源的隔离,还有一大特性是资源的限制,其资源的限制则是依赖于 cgroups 这一内核特性,之后的章节我也会对 cgroups 展开做一个说明。

    今天就先到这里

    13 条回复    2020-12-01 12:47:29 +08:00
    smartyang
        1
    smartyang  
       2020-11-27 23:37:36 +08:00 via Android
    还是不知道和虚拟机的区别
    SaltyLeo
        2
    SaltyLeo  
       2020-11-28 00:33:19 +08:00
    那到底和虚拟化技术一样吗?

    这文不对题啊...
    hanguofu
        3
    hanguofu  
       2020-11-28 01:08:33 +08:00 via Android
    谢谢分享学习笔记。有一个程序必须通过 unix domain socket 和 共享内存与其它程序交互,出于保密的目的,请问哪种 namespace 适合它?
    wewin
        4
    wewin  
    OP
       2020-11-28 08:20:53 +08:00
    @smartyang 核心就是虚拟化技术是借助 Hypervisor 这种软件实现的,是一种硬件虚拟化。容器是借助的 Namespace + cgroup 这种 Linux 内核特性,前者做隔离,后者做资源的限制,本质是在隔离环境中运行的一组进程,之后另外写一篇转门说明两者区别的吧。
    wewin
        5
    wewin  
    OP
       2020-11-28 08:23:08 +08:00
    @SaltyLeo 容器和虚拟化区别这个话题是挺多内容可以说的,这里说的是比较粗略。重点是想说 Namespace 的,之后可以写一篇转门谈下容器和虚拟化 。
    wewin
        6
    wewin  
    OP
       2020-11-28 08:34:09 +08:00
    @hanguofu 我理解的是 unix domain socket 这种的资源的隔离是 Net 这种 Namespace 提供的,共享内存隔离是 IPC 这种 Namespace 提供的,我可能没有特别理解你说的场景,我想的如果你是一组程序之间使用 socket 和 共享内存相互通讯,是不是可以让其都出去 Net 和 IPC 之外的 Namespace 中,就是其他几种都可以考虑开启。场景可以说的更详细下些,一起讨论下。
    hanguofu
        7
    hanguofu  
       2020-11-28 12:36:02 +08:00 via Android
    我希望这个应用程序对于和它交互的程序而言,是完全透明的,不存在的。希望保密的具体内容包括该程序名,该程序的代码,不允许其它程序 dump 该程序在运行时所占据的内存(共享内存除外)。出于保密的目的,namespace 对于该程序而言,是一个好的解决方案吗?
    hanguofu
        8
    hanguofu  
       2020-11-28 12:37:30 +08:00 via Android
    共享内存不需要保密,我也不关心里面的数据。
    dream4ever
        9
    dream4ever  
       2020-11-28 23:40:06 +08:00
    看到这个话题,想起来今天刚好看到 Stack Overflow 上的一篇讨论: https://stackoverflow.com/questions/16047306/how-is-docker-different-from-a-virtual-machine
    kiddingU
        10
    kiddingU  
       2020-11-30 09:45:40 +08:00
    容器是宿主机上的一个进程!!!记住这个就行了
    julyclyde
        11
    julyclyde  
       2020-11-30 19:54:56 +08:00
    @kiddingU 并不一定是一个,甚至有可能都不是一个子树
    kiddingU
        12
    kiddingU  
       2020-12-01 09:25:42 +08:00
    @julyclyde 容器是宿主机的一个普通进程,这个有啥毛病?万望指点。。
    julyclyde
        13
    julyclyde  
       2020-12-01 12:47:29 +08:00
    @kiddingU 容器里面可以不止一个进程啊
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1264 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 18:05 · PVG 02:05 · LAX 10:05 · JFK 13:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.