• 请不要在回答技术问题时复制粘贴 AI 生成的内容
ProgrammerAlan
V2EX  ›  程序员

Alan 的 Docker 容器学习笔记

  •  
  •   ProgrammerAlan · Jan 9, 2023 · 2314 views
    This topic created in 1248 days ago, the information mentioned may be changed or developed.

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

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

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

    不过我还是提倡,有状态的就不应该放到 k8s 里
    ProgrammerAlan
        20
    ProgrammerAlan  
    OP
       Jan 10, 2023
    @julyclyde 嗯嗯 好的啊 感谢!
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   3938 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 59ms · UTC 10:15 · PVG 18:15 · LAX 03:15 · JFK 06:15
    ♥ Do have faith in what you're doing.