V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ProgrammerAlan
V2EX  ›  程序员

Alan 的 Docker 容器学习笔记

  •  
  •   ProgrammerAlan · 2023-01-09 22:53:31 +08:00 · 1719 次点击
    这是一个创建于 687 天前的主题,其中的信息可能已经有所发展或是发生改变。

    (本文的内容主要来源于 Google 、百科和学过的一些专栏,目前没有实际的企业级应用容器化部署经验,写的比较浅薄见笑了)

    为什么会接触到 Docker

    运维同学使用 k8s 将业务迁移上云时遇到一些问题解决不了,需要我去验证一下并帮助解决问题。了解到容器是 k8s 的基础,并且根据我对问题的推测认为主要原因是文件目录没有挂载,所以我决定学习一下 Docker.

    Docker 容器

    Docker 这东西使用起来一点都不复杂,跟着 Docker 官网的说明,聪明的程序员十来分钟应该就能搞定,即使像我一样不太聪明的,结合 Google 上的部署教程 1 小时左右的时间也能搞定。

    简单来说,它就是个小工具,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux 或 Windows 操作系统的机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。

    遇到的是什么问题,这个问题的根本原因是什么,如何解决?

    问题描述:容器无法获取到宿主机的设备信息,具体点说就是无法在容器上执行 dmidecode 命令。

    想要解决这个问题,需要先了解一下执行 dmidecode 命令需要具备的条件以及容器的实现机制。

    dmidecode 命令

    dmidecode 命令可以让你在 Linux 系统下获取有关硬件方面的信息包括 UUID 、BIOS 、系统、主板、处理器、内存、缓存等等。

    常用用法如下(需要 root 权限):

    [root@localhost ~]$ dmidecode                          # 打印所有硬件信息
    [root@localhost ~]$ dmidecode -q                       # 打印所有硬件信息,比较简洁[root@localhost ~]$ dmidecode -h                       # 获取帮助
    [root@localhost ~]$ dmidecode | grep 'Product Name'    # 以过滤的方式来查看指定的硬件信息
    [root@localhost ~]$ dmidecode --type bios        # 查看 BIOS 相关的硬件信息
    [root@localhost ~]$ dmidecode --type system      # 查看系统相关的硬件信息
    [root@localhost ~]$ dmidecode --type baseboard   # 查看主板相关的硬件信息
    [root@localhost ~]$ dmidecode --type chassis     # 查看机箱相关的硬件信息
    [root@localhost ~]$ dmidecode --type processor   # 查看处理器相关的硬件信息
    [root@localhost ~]$ dmidecode --type memory      # 查看内存相关的硬件信息
    

    我们不可能每次都使用 root 权限执行命令,这时候我们可以提前使用 chmod + s 升级目录权限。chmod +s 就是给某个文件以 suid 权限,当一个具有执行权限的文件设置 SUID 权限后,用户执行这个文件时将以文件所有者的身份执行。

    具体代码如下:

    chmod +s /usr/sbin/dmidecode
    

    或者

    sudo chmod +s /usr/sbin/dmidecode
    

    再来看看容器背后的实现机制

    容器的两大关键技术 Namespace 和 Cgroups。我看过的教程都说,理解了这两个概念,基本上就能彻底搞懂容器的核心原理了。我的感受是,虽然没有彻底搞懂容器的核心原理,但是对容器的实现机制有一个大概的了解,然后能搞清楚运维同学提到的问题为什么会出现了。

    下面开始分享我学习两大关键技术的经验:

    首先我是跟着教程用容器启动服务

    具体的做法是先创建一个镜像,然后用这个镜像启动一个容器,也就是启动我们的 jar 包。(具体的操作可以看 Docker 官网教程)此时我们的项目就已经被 Docker 隔离在了一个跟宿主机完全不同的世界当中,这个世界可以称之为“沙盒”。“沙盒”的进程、网络还有文件系统都是独立的。

    然后就是思考 Docker 容器实现“沙盒”的技术手段。

    首先是学习实现“边界”的手段 Namespace

    也就是思考容器的独立运行环境到底是怎么创造的。

    容器是一个“单进程”模型,是一种特殊的进程。容器对被隔离应用的进程空间做了手脚,使得这些进程之间”相互隔离“,不能访问彼此的资源,这种技术,就是 Linux 里面的 Namespace 机制。

    这种隔离有两个作用:第一是可以充分地利用系统的资源,也就是说在一台宿主机上可以运行多个容器;第二是保证了安全性,因为不同容器之间不能访问对方的资源

    容器的网络和文件系统不一样,如何做到的呢?

    其实这里依靠的是 Network Namespace 和 Mount Namespace:

    Mount Namespace ,用于让被隔离进程只看到当前 Namespace 里的挂载点信息;

    Network Namespace ,用于让被隔离进程看到当前 Namespace 里的网络设备和配置。

    当然除了上面这些,Linux 操作系统还提供了 Mount 、UTS 、IPC 、Network 和 User 这些 Namespace ,用来对各种不同的进程上下文进行“障眼法”操作。

    这些 Namespace 尽管类型不同,但都是为了隔离容器资源,正是通过在创建容器进程时,指定了这个进程所需要启用的一组 Namespace 参数。所以,容器就只能“看”到当前 Namespace 所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的内容,它就完全看不到了。

    再就是学习约束“边界”的手段 Cgroups

    上面我们学习了,容器,是一种特殊的进程,它使用 Namespace 实现“障眼法”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。但对于宿主机来说在宿主机真实的进程空间里,这些被“隔离”了的进程跟其他进程并没有太大区别。也就是说这种做法有个问题就是:隔离得不彻底

    虽然容器之间在 Namespace“障眼法”的干扰下相互之间看不到对方容器内的情况。但是宿主机上,它与其他所有进程之间依然是平等的竞争关系。这就意味着,容器表面上被隔离了起来,但是它所能够使用到的资源(比如 CPU 、内存、磁盘、网络带宽),却是可以随时被宿主机上的其他进程(或者其他容器)占用的。

    所以我们要对容器做一些限制,实现限制的技术手段就是 Linux Cgroups ,全称是 Linux Control Group 。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU 、内存、磁盘、网络带宽等等。

    在今天的分享中,我就不和大家去深入学习 Cgroups 了。感兴趣的同学可以自行 Google 学习一下。

    问题的答案

    dmidecode 命令和容器背后的实现机制讲完了,但运维同学提出的问题好像还没有解决,我直接把答案放出来吧。

    如果要在 docker 容器中运行 dmidecode 命令,那么要将宿主机的如下两个目录挂载到容器中。 \1. /sbin/dmidecode --这个目录是 dmidecode 程序的目录,如果不挂载那么容器中识别不了 dmidecode 命令。 \2. /dev/mem --dmidecode 调用时会使用到 mem 这个文件,如果不挂载会找不到文件。 \3. 在启动时增加 --privileged 这个参数,让容器获得近似于宿主机 root 的权限。 完整的运行命令如下:

    docker run -d --privileged -v /sbin/dmidecode:/sbin/dmidecode -v /dev/mem:/dev/mem --name my-demo -p 9090:8080 demo
    

    (以近似于 root 的权限用后台模式启动 demo 这个镜像,取别名为 my-demo ,并挂载两个宿主机目录到容器中,将容器的 8080 端口映射到宿主机 9090 端口)

    总结一下,其实容器中想要用的任何东西,无论是文件还是程序还是什么别的,都可以通过挂载的形式从宿主机中挂载到容器中,让容器中可以访问到。

    安全性问题

    Docker 容器确实非常火热也很好用,但是在生产环境下当 Docker 运行在云提供商平台上时,安全性变得更加复杂,所以企业生产环境需要特别注意容器安全问题。

    比如说,上文使用 Privileged 参数,让容器获得近似于宿主机 root 的权限,这个配置就是不安全的。

    为什么这个 Privileged 配置不安全?

    如果配置了 privileged 参数,容器就可以获取所有的 capabilities ,那什么是 capabilities 呢?

    Linux capabilities:对于任意一个进程,在做任意一个特权操作的时候,都需要有这个特权操作对应的 capability 。

    privileged 由于包含了所有的 capabilities, 这样容器就可以轻易获取宿主机上的所有资源,这会对宿主机的安全产生威胁。类似于 root 权限的概念。

    所以,我们要根据容器中进程需要的最少特权来赋予 capabilities 。添加权限的参数是 --cap-add ,具体要授权什么 capabilities 可以 Google 搜索“linux capabilities 手册”了解。

    一些思考

    写了这么多其实只是学习了一些表面的东西。可能有同学会问我为什么不深入地去学习,比如说探究 Cgroups 、Namespace 原理,深入学习容器进程、网络、安全、存储、内存模块,甚至是自己去实现 Docker 。

    对此我的回答是,在深入学习容器之前我需要先去学习 linux 和一点 C,这不是我现阶段想做的事情。

    最后

    我把上面的答案告诉运维同学后,他们反馈说,用 K8s 挂载不了上面提到的目录。限于我目前对 K8s 不了解,这个问题他们也不着急,所以只能暂时搁置一下,等我学完 k8s 再去解决。学习完 k8s 之后我会和今天一样把学习经验分享出来。我在微信公众号发表了本文的英文版(借助了翻译软件),如果您感兴趣可以关注一下我: ProgrammerAlan 。

    作者简介

    鑫茂,深圳,Java 开发工程师,2022 年 3 月参加工作。

    喜读思维方法、哲学心理学以及历史等方面的书,偶尔写些文字。

    希望通过文章,结识更多同道中人。如果你对我感兴趣欢迎添加我的联系方式,可以一起讨论如何赚钱,实现共同富裕。

    20 条回复    2023-01-10 17:57:38 +08:00
    GoodRui
        1
    GoodRui  
       2023-01-09 23:01:39 +08:00 via iPhone
    感谢分享 !作为初学者感到受益匪浅~
    cdlnls
        2
    cdlnls  
       2023-01-09 23:30:08 +08:00
    容器应该算是一个高级的进程管理工具。容器是对具体进程的一层封装。
    ruanwx
        3
    ruanwx  
       2023-01-10 04:33:02 +08:00
    不错,有实践有思考👍🏻
    julyclyde
        4
    julyclyde  
       2023-01-10 11:17:19 +08:00   ❤️ 1
    所以,这不是穿上裤子放屁么
    ProgrammerAlan
        5
    ProgrammerAlan  
    OP
       2023-01-10 11:32:00 +08:00
    @julyclyde 额 所以你放屁的时候都是脱掉裤子嘛
    ProgrammerAlan
        6
    ProgrammerAlan  
    OP
       2023-01-10 11:53:30 +08:00
    @julyclyde 我的回复有点冲 可能您想说的是我为了解决运维同学的这个问题,而去学了一下 Docker 有点多此一举,甚至学习了 Docker 后仍无法解决他提出的问题,还需要继续去学习 k8s 。作为 Java 开发运维方面的这些事情确实不应该我来做,而是应该交给专业的人。但是这个运维同学是甲方公司的,提出的这个问题我们这边需要尽力去解决,而我们部门又没有会 k8s 的同事,所以只能交给我慢慢去磨,正好我也想借这个契机去学习如何容器化部署服务。
    julyclyde
        7
    julyclyde  
       2023-01-10 12:31:31 +08:00
    @ProgrammerAlan 问题是 dmidecode 本来也不该在容器内运行啊
    我觉得你们的运维的专业度甚至不如你

    这世界上真的存在不适合在容器内运行的东西的!醒醒!
    julyclyde
        8
    julyclyde  
       2023-01-10 12:31:53 +08:00
    @ProgrammerAlan 不必特地脱裤子啊,dmidecode 本来就是裸的
    套一层 docker 那才是穿上
    julyclyde
        9
    julyclyde  
       2023-01-10 12:34:52 +08:00
    dmidecode 本来光屁股放的挺爽的
    你们现在给穿上裤子,再给裤子上剪俩洞
    费工费时达到了和光屁股一样的效果
    ProgrammerAlan
        10
    ProgrammerAlan  
    OP
       2023-01-10 13:28:57 +08:00
    @julyclyde 原来是这个意思 受教啦!
    ProgrammerAlan
        11
    ProgrammerAlan  
    OP
       2023-01-10 13:35:50 +08:00
    @julyclyde 听完您的答案发现确实是在穿上裤子放屁,这种需要执行 dmidecode 命令的服务就不太适合用 k8s 上云,应该是要直接部署在物理机上。
    ProgrammerAlan
        12
    ProgrammerAlan  
    OP
       2023-01-10 13:37:36 +08:00
    @julyclyde 上面说话太冲了 向您道歉!
    julyclyde
        13
    julyclyde  
       2023-01-10 13:43:30 +08:00
    @ProgrammerAlan 你的程序执行 dmidecode 是为了获得什么信息然后做什么自适应的工作吗?
    cpu 核数和内存量?

    如果这样的话,应该读 *容器自己的* cgroup 或者 lxcfs 而不是整台机器的 dmidecode
    ProgrammerAlan
        14
    ProgrammerAlan  
    OP
       2023-01-10 13:50:38 +08:00
    @julyclyde 执行 dmidecode 主要是为了获取设备的 UUID ,让程序只能在这台设备上运行。
    ProgrammerAlan
        15
    ProgrammerAlan  
    OP
       2023-01-10 13:52:43 +08:00
    @julyclyde nice 学到了个有趣的知识 读容器自己的 cgroup 或者 lxcfs
    julyclyde
        16
    julyclyde  
       2023-01-10 16:11:29 +08:00
    @ProgrammerAlan 我觉得这个应该在调度层面来做吧
    pod 和 node 的亲和性
    julyclyde
        17
    julyclyde  
       2023-01-10 16:11:50 +08:00
    @ProgrammerAlan 这种有状态的其实我觉得甚至都不该放到 k8s 里面去
    ProgrammerAlan
        18
    ProgrammerAlan  
    OP
       2023-01-10 16:23:36 +08:00
    @julyclyde "调度层面", "pod", "node 的亲和性" 这些不太懂哈哈, 我和领导沟通过了决定让甲方把这个服务单独部署在物理机,不放到 k8s 里面去。
    julyclyde
        19
    julyclyde  
       2023-01-10 17:49:13 +08:00
    @ProgrammerAlan k8s 可以给 node 和 pod 分别做 label ,然后做一些亲和性匹配,就可以实现你那个需求了

    不过我还是提倡,有状态的就不应该放到 k8s 里
    ProgrammerAlan
        20
    ProgrammerAlan  
    OP
       2023-01-10 17:57:38 +08:00
    @julyclyde 嗯嗯 好的啊 感谢!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3554 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 04:43 · PVG 12:43 · LAX 20:43 · JFK 23:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.